Could you also add some documentation to the readme file similar to "machine" for things like build and install using uv?
1 # machine
2 CLI utility to create and manage VMs
3
4 Supports [DigitalOcean](https://www.digitalocean.com/) and [Vultr](https://www.vultr.com/) hosting platforms.
5
6 ## Prerequisites
7
8 This project uses [uv](https://docs.astral.sh/uv/) for dependency management and builds.
9
10 Install uv:
11 ```
12 curl -LsSf https://astral.sh/uv/install.sh | sh
13 ```
14
15 ## Development Setup
16
17 ```
18 uv sync
19 ```
20
21 This creates a `.venv` virtual environment and installs all dependencies (including dev tools like ruff).
22
23 Run the CLI during development:
24 ```
25 uv run machine --help
26 ```
27
28 Run the linter:
29 ```
30 uv run ruff check machine/
31 ```
32
33 ## Build
34
35 Build a self-contained executable using [shiv](https://github.com/linkedin/shiv):
36 ```
37 ./sh/build-package.sh
38 ```
39
40 This produces `build/machine`, a single-file Python zipapp.
41
42 ## Install
43
44 Install directly from the GitHub repository using uv:
45 ```
46 uv tool install git+https://github.com/stirlingbridge/machine.git
47 ```
48
49 Alternatively, download the `machine` binary from the [releases page](https://github.com/stirlingbridge/machine/releases), make it executable, and place it on your PATH:
50 ```
51 chmod +x machine
52 sudo mv machine /usr/local/bin/
53 ```
54
55 ## Usage
56
57 ### Config File
58 Access token and other settings configured in the file `~/.machine/config.yml`. The config file contains a provider section (either `digital-ocean` or `vultr`) and a `machines` section.
59
60 If only one provider section is present, it is auto-detected. If multiple provider sections exist, add a `provider:` key to select one explicitly:
61
62 ```yaml
63 provider: vultr
64 ```
65
66 #### DigitalOcean Config
67
68 ```yaml
69 digital-ocean:
70 access-token: dop_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
71 ssh-key: my-ssh-key-name
72 dns-zone: example.com
73 machine-size: s-4vcpu-8gb
74 image: ubuntu-22-04-x64
75 region: nyc3
76 project: Infrastructure
77
78 machines:
79 example:
80 new-user-name: alice
81 script-dir: /opt/setup-scripts
82 script-url: https://raw.githubusercontent.com/example/setup-machine.sh
83 script-path: /opt/setup-scripts/setup-machine.sh
84 script-args: "-y"
85 ```
86
87 | Key | Required | Description |
88 |-----|----------|-------------|
89 | `access-token` | Yes | DigitalOcean API access token |
90 | `ssh-key` | Yes | Name of the SSH key in your DigitalOcean account to use for new machines |
91 | `dns-zone` | No | DNS zone for automatic DNS record creation/deletion |
92 | `machine-size` | Yes | Default machine size slug (e.g. `s-4vcpu-8gb`) |
93 | `image` | Yes | Default image name (e.g. `ubuntu-22-04-x64`) |
94 | `region` | Yes | Default region code (e.g. `nyc3`) |
95 | `project` | No | DigitalOcean project name to assign new machines to |
96
97 Supported regions: `NYC1`, `NYC3`, `AMS3`, `SFO2`, `SFO3`, `SGP1`, `LON1`, `FRA1`, `TOR1`, `BLR1`, `SYD1`
98
99 #### Vultr Config
100
101 ```yaml
102 vultr:
103 api-key: ${VULTR_API_KEY}
104 ssh-key: my-ssh-key-name
105 dns-zone: example.com
106 machine-size: vc2-1c-1gb
107 image: 2136
108 region: ewr
109
110 machines:
111 example:
112 new-user-name: alice
113 script-dir: /opt/setup-scripts
114 script-url: https://raw.githubusercontent.com/example/setup-machine.sh
115 script-path: /opt/setup-scripts/setup-machine.sh
116 script-args: "-y"
117 ```
118
119 | Key | Required | Description |
120 |-----|----------|-------------|
121 | `api-key` | Yes | Vultr API key (from https://my.vultr.com/settings/#settingsapi) |
122 | `ssh-key` | Yes | Name of the SSH key in your Vultr account to use for new machines |
123 | `dns-zone` | No | DNS zone for automatic DNS record creation/deletion |
124 | `machine-size` | Yes | Vultr plan slug (e.g. `vc2-1c-1gb`). Use `machine list-plans` or the Vultr API to list available plans |
125 | `image` | Yes | Vultr OS ID (numeric, e.g. `2136` for Ubuntu 24.04). Use `machine list-os` or the Vultr API to list available OS IDs |
126 | `region` | Yes | Vultr region code (e.g. `ewr`) |
127
128 Supported regions: `ewr`, `ord`, `dfw`, `sea`, `lax`, `atl`, `ams`, `lhr`, `fra`, `sjc`, `syd`, `nrt`, `cdg`, `icn`, `mia`, `sgp`, `sto`, `mex`, `mad`, `sao`, `del`, `hnl`, `yto`, `blr`, `jnb`, `bom`, `tlv`
129
130 **Note:** Vultr does not have a "projects" concept, so the `project` config key and the `projects` command are not applicable when using the Vultr provider.
131
132 #### Machines Section
133
134 **machines section:**
135
136 Each entry under `machines:` defines a machine type that can be referenced with `create --type`:
137
138 | Key | Required | Description |
139 |-----|----------|-------------|
140 | `new-user-name` | Yes | Username for the non-root user created on the machine |
141 | `script-url` | No | URL to download an initialization script from |
142 | `script-dir` | No | Directory to store the initialization script |
143 | `script-path` | No | Full path for the initialization script |
144 | `script-args` | No | Arguments passed to the initialization script (supports variable expansion) |
145
146 If `script-url`, `script-dir`, and `script-path` are all provided, the script is downloaded and executed as the new user during cloud-init. The following variables are available for expansion in `script-args`:
147
148 - `$MACHINE_SCRIPT_URL` — URL of the initialization script
149 - `$MACHINE_SCRIPT_DIR` — directory path for the script
150 - `$MACHINE_FQDN` — fully qualified domain name of the machine (if DNS is configured)
151
152 #### Environment Variable Substitution
153
154 Config values support environment variable substitution using `${VAR}` syntax, similar to Docker Compose files. This is useful for keeping secrets like API tokens out of the config file.
155
156 Supported forms:
157 - `${VAR}` — substitutes the value of `VAR`; errors if the variable is not set
158 - `${VAR:-default}` — substitutes the value of `VAR`, or `default` if the variable is not set
159
160 Examples:
161 ```yaml
162 # DigitalOcean
163 digital-ocean:
164 access-token: ${DO_API_TOKEN}
165 ssh-key: ${SSH_KEY_NAME:-my-ssh-key}
166 dns-zone: example.com
167 machine-size: ${MACHINE_SIZE:-s-4vcpu-8gb}
168 image: ubuntu-22-04-x64
169 region: ${DO_REGION:-nyc3}
170 project: Infrastructure
171 ```
172
173 ```yaml
174 # Vultr
175 vultr:
176 api-key: ${VULTR_API_KEY}
177 ssh-key: ${SSH_KEY_NAME:-my-ssh-key}
178 dns-zone: example.com
179 machine-size: ${MACHINE_SIZE:-vc2-1c-1gb}
180 image: 2136
181 region: ${VULTR_REGION:-ewr}
182 ```
183
184 Substitution is applied to all string values throughout the config file, including the `machines` section.
185
186 #### Advanced Machine Setup
187
188 Examples of advanced machine setup scripts can be found in [the machine-provisioning repository](https://github.com/bozemanpass/machine-provisioning).
189
190 ### Session Management
191
192 Each invocation of `machine` uses a session ID (auto-generated and stored in `~/.machine/session-id.yml`). Machines are tagged with their session ID on creation. By default, `list`, `status`, `list-domain`, and `destroy` only operate on machines from the current session. Use the `--all` flag to include machines from other sessions or machines not created by this tool.
193
194 The session ID can be overridden with the global `--session-id` option.
195
196 ### Automatic Tagging
197
198 Machines created by this tool are automatically tagged with:
199
200 - `machine:created` — identifies the machine as created by this tool
201 - `machine:type:<type-name>` — the machine type from the config
202 - `machine:session:<session-id>` — the session that created the machine
203
204 ### Global Options
205 ```
206 $ machine --help
207 Usage: machine [OPTIONS] COMMAND [ARGS]...
208
209 Options:
210 --debug Enable debug output
211 --quiet Suppress all non-essential output
212 --verbose Enable verbose output
213 --dry-run Run but do not do anything
214 --config-file <PATH> Specify the config file (default
215 ~/.machine/config.yml)
216 --session-id <ID> Override the default session ID
217 -h, --help Show this message and exit.
218
219 Commands:
220 create Create a machine
221 destroy Destroy one or more machines
222 domains List dns domains
223 list List machines
224 list-domain List domain records
225 projects List projects
226 ssh-keys List ssh keys
227 status Machine status
228 types List configured machine types
229 version Display version
230 ```
231
232 ### Commands
233
234 #### create
235
236 Create a new machine on the configured provider. By default, the machine is initialized with cloud-init (using the specified `--type` from config) and a DNS A record is created.
237
238 ```
239 $ machine create --help
240 Usage: machine create [OPTIONS]
241
242 Create a machine
243
244 Options:
245 -n, --name <MACHINE-NAME> Name for new machine [required]
246 -t, --tag <TAG-TEXT> Tag to be applied to new machine
247 -m, --type <MACHINE-TYPE> Machine type from config (required if --initialize)
248 -r, --region <REGION-CODE> Region (overrides config default)
249 -s, --machine-size <MACHINE-SLUG>
250 Machine size (overrides config default)
251 -s, --image <IMAGE-NAME> Image (overrides config default)
252 --wait-for-ip / --no-wait-for-ip Wait for IP address assignment (default: off)
253 --update-dns / --no-update-dns Create DNS A record (default: on)
254 --initialize / --no-initialize Initialize with cloud-init (default: on)
255 -o, --output <FORMAT> Output format (json)
256 -h, --help Show this message and exit.
257 ```
258
259 Output formats:
260 - Default: human-readable status messages
261 - `--output json`: JSON object with id, name, tags, region, ip, type
262
263 JSON output example:
264 ```json
265 {
266 "id": "12345678",
267 "name": "my-machine",
268 "tags": ["machine:created", "machine:type:example", "machine:session:abc12345"],
269 "region": "nyc3",
270 "ip": "203.0.113.10",
271 "type": "example"
272 }
273 ```
274
275 When `--update-dns` is enabled (the default), the command waits for the instance's IP address and creates an A record in the configured `dns-zone` with a 5-minute TTL.
276
277 When `--initialize` is enabled (the default), a cloud-config user-data payload is generated that creates a non-root user with sudo access, installs the SSH key, and optionally downloads and runs an initialization script.
278
279 If a `project` is configured (DigitalOcean only), the machine is automatically assigned to that project.
280
281 #### destroy
282
283 Destroy one or more machines by instance ID. By default, requires confirmation and deletes associated DNS records.
284
285 ```
286 $ machine destroy --help
287 Usage: machine destroy [OPTIONS] [DROPLET-IDS]...
288
289 Destroy one or more machines
290
291 Options:
292 --confirm / --no-confirm Require confirmation (default: on)
293 --delete-dns / --no-delete-dns Delete associated DNS records (default: on)
294 --all Include machines not created by this tool
295 or by other sessions
296 -h, --help Show this message and exit.
297 ```
298
299 Confirmation requires typing exactly `YES` (not "y", "yes", or "Yes"). Use `--no-confirm` to skip.
300
301 Safety checks prevent destroying machines that were not created by this tool or that belong to a different session, unless `--all` is specified.
302
303 #### list
304
305 List machines with optional filtering.
306
307 ```
308 $ machine list --help
309 Usage: machine list [OPTIONS]
310
311 List machines
312
313 Options:
314 --id <MACHINE-ID> Filter by id
315 -n, --name <MACHINE-NAME> Filter by name
316 -t, --tag <TAG-TEXT> Filter by tag
317 -m, --type <MACHINE-TYPE> Filter by type
318 -r, --region <REGION> Filter by region
319 -o, --output <FORMAT> Output format (json)
320 -q, --quiet Only display machine IDs
321 --unique Return an error if more than one match
322 --all Include all machines from all sessions
323 -h, --help Show this message and exit.
324 ```
325
326 Output formats:
327 - Default: `name (id, region, type): ip_address`
328 - `--quiet`: droplet IDs only
329 - `--output json`: JSON array of machine objects
330
331 JSON output example:
332 ```json
333 [
334 {
335 "id": "12345678",
336 "name": "my-machine",
337 "tags": ["machine:created", "machine:type:example", "machine:session:abc12345"],
338 "region": "nyc3",
339 "ip": "203.0.113.10",
340 "type": "example"
341 }
342 ]
343 ```
344
345 #### status
346
347 Check the status of machines, including querying a custom status endpoint.
348
349 ```
350 $ machine status --help
351 Usage: machine status [OPTIONS]
352
353 Machine status
354
355 Options:
356 --id <MACHINE-ID> Filter by id
357 -n, --name <MACHINE-NAME> Filter by name
358 -t, --tag <TAG-TEXT> Filter by tag
359 -m, --type <MACHINE-TYPE> Filter by type
360 -r, --region <REGION> Filter by region
361 -o, --output <FORMAT> Output format (json)
362 --status-check <CHECK> Status check to perform (default: cloud-init-status)
363 -q, --quiet Only display machine IDs
364 --all Include all machines from all sessions
365 -h, --help Show this message and exit.
366 ```
367
368 Output formats:
369 - Default: human-readable status line
370 - `--output json`: JSON array of status objects
371
372 JSON output example:
373 ```json
374 [
375 {
376 "name": "my-machine",
377 "id": "12345678",
378 "droplet-status": "active",
379 "cloud-init-status": "done"
380 }
381 ]
382 ```
383
384 In addition to the provider-reported instance status, this command queries each machine at `http://<ip>:4242/cgi-bin/<status-check>` (default: `cloud-init-status`) for custom status information. If the endpoint is unreachable, the status is reported as `UNKNOWN`.
385
386 #### list-domain
387
388 List DNS records within a domain zone.
389
390 ```
391 $ machine list-domain --help
392 Usage: machine list-domain [OPTIONS] [ZONE]
393
394 List domain records
395
396 Options:
397 -n, --name <RECORD-NAME> Filter by record name
398 -m, --type <RECORD-TYPE> Filter by record type (default: A and AAAA, use * for all)
399 -o, --output <FORMAT> Output format (json)
400 -q, --quiet Only display record names
401 --all Include all records from all sessions
402 -h, --help Show this message and exit.
403 ```
404
405 If `ZONE` is omitted, uses the `dns-zone` from config. By default, only shows A and AAAA records associated with machines from the current session.
406
407 Output formats:
408 - Default: `name\ttype\tdata`
409 - `--quiet`: record names only
410 - `--output json`: JSON array of record objects
411
412 JSON output example:
413 ```json
414 [
415 {
416 "id": "98765432",
417 "droplet": {
418 "id": "12345678",
419 "name": "my-machine",
420 "tags": ["machine:created"],
421 "region": "nyc3",
422 "ip": "203.0.113.10",
423 "type": "example"
424 },
425 "name": "my-machine",
426 "fqdn": "my-machine.example.com",
427 "zone": "example.com",
428 "data": "203.0.113.10",
429 "ttl": 300,
430 "type": "A"
431 }
432 ]
433 ```
434
435 #### domains
436
437 List all DNS domains in your provider account.
438
439 Options:
440 - `-o, --output <FORMAT>`: Output format (`json`)
441
442 JSON output example:
443 ```json
444 ["example.com", "example.org"]
445 ```
446
447 #### ssh-keys
448
449 List SSH keys in your provider account. Output format: `id: name (fingerprint)`
450
451 #### projects
452
453 List project names (DigitalOcean only).
454
455 Options:
456 - `-o, --output <FORMAT>`: Output format (`json`)
457
458 JSON output example:
459 ```json
460 ["Infrastructure", "Web Apps"]
461 ```
462
463 #### types
464
465 List all machine types defined in the config file (from the `machines` section).
466
467 Options:
468 - `-o, --output <FORMAT>`: Output format (`json`)
469
470 JSON output example:
471 ```json
472 ["example", "webserver"]
473 ```
474
[result-id: r3]# cluster Utility for provisioning k8s clusters ## Prerequisites This project uses [uv](https://docs.astral.sh/uv/) for dependency management and builds. Install uv: ``` curl -LsSf https://astral.sh/uv/install.sh | sh ``` ## Development Setup ``` uv sync ``` This creates a `.venv` virtual environment and installs all dependencies (including dev tools like ruff). Run the CLI during development: ``` uv run cluster --help ``` Run the linter: ``` uv run ruff check cluster/ ``` ## Build Build a self-contained executable using [shiv](https://github.com/linkedin/shiv): ``` ./sh/build-package.sh ``` This produces `build/cluster`, a single-file Python zipapp. ## Install Install directly from the GitHub repository using uv: ``` uv tool install git+https://github.com/stirlingbridge/cluster.git ``` Alternatively, download the `cluster` binary from the [releases page](https://github.com/stirlingbridge/cluster/releases), make it executable, and place it on your PATH: ``` chmod +x cluster sudo mv cluster /usr/local/bin/ ```
The file /home/david/projects/stirlingbridge/cluster/README.md has been updated successfully. (file state is current in your context — no need to Read it back)
Done. The README now covers Prerequisites (uv install), Development Setup (uv sync, running the CLI, linting), Build (shiv), and Install (via uv tool install or downloading a release binary) -- matching the structure of machine's README but with cluster-specific details.