anon-pi 0.11.0 → 0.12.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/README.md +4 -3
- package/dist/anon-pi.d.ts +62 -23
- package/dist/anon-pi.d.ts.map +1 -1
- package/dist/anon-pi.js +74 -21
- package/dist/anon-pi.js.map +1 -1
- package/dist/cli.js +54 -85
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/src/anon-pi.ts +88 -23
- package/src/cli.ts +61 -88
package/src/cli.ts
CHANGED
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
parseKeptKey,
|
|
62
62
|
keyProject,
|
|
63
63
|
resolveManagedMatches,
|
|
64
|
+
parseNetcagePsJson,
|
|
64
65
|
parseNetcagePortsJson,
|
|
65
66
|
forwardablePorts,
|
|
66
67
|
formatPortsHint,
|
|
@@ -177,7 +178,7 @@ function main(argv: string[]): number {
|
|
|
177
178
|
return runInit(args.slice(1));
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
// Host-access verbs (netcage >= 0.
|
|
181
|
+
// Host-access verbs (netcage >= 0.10.0). `forward` opens a host->jail port; the
|
|
181
182
|
// `ports` sibling lists a jail's open listeners. Dispatched BEFORE the launch
|
|
182
183
|
// grammar so `forward`/`ports` are never parsed as a project name.
|
|
183
184
|
if (args[0] === 'forward') {
|
|
@@ -418,13 +419,15 @@ function executeLaunchPlan(
|
|
|
418
419
|
if (intent.keep) {
|
|
419
420
|
const decision = resolveRunVsStart(intent, queryKeptContainers());
|
|
420
421
|
if (decision.action === 'start') {
|
|
421
|
-
return spawnNetcage(['start', '-a', '-i', decision.ref]
|
|
422
|
+
return spawnNetcage(['start', '-a', '-i', decision.ref], {
|
|
423
|
+
enteringJail: true,
|
|
424
|
+
});
|
|
422
425
|
}
|
|
423
426
|
// A fresh `--keep` run: the RunPlan already omits --rm so it is left kept.
|
|
424
|
-
return spawnNetcage(keyed);
|
|
427
|
+
return spawnNetcage(keyed, {enteringJail: true});
|
|
425
428
|
}
|
|
426
429
|
|
|
427
|
-
return spawnNetcage(keyed);
|
|
430
|
+
return spawnNetcage(keyed, {enteringJail: true});
|
|
428
431
|
}
|
|
429
432
|
|
|
430
433
|
// --- the interactive host-side menu (the ONLY untested I/O) -------------------
|
|
@@ -2021,7 +2024,7 @@ USAGE
|
|
|
2021
2024
|
--bind passed through to netcage (127.0.0.1 default, or 0.0.0.0 for LAN).
|
|
2022
2025
|
-m the machine the container runs on (else the default machine).
|
|
2023
2026
|
|
|
2024
|
-
Wraps \`netcage forward\` (netcage >= 0.
|
|
2027
|
+
Wraps \`netcage forward\` (netcage >= 0.10.0). If several containers match, you pick
|
|
2025
2028
|
one (each row shows its open in-jail ports). The forward runs until Ctrl-C.
|
|
2026
2029
|
`;
|
|
2027
2030
|
|
|
@@ -2031,7 +2034,7 @@ const PORTS_HELP = `anon-pi ports - list a running anon-pi container's open in-j
|
|
|
2031
2034
|
USAGE
|
|
2032
2035
|
anon-pi ports [<project>] [-m <machine>]
|
|
2033
2036
|
|
|
2034
|
-
Wraps \`netcage ports --json\` (netcage >= 0.
|
|
2037
|
+
Wraps \`netcage ports --json\` (netcage >= 0.10.0), which reads the jail's
|
|
2035
2038
|
/proc/net/tcp* image-independently (works even with no ss/netstat in the image).
|
|
2036
2039
|
Use it to find which port to \`anon-pi forward\`.
|
|
2037
2040
|
`;
|
|
@@ -2092,33 +2095,44 @@ function homeFresh(machineHome: string): boolean {
|
|
|
2092
2095
|
* fresh `run` (safe: it never wrongly resumes, it just creates a new container).
|
|
2093
2096
|
*/
|
|
2094
2097
|
function queryKeptContainers(): KeptContainer[] {
|
|
2095
|
-
// Ask netcage for its managed containers as JSON
|
|
2096
|
-
//
|
|
2097
|
-
//
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2098
|
+
// Ask netcage for ALL its managed containers as JSON (netcage >= 0.10.0
|
|
2099
|
+
// forwards podman's --format json over its managed scope), then keep the ones
|
|
2100
|
+
// carrying an anon-pi.key label (a sidecar has none) and decode it. -a so a
|
|
2101
|
+
// STOPPED kept container is included (run-vs-start resumes it).
|
|
2102
|
+
return queryManagedContainers({all: true}).map(({key, ref}) => ({key, ref}));
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
/**
|
|
2106
|
+
* Decode a base64 anon-pi.key label back to its identity key (the reverse of
|
|
2107
|
+
* withKeyLabel's encode; keptContainerKey embeds newlines, so it is base64'd to
|
|
2108
|
+
* stay a single safe label value). undefined on a decode error.
|
|
2109
|
+
*/
|
|
2110
|
+
function decodeKeyLabel(raw: string): string | undefined {
|
|
2111
|
+
try {
|
|
2112
|
+
return Buffer.from(raw, 'base64').toString('utf8');
|
|
2113
|
+
} catch {
|
|
2114
|
+
return undefined;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2111
2117
|
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2118
|
+
/**
|
|
2119
|
+
* The shared `netcage ps --format json` query: parse the JSON to anon-pi's
|
|
2120
|
+
* containers (pure parseNetcagePsJson keeps only anon-pi.key-labelled entries,
|
|
2121
|
+
* optionally running-only), then base64-DECODE each key. Best-effort: [] on any
|
|
2122
|
+
* failure. `all` => `-a` (include stopped); else running only.
|
|
2123
|
+
*/
|
|
2124
|
+
function queryManagedContainers(opts: {
|
|
2125
|
+
all?: boolean;
|
|
2126
|
+
}): {key: string; ref: string; name: string}[] {
|
|
2127
|
+
const args = ['ps'];
|
|
2128
|
+
if (opts.all) args.push('-a');
|
|
2129
|
+
args.push('--filter', 'label=netcage.managed', '--format', 'json');
|
|
2130
|
+
const res = spawnSync('netcage', args, {encoding: 'utf8'});
|
|
2131
|
+
if (res.error || res.status !== 0 || !res.stdout) return [];
|
|
2132
|
+
const out: {key: string; ref: string; name: string}[] = [];
|
|
2133
|
+
for (const e of parseNetcagePsJson(res.stdout, {runningOnly: !opts.all})) {
|
|
2134
|
+
const key = decodeKeyLabel(e.key);
|
|
2135
|
+
if (key !== undefined) out.push({key, ref: e.ref, name: e.name});
|
|
2122
2136
|
}
|
|
2123
2137
|
return out;
|
|
2124
2138
|
}
|
|
@@ -2132,32 +2146,7 @@ function queryKeptContainers(): KeptContainer[] {
|
|
|
2132
2146
|
* failure, so the caller reports "nothing running" cleanly.
|
|
2133
2147
|
*/
|
|
2134
2148
|
function queryRunningContainers(): ManagedContainer[] {
|
|
2135
|
-
|
|
2136
|
-
'netcage',
|
|
2137
|
-
[
|
|
2138
|
-
'ps',
|
|
2139
|
-
'--filter',
|
|
2140
|
-
'label=netcage.managed',
|
|
2141
|
-
'--format',
|
|
2142
|
-
'{{.ID}}\t{{.Names}}\t{{.Labels}}',
|
|
2143
|
-
],
|
|
2144
|
-
{encoding: 'utf8'},
|
|
2145
|
-
);
|
|
2146
|
-
if (res.error || res.status !== 0 || !res.stdout) return [];
|
|
2147
|
-
const out: ManagedContainer[] = [];
|
|
2148
|
-
for (const line of res.stdout.split('\n')) {
|
|
2149
|
-
const trimmed = line.trim();
|
|
2150
|
-
if (trimmed === '') continue;
|
|
2151
|
-
const cols = trimmed.split('\t');
|
|
2152
|
-
if (cols.length < 3) continue;
|
|
2153
|
-
const ref = cols[0].trim();
|
|
2154
|
-
const name = cols[1].trim();
|
|
2155
|
-
const key = extractKeyLabel(cols.slice(2).join('\t'));
|
|
2156
|
-
if (ref !== '' && key !== undefined) {
|
|
2157
|
-
out.push({key, ref, name: name || ref});
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
return out;
|
|
2149
|
+
return queryManagedContainers({all: false});
|
|
2161
2150
|
}
|
|
2162
2151
|
|
|
2163
2152
|
/**
|
|
@@ -2376,28 +2365,6 @@ function netcageMissing(): number {
|
|
|
2376
2365
|
return 1;
|
|
2377
2366
|
}
|
|
2378
2367
|
|
|
2379
|
-
/**
|
|
2380
|
-
* Pull the anon-pi key out of a podman `{{.Labels}}` rendering (a
|
|
2381
|
-
* comma-separated `k=v` list). The key is stamped as `anon-pi.key=<opaque>`;
|
|
2382
|
-
* because keptContainerKey embeds newlines, the CLI base64-encodes it when
|
|
2383
|
-
* stamping (withKeyLabel) and decodes it here, so a `\n` never breaks the label.
|
|
2384
|
-
*/
|
|
2385
|
-
function extractKeyLabel(labels: string): string | undefined {
|
|
2386
|
-
for (const pair of labels.split(',')) {
|
|
2387
|
-
const eq = pair.indexOf('=');
|
|
2388
|
-
if (eq < 0) continue;
|
|
2389
|
-
const k = pair.slice(0, eq).trim();
|
|
2390
|
-
if (k !== ANON_PI_KEY_LABEL) continue;
|
|
2391
|
-
const v = pair.slice(eq + 1).trim();
|
|
2392
|
-
try {
|
|
2393
|
-
return Buffer.from(v, 'base64').toString('utf8');
|
|
2394
|
-
} catch {
|
|
2395
|
-
return undefined;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
return undefined;
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
2368
|
/**
|
|
2402
2369
|
* Insert the anon-pi identity label into a `netcage run` argv (right after
|
|
2403
2370
|
* `run`), so a kept container can be found on re-entry. The key is base64'd
|
|
@@ -2463,14 +2430,20 @@ function firstLine(path: string): string | undefined {
|
|
|
2463
2430
|
}
|
|
2464
2431
|
|
|
2465
2432
|
/** Spawn netcage with inherited stdio; propagate its exit code. */
|
|
2466
|
-
function spawnNetcage(
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
//
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
)
|
|
2433
|
+
function spawnNetcage(
|
|
2434
|
+
netcageArgs: string[],
|
|
2435
|
+
opts: {enteringJail?: boolean} = {},
|
|
2436
|
+
): number {
|
|
2437
|
+
// Explain the pause on a LAUNCH: netcage sets up the jail (netns, firewall,
|
|
2438
|
+
// DNS, container start) BEFORE pi paints, so without this line the user sees
|
|
2439
|
+
// only a blinking cursor. Goes to stderr (never pollutes piped stdout), and is
|
|
2440
|
+
// transient (pi clears the screen when its TUI comes up). NOT printed for
|
|
2441
|
+
// `forward` (it attaches to an existing jail, and prints its own line).
|
|
2442
|
+
if (opts.enteringJail) {
|
|
2443
|
+
process.stderr.write(
|
|
2444
|
+
'anon-pi: entering the netcage jail (setting up forced-egress)\u2026\n',
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2474
2447
|
const res = spawnSync('netcage', netcageArgs, {stdio: 'inherit'});
|
|
2475
2448
|
if (res.error) {
|
|
2476
2449
|
process.stderr.write(
|