anon-pi 0.1.1 → 0.3.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 +35 -7
- package/README.md +65 -23
- package/dist/anon-pi.d.ts +157 -44
- package/dist/anon-pi.d.ts.map +1 -1
- package/dist/anon-pi.js +311 -104
- package/dist/anon-pi.js.map +1 -1
- package/dist/cli.js +92 -18
- package/dist/cli.js.map +1 -1
- package/examples/Dockerfile.pi-webveil +100 -0
- package/package.json +3 -2
- package/src/anon-pi.ts +391 -135
- package/src/cli.ts +129 -19
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// anon-pi CLI
|
|
3
|
-
//
|
|
4
|
-
// stdio so
|
|
5
|
-
|
|
2
|
+
// anon-pi CLI. Two commands:
|
|
3
|
+
// anon-pi [WORKDIR] resolve the run plan (pure) and exec `netcage run ...`
|
|
4
|
+
// with inherited stdio (so -it is a real interactive TTY).
|
|
5
|
+
// The seed models.json is mounted read-only and copied
|
|
6
|
+
// into the container's ~/.pi/agent by the run command, so
|
|
7
|
+
// it layers onto the image's config (extensions survive).
|
|
8
|
+
// anon-pi import generate the seed models.json from the host models.json,
|
|
9
|
+
// carrying only the provider that serves ANON_PI_LLM.
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
11
|
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import {
|
|
8
|
-
import { AnonPiError, buildRunPlan, envFromProcess, HELP } from './anon-pi.js';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { AnonPiError, buildRunPlan, envFromProcess, HELP, MODELS_FILE, pickProviderForLlm, resolveConfigSeed, resolveSourceModelsPath, } from './anon-pi.js';
|
|
9
14
|
function main(argv) {
|
|
10
15
|
const args = argv.slice(2);
|
|
11
16
|
if (args.includes('--help') || args.includes('-h')) {
|
|
12
17
|
process.stdout.write(HELP);
|
|
13
18
|
return 0;
|
|
14
19
|
}
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
// Subcommand dispatch: the first bare token may be `import`.
|
|
21
|
+
if (args[0] === 'import') {
|
|
22
|
+
return runImport(args.slice(1));
|
|
23
|
+
}
|
|
24
|
+
return runLaunch(args);
|
|
25
|
+
}
|
|
26
|
+
// --- anon-pi [WORKDIR] : launch pi jailed -----------------------------------
|
|
27
|
+
function runLaunch(args) {
|
|
28
|
+
// One optional positional (the workdir) + the --ephemeral flag. Reject other
|
|
29
|
+
// flags so a typo is not silently swallowed: anon-pi owns the netcage argv.
|
|
30
|
+
const ephemeralFlag = args.includes('--ephemeral') || args.includes('--eph');
|
|
18
31
|
const positionals = args.filter((a) => !a.startsWith('-'));
|
|
19
|
-
const flags = args.filter((a) => a.startsWith('-'));
|
|
32
|
+
const flags = args.filter((a) => a.startsWith('-') && a !== '--ephemeral' && a !== '--eph');
|
|
20
33
|
if (flags.length > 0) {
|
|
21
34
|
process.stderr.write(`anon-pi: unknown option(s): ${flags.join(' ')}\nRun \`anon-pi --help\`.\n`);
|
|
22
35
|
return 2;
|
|
@@ -26,6 +39,8 @@ function main(argv) {
|
|
|
26
39
|
return 2;
|
|
27
40
|
}
|
|
28
41
|
const env = envFromProcess(process.env);
|
|
42
|
+
if (ephemeralFlag)
|
|
43
|
+
env.ephemeral = true;
|
|
29
44
|
let plan;
|
|
30
45
|
try {
|
|
31
46
|
plan = buildRunPlan(env, positionals[0], existsSync, existsSync);
|
|
@@ -43,15 +58,19 @@ function main(argv) {
|
|
|
43
58
|
'(https://github.com/wighawag/netcage). Linux only.\n');
|
|
44
59
|
return 1;
|
|
45
60
|
}
|
|
46
|
-
// The one side-effect: seed the per-session config from the canonical seed the
|
|
47
|
-
// FIRST time this workdir is used. Reuse-if-present, seed-if-absent.
|
|
48
|
-
if (plan.needsSeed) {
|
|
49
|
-
mkdirSync(dirname(plan.sessionAgentDir), { recursive: true });
|
|
50
|
-
cpSync(plan.configSeed, plan.sessionAgentDir, { recursive: true });
|
|
51
|
-
process.stderr.write(`anon-pi: seeded session config -> ${plan.sessionAgentDir}\n`);
|
|
52
|
-
}
|
|
53
|
-
// Ensure the workdir exists (a fresh named folder is fine).
|
|
54
61
|
mkdirSync(plan.workdir, { recursive: true });
|
|
62
|
+
if (env.ephemeral) {
|
|
63
|
+
// No host state dir: pi writes to the container's own --rm layer, so the
|
|
64
|
+
// session leaves NO trace on the host and there is nothing to clean up.
|
|
65
|
+
process.stderr.write('anon-pi: ephemeral session (nothing persisted; no host state)\n');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Persistent mode: create the per-workdir state home to mount.
|
|
69
|
+
mkdirSync(plan.stateDir, { recursive: true });
|
|
70
|
+
if (plan.fresh) {
|
|
71
|
+
process.stderr.write(`anon-pi: new session home ${plan.stateDir} (seeding on first launch)\n`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
55
74
|
// Hand off to netcage with inherited stdio so -it is a real interactive TTY.
|
|
56
75
|
const res = spawnSync('netcage', plan.netcageArgs, { stdio: 'inherit' });
|
|
57
76
|
if (res.error) {
|
|
@@ -61,6 +80,61 @@ function main(argv) {
|
|
|
61
80
|
// Propagate netcage's exit code (which itself propagates the tool's).
|
|
62
81
|
return res.status ?? 1;
|
|
63
82
|
}
|
|
83
|
+
// --- anon-pi import : write the seed models.json ----------------------------
|
|
84
|
+
function runImport(args) {
|
|
85
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
86
|
+
const stray = args.filter((a) => a.startsWith('-') && a !== '--force' && a !== '-f');
|
|
87
|
+
if (stray.length > 0) {
|
|
88
|
+
process.stderr.write(`anon-pi import: unknown option(s): ${stray.join(' ')}\nRun \`anon-pi --help\`.\n`);
|
|
89
|
+
return 2;
|
|
90
|
+
}
|
|
91
|
+
const env = envFromProcess(process.env);
|
|
92
|
+
if (!env.llmDirect || env.llmDirect.trim() === '') {
|
|
93
|
+
process.stderr.write('anon-pi import: set ANON_PI_LLM to the RFC1918/link-local IP[:port] of the local\n' +
|
|
94
|
+
'model whose provider should be imported (e.g. ANON_PI_LLM=192.168.1.150:8080).\n');
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
const source = resolveSourceModelsPath(env);
|
|
98
|
+
if (!existsSync(source)) {
|
|
99
|
+
process.stderr.write(`anon-pi import: host models.json not found at ${source}.\n` +
|
|
100
|
+
'Set ANON_PI_SOURCE_MODELS to your pi models.json, or run pi once to create it.\n');
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
let hostModels;
|
|
104
|
+
try {
|
|
105
|
+
hostModels = JSON.parse(readFileSync(source, 'utf8'));
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
process.stderr.write(`anon-pi import: could not parse ${source}: ${e.message}\n`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = pickProviderForLlm(hostModels, env.llmDirect);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
if (e instanceof AnonPiError) {
|
|
117
|
+
process.stderr.write(e.message + '\n');
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
const seedDir = resolveConfigSeed(env);
|
|
123
|
+
const dest = join(seedDir, MODELS_FILE);
|
|
124
|
+
if (existsSync(dest) && !force) {
|
|
125
|
+
process.stderr.write(`anon-pi import: ${dest} already exists. Re-run with --force to overwrite.\n`);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
if (result.apiKeyLooksReal) {
|
|
129
|
+
process.stderr.write(`anon-pi import: WARNING: provider "${result.name}" carries a real-looking apiKey; it\n` +
|
|
130
|
+
'will be written into the seed. For a local model this is usually fine, but review\n' +
|
|
131
|
+
`${dest} if that key identifies you.\n`);
|
|
132
|
+
}
|
|
133
|
+
mkdirSync(seedDir, { recursive: true });
|
|
134
|
+
writeFileSync(dest, JSON.stringify(result.models, null, 2) + '\n');
|
|
135
|
+
process.stderr.write(`anon-pi import: wrote ${dest} (provider "${result.name}"). Run \`anon-pi\` to launch.\n`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
64
138
|
function hasNetcage() {
|
|
65
139
|
const which = spawnSync(process.platform === 'win32' ? 'where' : 'command', ['-v', 'netcage'], {
|
|
66
140
|
stdio: 'ignore',
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# A fuller ANON_PI_IMAGE example: pi + the pi-webveil extension + a local SearXNG
|
|
2
|
+
# (over a Unix socket) for anonymized web search, baked into ONE image (the
|
|
3
|
+
# capability layer). Modelled on a real working host setup (Unix socket,
|
|
4
|
+
# http-socket, json+limiter:false, unix: baseUrl, egress: direct).
|
|
5
|
+
#
|
|
6
|
+
# STATUS: a documented REFERENCE, not a CI-built image. The topology matches a
|
|
7
|
+
# working host, but pin versions and test the build for your platform before
|
|
8
|
+
# relying on it (SearXNG's pip install + engine set drift over time).
|
|
9
|
+
#
|
|
10
|
+
# WHY THE USUAL SearXNG ANONYMITY CAVEAT DOES NOT APPLY HERE:
|
|
11
|
+
# webveil warns that a LOCAL SearXNG crawls the web from its OWN process egress
|
|
12
|
+
# (direct), so a socks5 backend hop to 127.0.0.1 is FALSE confidence. Under
|
|
13
|
+
# anon-pi that risk is gone: netcage forces ALL TCP+DNS egress from EVERY
|
|
14
|
+
# process in the jail through the socks5h proxy, fail-closed. SearXNG's crawl is
|
|
15
|
+
# anonymized by the network layer regardless. So we use webveil's plain
|
|
16
|
+
# `egress: direct` and let netcage do the anonymizing.
|
|
17
|
+
#
|
|
18
|
+
# WHY A UNIX SOCKET (matches the host setup, avoids the loopback-TCP guard):
|
|
19
|
+
# webveil's `unix:` baseUrl speaks HTTP over a Unix socket, so uWSGI must use
|
|
20
|
+
# `http-socket = <path>` (NOT `socket = <path>`, which is the native uwsgi
|
|
21
|
+
# protocol webveil cannot speak). SearXNG also needs `json` in search.formats
|
|
22
|
+
# and `server.limiter: false` for a local instance.
|
|
23
|
+
#
|
|
24
|
+
# Build:
|
|
25
|
+
# podman build -t localhost/anon-pi-webveil:latest -f Dockerfile.pi-webveil .
|
|
26
|
+
# export ANON_PI_IMAGE=localhost/anon-pi-webveil:latest
|
|
27
|
+
# Then: `anon-pi import` (your local model), then `anon-pi <workdir>`.
|
|
28
|
+
FROM node:24-bookworm-slim
|
|
29
|
+
|
|
30
|
+
# --- system deps: pi's tools + SearXNG's Python/uwsgi runtime ----------------
|
|
31
|
+
RUN apt-get update \
|
|
32
|
+
&& apt-get install -y --no-install-recommends \
|
|
33
|
+
bash ca-certificates git ripgrep \
|
|
34
|
+
python3 python3-venv python3-pip python3-dev \
|
|
35
|
+
build-essential libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev \
|
|
36
|
+
uwsgi uwsgi-plugin-python3 \
|
|
37
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
38
|
+
|
|
39
|
+
# --- pi + the pi-webveil extension --------------------------------------------
|
|
40
|
+
# Install pi globally, then install the extension into the STAGING dir
|
|
41
|
+
# (PI_CODING_AGENT_DIR), NOT ~/.pi/agent: anon-pi mounts a persistent home over
|
|
42
|
+
# ~/.pi/agent and promotes the staging dir into it on first launch. Installing
|
|
43
|
+
# straight into ~/.pi/agent would be shadowed by that mount.
|
|
44
|
+
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
45
|
+
ENV ANON_PI_STAGE=/opt/anon-pi-seed/agent
|
|
46
|
+
RUN mkdir -p "$ANON_PI_STAGE" \
|
|
47
|
+
&& PI_CODING_AGENT_DIR="$ANON_PI_STAGE" pi install npm:pi-webveil
|
|
48
|
+
|
|
49
|
+
# --- SearXNG in a venv -------------------------------------------------------
|
|
50
|
+
ENV SEARXNG_HOME=/opt/searxng
|
|
51
|
+
RUN python3 -m venv "$SEARXNG_HOME/venv" \
|
|
52
|
+
&& "$SEARXNG_HOME/venv/bin/pip" install --no-cache-dir -U pip setuptools wheel \
|
|
53
|
+
&& "$SEARXNG_HOME/venv/bin/pip" install --no-cache-dir searxng
|
|
54
|
+
|
|
55
|
+
# settings.yml: JSON API on + limiter off (both required for a local instance).
|
|
56
|
+
RUN mkdir -p /etc/searxng \
|
|
57
|
+
&& printf '%s\n' \
|
|
58
|
+
'use_default_settings: true' \
|
|
59
|
+
'server:' \
|
|
60
|
+
' secret_key: "change-me-anon-pi"' \
|
|
61
|
+
' limiter: false' \
|
|
62
|
+
' public_instance: false' \
|
|
63
|
+
'search:' \
|
|
64
|
+
' formats: [html, json]' \
|
|
65
|
+
> /etc/searxng/settings.yml
|
|
66
|
+
ENV SEARXNG_SETTINGS_PATH=/etc/searxng/settings.yml
|
|
67
|
+
|
|
68
|
+
# webveil.json + trust.json go in the STAGING dir (promoted into the persistent
|
|
69
|
+
# home on first launch). webveil points at the Unix socket, backend hop DIRECT
|
|
70
|
+
# (netcage anonymizes it): no webveil-side proxy config is needed in-jail because
|
|
71
|
+
# netcage forces every process's egress through the proxy.
|
|
72
|
+
RUN printf '%s\n' \
|
|
73
|
+
'{' \
|
|
74
|
+
' "backend": "searxng",' \
|
|
75
|
+
' "baseUrl": "unix:/run/searxng/socket",' \
|
|
76
|
+
' "egress": { "mode": "direct" }' \
|
|
77
|
+
'}' \
|
|
78
|
+
> "$ANON_PI_STAGE/webveil.json" \
|
|
79
|
+
&& printf '{"/work": true}\n' > "$ANON_PI_STAGE/trust.json"
|
|
80
|
+
|
|
81
|
+
# --- entrypoint: start SearXNG (HTTP over the Unix socket), then exec CMD ------
|
|
82
|
+
# anon-pi passes `sh -c 'cp ... && exec pi'` as the CMD; a Dockerfile ENTRYPOINT
|
|
83
|
+
# is PREPENDED to it (OCI), so this wrapper starts SearXNG then execs anon-pi's
|
|
84
|
+
# command. `http-socket` (not `socket`) makes uWSGI speak HTTP over the socket,
|
|
85
|
+
# which is what webveil's `unix:` baseUrl requires.
|
|
86
|
+
RUN mkdir -p /run/searxng && printf '%s\n' \
|
|
87
|
+
'#!/bin/sh' \
|
|
88
|
+
'set -e' \
|
|
89
|
+
'uwsgi --master --plugin python3,http \' \
|
|
90
|
+
' --http-socket /run/searxng/socket --chmod-socket=666 \' \
|
|
91
|
+
' --venv "$SEARXNG_HOME/venv" --module searx.webapp \' \
|
|
92
|
+
' --daemonize /tmp/searxng.log' \
|
|
93
|
+
'# Wait for the socket to appear (bounded).' \
|
|
94
|
+
'i=0; while [ ! -S /run/searxng/socket ] && [ $i -lt 30 ]; do i=$((i+1)); sleep 0.5; done' \
|
|
95
|
+
'exec "$@"' \
|
|
96
|
+
> /usr/local/bin/anon-pi-entrypoint \
|
|
97
|
+
&& chmod +x /usr/local/bin/anon-pi-entrypoint
|
|
98
|
+
ENTRYPOINT ["/usr/local/bin/anon-pi-entrypoint"]
|
|
99
|
+
|
|
100
|
+
WORKDIR /work
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anon-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Launch pi inside a netcage: anonymized web egress through a socks5h proxy, one direct hole for a local model, seeded pi config on the host.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"keywords": [
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"dist",
|
|
41
41
|
"src",
|
|
42
42
|
"LICENSE",
|
|
43
|
-
"Dockerfile.pi"
|
|
43
|
+
"Dockerfile.pi",
|
|
44
|
+
"examples"
|
|
44
45
|
],
|
|
45
46
|
"engines": {
|
|
46
47
|
"node": ">=20"
|