boxman 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
boxman-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: boxman
3
+ Version: 0.1.0
4
+ Summary: Set up and manage an Ubuntu development box
5
+ Project-URL: Homepage, https://github.com/vivainio/boxman
6
+ Project-URL: Documentation, https://vivainio.github.io/boxman/
7
+ Project-URL: Repository, https://github.com/vivainio/boxman
8
+ Project-URL: Issues, https://github.com/vivainio/boxman/issues
9
+ Keywords: ubuntu,development,ec2,cloudformation,cli
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: System :: Systems Administration
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: ec2
22
+ Requires-Dist: boto3>=1.34; extra == "ec2"
23
+
24
+ # boxman
25
+
26
+ [Read the Boxman book](https://vivainio.github.io/boxman/) for the concepts, setup steps, and command reference.
27
+
28
+ Set up and manage an Ubuntu 24.04 development box. `boxman` is a Python command
29
+ with system, user, and vault operations.
30
+
31
+ ## Install a host
32
+
33
+ From this checkout, run the system step as root, then the user step from each
34
+ login account. The first command works with the Ubuntu system Python before
35
+ `uv` is installed:
36
+
37
+ ```bash
38
+ sudo python3 -m boxman.cli system
39
+ python3 -m boxman.cli user
40
+ ```
41
+
42
+ With `uv` available, run it without installing the package:
43
+
44
+ ```bash
45
+ uvx --from . boxman vault status
46
+ ```
47
+
48
+ After publishing the repository, `uvx --from
49
+ git+https://github.com/vivainio/boxman boxman ...` can run the same console
50
+ command. For a persistent command, use `uv tool install .` from this checkout.
51
+
52
+ `boxman system` uses zipget to install the apt packages declared in
53
+ `linux-tools.toml`, sets up Git LFS, and configures rootless Podman for normal
54
+ login users. It accepts explicit usernames, or `--packages-only` for a
55
+ container build. It downloads zipget if no version supporting
56
+ `recipe --system-only` is on root's PATH. A fresh install needs a released
57
+ zipget with that option.
58
+
59
+ `boxman user` installs the tools in the recipe, Node.js 22, Claude Code,
60
+ Copilot CLI, and uv. Run the user step for each account.
61
+ Run `boxman verify` afterward to check the installed commands and rootless
62
+ Podman.
63
+
64
+ The same package set can be used in a dev container:
65
+
66
+ ```bash
67
+ podman build -f container/Dockerfile -t boxman-dev .
68
+ ```
69
+
70
+ The container build skips host login-user and systemd configuration. FUSE
71
+ mounting may require additional container privileges, so the vault commands
72
+ are intended for the native host.
73
+
74
+ ## Private directory
75
+
76
+ The recipe installs `gocryptfs` and FUSE 3. Each user initializes their own
77
+ vault once and unlocks it after a reboot or unmount:
78
+
79
+ ```bash
80
+ uvx --from . boxman vault init
81
+ uvx --from . boxman vault unlock
82
+ uvx --from . boxman vault status
83
+ uvx --from . boxman claude # starts Claude with its config in the mounted vault
84
+ uvx --from . boxman vault lock # after stopping processes that use the mount
85
+ ```
86
+
87
+ Encrypted files live in `~/.private.cipher`; the plaintext mount is
88
+ `~/private`. Initialization and unlocking prompt for the password. Keep the
89
+ password and gocryptfs recovery key outside the host. Back up the encrypted
90
+ directory, including `gocryptfs.conf`, while keeping the plaintext mount out
91
+ of backups.
92
+
93
+ `boxman claude` refuses to run unless the vault is mounted. Use it before the
94
+ first Claude login. It sets `CLAUDE_CONFIG_DIR` to `~/private/claude` and does
95
+ not move existing credentials from `~/.claude`. Configure other tools'
96
+ credential locations separately if they should use the vault. Persistent
97
+ agents need the vault mounted while they use credentials. Locking fails while
98
+ a process holds files in the mount open.
99
+
100
+ The vault protects its backing files and snapshots while locked. It does not
101
+ hide credentials from the user's running processes or a host administrator
102
+ while unlocked. Keep home directory permissions private to each Unix owner.
103
+
104
+ ## EC2 host
105
+
106
+ Install the optional AWS dependency, then create
107
+ `${XDG_CONFIG_HOME:-~/.config}/boxman/ec2.toml`:
108
+
109
+ ```bash
110
+ uv tool install '.[ec2]'
111
+ mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/boxman"
112
+ ```
113
+
114
+ ```toml
115
+ [ec2]
116
+ profile = "your-aws-profile"
117
+ region = "your-region"
118
+ stack_name = "your-stack-name"
119
+
120
+ [ec2.tags]
121
+ Owner = "your-owner"
122
+ Environment = "your-environment"
123
+ ```
124
+
125
+ The config contains no credentials; AWS uses the named profile. Supply the tags
126
+ required by your account. The TOML file selects the AWS profile, region, stack,
127
+ and tags. Command-line options override the file;
128
+ `--tag KEY=VALUE` adds or overrides a tag. `--config PATH` selects another TOML
129
+ file. Put global options before the action:
130
+
131
+ ```bash
132
+ boxman ec2 init --vpc-id vpc-... --subnet-id subnet-... \
133
+ --instance-type t3.xlarge --volume-size-gb 100 \
134
+ --instance-name mybox \
135
+ --ami-id /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id
136
+ boxman ec2 deploy
137
+ boxman ec2 status
138
+ boxman ec2 start
139
+ boxman ec2 stop
140
+ boxman ec2 connect -u myuser
141
+ boxman ec2 ssh -u myuser
142
+ boxman ec2 ssh-config -u myuser --alias mybox
143
+ boxman ec2 run -u myuser 'uname -a'
144
+ ```
145
+
146
+ `init` writes `${XDG_CONFIG_HOME:-~/.config}/boxman/stacks/<stack_name>.yaml`
147
+ with the instance settings as parameter defaults and refuses to overwrite an
148
+ existing file. Edit that YAML to customize the stack. `deploy` reads the YAML
149
+ for the configured stack name and sends its defaults as CloudFormation
150
+ parameters. Tags from `[ec2.tags]` and repeatable
151
+ `--tag KEY=VALUE` options become CloudFormation stack tags. A single config
152
+ file selects one stack; use `--config PATH` for another box.
153
+
154
+ Deployment creates or updates a CloudFormation stack containing an Ubuntu EC2
155
+ instance, an SSM role, and an EC2 Instance Connect Endpoint. SSH uses that
156
+ endpoint and installs a generated public key through SSM. `ssh-config` writes a
157
+ marked host entry to `~/.ssh/config`. The stack name selects the instance for
158
+ all subsequent commands. Starting, stopping, and deploying incur AWS charges.
159
+
160
+ ## Release
161
+
162
+ Build the Zensical book locally with `zensical build --clean`. The docs workflow
163
+ publishes it to GitHub Pages when documentation changes on `main`.
164
+
165
+ A published GitHub release triggers the PyPI workflow. Use a `vX.Y.Z` tag;
166
+ the workflow sets the package version from the release tag, builds the wheel
167
+ and source distribution, and publishes with PyPI trusted publishing. Configure
168
+ a PyPI trusted publisher for repository `vivainio/boxman`, workflow
169
+ `publish.yml`, environment `pypi` before the first release.
boxman-0.1.0/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # boxman
2
+
3
+ [Read the Boxman book](https://vivainio.github.io/boxman/) for the concepts, setup steps, and command reference.
4
+
5
+ Set up and manage an Ubuntu 24.04 development box. `boxman` is a Python command
6
+ with system, user, and vault operations.
7
+
8
+ ## Install a host
9
+
10
+ From this checkout, run the system step as root, then the user step from each
11
+ login account. The first command works with the Ubuntu system Python before
12
+ `uv` is installed:
13
+
14
+ ```bash
15
+ sudo python3 -m boxman.cli system
16
+ python3 -m boxman.cli user
17
+ ```
18
+
19
+ With `uv` available, run it without installing the package:
20
+
21
+ ```bash
22
+ uvx --from . boxman vault status
23
+ ```
24
+
25
+ After publishing the repository, `uvx --from
26
+ git+https://github.com/vivainio/boxman boxman ...` can run the same console
27
+ command. For a persistent command, use `uv tool install .` from this checkout.
28
+
29
+ `boxman system` uses zipget to install the apt packages declared in
30
+ `linux-tools.toml`, sets up Git LFS, and configures rootless Podman for normal
31
+ login users. It accepts explicit usernames, or `--packages-only` for a
32
+ container build. It downloads zipget if no version supporting
33
+ `recipe --system-only` is on root's PATH. A fresh install needs a released
34
+ zipget with that option.
35
+
36
+ `boxman user` installs the tools in the recipe, Node.js 22, Claude Code,
37
+ Copilot CLI, and uv. Run the user step for each account.
38
+ Run `boxman verify` afterward to check the installed commands and rootless
39
+ Podman.
40
+
41
+ The same package set can be used in a dev container:
42
+
43
+ ```bash
44
+ podman build -f container/Dockerfile -t boxman-dev .
45
+ ```
46
+
47
+ The container build skips host login-user and systemd configuration. FUSE
48
+ mounting may require additional container privileges, so the vault commands
49
+ are intended for the native host.
50
+
51
+ ## Private directory
52
+
53
+ The recipe installs `gocryptfs` and FUSE 3. Each user initializes their own
54
+ vault once and unlocks it after a reboot or unmount:
55
+
56
+ ```bash
57
+ uvx --from . boxman vault init
58
+ uvx --from . boxman vault unlock
59
+ uvx --from . boxman vault status
60
+ uvx --from . boxman claude # starts Claude with its config in the mounted vault
61
+ uvx --from . boxman vault lock # after stopping processes that use the mount
62
+ ```
63
+
64
+ Encrypted files live in `~/.private.cipher`; the plaintext mount is
65
+ `~/private`. Initialization and unlocking prompt for the password. Keep the
66
+ password and gocryptfs recovery key outside the host. Back up the encrypted
67
+ directory, including `gocryptfs.conf`, while keeping the plaintext mount out
68
+ of backups.
69
+
70
+ `boxman claude` refuses to run unless the vault is mounted. Use it before the
71
+ first Claude login. It sets `CLAUDE_CONFIG_DIR` to `~/private/claude` and does
72
+ not move existing credentials from `~/.claude`. Configure other tools'
73
+ credential locations separately if they should use the vault. Persistent
74
+ agents need the vault mounted while they use credentials. Locking fails while
75
+ a process holds files in the mount open.
76
+
77
+ The vault protects its backing files and snapshots while locked. It does not
78
+ hide credentials from the user's running processes or a host administrator
79
+ while unlocked. Keep home directory permissions private to each Unix owner.
80
+
81
+ ## EC2 host
82
+
83
+ Install the optional AWS dependency, then create
84
+ `${XDG_CONFIG_HOME:-~/.config}/boxman/ec2.toml`:
85
+
86
+ ```bash
87
+ uv tool install '.[ec2]'
88
+ mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/boxman"
89
+ ```
90
+
91
+ ```toml
92
+ [ec2]
93
+ profile = "your-aws-profile"
94
+ region = "your-region"
95
+ stack_name = "your-stack-name"
96
+
97
+ [ec2.tags]
98
+ Owner = "your-owner"
99
+ Environment = "your-environment"
100
+ ```
101
+
102
+ The config contains no credentials; AWS uses the named profile. Supply the tags
103
+ required by your account. The TOML file selects the AWS profile, region, stack,
104
+ and tags. Command-line options override the file;
105
+ `--tag KEY=VALUE` adds or overrides a tag. `--config PATH` selects another TOML
106
+ file. Put global options before the action:
107
+
108
+ ```bash
109
+ boxman ec2 init --vpc-id vpc-... --subnet-id subnet-... \
110
+ --instance-type t3.xlarge --volume-size-gb 100 \
111
+ --instance-name mybox \
112
+ --ami-id /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id
113
+ boxman ec2 deploy
114
+ boxman ec2 status
115
+ boxman ec2 start
116
+ boxman ec2 stop
117
+ boxman ec2 connect -u myuser
118
+ boxman ec2 ssh -u myuser
119
+ boxman ec2 ssh-config -u myuser --alias mybox
120
+ boxman ec2 run -u myuser 'uname -a'
121
+ ```
122
+
123
+ `init` writes `${XDG_CONFIG_HOME:-~/.config}/boxman/stacks/<stack_name>.yaml`
124
+ with the instance settings as parameter defaults and refuses to overwrite an
125
+ existing file. Edit that YAML to customize the stack. `deploy` reads the YAML
126
+ for the configured stack name and sends its defaults as CloudFormation
127
+ parameters. Tags from `[ec2.tags]` and repeatable
128
+ `--tag KEY=VALUE` options become CloudFormation stack tags. A single config
129
+ file selects one stack; use `--config PATH` for another box.
130
+
131
+ Deployment creates or updates a CloudFormation stack containing an Ubuntu EC2
132
+ instance, an SSM role, and an EC2 Instance Connect Endpoint. SSH uses that
133
+ endpoint and installs a generated public key through SSM. `ssh-config` writes a
134
+ marked host entry to `~/.ssh/config`. The stack name selects the instance for
135
+ all subsequent commands. Starting, stopping, and deploying incur AWS charges.
136
+
137
+ ## Release
138
+
139
+ Build the Zensical book locally with `zensical build --clean`. The docs workflow
140
+ publishes it to GitHub Pages when documentation changes on `main`.
141
+
142
+ A published GitHub release triggers the PyPI workflow. Use a `vX.Y.Z` tag;
143
+ the workflow sets the package version from the release tag, builds the wheel
144
+ and source distribution, and publishes with PyPI trusted publishing. Configure
145
+ a PyPI trusted publisher for repository `vivainio/boxman`, workflow
146
+ `publish.yml`, environment `pypi` before the first release.
File without changes
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env python3
2
+ """Set up and manage a Linux development box."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ def fail(message: str) -> None:
13
+ raise SystemExit(message)
14
+
15
+
16
+ def run(*args: str) -> None:
17
+ result = subprocess.run(args, check=False)
18
+ if result.returncode:
19
+ raise SystemExit(result.returncode)
20
+
21
+
22
+ def mounted(path: Path) -> bool:
23
+ return subprocess.run(
24
+ ("mountpoint", "-q", str(path)),
25
+ stdout=subprocess.DEVNULL,
26
+ stderr=subprocess.DEVNULL,
27
+ check=False,
28
+ ).returncode == 0
29
+
30
+
31
+ def vault_paths() -> tuple[Path, Path]:
32
+ home = Path.home()
33
+ cipher, plain = home / ".private.cipher", home / "private"
34
+ if cipher.is_symlink() or plain.is_symlink():
35
+ fail("Private vault paths must not be symlinks")
36
+ return cipher, plain
37
+
38
+
39
+ def private_dir(path: Path) -> None:
40
+ path.mkdir(mode=0o700, parents=True, exist_ok=True)
41
+ path.chmod(0o700)
42
+
43
+
44
+ def vault(action: str) -> None:
45
+ if os.geteuid() == 0:
46
+ fail("Run vault commands as your login user, not root")
47
+ os.umask(0o077)
48
+ cipher, plain = vault_paths()
49
+
50
+ if action == "status":
51
+ print("Private vault is unlocked" if mounted(plain) else "Private vault is locked")
52
+ elif action == "init":
53
+ if mounted(plain):
54
+ fail("Private vault is already unlocked")
55
+ if (cipher / "gocryptfs.conf").exists():
56
+ fail("Private vault is already initialized")
57
+ private_dir(cipher)
58
+ private_dir(plain)
59
+ if any(cipher.iterdir()):
60
+ fail("Encrypted backing directory is not empty")
61
+ if any(plain.iterdir()):
62
+ fail("Plaintext mount directory is not empty")
63
+ run("gocryptfs", "-init", str(cipher))
64
+ print("Keep the password and recovery key outside this host.")
65
+ elif action == "unlock":
66
+ if not (cipher / "gocryptfs.conf").is_file():
67
+ fail("Private vault is not initialized; run boxman vault init")
68
+ private_dir(cipher)
69
+ private_dir(plain)
70
+ if mounted(plain):
71
+ print("Private vault is already unlocked")
72
+ return
73
+ if any(plain.iterdir()):
74
+ fail("Plaintext mount directory is not empty; refusing to hide its files")
75
+ run("gocryptfs", str(cipher), str(plain))
76
+ elif action == "lock":
77
+ if not mounted(plain):
78
+ print("Private vault is already locked")
79
+ return
80
+ run("fusermount3", "-u", str(plain))
81
+ else:
82
+ fail("Usage: boxman vault {init|unlock|lock|status}")
83
+
84
+
85
+ def claude(args: list[str]) -> None:
86
+ if os.geteuid() == 0:
87
+ fail("Run Claude as your login user, not root")
88
+ _, plain = vault_paths()
89
+ if not mounted(plain):
90
+ fail("Private vault is locked; run boxman vault unlock first")
91
+ config = plain / "claude"
92
+ if config.is_symlink():
93
+ fail("Claude config directory must not be a symlink")
94
+ os.umask(0o077)
95
+ private_dir(config)
96
+ os.environ["CLAUDE_CONFIG_DIR"] = str(config)
97
+ os.execvp("claude", ["claude", *args])
98
+
99
+
100
+ def main() -> None:
101
+ if len(sys.argv) < 2:
102
+ fail("Usage: boxman {system|user|vault|claude|verify|ec2} ...")
103
+ command, *args = sys.argv[1:]
104
+ if command == "system":
105
+ from boxman import system
106
+
107
+ system.main(args)
108
+ elif command == "user":
109
+ if args:
110
+ fail("Usage: boxman user")
111
+ from boxman import user
112
+
113
+ user.main()
114
+ elif command == "vault":
115
+ if len(args) != 1:
116
+ fail("Usage: boxman vault {init|unlock|lock|status}")
117
+ vault(args[0])
118
+ elif command == "claude":
119
+ claude(args)
120
+ elif command == "ec2":
121
+ from boxman import ec2
122
+
123
+ ec2.main(args)
124
+ elif command == "verify":
125
+ if args:
126
+ fail("Usage: boxman verify")
127
+ from boxman import verify
128
+
129
+ verify.main()
130
+ else:
131
+ fail("Usage: boxman {system|user|vault|claude|verify|ec2} ...")
132
+
133
+
134
+ if __name__ == "__main__":
135
+ main()
File without changes
@@ -0,0 +1,126 @@
1
+ AWSTemplateFormatVersion: "2010-09-09"
2
+ Description: >
3
+ EC2 development box:
4
+ one instance, no inbound security-group rules, SSM-only access.
5
+
6
+ Parameters:
7
+ VpcId:
8
+ Type: AWS::EC2::VPC::Id
9
+ SubnetId:
10
+ Type: AWS::EC2::Subnet::Id
11
+ InstanceType:
12
+ Type: String
13
+ VolumeSizeGb:
14
+ Type: Number
15
+ InstanceName:
16
+ Type: String
17
+ LatestAmiId:
18
+ Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
19
+
20
+ Resources:
21
+ SecurityGroup:
22
+ Type: AWS::EC2::SecurityGroup
23
+ Properties:
24
+ GroupName: !Sub "${AWS::StackName}-sg"
25
+ GroupDescription: Shared coding-agent dev box - no inbound rules, SSM only
26
+ VpcId: !Ref VpcId
27
+ Tags:
28
+ - Key: Name
29
+ Value: !Sub "${AWS::StackName}-sg"
30
+ # No SecurityGroupIngress here: no inbound rules from the internet, on
31
+ # purpose. The only inbound access is via SecurityGroupIngressFromEice
32
+ # below, scoped to the Instance Connect Endpoint's own security group.
33
+ # No SecurityGroupEgress: keeps CloudFormation's default allow-all outbound.
34
+
35
+ EiceSecurityGroup:
36
+ Type: AWS::EC2::SecurityGroup
37
+ Properties:
38
+ GroupName: !Sub "${AWS::StackName}-eice-sg"
39
+ GroupDescription: EC2 Instance Connect Endpoint - no inbound rules needed
40
+ VpcId: !Ref VpcId
41
+ Tags:
42
+ - Key: Name
43
+ Value: !Sub "${AWS::StackName}-eice-sg"
44
+
45
+ SecurityGroupIngressFromEice:
46
+ Type: AWS::EC2::SecurityGroupIngress
47
+ Properties:
48
+ GroupId: !Ref SecurityGroup
49
+ IpProtocol: tcp
50
+ FromPort: 22
51
+ ToPort: 22
52
+ SourceSecurityGroupId: !Ref EiceSecurityGroup
53
+ Description: SSH from the EC2 Instance Connect Endpoint only
54
+
55
+ InstanceConnectEndpoint:
56
+ Type: AWS::EC2::InstanceConnectEndpoint
57
+ Properties:
58
+ SubnetId: !Ref SubnetId
59
+ SecurityGroupIds:
60
+ - !Ref EiceSecurityGroup
61
+ PreserveClientIp: true
62
+ Tags:
63
+ - Key: Name
64
+ Value: !Sub "${AWS::StackName}-eice"
65
+
66
+ InstanceRole:
67
+ Type: AWS::IAM::Role
68
+ Properties:
69
+ RoleName: !Sub "${AWS::StackName}-ssm-role"
70
+ AssumeRolePolicyDocument:
71
+ Version: "2012-10-17"
72
+ Statement:
73
+ - Effect: Allow
74
+ Principal:
75
+ Service: ec2.amazonaws.com
76
+ Action: sts:AssumeRole
77
+ ManagedPolicyArns:
78
+ - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
79
+ Tags:
80
+ - Key: Name
81
+ Value: !Sub "${AWS::StackName}-ssm-role"
82
+
83
+ InstanceProfile:
84
+ Type: AWS::IAM::InstanceProfile
85
+ Properties:
86
+ InstanceProfileName: !Sub "${AWS::StackName}-ssm-profile"
87
+ Roles:
88
+ - !Ref InstanceRole
89
+
90
+ Instance:
91
+ Type: AWS::EC2::Instance
92
+ Properties:
93
+ ImageId: !Ref LatestAmiId
94
+ InstanceType: !Ref InstanceType
95
+ SubnetId: !Ref SubnetId
96
+ SecurityGroupIds:
97
+ - !Ref SecurityGroup
98
+ IamInstanceProfile: !Ref InstanceProfile
99
+ BlockDeviceMappings:
100
+ - DeviceName: /dev/sda1
101
+ Ebs:
102
+ VolumeSize: !Ref VolumeSizeGb
103
+ VolumeType: gp3
104
+ DeleteOnTermination: true
105
+ MetadataOptions:
106
+ HttpTokens: required
107
+ HttpPutResponseHopLimit: 2
108
+ Tags:
109
+ - Key: Name
110
+ Value: !Ref InstanceName
111
+
112
+ Outputs:
113
+ InstanceId:
114
+ Value: !Ref Instance
115
+ SecurityGroupId:
116
+ Value: !Ref SecurityGroup
117
+ InstanceRoleArn:
118
+ Value: !GetAtt InstanceRole.Arn
119
+ InstanceProfileArn:
120
+ Value: !GetAtt InstanceProfile.Arn
121
+ # No PublicIp output: !GetAtt Instance.PublicIp fails outright whenever the
122
+ # instance isn't running (stopped by uptime governance, mid-restart, etc.),
123
+ # breaking every subsequent stack operation. basbox.py status() already
124
+ # reads the live public IP via ec2:DescribeInstances instead.
125
+ InstanceConnectEndpointId:
126
+ Value: !Ref InstanceConnectEndpoint
@@ -0,0 +1,80 @@
1
+ # zipget recipe for boxman on Ubuntu 24.04
2
+
3
+ [system_packages]
4
+ apt = [
5
+ "acl", "build-essential", "ca-certificates", "curl", "dbus-user-session",
6
+ "direnv", "file", "fuse3", "fuse-overlayfs", "gh", "git", "git-lfs",
7
+ "gocryptfs", "jq",
8
+ "less", "lf", "libsecret-tools", "openssh-client", "podman", "python3",
9
+ "python3-venv", "python-is-python3", "rsync", "shellcheck",
10
+ "slirp4netns", "uidmap", "unzip", "xz-utils", "zip", "zstd",
11
+ ]
12
+
13
+ [vars]
14
+ bin_dir = "~/.local/bin"
15
+
16
+ [herdr]
17
+ # Agent multiplexer - https://herdr.dev
18
+ github = { repo = "herdrdev/herdr", asset = "herdr-linux-x86_64" }
19
+ save_as = "${bin_dir}/herdr"
20
+ executable = true
21
+
22
+ [cship]
23
+ # Claude Code statusline - https://cship.dev
24
+ github = { repo = "stephenleo/cship", asset = "cship-x86_64-unknown-linux-musl" }
25
+ save_as = "${bin_dir}/cship"
26
+ executable = true
27
+
28
+ [microsoft-edit]
29
+ # Terminal-based text editor - https://github.com/microsoft/edit
30
+ # Note: only gnu builds available (no musl)
31
+ github = { repo = "microsoft/edit", asset = "x86_64-linux-gnu.tar.gz" }
32
+ unzip_to = "${bin_dir}"
33
+ files = "edit"
34
+
35
+ [starship]
36
+ # Cross-shell prompt - https://github.com/starship/starship
37
+ github = { repo = "starship/starship", asset = "x86_64-unknown-linux-musl.tar.gz" }
38
+ unzip_to = "${bin_dir}"
39
+ files = "starship"
40
+
41
+ [ripgrep]
42
+ # Fast regex search tool - https://github.com/BurntSushi/ripgrep
43
+ github = { repo = "BurntSushi/ripgrep", asset = "x86_64-unknown-linux-musl.tar.gz" }
44
+ unzip_to = "${bin_dir}"
45
+ files = "*/rg"
46
+
47
+ [fd]
48
+ # Fast find alternative - https://github.com/sharkdp/fd
49
+ github = { repo = "sharkdp/fd", asset = "x86_64-unknown-linux-musl.tar.gz" }
50
+ unzip_to = "${bin_dir}"
51
+ files = "*/fd"
52
+
53
+ [bat]
54
+ # Cat with syntax highlighting - https://github.com/sharkdp/bat
55
+ github = { repo = "sharkdp/bat", asset = "x86_64-unknown-linux-musl.tar.gz" }
56
+ unzip_to = "${bin_dir}"
57
+ files = "*/bat"
58
+
59
+ [fzf]
60
+ # Fuzzy finder - https://github.com/junegunn/fzf
61
+ github = { repo = "junegunn/fzf", asset = "linux_amd64.tar.gz" }
62
+ unzip_to = "${bin_dir}"
63
+ files = "fzf"
64
+
65
+ [zoxide]
66
+ # Smarter cd command - https://github.com/ajeetdsouza/zoxide
67
+ github = { repo = "ajeetdsouza/zoxide", asset = "x86_64-unknown-linux-musl.tar.gz" }
68
+ unzip_to = "${bin_dir}"
69
+ files = "zoxide"
70
+
71
+ [delta]
72
+ # Git diff viewer - https://github.com/dandavison/delta
73
+ github = { repo = "dandavison/delta", asset = "x86_64-unknown-linux-musl.tar.gz" }
74
+ unzip_to = "${bin_dir}"
75
+ files = "*/delta"
76
+
77
+ [aws-cli]
78
+ # AWS CLI v2 - official installer
79
+ url = "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
80
+ unzip_to = "./aws-cli-installer"