instabox 0.6.0 → 0.6.1
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.
- package/bin/instabox.mjs +2 -1
- package/package.json +1 -1
- package/skills/instabox/SKILL.md +31 -20
- package/skills/instabox-basic-setup/SKILL.md +57 -57
- package/skills/instabox-basic-setup/scripts/content.mjs +10 -5
- package/skills/instabox-basic-setup/scripts/install-files.py +82 -0
- package/skills/instabox-basic-setup/scripts/lib.mjs +61 -39
- package/skills/instabox-basic-setup/scripts/plan.mjs +248 -230
- package/skills/instabox-basic-setup/scripts/push.mjs +318 -151
- package/skills/instabox-basic-setup/scripts/scan.mjs +73 -46
- package/skills/instabox-move-repo/SKILL.md +22 -12
- package/skills/instabox-snapshots/SKILL.md +8 -2
package/bin/instabox.mjs
CHANGED
|
@@ -34,7 +34,8 @@ const API = (
|
|
|
34
34
|
process.env.INSTABOX_API ||
|
|
35
35
|
'https://api.instabox.sh'
|
|
36
36
|
).replace(/\/$/, '');
|
|
37
|
-
|
|
37
|
+
// The template a new box starts from: the CLI's and dashboard's default (shown as standard).
|
|
38
|
+
const IMAGE = process.env.INSTABOX_IMAGE || 'agent-browser';
|
|
38
39
|
// SSH certificate lifetime for a box with no timeout of its own.
|
|
39
40
|
const CERT_MIN = 120;
|
|
40
41
|
// With no provider selected, the browser confirms its existing Instabox account
|
package/package.json
CHANGED
package/skills/instabox/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# instabox CLI
|
|
9
9
|
|
|
10
|
-
An instabox sandbox is a Linux VM (Debian, one user with passwordless sudo: named after your local username when created with `instabox create`, `user` from the dashboard, `dev` on older boxes; `instabox get <box>` shows it). It has a root disk and a persistent `/data` disk; HOME is `/data/home`.
|
|
10
|
+
An instabox sandbox is a Linux VM (Debian, one user with passwordless sudo: named after your local username when created with `instabox create`, `user` from the dashboard, `dev` on older boxes; `instabox get <box>` shows it). It has a root disk and a persistent `/data` disk; HOME is `/data/home`. Each box belongs to an organization; access follows its membership and sandbox permissions.
|
|
11
11
|
|
|
12
12
|
## Sign in
|
|
13
13
|
|
|
@@ -16,15 +16,15 @@ instabox whoami # who is signed in (fails if nobody)
|
|
|
16
16
|
instabox login # confirm the browser's account, or choose GitHub / Google
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
A GitHub sign-in and a Google sign-in are separate accounts with separate boxes. Both `instabox login` and `npx instabox@latest` open account confirmation in the browser: use its current Instabox account or choose another. To go directly to a provider, use `instabox login --provider github|google`, or `npx instabox@latest --github|--google`. The CLI shares the sign-in `npx` made. Device-code sign-in (`--device`) uses GitHub.
|
|
19
|
+
A GitHub sign-in and a Google sign-in with the same verified email share one account; with different emails they are separate accounts with separate boxes. Both `instabox login` and `npx instabox@latest` open account confirmation in the browser: use its current Instabox account or choose another. To go directly to a provider, use `instabox login --provider github|google`, or `npx instabox@latest --github|--google`. The CLI shares the sign-in `npx` made. Device-code sign-in (`--device`) uses GitHub.
|
|
20
20
|
|
|
21
21
|
For CI or an agent without a browser, use an API key instead: `export INSTABOX_API_KEY=ibk_...` (the user creates it on the dashboard's API Keys page). Never print, commit or log a key.
|
|
22
22
|
|
|
23
23
|
## Templates
|
|
24
24
|
|
|
25
|
-
`
|
|
25
|
+
`agent-browser` is the default, so `create` needs no `--template` for ordinary work. It is Debian with git, gh, Node.js 22, Python (uv), Go, Rust, a C toolchain, Docker, Claude Code and Codex, plus a desktop (VNC, Chromium) ready for browsing or driving a GUI from the start. The dashboard shows it as **standard**. Pick another template only for its one purpose:
|
|
26
26
|
|
|
27
|
-
- `
|
|
27
|
+
- `universal`: the same tools without the desktop, which installs itself the first time it is used (about half a minute); a smaller root disk. The dashboard shows it as **basic**.
|
|
28
28
|
- `herdr`: for the herdr client.
|
|
29
29
|
- `orca`: for Orca desktop and mobile.
|
|
30
30
|
|
|
@@ -40,39 +40,50 @@ instabox ssh work # interactive shell (leave with:
|
|
|
40
40
|
instabox exec work -- uname -a # one command, no SSH client needed
|
|
41
41
|
instabox exec work --cwd /data/home -- ls -la # exec runs `bash -lc` as the box's user
|
|
42
42
|
instabox files work /data/home # list a directory (or print a text file)
|
|
43
|
-
instabox auto-suspend work 10m #
|
|
43
|
+
instabox auto-suspend work 10m # idle time before it pauses (fixed at 30m unless an admin gave the account manual lifecycle)
|
|
44
44
|
instabox timeout work 2h --on-timeout pause # pause (keeps memory) or kill at the deadline
|
|
45
45
|
instabox stop work && instabox start work # stop ends processes, keeps disks
|
|
46
46
|
instabox suspend work && instabox resume work # pause/resume keeps running processes
|
|
47
|
+
instabox rename work api # rename in place, any state; ID and port URLs stay
|
|
47
48
|
instabox rm work # delete the box
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
- Names or IDs (`m_...`) both work wherever `<id-or-name>` is accepted.
|
|
51
|
-
- `create` options: `--template T` or `--snapshot S`, `--auto-suspend D|0`, `--timeout D [--on-timeout pause|kill]`, `--meta K=V`, `--
|
|
52
|
-
- A
|
|
52
|
+
- `create` options: `--template T` or `--snapshot S`, `--auto-suspend D|0`, `--timeout D [--on-timeout pause|kill]`, `--meta K=V`, `--org O`, `--secrets all|NAME,NAME|none`, `--env-secrets NAME,NAME|none`, `--no-wait`. Every box is the same size (`instabox usage` shows it); there are no size flags.
|
|
53
|
+
- A box pauses itself after 30 idle minutes (no agent working, no terminal use, little CPU) and wakes on the next `exec`, `ssh`, `files`, public port request or `instabox wake <box>`; a paused box keeps its memory and processes. The 30 minutes are fixed for every account unless an admin gave it manual lifecycle: only then do `--auto-suspend 0` (always on) and `--auto-suspend 2h` apply, and the CLI prints a note when the server kept 30m. Timeouts are capped at 4h the same way. Do not try to keep a box on by other means; a paused box resumes on the next command.
|
|
53
54
|
- Use `exec` to run a command and read its output, and `ssh` for an interactive shell.
|
|
54
55
|
- `exec` returns the command's exit code and takes `--stdin` to pipe input. It stops the command after `--timeout` seconds: 60 by default, 600 at most. Run anything longer (installs, builds, test suites) over SSH, which has no time limit and also returns the exit code: `instabox ssh work -- 'cd /data/workspace/app && npm ci'`.
|
|
55
56
|
- The box's HOME has `MACHINE.md` (which box it is, its size, what survives a stop, the installed tools) and `AGENTS.md`. Both are worth reading before working in the box; they appear a moment after it starts.
|
|
56
57
|
- A public URL for a web server in the box: `instabox port publish work 3000` prints it (`instabox port ls`, `instabox port unpublish work 3000`). The URL works while the box runs.
|
|
57
58
|
|
|
58
|
-
##
|
|
59
|
+
## Organization Secrets
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
Secrets are encrypted key=value entries owned by an org. Each entry has one delivery mode:
|
|
62
|
+
|
|
63
|
+
- `proxy`: the box receives a placeholder. The outbound proxy replaces it in ordinary HTTPS header values only when the destination matches the Secret's allowed hosts. Code in the box can use this credential but does not receive its real value through Secret delivery. No header-name configuration is needed.
|
|
64
|
+
- `env`: the box receives the real value. Use this explicitly for native database credentials, local signing, and clients that cannot use header substitution. Code running in the box can read and copy the value.
|
|
65
|
+
|
|
66
|
+
A new box without Secret selection flags keeps the existing Vault behavior. Opt into Secrets with `--secrets` or `--env-secrets`, or use `secrets select` later. At creation, `--env-secrets` alone also selects all org proxy Secrets. Select env names explicitly; there is no env `all` mode. Metadata labels do not select Secrets, and Secrets do not deliver files.
|
|
61
67
|
|
|
62
68
|
```sh
|
|
63
|
-
instabox
|
|
64
|
-
instabox
|
|
65
|
-
instabox
|
|
66
|
-
instabox
|
|
67
|
-
instabox
|
|
68
|
-
instabox
|
|
69
|
+
instabox secrets ls --org team # metadata only
|
|
70
|
+
instabox secrets show work # selection and independent proxy/guest status
|
|
71
|
+
instabox secrets set SERVICE_API_KEY --org team --delivery proxy --host api.example.com
|
|
72
|
+
instabox secrets set DATABASE_URL --org team --delivery env
|
|
73
|
+
instabox create --name app --org team --secrets SERVICE_API_KEY --env-secrets DATABASE_URL
|
|
74
|
+
instabox secrets select work --env-secrets DATABASE_URL
|
|
75
|
+
instabox secrets push work # refresh managed env/trust on a running box
|
|
76
|
+
instabox create --name tenant --no-vault --secrets none --env-secrets none
|
|
69
77
|
```
|
|
70
78
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
79
|
+
- Values for `set` come from stdin or a hidden prompt. Never print a credential or put its value into agent output, chat, command arguments, or logs. Before asking for a credential, check metadata with `secrets ls --org <org>` and `secrets show <box>`. A silent check such as `test "${NAME+x}" = x` tests variable presence without revealing it; it does not prove successful proxy authorization.
|
|
80
|
+
- Org commands default to the signed-in user's personal org. Use `--org` for a team, and select only Secrets from the box's org. Value, host, delivery-mode and deletion changes require an org owner/admin sign-in. API keys with the appropriate machine scopes can inspect metadata and manage sandbox selections, but cannot retrieve or change Secret values.
|
|
81
|
+
- Selection updates replace each supplied list; omitted lists remain unchanged. Preserve existing names when adding to a selection. Proxy selection accepts `all`; both selections accept `none` or `[]`. JSON arrays allow a Secret literally named `all` or `none`.
|
|
82
|
+
- Proxy delivery requires allowed hosts and works with literal placeholders in HTTPS headers on port 443. Do not hash, encode, or sign with a placeholder. Native database TLS is not HTTPS. Unsupported traffic never falls back to real-value env delivery.
|
|
83
|
+
- Rotating an existing proxy value preserves its placeholder. Env refresh and newly added variables affect new shells that load the managed environment; restart or reconfigure existing applications and connection pools. Removing a selection cannot recover env values already copied into processes, files, or snapshots. Revoke or rotate those credentials at the upstream service when necessary.
|
|
84
|
+
- For a box intended for another person or tenant, disable personal Vault injection with `--no-vault` and explicitly select only the org Secrets they may use. For a fresh box with neither system's credentials, use `--no-vault --secrets none --env-secrets none`. Empty Secret selections alone still permit Vault injection. These flags do not remove credentials already copied into a source snapshot.
|
|
85
|
+
- For an existing box, disable Vault with `start BOX --no-vault` or `resume BOX --no-vault` and explicitly clear both Secret selections with `secrets select BOX --secrets none --env-secrets none`. Refresh a running guest with both `vault push BOX` and `secrets push BOX`. Existing processes and copied files can retain values; disabling either system does not recover those copies.
|
|
86
|
+
- Vault remains available through `vault set`, `set-file`, `import`, `ls`, `rm`, `show`, and `push`. The CLI warns about future retirement. A box may use Vault and Secrets together; Secrets wins same-named environment variables. Neither store automatically copies or deletes the other. Move values deliberately when the user is ready.
|
|
76
87
|
|
|
77
88
|
## Moving work onto a box
|
|
78
89
|
|
|
@@ -1,108 +1,108 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: instabox-basic-setup
|
|
3
|
-
description: Use when the user wants their instabox
|
|
3
|
+
description: Use when the user wants their instabox setup to use local tokens, git config or editor config — "put my tokens / dotfiles / git config into instabox", "set up my box like my laptop", "move my setup to instabox". Produces a names-only scan and a reviewable plan, saves selected credentials as org key=value Secrets with explicit proxy/env modes, grants them only through sandbox selections, and copies approved ordinary config files directly to named running sandboxes. It does not put files in the Secret store or automatically give real credentials to future boxes. Moves no code; for a repo, use instabox-move-repo.
|
|
4
4
|
metadata:
|
|
5
5
|
installed-by: instabox
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Basic setup with org Secrets and sandbox config copies
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Use the user's chosen org and sandbox IDs. Secrets are shared within an org, but real-value `env` delivery requires explicit sandbox selection. A `proxy` Secret supplies a placeholder for HTTPS header substitution at its allowed hosts. Other org sandboxes selecting all proxy Secrets, including future sandboxes, can also use a newly saved proxy credential. Updating an existing Secret affects every sandbox already authorized to use it.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Git and editor configuration is copied separately, once, to the named running sandboxes. It is not stored as Secrets, does not consume Secret quota, and is not reapplied on start or resume. This skill moves no repository or agent history. Use `instabox-move-repo` for code and conversation history.
|
|
13
13
|
|
|
14
|
-
| Group |
|
|
14
|
+
| Group | Destination | Sources |
|
|
15
15
|
| --- | --- | --- |
|
|
16
|
-
| Tokens |
|
|
17
|
-
| Git |
|
|
18
|
-
| Editor and terminal |
|
|
16
|
+
| Tokens | Encrypted org key=value Secrets; explicit per-token `proxy` or `env` mode | GitHub token through `gh auth token`; known service credentials in the environment or shell startup files; other credential-like exports listed unchecked |
|
|
17
|
+
| Git | Files under the selected sandbox's home | `.gitconfig`, `.config/git/config`, global gitignore |
|
|
18
|
+
| Editor and terminal | Files under the selected sandbox's home | `.vimrc`, nvim config without plugin checkouts, `.tmux.conf` |
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The scripts beside this file require Node 18+ locally and Python 3 in sandboxes receiving config files:
|
|
21
21
|
|
|
22
|
-
| Script |
|
|
22
|
+
| Script | Responsibility |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| `scan.mjs` | Read-only inventory
|
|
25
|
-
| `plan.mjs` |
|
|
26
|
-
| `push.mjs` |
|
|
27
|
-
| `content.mjs` |
|
|
24
|
+
| `scan.mjs` | Read-only inventory of names, source locations, sizes and manual setup requirements; no credential values in output |
|
|
25
|
+
| `plan.mjs` | Write and strictly parse the editable plan, binding it to an org and Secret metadata |
|
|
26
|
+
| `push.mjs` | Read approved values into memory, validate all inputs and quotas, save Secrets through CLI stdin, merge explicit sandbox selections, and copy config |
|
|
27
|
+
| `content.mjs` | Rewrite HOME paths, remove recognized credential lines, and adapt git/editor configuration for Linux |
|
|
28
|
+
| `install-files.py` | Install ordinary config files privately, refusing symlinks and unsafe paths; no Secret-store or legacy Vault access |
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
Never print credential values, shell startup files, credential-bearing config, or `gh auth token` output. Do not run these scripts against the user's machine unless their request authorizes this setup. Scripts may read credentials internally; their output is limited to names, sizes and outcomes. If a value cannot be obtained, let the user supply it through a hidden CLI prompt with the appropriate org and delivery mode.
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## 1. Scan
|
|
32
|
+
## 1. Scan and choose the org
|
|
34
33
|
|
|
35
34
|
```bash
|
|
36
|
-
instabox whoami
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
instabox whoami
|
|
36
|
+
instabox org ls --json
|
|
37
|
+
S=~/.claude/skills/instabox-basic-setup/scripts # or the installed ~/.agents/skills path
|
|
38
|
+
W=$(mktemp -d)
|
|
39
39
|
node "$S/scan.mjs" > "$W/scan.json"
|
|
40
|
-
|
|
40
|
+
ORG_ID=<stable-org-id>
|
|
41
|
+
instabox secrets ls --org "$ORG_ID" --json > "$W/secrets.json"
|
|
41
42
|
instabox usage --json | jq '.limits.vault // {}' > "$W/limits.json"
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Saving Secret values requires an org owner/admin sign-in. An API key can read metadata but cannot import values. Do not change the user's authentication silently: if value management is rejected, explain that they need an owner/admin session. A 404 for Secret routes means Secret management is not configured on that server.
|
|
46
|
+
|
|
47
|
+
Use `instabox get <box> --json` to resolve each intended sandbox to its stable `m_...` ID and confirm its `orgId`. No sandbox is required when the user only wants to save credentials. Config copying requires explicitly named running sandboxes; do not create a box merely to verify setup unless creation is authorized.
|
|
45
48
|
|
|
46
|
-
## 2.
|
|
49
|
+
## 2. Prepare a concrete plan
|
|
47
50
|
|
|
48
51
|
```bash
|
|
49
|
-
node "$S/plan.mjs" write --scan "$W/scan.json" --
|
|
52
|
+
node "$S/plan.mjs" write --scan "$W/scan.json" --secrets "$W/secrets.json" \
|
|
53
|
+
--org "$ORG_ID" --sandbox <sandbox-id> > "$W/plan.md"
|
|
50
54
|
```
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
Repeat `--sandbox` for additional targets, or omit it to save Secrets without adding env grants or copying files. The plan lists `Org`, `Sandboxes`, checkboxes and one `Delivery` line per token. The org is bound to the metadata used to generate the plan; regenerate for another org. Sandbox IDs and delivery policies can be edited in the plan.
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
A token policy looks like either of these:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
Delivery `env:SERVICE_API_KEY`: `proxy` hosts `api.example.com`
|
|
62
|
+
Delivery `env:DATABASE_URL`: `env` hosts `-`
|
|
63
|
+
```
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
- Something already in the Vault, set there some other way than this skill, is unchecked and says so: checking it **replaces** it (for every box its selector reached). Items this skill set on an earlier run are replaced without asking (the manifest, step 3).
|
|
65
|
+
The `env:` prefix is the scanner's variable ID, not its delivery mode. New token rows start with explicit `env` mode for client compatibility; this gives the named sandboxes the real value. Choose `proxy` and exact approved hosts when the client sends the value literally in an HTTPS header on port 443. Do not infer hosts from a token's value or silently switch modes when a client fails. Native database protocols, local signing and git's encoded credential exchange need the real value or a separate protocol-aware broker. Proxy placeholders cannot be used as real signing or encoding inputs.
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
Well-known token rows are checked by default; unknown and keychain-backed sources are unchecked. Existing Secrets changed outside the last successful run are unchecked, and checking them authorizes replacement in the org. A previous proxy mode is preserved as the starting policy on a repeat run. Files are unchecked when no sandbox is selected. Checking a config file authorizes replacement of that path on each named sandbox.
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
Show the concrete names, modes, allowed hosts, org and target sandboxes. Obtain any choices or authorization the original request did not already provide before reading values or writing. Do not ask again for an action already explicitly authorized. Explain that env values are readable by sandbox code and may persist in processes, files or snapshots; removing a selection cannot recover them.
|
|
70
|
+
|
|
71
|
+
Parse the approved plan:
|
|
62
72
|
|
|
63
73
|
```bash
|
|
64
|
-
node "$S/plan.mjs" parse --scan "$W/scan.json" --
|
|
74
|
+
node "$S/plan.mjs" parse --scan "$W/scan.json" --secrets "$W/secrets.json" \
|
|
75
|
+
--org "$ORG_ID" --limits "$W/limits.json" "$W/plan.md" > "$W/selection.json"
|
|
65
76
|
```
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
- Exit 3: over the Vault's limits (100 items, 256 KiB in all, items already there included; 64 KiB per item). The message gives the totals and the biggest lines: ask the user what to uncheck (nvim's files are one item each), or which old Vault items to remove (`instabox vault rm NAME`), then parse again.
|
|
78
|
+
Exit 1 identifies malformed or missing rows, invalid policies, a changed org/catalog, unsafe config, or a file selection without targets. Fix choices without changing item IDs or deleting rows. Exit 3 identifies quota or copy-size limits. Config files have their own copy limits and do not count as Secret entries. Values are measured before push; plan generation never reads token values. Legacy selector-based plans and manifests grant no authority in the new model and must be regenerated.
|
|
69
79
|
|
|
70
|
-
## 3.
|
|
80
|
+
## 3. Dry-run, then apply the approved selection
|
|
71
81
|
|
|
72
82
|
```bash
|
|
73
|
-
node "$S/push.mjs" --selection "$W/selection.json" --dry-run
|
|
83
|
+
node "$S/push.mjs" --selection "$W/selection.json" --dry-run
|
|
74
84
|
node "$S/push.mjs" --selection "$W/selection.json"
|
|
75
85
|
```
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
The dry-run reads the approved credential sources and prepares filtered config, but makes no writes to InstaBox and does not change its local manifest. Sourcing a shell startup file may run its local initialization code; an approved keychain-backed source may invoke its lookup. Values remain in memory and CLI stdin, never in the plan, manifest, arguments or logs. Review the dry-run's metadata before applying any action not yet authorized.
|
|
78
88
|
|
|
79
|
-
The
|
|
89
|
+
The pusher verifies org ownership, target orgs, Secret IDs and versions, input limits and total Secret quota before mutations. If a selected Secret changed since planning, regenerate the plan. These are preflight checks, not a transaction across all CLI operations: concurrent org edits can still occur during application. A failed run reports partial outcomes; successfully saved Secrets remain in the org. Do not assume rollback or automatic removal of previously copied files.
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
Only selected token names are added to each target's grants; existing selections are preserved, including proxy `all`. Env selection never becomes `all`. Running targets receive an immediate managed-environment refresh; stopped targets can receive selections for their next start, but config copying requires them to be running. A failed or pending guest refresh does not install config that may depend on those credentials. Existing processes need to reload their environment or restart.
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
- `~/.gitconfig` also: commit and tag signing is turned off (`gpgsign`: the signing key is not on the box), `url."git@...".insteadOf` rewrites are commented out (the box has no SSH key), and when `GH_TOKEN` is in the Vault, gh's credential helper for github.com is added (what `gh auth setup-git` writes), so `git clone https://github.com/...` and `git push` use the token.
|
|
93
|
+
Files are transformed before direct copying: this machine's HOME becomes `/data/home`, recognized credential lines are removed, macOS-only lines and unsupported signing/SSH rewrites are commented out. Structured config suspected of containing credentials is rejected rather than copied verbatim. This filtering is best effort, not a guarantee that arbitrary configuration is credential-free. The gh git helper is added only when this plan explicitly selects `GH_TOKEN` with env delivery. An existing org Secret by itself is not a sandbox grant; reuse it through an explicitly reviewed `instabox secrets select` update if needed, preserving other selected names.
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
The local manifest at `~/.config/instabox-basic-setup/pushed.json` stores org IDs, Secret IDs/versions/modes and copied sandbox paths, never values. It does not grant reuse across orgs or across deletion and recreation of a Secret. It records completed work for a repeat plan; it does not sync changes automatically.
|
|
87
96
|
|
|
88
|
-
|
|
97
|
+
## 4. Verify and report
|
|
89
98
|
|
|
90
99
|
```bash
|
|
91
|
-
|
|
92
|
-
instabox
|
|
93
|
-
instabox vault show "$BOX" # what it gets with its metadata
|
|
94
|
-
instabox exec "$BOX" -- 'gh auth status; git config --get user.name; test -n "${CLAUDE_CODE_OAUTH_TOKEN-}${ANTHROPIC_API_KEY-}" && echo claude token set'
|
|
100
|
+
instabox secrets show <box>
|
|
101
|
+
instabox exec <box> -- 'test "${GH_TOKEN+x}" = x && test -f "$HOME/.gitconfig"'
|
|
95
102
|
```
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
## 5. Report
|
|
100
|
-
|
|
101
|
-
Tell the user, in short sections:
|
|
104
|
+
Use silent presence checks and metadata; never print variables to verify them. A placeholder's presence does not prove proxy authorization or upstream success. Confirm client behavior without logging credentials, and explain application restarts when env values changed.
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
- **Left alone**: what they unchecked, items kept because they were set some other way, failures with the reason. Items from an earlier run they unchecked this time are still in the Vault (the push prints them): `instabox vault rm NAME` removes one, and boxes lose it at their next write.
|
|
105
|
-
- **Do these yourself**: every entry of `.manual` in `selection.json`, with its command, for example: Claude Code's sign-in — run `claude setup-token` here yourself, then `instabox vault set CLAUDE_CODE_OAUTH_TOKEN` and paste it (or `claude`, then `/login`, on each box); Codex — `codex login --device-auth` on the box, or `instabox vault set OPENAI_API_KEY`; `gh auth login` here if gh is not signed in.
|
|
106
|
-
- **Good to know**: Vault files are written again at every start and resume, so an edit made to them on a box is replaced; change them here and run this skill again (or `instabox vault rm` the item to keep a box's own copy). Tokens are environment variables of login shells (SSH, `instabox exec`, the terminal), not of systemd services. Shell aliases, SSH keys, cloud credential files and agent config are not part of this setup.
|
|
106
|
+
Report the org Secret names and modes saved, sandbox IDs whose selections changed, config paths copied, failures and manual setup steps. Saving env Secrets without targets adds no grants; replacing a value still affects sandboxes already authorized to use it. Future boxes without Secret selection flags keep their existing Vault behavior. At creation, selecting env Secrets alone also selects all org proxy Secrets; add an explicit proxy selection to narrow that access. Previously saved credentials or config not selected this time remain; deletion or cleanup needs its own intended scope. Rotate or revoke upstream when a previously delivered env value must stop working.
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
Config edits in a sandbox are not overwritten at every resume. Shell aliases, SSH keys, cloud credential files, agent sign-in databases and repositories are outside this inventory. Complete the selected manual sign-in steps only within existing authorization. Remove `$W` when no longer needed; it contains metadata and choices, not credential values.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// What a
|
|
1
|
+
// What a directly copied configuration file becomes: the local file with this
|
|
2
2
|
// machine's HOME rewritten to the box's, lines that look like credentials
|
|
3
3
|
// removed and macOS-only lines commented out. ~/.gitconfig also gets a few
|
|
4
4
|
// changes so git works on a box that has GH_TOKEN and no SSH key.
|
|
@@ -43,7 +43,7 @@ export function scrubText(text, { path, home }) {
|
|
|
43
43
|
// The credential helper `gh auth setup-git` writes: git over HTTPS uses GH_TOKEN through gh.
|
|
44
44
|
export const GH_HELPER = [
|
|
45
45
|
'',
|
|
46
|
-
`# ${TAG}: git over HTTPS to GitHub uses
|
|
46
|
+
`# ${TAG}: git over HTTPS to GitHub uses an explicitly selected env GH_TOKEN through gh`,
|
|
47
47
|
'[credential "https://github.com"]',
|
|
48
48
|
'\thelper =',
|
|
49
49
|
'\thelper = !gh auth git-credential',
|
|
@@ -53,7 +53,7 @@ export const GH_HELPER = [
|
|
|
53
53
|
'',
|
|
54
54
|
].join('\n');
|
|
55
55
|
|
|
56
|
-
// ~/.gitconfig for the box. ghToken: GH_TOKEN
|
|
56
|
+
// ~/.gitconfig for the box. ghToken: GH_TOKEN is explicitly selected for env delivery, so the gh helper is added.
|
|
57
57
|
// Changes, besides scrubText's:
|
|
58
58
|
// - commit/tag signing is turned off: the signing key (GPG, SSH, 1Password) is not on the box;
|
|
59
59
|
// - url.<ssh>.insteadOf rewrites are commented out: the box has no SSH key, and they would
|
|
@@ -90,9 +90,14 @@ export function fileContent(f, { home, ghToken = false } = {}) {
|
|
|
90
90
|
} catch (e) {
|
|
91
91
|
return { error: `${f.src}: ${e.code || 'unreadable'}` };
|
|
92
92
|
}
|
|
93
|
-
if (buf.includes(0)) return { error: `${f.src}: a binary file (
|
|
93
|
+
if (buf.includes(0)) return { error: `${f.src}: a binary file (config copying supports UTF-8 text only)` };
|
|
94
94
|
const text = buf.toString('utf8');
|
|
95
|
+
if (!Buffer.from(text).equals(buf)) return { error: `${f.src}: invalid UTF-8 text` };
|
|
95
96
|
if (f.transform === 'gitconfig') return gitconfigText(text, { home, ghToken });
|
|
96
|
-
if (f.transform === 'raw')
|
|
97
|
+
if (f.transform === 'raw') {
|
|
98
|
+
if (SECRET_VALUE.test(text) || text.split('\n').some((line) => SECRET_KEY_LINE.test(line)))
|
|
99
|
+
return { error: `${f.src}: possible credential in structured config; review it separately` };
|
|
100
|
+
return { text, secrets: 0, macos: 0 };
|
|
101
|
+
}
|
|
97
102
|
return scrubText(text, { path: f.path, home });
|
|
98
103
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# One-time config copying, run as the sandbox user. This is not Secret delivery.
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import stat
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
HOME = '/data/home'
|
|
8
|
+
MARKER = 'INSTABOX-BASIC-SETUP '
|
|
9
|
+
DIRECTORY = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def valid_path(path):
|
|
13
|
+
return (isinstance(path, str) and 0 < len(path) <= 255
|
|
14
|
+
and not any(ord(c) < 32 or ord(c) == 127 or c == '\\' for c in path)
|
|
15
|
+
and all(p and p != '.' and not p.startswith('..') for p in path.split('/')))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def install_files(files, home=HOME):
|
|
19
|
+
results = []
|
|
20
|
+
for entry in files:
|
|
21
|
+
path = entry.get('path')
|
|
22
|
+
result = {'path': path, 'status': 'failed'}
|
|
23
|
+
results.append(result)
|
|
24
|
+
if not valid_path(path) or not isinstance(entry.get('text'), str):
|
|
25
|
+
continue
|
|
26
|
+
data = entry['text'].encode('utf-8')
|
|
27
|
+
if len(data) > 64 * 1024 or b'\x00' in data:
|
|
28
|
+
continue
|
|
29
|
+
fd = None
|
|
30
|
+
temporary = None
|
|
31
|
+
try:
|
|
32
|
+
fd = os.open(home, DIRECTORY)
|
|
33
|
+
parts = path.split('/')
|
|
34
|
+
for part in parts[:-1]:
|
|
35
|
+
try:
|
|
36
|
+
os.mkdir(part, 0o700, dir_fd=fd)
|
|
37
|
+
except FileExistsError:
|
|
38
|
+
pass
|
|
39
|
+
child = os.open(part, DIRECTORY, dir_fd=fd)
|
|
40
|
+
os.close(fd)
|
|
41
|
+
fd = child
|
|
42
|
+
name = parts[-1]
|
|
43
|
+
try:
|
|
44
|
+
current = os.stat(name, dir_fd=fd, follow_symlinks=False)
|
|
45
|
+
if not stat.S_ISREG(current.st_mode):
|
|
46
|
+
continue
|
|
47
|
+
except FileNotFoundError:
|
|
48
|
+
pass
|
|
49
|
+
temporary = '.instabox-setup-' + os.urandom(8).hex()
|
|
50
|
+
output = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=fd)
|
|
51
|
+
with os.fdopen(output, 'wb') as stream:
|
|
52
|
+
stream.write(data)
|
|
53
|
+
stream.flush()
|
|
54
|
+
os.fsync(stream.fileno())
|
|
55
|
+
os.fchmod(stream.fileno(), 0o600)
|
|
56
|
+
os.rename(temporary, name, src_dir_fd=fd, dst_dir_fd=fd)
|
|
57
|
+
temporary = None
|
|
58
|
+
result['status'] = 'written'
|
|
59
|
+
except Exception:
|
|
60
|
+
# The remote shell and filesystem are untrusted. Return only fixed
|
|
61
|
+
# outcomes; exception text and config contents never reach logs.
|
|
62
|
+
pass
|
|
63
|
+
finally:
|
|
64
|
+
if fd is not None:
|
|
65
|
+
if temporary:
|
|
66
|
+
try:
|
|
67
|
+
os.unlink(temporary, dir_fd=fd)
|
|
68
|
+
except OSError:
|
|
69
|
+
pass
|
|
70
|
+
os.close(fd)
|
|
71
|
+
return results
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == '__main__':
|
|
75
|
+
try:
|
|
76
|
+
payload = json.load(sys.stdin)
|
|
77
|
+
result = install_files(payload['files'])
|
|
78
|
+
sys.stdout.write(MARKER + json.dumps({'files': result}) + '\n')
|
|
79
|
+
sys.exit(0 if all(item['status'] == 'written' for item in result) else 1)
|
|
80
|
+
except Exception:
|
|
81
|
+
sys.stdout.write(MARKER + '{"error":"config copy failed"}\n')
|
|
82
|
+
sys.exit(1)
|