anon-pi 0.4.0 → 0.5.0
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/Dockerfile.pi +21 -9
- package/README.md +199 -64
- package/dist/anon-pi.d.ts +849 -75
- package/dist/anon-pi.d.ts.map +1 -1
- package/dist/anon-pi.js +1222 -260
- package/dist/anon-pi.js.map +1 -1
- package/dist/cli.js +1243 -112
- package/dist/cli.js.map +1 -1
- package/examples/Dockerfile.pi-webveil +9 -3
- package/package.json +1 -1
- package/src/anon-pi.ts +1723 -312
- package/src/cli.ts +1470 -124
package/Dockerfile.pi
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
# bash/grep/find expect.
|
|
5
5
|
#
|
|
6
6
|
# HOW anon-pi USES THIS IMAGE (stateful model):
|
|
7
|
-
# anon-pi mounts a PERSISTENT
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
# anon-pi mounts a machine's PERSISTENT host home at the container's /root, so
|
|
8
|
+
# pi's sessions/history/settings and any extensions you `pi install` persist
|
|
9
|
+
# across launches. On a FRESH home, anon-pi seeds ~/.pi/agent once from this
|
|
10
|
+
# image's STAGING dir (/opt/anon-pi-seed/agent) + your imported models.json,
|
|
11
|
+
# then stamps a marker and never overwrites again.
|
|
12
12
|
#
|
|
13
13
|
# So image defaults (extensions, trust.json) go in the STAGING dir, NOT in
|
|
14
14
|
# ~/.pi/agent (which is the mount and would be shadowed). Set
|
|
@@ -27,13 +27,23 @@ RUN apt-get update \
|
|
|
27
27
|
# The official pi coding agent CLI (puts `pi` on PATH).
|
|
28
28
|
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
29
29
|
|
|
30
|
+
# Ensure /root has base shell dotfiles present in the image. The machine home
|
|
31
|
+
# bind-mounts over /root, so on a FRESH home these are the defaults the seed can
|
|
32
|
+
# promote / a shell can fall back to (a bare debian /root may omit .bashrc).
|
|
33
|
+
RUN for f in .bashrc .profile; do \
|
|
34
|
+
[ -f "/root/$f" ] || { [ -f "/etc/skel/$f" ] && cp "/etc/skel/$f" "/root/$f"; }; \
|
|
35
|
+
done; true
|
|
36
|
+
|
|
30
37
|
# The staging dir anon-pi promotes into a fresh home on first launch.
|
|
31
38
|
ENV ANON_PI_STAGE=/opt/anon-pi-seed/agent
|
|
32
39
|
RUN mkdir -p "$ANON_PI_STAGE"
|
|
33
40
|
|
|
34
|
-
# Trust
|
|
35
|
-
# in the persistent home on first launch;
|
|
36
|
-
|
|
41
|
+
# Trust the two cwd roots pi launches into so it never prompts on the mounted
|
|
42
|
+
# project (staged, so it lands in the persistent machine home on first launch;
|
|
43
|
+
# anon-pi never writes this for you):
|
|
44
|
+
# /projects : the projects-root cwd (a project is /projects/<name>)
|
|
45
|
+
# /work : the distinct --mount root cwd (--mount <parent> mounts at /work)
|
|
46
|
+
RUN printf '{"/projects": true, "/work": true}\n' > "$ANON_PI_STAGE/trust.json"
|
|
37
47
|
|
|
38
48
|
# --- Optional: extensions + skills (the capability layer) -------------------
|
|
39
49
|
# Install into the STAGING dir (PI_CODING_AGENT_DIR), so `pi install` records the
|
|
@@ -48,4 +58,6 @@ RUN printf '{"/work": true}\n' > "$ANON_PI_STAGE/trust.json"
|
|
|
48
58
|
# installs and configures that service in the image too. See the worked example
|
|
49
59
|
# examples/Dockerfile.pi-webveil.
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
# The default cwd is the projects root /projects (the projects-root mount
|
|
62
|
+
# target); a bare `--mount` re-roots the cwd at the distinct /work instead.
|
|
63
|
+
WORKDIR /projects
|
package/README.md
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
# anon-pi
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run [pi](https://github.com/earendil-works/pi-mono) on anonymized, jailed **machines**: all of pi's web and DNS egress is forced through a socks5h proxy (fail-closed, leak-proof) by [netcage](https://github.com/wighawag/netcage), while ONE direct hole is opened to a local model on your LAN. Your machines and their conversations live in a browsable host workspace (`~/.anon-pi/`); the container itself is disposable.
|
|
4
4
|
|
|
5
|
-
anon-pi is a thin, opinionated launcher over `netcage run`. It is a separate package on purpose: netcage wraps any tool and stays tool-agnostic; anon-pi holds the pi-specific opinion.
|
|
5
|
+
anon-pi is a thin, opinionated launcher over `netcage run`. It is a separate package on purpose: netcage wraps any tool and stays tool-agnostic; anon-pi holds the pi-specific opinion. netcage owns the jail (network namespaces, firewall, DNS); anon-pi never touches Podman directly and never weakens the forced-egress invariant.
|
|
6
|
+
|
|
7
|
+
> **Upgrading from 0.4.0?** The model changed: a bare positional is now a
|
|
8
|
+
> **project**, not a host path, and `import` / `--fresh` / `--ephemeral` are
|
|
9
|
+
> gone. See [Migrating from 0.4.0](#migrating-from-040).
|
|
10
|
+
|
|
11
|
+
## The model: machines + projects
|
|
12
|
+
|
|
13
|
+
- A **machine** is an **image + a persistent host home**. The home (`machines/<name>/home`) is bind-mounted into the jail at `/root`, so it holds your shell config, pi config + extensions, and your pi conversations. A machine is a named, durable, anonymized workstation. The container is disposable; the home survives.
|
|
14
|
+
- A **project** is a work folder under the **projects root**, mounted into the jail at `/projects/<name>` (pi's cwd). It is just files, image-agnostic. Because pi keys a conversation by its launch cwd, `/projects/<name>` is the conversation key: the same project used from two machines has a separate conversation in each machine's home.
|
|
15
|
+
- The **projects root** is global by default (`~/.anon-pi/projects/`, shared across machines). Override it per-launch with `--mount <host-parent>`, or persistently via `config.json` / `ANON_PI_PROJECTS`.
|
|
16
|
+
- The **proxy** is a `socks5h://` endpoint that anonymizes all egress. It is REQUIRED and never guessed: no proxy, no launch (fail-closed).
|
|
17
|
+
- The **local model** is the one non-proxied path: an RFC1918/link-local `host:port` reached directly, so pi can call a LAN model while everything else stays proxied.
|
|
6
18
|
|
|
7
19
|
## Requirements
|
|
8
20
|
|
|
9
21
|
- **Linux.** anon-pi inherits netcage's platform reality (network namespaces + nftables + rootless Podman). See [Platform](#platform).
|
|
10
22
|
- **[`netcage`](https://github.com/wighawag/netcage)** on your `PATH`.
|
|
11
|
-
- A running **socks5h proxy** (local Tor, `ssh -D`, ...).
|
|
12
|
-
- A **container image with `pi` on its `PATH
|
|
23
|
+
- A running **socks5h proxy** (local Tor, `ssh -D`, wireproxy, ...).
|
|
24
|
+
- A **container image with `pi` on its `PATH`**. `anon-pi init` can build one for you from a shipped `Dockerfile.pi`; see [Providing a pi image](#providing-a-pi-image).
|
|
13
25
|
|
|
14
26
|
## Install
|
|
15
27
|
|
|
@@ -19,83 +31,195 @@ npm i -g anon-pi
|
|
|
19
31
|
npx anon-pi
|
|
20
32
|
```
|
|
21
33
|
|
|
22
|
-
##
|
|
34
|
+
## Quick start
|
|
23
35
|
|
|
24
36
|
```sh
|
|
25
|
-
anon-pi
|
|
37
|
+
anon-pi init # one-time: verify your proxy, capture your local model, pick/build an image
|
|
38
|
+
anon-pi # bare: pick a project (or a shell, or a new project) from the menu
|
|
39
|
+
anon-pi recon # or launch straight into a project
|
|
26
40
|
```
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
- The session config+state is keyed to this folder: re-running `anon-pi` on the same folder **resumes** the same pi config and history.
|
|
42
|
+
`init` is interactive and re-runnable. It:
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
1. **Proxy** — probes common SOCKS ports, confirms SOCKS5 with a real handshake, shows the findings (evidence only, it never labels the exit provider), then runs `netcage verify` and shows the real EXIT IP as proof it is not your host IP. You confirm on that evidence.
|
|
45
|
+
2. **Local model** — captures the `host:port` of your model, probes reachability, and generates the machine's `models.json`.
|
|
46
|
+
3. **Image** — pick a shipped `Dockerfile` (built via `podman build`), an existing image ref, or skip.
|
|
47
|
+
|
|
48
|
+
It then writes `~/.anon-pi/config.json` + the `default` machine. It **never destroys** an existing home; it pre-fills your current values and only adds/updates config + the default machine.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
35
51
|
|
|
36
|
-
|
|
37
|
-
anon-pi
|
|
52
|
+
```
|
|
53
|
+
anon-pi MENU: pick a project (pi), a shell, or a new project
|
|
54
|
+
anon-pi <project> pi in the project (/projects/<project>); exit pi -> host
|
|
55
|
+
anon-pi <project> <pi-args…> forward args to pi (headless/one-shot; no TTY needed)
|
|
56
|
+
anon-pi --shell [<project>] a jailed bash (at ~, or cd'd into <project>) - the project-hopper
|
|
57
|
+
anon-pi -m <machine> [<p>] the same, on <machine> (its own image + home + conversations)
|
|
58
|
+
anon-pi --mount <parent> [<p>] root at a HOST parent folder instead of the projects root
|
|
59
|
+
anon-pi init onboard: verify your proxy, capture your local model, pick an image
|
|
60
|
+
anon-pi machine … manage machines (create / list / set-image / rm)
|
|
61
|
+
anon-pi --delete-home [<m>] delete a machine's home (config + convos); keep its image pin + files
|
|
62
|
+
anon-pi --delete-project <p> delete a project's files + its per-machine sessions; keep the homes
|
|
38
63
|
```
|
|
39
64
|
|
|
40
|
-
|
|
65
|
+
A `<project>` is a folder under the projects root (mounted at `/projects`; pi's cwd). The token `.` means the root itself (a scratch pi at `/projects`, at `/work` under `--mount`, or at `~` for a shell). A named project is created on the host if it does not exist yet.
|
|
41
66
|
|
|
42
|
-
|
|
67
|
+
Every subcommand carries its own help: `anon-pi --help` (the launch surface), `anon-pi init --help`, and `anon-pi machine --help`.
|
|
43
68
|
|
|
44
|
-
|
|
45
|
-
| --- | --- | --- | --- |
|
|
46
|
-
| `ANON_PI_IMAGE` | for run | | container image with `pi` on `PATH` |
|
|
47
|
-
| `ANON_PI_LLM` | yes | | RFC1918/link-local `IP[:port]` of the local model (the one direct hole) |
|
|
48
|
-
| `ANON_PI_PROXY` | yes | | the socks5h proxy (Tor/wireproxy/ssh -D). No default: it is what anonymizes |
|
|
49
|
-
| `ANON_PI_EPHEMERAL` | no | (off) | set to `1` for a throwaway, non-persistent session |
|
|
50
|
-
| `ANON_PI_HOME` | no | `$XDG_CONFIG_HOME/anon-pi` or `~/.config/anon-pi` | anon-pi home |
|
|
51
|
-
| `ANON_PI_CONFIG` | no | `<ANON_PI_HOME>/agent` | canonical seed dir (holds the imported `models.json`) |
|
|
52
|
-
| `ANON_PI_SOURCE_MODELS` | no | `~/.pi/agent/models.json` | (`import`) the host `models.json` to read from |
|
|
69
|
+
### Common tasks
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
| I want to… | Command |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| Set up (first time / reconfigure) | `anon-pi init` |
|
|
74
|
+
| Just pick something to work on | `anon-pi` (the menu) |
|
|
75
|
+
| Work in a project | `anon-pi <project>` |
|
|
76
|
+
| Resume a project's conversation | `anon-pi <project>` (same machine + project ⇒ same session) |
|
|
77
|
+
| Run a one-shot prompt (scriptable) | `anon-pi <project> -p "…"` |
|
|
78
|
+
| Hop between projects / poke the box | `anon-pi --shell` then `cd /projects/<p> && pi` |
|
|
79
|
+
| A scratch pi not tied to a subfolder | `anon-pi .` |
|
|
80
|
+
| Use a separate anonymized environment | `anon-pi -m <machine> <project>` |
|
|
81
|
+
| Jail pi into a host folder you edit with host tools | `anon-pi --mount <host-parent> <subfolder>` |
|
|
82
|
+
| Install system tools and keep them | `anon-pi --keep --shell` (then `apt install …`) |
|
|
83
|
+
| Add a second machine | `anon-pi machine create <name> --image <ref>` |
|
|
84
|
+
| Reset a machine's conversations | `anon-pi --delete-home [<machine>]` |
|
|
85
|
+
| Delete a project (files + its sessions) | `anon-pi --delete-project <project>` |
|
|
55
86
|
|
|
56
|
-
|
|
87
|
+
### A first session, end to end
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
anon-pi init # verify proxy (see the real exit IP), capture your model, build an image
|
|
91
|
+
anon-pi recon # creates ~/.anon-pi/projects/recon, launches pi there
|
|
92
|
+
# … work in pi; exit pi returns you to your host shell …
|
|
93
|
+
anon-pi recon # later: same project, same machine ⇒ your conversation resumes
|
|
94
|
+
anon-pi --shell # sit on the machine: cd /projects/recon && pi, run tmux, etc.
|
|
95
|
+
```
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
2. **First-launch seed (only when the home is fresh).** anon-pi mounts the image's staged defaults and your imported `models.json`, and the container promotes them into the fresh home, then stamps a `.anon-pi-seed` marker. On later launches the marker is present, so nothing is re-copied and **your changes (added models, installed extensions) are never clobbered**.
|
|
60
|
-
3. **Run.** `netcage run --proxy <proxy> --allow-direct <ANON_PI_LLM> -it -v <workdir> -v <state>:/root/.pi/agent [-v <models.json>:...:ro] <image> sh -c '<seed-if-fresh> && exec pi'`.
|
|
97
|
+
Nothing you care about lives in the container: your conversation, config, and files are all in `~/.anon-pi/`, so the throwaway container going away on exit loses nothing.
|
|
61
98
|
|
|
62
|
-
|
|
99
|
+
### The bare menu
|
|
63
100
|
|
|
64
|
-
|
|
101
|
+
Run `anon-pi` with no project and you get an interactive, arrow-key menu (up/down or `k`/`j` to move, Enter to select, Ctrl-C to quit). It lists the projects under the active root and marks which the current machine has already worked on ("used" vs "new here"), plus a `.` here entry, a shell entry, and a "new project" entry. Picking a project launches it **byte-for-byte identically** to typing the equivalent command. `-m <machine>` and `--mount <parent>` with no project also open the menu (scoped to that machine / root).
|
|
65
102
|
|
|
66
|
-
|
|
103
|
+
The menu needs a TTY. Without one it refuses and tells you to name a project directly (`anon-pi <project>`).
|
|
104
|
+
|
|
105
|
+
### `--shell`: the project-hopper
|
|
106
|
+
|
|
107
|
+
pi cannot `cd` into a different project mid-session (a conversation is keyed to its launch cwd). So when you want to move between projects, or poke around the machine, use a jailed shell:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
anon-pi --shell # bash at ~ (the machine home), inside the jail
|
|
111
|
+
anon-pi --shell recon # bash cd'd into /projects/recon
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
From inside the shell you can `cd` between `/projects/*` and run `pi` yourself in whichever one you want. The shell forwards no arguments (`anon-pi --shell recon extra` is an error); run pi from inside it instead. Same forced-egress jail as a pi launch.
|
|
115
|
+
|
|
116
|
+
### Headless / one-shot
|
|
117
|
+
|
|
118
|
+
Any tokens after the project are forwarded to pi verbatim, and this path does **not** need a TTY (so it fits scripts and pipes):
|
|
67
119
|
|
|
68
120
|
```sh
|
|
69
|
-
anon-pi
|
|
121
|
+
anon-pi recon -p "summarize the findings in ./notes"
|
|
70
122
|
```
|
|
71
123
|
|
|
72
|
-
###
|
|
124
|
+
### `--mount`: root at a host parent (the caveat)
|
|
125
|
+
|
|
126
|
+
`--mount <parent>` re-roots this launch at a HOST parent folder instead of the projects root: the parent is mounted at `/work`, and a `<project>` positional then names a subfolder under it (`/work/<project>`).
|
|
127
|
+
|
|
128
|
+
The caveat: `<parent>` is a **host parent directory**, not a single project path. anon-pi mounts the parent and treats the positional as a name under it; it does not mount an arbitrary host folder as the project itself. Use `--mount` when you want a whole host tree available at `/work` (for example your real code checkout's parent), and pick the subfolder as the project.
|
|
129
|
+
|
|
130
|
+
### Kept vs throwaway (`--rm` / `--keep`)
|
|
73
131
|
|
|
74
|
-
|
|
132
|
+
The container is **throwaway by default** (`--rm`): it is deleted the moment pi exits. Your machine home and project files persist regardless (they are host mounts); only the container's own scratch filesystem is discarded.
|
|
133
|
+
|
|
134
|
+
Pass `--keep` for an exploratory flow where the container's filesystem should survive across exits (for example you `apt install` something, quit, and re-enter the same container):
|
|
75
135
|
|
|
76
136
|
```sh
|
|
77
|
-
|
|
137
|
+
anon-pi --keep recon # keep this container; re-entering resumes it
|
|
78
138
|
```
|
|
79
139
|
|
|
140
|
+
anon-pi finds a kept container by netcage's managed label and `netcage start`s it on re-entry. `--keep` and `--rm` together is an error (pick one; `--rm` is the default).
|
|
141
|
+
|
|
142
|
+
## Managing machines
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
anon-pi machine create <name> [--image <ref>] create a machine, pin its image
|
|
146
|
+
anon-pi machine list list machines and their images
|
|
147
|
+
anon-pi machine set-image <name> <ref> re-pin the image (WARNS; no reseed)
|
|
148
|
+
anon-pi machine rm <name> [--yes] delete the machine + its home
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A machine's home is seeded on FIRST LAUNCH, not at create. `set-image` re-pins the image only and **warns**: it does not reseed or touch the home, so the home's extensions were built for the old image. `rm` confirms on a TTY, skips the prompt with `--yes`, and aborts non-interactively without it (it never deletes unprompted in a script). `create` with no `--image` and no TTY is an error (a machine needs an image to launch).
|
|
152
|
+
|
|
153
|
+
If you never create a machine explicitly, launches use the `default` machine (which `init` creates). Give a machine its own image + home + conversations by naming it with `-m`.
|
|
154
|
+
|
|
155
|
+
## Deleting data
|
|
156
|
+
|
|
157
|
+
The destructive verbs replace the old `--fresh`. Each confirms on a TTY, skips with `--yes`, and aborts non-interactively without it.
|
|
158
|
+
|
|
159
|
+
- `anon-pi --delete-home [<machine>]` deletes ONE machine's home (its config, conversations, shell env), but **keeps its image pin** (relaunch to seed a fresh home) and **keeps all project files** (they live under the projects root, not in the home). The machine defaults to `config.defaultMachine` (else `default`) when omitted.
|
|
160
|
+
- `anon-pi --delete-project <project>` deletes that project's files (its folder under the projects root) AND that project's per-machine session dir in every machine home. The machine homes are otherwise kept. The project name is required.
|
|
161
|
+
|
|
162
|
+
## Environment
|
|
163
|
+
|
|
164
|
+
Environment variables are **overrides**: env wins over `config.json`, which wins over a machine's `machine.json`, which wins over the built-in defaults.
|
|
165
|
+
|
|
166
|
+
| Var | Required | Default | Meaning |
|
|
167
|
+
| --- | --- | --- | --- |
|
|
168
|
+
| `ANON_PI_PROXY` | yes | (none) | the socks5h proxy URL (Tor/wireproxy/`ssh -D`). No default: it is what anonymizes, so it is never guessed (fail-closed). |
|
|
169
|
+
| `ANON_PI_LLM` | yes | (none) | RFC1918/link-local `IP[:port]` of the local model (the one direct hole). |
|
|
170
|
+
| `ANON_PI_IMAGE` | fallback | (none) | container image with `pi` on `PATH`, used when a machine has no image pinned. |
|
|
171
|
+
| `ANON_PI_HOME` | no | `~/.anon-pi` | the anon-pi workspace dir (holds `config.json`, `machines/`, `projects/`). NOT under `~/.config`. |
|
|
172
|
+
| `ANON_PI_PROJECTS` | no | `<ANON_PI_HOME>/projects` | projects-root override (the host dir mounted at `/projects`). |
|
|
173
|
+
|
|
174
|
+
`config.json` (written by `init`) holds the persistent `proxy`, `llm`, `defaultMachine`, and optional `projects` root; a machine's `machine.json` holds its pinned `image` and optional per-machine `projects` override. Env vars override all of these per-launch.
|
|
175
|
+
|
|
176
|
+
## Forced egress: honesty by evidence
|
|
177
|
+
|
|
178
|
+
anon-pi does not tell you "you are anonymous". It composes a launch that **forces** every bit of pi's TCP egress through your `socks5h://` proxy (fail-closed, DNS resolved proxy-side so hostnames never hit your host resolver), with exactly one direct hole to your local model. That is the invariant netcage enforces; anon-pi never strips or adds an egress flag.
|
|
179
|
+
|
|
180
|
+
For proof, it shows you **evidence**, never a label: `init` (and `netcage verify`) run a real request through the proxy and print the actual EXIT IP, so you can see it is not your host IP. It deliberately **never claims which exit provider** you are using (Tor, a VPN, an SSH tunnel): it can only observe the exit, not name the network behind it. A running `tor`/`wireproxy` process is surfaced as a WEAK local hint, clearly not a claim about the exit.
|
|
181
|
+
|
|
182
|
+
## Layout on disk
|
|
183
|
+
|
|
184
|
+
Everything anon-pi keeps lives under `~/.anon-pi/` (override with `ANON_PI_HOME`):
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
~/.anon-pi/
|
|
188
|
+
config.json proxy, llm, defaultMachine, (optional) projects root
|
|
189
|
+
machines/
|
|
190
|
+
default/
|
|
191
|
+
machine.json pinned image, (optional) per-machine projects override
|
|
192
|
+
home/ the persistent $HOME bind-mounted at /root
|
|
193
|
+
.pi/agent/ pi config, extensions, sessions (your conversations)
|
|
194
|
+
models.json the generated local-model seed (mounted read-only on a fresh home)
|
|
195
|
+
projects/ the default global projects root (mounted at /projects)
|
|
196
|
+
recon/
|
|
197
|
+
...
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The home is the durable, inspectable store. On a FRESH machine home, the image's staged defaults (`/opt/anon-pi-seed/agent`) and the machine's `models.json` are promoted in once and a marker is stamped; after that pi owns the home and your changes (added models, installed extensions) are never clobbered.
|
|
201
|
+
|
|
80
202
|
## Providing a pi image
|
|
81
203
|
|
|
82
|
-
anon-pi does not ship or default an image:
|
|
204
|
+
anon-pi does not ship or default an image: a machine points at an image that has the `pi` CLI on its `PATH`. pi's maintainers do not publish an official prebuilt image, so the reputable path is to **build a small one from the upstream-documented recipe** (which installs the official [`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) npm package, no third-party image to trust).
|
|
83
205
|
|
|
84
|
-
|
|
206
|
+
`anon-pi init` can build it for you: pick the shipped `Dockerfile.pi` and it runs `podman build` and pins the result. You can also build it yourself:
|
|
85
207
|
|
|
86
208
|
```sh
|
|
87
209
|
# from wherever this package's Dockerfile.pi is (e.g. node_modules/anon-pi)
|
|
88
210
|
podman build -t localhost/anon-pi-pi:latest -f Dockerfile.pi .
|
|
89
211
|
export ANON_PI_IMAGE=localhost/anon-pi-pi:latest
|
|
212
|
+
# or pin it to a machine:
|
|
213
|
+
anon-pi machine set-image default localhost/anon-pi-pi:latest
|
|
90
214
|
```
|
|
91
215
|
|
|
92
|
-
The image only needs `pi` reachable on `PATH`. anon-pi
|
|
216
|
+
The image only needs `pi` reachable on `PATH`. anon-pi bind-mounts the machine home over the container's `/root` and seeds a fresh home from the image's staging dir, so the image needs **no `ENTRYPOINT` and no config volume**.
|
|
93
217
|
|
|
94
218
|
A community image also exists ([`gni/pi-coding-agent-container`](https://github.com/gni/pi-coding-agent-container)); it is third-party and unvetted, so review it yourself before trusting it with your (anonymized) credentials.
|
|
95
219
|
|
|
96
220
|
### Extensions, skills, and their services go in the image
|
|
97
221
|
|
|
98
|
-
anon-pi deliberately
|
|
222
|
+
anon-pi deliberately generates **only your local model's `models.json`**, never copies your extensions or skills. That is on purpose: your extension set is an identity fingerprint, extensions run code and can leak, and many need a runtime that a copied folder cannot carry (for example `pi-webveil` needs a running SearXNG). The right home for capabilities is the **image**, installed once, reviewably, into the STAGING dir so anon-pi promotes them into a fresh machine home:
|
|
99
223
|
|
|
100
224
|
```dockerfile
|
|
101
225
|
FROM node:24-bookworm-slim
|
|
@@ -103,45 +227,56 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
103
227
|
bash ca-certificates git ripgrep && rm -rf /var/lib/apt/lists/*
|
|
104
228
|
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
105
229
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
|
|
230
|
+
ENV ANON_PI_STAGE=/opt/anon-pi-seed/agent
|
|
231
|
+
RUN mkdir -p "$ANON_PI_STAGE"
|
|
232
|
+
# Trust the two cwd roots so pi never prompts on the mounted project:
|
|
233
|
+
RUN printf '{"/projects": true, "/work": true}\n' > "$ANON_PI_STAGE/trust.json"
|
|
234
|
+
|
|
235
|
+
# Extensions are installed with `pi install` into the STAGING dir (recorded
|
|
236
|
+
# there, promoted into the fresh home), NOT a global npm install:
|
|
237
|
+
# RUN PI_CODING_AGENT_DIR="$ANON_PI_STAGE" pi install npm:pi-webveil
|
|
238
|
+
# ...and an extension that needs a service (pi-webveil -> SearXNG) also installs
|
|
110
239
|
# and configures that service in the image. Its egress is forced through the
|
|
111
240
|
# socks proxy by netcage at runtime, so it must be happy with proxy-only,
|
|
112
241
|
# DNS-through-proxy networking.
|
|
113
242
|
|
|
114
|
-
WORKDIR /
|
|
243
|
+
WORKDIR /projects
|
|
115
244
|
```
|
|
116
245
|
|
|
117
|
-
Install image defaults into the **staging dir** (`PI_CODING_AGENT_DIR=/opt/anon-pi-seed/agent pi install ...`), NOT `~/.pi/agent`: anon-pi mounts
|
|
246
|
+
Install image defaults into the **staging dir** (`PI_CODING_AGENT_DIR=/opt/anon-pi-seed/agent pi install ...`), NOT `~/.pi/agent`: anon-pi mounts the persistent home over `/root` (so `~/.pi/agent` is the mount) and promotes the staging dir into it on a fresh launch. Anything you then `pi install` *inside* a session also persists (it is written to the mounted home). See the shipped `Dockerfile.pi` comments for the exact form.
|
|
118
247
|
|
|
119
|
-
A worked example ships in this package: [`examples/Dockerfile.pi-webveil`](examples/Dockerfile.pi-webveil) builds pi + the `pi-webveil` extension (staged) + a local SearXNG
|
|
248
|
+
A worked example ships in this package: [`examples/Dockerfile.pi-webveil`](examples/Dockerfile.pi-webveil) builds pi + the `pi-webveil` extension (staged) + a local SearXNG. Note the anonymity subtlety it documents: SearXNG's own crawl is anonymized here **because netcage forces every process's egress through the proxy**, so webveil's plain `egress: direct` is correct in-jail (the usual "local SearXNG leaks your IP" caveat does not apply).
|
|
120
249
|
|
|
121
|
-
##
|
|
250
|
+
## Trusting the project
|
|
122
251
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```sh
|
|
126
|
-
export ANON_PI_LLM=192.168.1.150:8080
|
|
127
|
-
anon-pi import
|
|
128
|
-
```
|
|
252
|
+
pi treats a mounted project as untrusted until approved. The shipped Dockerfiles stage a `trust.json` trusting `/projects` and `/work` (in `/opt/anon-pi-seed/agent`), which is promoted into the machine home on first launch, so you are not prompted. You can also approve once inside a session; it persists in the home.
|
|
129
253
|
|
|
130
|
-
|
|
254
|
+
## Migrating from 0.4.0
|
|
131
255
|
|
|
132
|
-
|
|
256
|
+
anon-pi 0.4.0 was a **per-workdir** launcher: a bare positional was a host folder mounted at `/work`, session state lived per-workdir under `~/.config/anon-pi/state/<slug>/`, and you seeded it with `anon-pi import`. That model is gone. What changed:
|
|
133
257
|
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
258
|
+
- **A bare positional is now a PROJECT, not a host path.** `anon-pi ./recon` no longer mounts host `./recon`; it means the project `recon` under the projects root (`/projects/recon`). To make a host folder available, use `--mount <host-parent>` (mounted at `/work`) and select the subfolder as the project.
|
|
259
|
+
- **`anon-pi import` is GONE.** Onboarding is now `anon-pi init`, which (among other things) generates the local-model `models.json` for the default machine. There is no separate import step.
|
|
260
|
+
- **`--fresh` is GONE.** To reset, use the explicit data verbs: `anon-pi --delete-home [<machine>]` (wipe a machine's home, keep its image pin + project files) and `anon-pi --delete-project <project>` (wipe a project's files + its per-machine sessions).
|
|
261
|
+
- **`--ephemeral` / `ANON_PI_EPHEMERAL` are GONE.** The container is **throwaway by default** now (`--rm`); use `--keep` when you want it to survive across exits. There is no separate ephemeral mode.
|
|
262
|
+
- **The layout moved.** Everything now lives under `~/.anon-pi/` (`config.json` + `machines/` + `projects/`), **not** under `~/.config/anon-pi`. `ANON_PI_HOME` still overrides the root; the old `ANON_PI_CONFIG` / `ANON_PI_SOURCE_MODELS` variables are gone.
|
|
263
|
+
- **Old state is NOT migrated.** anon-pi does not read or convert your old `~/.config/anon-pi/state/<slug>/` directories. Once you have moved to the new model you can delete the old tree:
|
|
137
264
|
|
|
138
|
-
|
|
265
|
+
```sh
|
|
266
|
+
rm -rf ~/.config/anon-pi # old 0.4.0 state; nothing new reads it
|
|
267
|
+
```
|
|
139
268
|
|
|
140
|
-
|
|
269
|
+
Start fresh with `anon-pi init`, then `anon-pi` (the menu) or `anon-pi <project>`.
|
|
141
270
|
|
|
142
|
-
##
|
|
271
|
+
## Troubleshooting
|
|
143
272
|
|
|
144
|
-
|
|
273
|
+
- **`netcage not found on PATH`** — anon-pi is a launcher for [netcage](https://github.com/wighawag/netcage); install netcage first (Linux only).
|
|
274
|
+
- **`set ANON_PI_PROXY …` (fail-closed)** — no proxy is configured. Run `anon-pi init` to detect + verify one, or export `ANON_PI_PROXY=socks5h://<host:port>`. There is deliberately no default: the proxy is what anonymizes, so it is never guessed.
|
|
275
|
+
- **A launch says the machine has no image** — pin one: `anon-pi machine set-image default <ref>`, or export `ANON_PI_IMAGE=<ref>`, or re-run `anon-pi init` and pick/build a `Dockerfile`. See [Providing a pi image](#providing-a-pi-image).
|
|
276
|
+
- **`no TTY` on a bare `anon-pi`** — the menu and interactive pi need a terminal. In a script, name the project and forward args: `anon-pi <project> <pi-args…>` (that path needs no TTY).
|
|
277
|
+
- **The exit IP looks like your home IP** — the proxy is not actually anonymizing. Re-run `anon-pi init`; its `netcage verify` step prints the real exit IP as proof. anon-pi never claims a provider, only shows you the exit.
|
|
278
|
+
- **A destructive verb won't run in a script** — `machine rm` / `--delete-home` / `--delete-project` confirm on a TTY and abort non-interactively; pass `--yes` to proceed unattended.
|
|
279
|
+
- **`--keep` re-entry started a fresh container** — a kept container is matched by its `(machine, projects-root, project)` identity; changing any of those (a different `-m`, a different `--mount` parent, a different project) is a different launch and gets its own container.
|
|
145
280
|
|
|
146
281
|
## Platform
|
|
147
282
|
|