create-op-node 0.10.7 → 0.10.8
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/dist/cli.js +183 -129
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2890,87 +2890,89 @@ async function runReset(input, deps = DEFAULT_DEPS) {
|
|
|
2890
2890
|
phases.push(ph);
|
|
2891
2891
|
deps.onPhase?.(ph);
|
|
2892
2892
|
};
|
|
2893
|
+
push(await resetStopStackPhase(input, deps));
|
|
2894
|
+
push(await resetLaunchAgentPhase(input, deps));
|
|
2895
|
+
push(await resetDockerLogoutPhase(input, deps));
|
|
2896
|
+
return { phases };
|
|
2897
|
+
}
|
|
2898
|
+
async function resetStopStackPhase(input, deps) {
|
|
2893
2899
|
if (!input.stack) {
|
|
2894
|
-
|
|
2900
|
+
return {
|
|
2895
2901
|
name: RESET_PHASES.STOP_STACK,
|
|
2896
2902
|
status: "skipped",
|
|
2897
2903
|
detail: "--skip-stack or no repo path resolved"
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
if (input.dryRun) {
|
|
2907
|
+
return {
|
|
2901
2908
|
name: RESET_PHASES.STOP_STACK,
|
|
2902
2909
|
status: "dry-run",
|
|
2903
2910
|
detail: `would run: docker compose -f ${input.stack.composeFiles.join(" -f ")} down${input.stack.wipeVolumes ? " -v" : ""}${input.stack.removeOrphans ? " --remove-orphans" : ""}${input.stack.wipeImages ? " --rmi all" : ""}`
|
|
2904
|
-
}
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
input.stack.wipeVolumes ? "volumes destroyed" : "containers stopped, volumes preserved",
|
|
2917
|
-
input.stack.wipeImages ? "images removed (next bootstrap will re-pull)" : null
|
|
2918
|
-
].filter(Boolean);
|
|
2919
|
-
push({
|
|
2920
|
-
name: RESET_PHASES.STOP_STACK,
|
|
2921
|
-
status: "ok",
|
|
2922
|
-
detail: bits.join("; ")
|
|
2923
|
-
});
|
|
2924
|
-
} else {
|
|
2925
|
-
push({ name: RESET_PHASES.STOP_STACK, status: "fail", detail: result2.reason ?? "unknown failure" });
|
|
2926
|
-
}
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
const result2 = await deps.composeDown({
|
|
2914
|
+
files: input.stack.composeFiles,
|
|
2915
|
+
cwd: input.stack.repoPath,
|
|
2916
|
+
...input.stack.envFile ? { envFile: input.stack.envFile } : {},
|
|
2917
|
+
wipeVolumes: input.stack.wipeVolumes,
|
|
2918
|
+
removeOrphans: input.stack.removeOrphans,
|
|
2919
|
+
...input.stack.wipeImages ? { removeImages: "all" } : {}
|
|
2920
|
+
});
|
|
2921
|
+
if (!result2.ok) {
|
|
2922
|
+
return { name: RESET_PHASES.STOP_STACK, status: "fail", detail: result2.reason ?? "unknown failure" };
|
|
2927
2923
|
}
|
|
2924
|
+
const bits = [
|
|
2925
|
+
input.stack.wipeVolumes ? "volumes destroyed" : "containers stopped, volumes preserved",
|
|
2926
|
+
input.stack.wipeImages ? "images removed (next bootstrap will re-pull)" : null
|
|
2927
|
+
].filter(Boolean);
|
|
2928
|
+
return { name: RESET_PHASES.STOP_STACK, status: "ok", detail: bits.join("; ") };
|
|
2929
|
+
}
|
|
2930
|
+
async function resetLaunchAgentPhase(input, deps) {
|
|
2928
2931
|
if (!input.launchAgent) {
|
|
2929
|
-
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
+
return { name: RESET_PHASES.LAUNCH_AGENT, status: "skipped", detail: "--skip-launch-agent or no plist found" };
|
|
2933
|
+
}
|
|
2934
|
+
if (input.dryRun) {
|
|
2935
|
+
return {
|
|
2932
2936
|
name: RESET_PHASES.LAUNCH_AGENT,
|
|
2933
2937
|
status: "dry-run",
|
|
2934
2938
|
detail: `would unload ${input.launchAgent.paths.plistFile}, rm plist${input.launchAgent.keepKeyFile ? "" : " + key file"}`
|
|
2935
|
-
}
|
|
2936
|
-
} else {
|
|
2937
|
-
const result2 = await deps.teardownLaunchAgent(
|
|
2938
|
-
input.launchAgent.paths,
|
|
2939
|
-
{ keepKeyFile: input.launchAgent.keepKeyFile }
|
|
2940
|
-
);
|
|
2941
|
-
if (result2.ok) {
|
|
2942
|
-
const removed = result2.steps.filter((s) => s.step !== "unload").map((s) => s.step).join(", ");
|
|
2943
|
-
push({ name: RESET_PHASES.LAUNCH_AGENT, status: "ok", detail: `unloaded + ${removed}` });
|
|
2944
|
-
} else {
|
|
2945
|
-
const fail = result2.steps.find((s) => !s.ok);
|
|
2946
|
-
push({
|
|
2947
|
-
name: RESET_PHASES.LAUNCH_AGENT,
|
|
2948
|
-
status: "fail",
|
|
2949
|
-
detail: `${fail?.step}: ${fail?.reason ?? "unknown"}`
|
|
2950
|
-
});
|
|
2951
|
-
}
|
|
2939
|
+
};
|
|
2952
2940
|
}
|
|
2941
|
+
const result2 = await deps.teardownLaunchAgent(
|
|
2942
|
+
input.launchAgent.paths,
|
|
2943
|
+
{ keepKeyFile: input.launchAgent.keepKeyFile }
|
|
2944
|
+
);
|
|
2945
|
+
if (!result2.ok) {
|
|
2946
|
+
const fail = result2.steps.find((s) => !s.ok);
|
|
2947
|
+
return {
|
|
2948
|
+
name: RESET_PHASES.LAUNCH_AGENT,
|
|
2949
|
+
status: "fail",
|
|
2950
|
+
detail: `${fail?.step}: ${fail?.reason ?? "unknown"}`
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
const removed = result2.steps.filter((s) => s.step !== "unload").map((s) => s.step).join(", ");
|
|
2954
|
+
return { name: RESET_PHASES.LAUNCH_AGENT, status: "ok", detail: `unloaded + ${removed}` };
|
|
2955
|
+
}
|
|
2956
|
+
async function resetDockerLogoutPhase(input, deps) {
|
|
2953
2957
|
if (!input.dockerLogout) {
|
|
2954
|
-
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2958
|
+
return { name: RESET_PHASES.DOCKER_LOGOUT, status: "skipped", detail: "--skip-docker-logout" };
|
|
2959
|
+
}
|
|
2960
|
+
if (input.dryRun) {
|
|
2961
|
+
return {
|
|
2957
2962
|
name: RESET_PHASES.DOCKER_LOGOUT,
|
|
2958
2963
|
status: "dry-run",
|
|
2959
2964
|
detail: `would run: docker logout ${input.dockerLogout.registry}`
|
|
2960
|
-
}
|
|
2961
|
-
} else {
|
|
2962
|
-
const result2 = await deps.dockerLogout(input.dockerLogout.registry);
|
|
2963
|
-
if (result2.ok) {
|
|
2964
|
-
push({
|
|
2965
|
-
name: RESET_PHASES.DOCKER_LOGOUT,
|
|
2966
|
-
status: "ok",
|
|
2967
|
-
detail: `credentials removed from ${input.dockerLogout.registry}`
|
|
2968
|
-
});
|
|
2969
|
-
} else {
|
|
2970
|
-
push({ name: RESET_PHASES.DOCKER_LOGOUT, status: "warn", detail: result2.reason ?? "unknown" });
|
|
2971
|
-
}
|
|
2965
|
+
};
|
|
2972
2966
|
}
|
|
2973
|
-
|
|
2967
|
+
const result2 = await deps.dockerLogout(input.dockerLogout.registry);
|
|
2968
|
+
if (result2.ok) {
|
|
2969
|
+
return {
|
|
2970
|
+
name: RESET_PHASES.DOCKER_LOGOUT,
|
|
2971
|
+
status: "ok",
|
|
2972
|
+
detail: `credentials removed from ${input.dockerLogout.registry}`
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
return { name: RESET_PHASES.DOCKER_LOGOUT, status: "warn", detail: result2.reason ?? "unknown" };
|
|
2974
2976
|
}
|
|
2975
2977
|
async function fileExists2(path) {
|
|
2976
2978
|
try {
|
|
@@ -3026,6 +3028,43 @@ var resetCommand = new Command("reset").description(
|
|
|
3026
3028
|
const wipeImages = opts.wipeImages ?? false;
|
|
3027
3029
|
const wipeData = (opts.wipeData ?? false) || wipeImages;
|
|
3028
3030
|
const removeOrphans = opts.removeOrphans ?? true;
|
|
3031
|
+
const region = await resolveResetRegion(opts);
|
|
3032
|
+
const owner = opts.owner ?? "OpusPopuli";
|
|
3033
|
+
const repoName = `opuspopuli-node-${region}`;
|
|
3034
|
+
const launchAgentPaths = defaultPaths();
|
|
3035
|
+
const plistExists = await fileExists2(launchAgentPaths.plistFile);
|
|
3036
|
+
const keyFileExists = await fileExists2(launchAgentPaths.keyFile);
|
|
3037
|
+
const { repoPath, stackSkipReason } = await locateResetRepo(opts, owner, repoName);
|
|
3038
|
+
const runningContainers = repoPath ? await composePs({
|
|
3039
|
+
files: resolveComposeFiles(repoPath, opts.composeFile),
|
|
3040
|
+
cwd: repoPath,
|
|
3041
|
+
...opts.envFile ? { envFile: opts.envFile } : {}
|
|
3042
|
+
}) : null;
|
|
3043
|
+
renderSnapshotNote({
|
|
3044
|
+
region,
|
|
3045
|
+
repoPath,
|
|
3046
|
+
stackSkipReason,
|
|
3047
|
+
runningContainers,
|
|
3048
|
+
plistExists,
|
|
3049
|
+
keyFileExists,
|
|
3050
|
+
launchAgentPaths,
|
|
3051
|
+
opts,
|
|
3052
|
+
wipeData,
|
|
3053
|
+
wipeImages
|
|
3054
|
+
});
|
|
3055
|
+
await confirmReset({ opts, region, wipeData });
|
|
3056
|
+
const input = buildResetInput({
|
|
3057
|
+
opts,
|
|
3058
|
+
repoPath,
|
|
3059
|
+
plistExists,
|
|
3060
|
+
launchAgentPaths,
|
|
3061
|
+
wipeData,
|
|
3062
|
+
wipeImages,
|
|
3063
|
+
removeOrphans
|
|
3064
|
+
});
|
|
3065
|
+
await runResetWithSpinners({ opts, region, wipeData, input });
|
|
3066
|
+
});
|
|
3067
|
+
async function resolveResetRegion(opts) {
|
|
3029
3068
|
const region = opts.region ? opts.region : unwrap(
|
|
3030
3069
|
await p3.text({
|
|
3031
3070
|
message: "Region label (the slug used during init \u2014 e.g. us-ca)?",
|
|
@@ -3037,51 +3076,52 @@ var resetCommand = new Command("reset").description(
|
|
|
3037
3076
|
p3.cancel(`--region ${JSON.stringify(region)} is not a valid region slug.`);
|
|
3038
3077
|
process.exit(2);
|
|
3039
3078
|
}
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
let repoPath;
|
|
3046
|
-
let stackSkipReason;
|
|
3047
|
-
if (!opts.skipStack) {
|
|
3048
|
-
const located = await locateOrCloneRepo({
|
|
3049
|
-
owner,
|
|
3050
|
-
name: repoName,
|
|
3051
|
-
cwd: process.cwd(),
|
|
3052
|
-
allowClone: false,
|
|
3053
|
-
...opts.repoDir ? { explicit: opts.repoDir } : {}
|
|
3054
|
-
});
|
|
3055
|
-
switch (located.kind) {
|
|
3056
|
-
case "found":
|
|
3057
|
-
repoPath = located.path;
|
|
3058
|
-
break;
|
|
3059
|
-
case "explicit-not-a-node-repo":
|
|
3060
|
-
p3.cancel(
|
|
3061
|
-
`--repo-dir ${JSON.stringify(located.path)} doesn't look like a node repo (missing one of: ${NODE_REPO_MARKERS.join(", ")}). Verify the path or drop --repo-dir to let reset search the cwd.`
|
|
3062
|
-
);
|
|
3063
|
-
process.exit(2);
|
|
3064
|
-
break;
|
|
3065
|
-
case "clone-disallowed":
|
|
3066
|
-
stackSkipReason = `no checkout found at ${process.cwd()}; pass --repo-dir to target one`;
|
|
3067
|
-
break;
|
|
3068
|
-
case "cloned":
|
|
3069
|
-
case "gh-not-installed":
|
|
3070
|
-
case "clone-failed":
|
|
3071
|
-
stackSkipReason = `unexpected outcome ${located.kind} from locateOrCloneRepo with allowClone=false`;
|
|
3072
|
-
break;
|
|
3073
|
-
}
|
|
3074
|
-
} else {
|
|
3075
|
-
stackSkipReason = "--skip-stack";
|
|
3076
|
-
}
|
|
3077
|
-
let runningContainers = null;
|
|
3078
|
-
if (repoPath) {
|
|
3079
|
-
runningContainers = await composePs({
|
|
3080
|
-
files: resolveComposeFiles(repoPath, opts.composeFile),
|
|
3081
|
-
cwd: repoPath,
|
|
3082
|
-
...opts.envFile ? { envFile: opts.envFile } : {}
|
|
3083
|
-
});
|
|
3079
|
+
return region;
|
|
3080
|
+
}
|
|
3081
|
+
async function locateResetRepo(opts, owner, repoName) {
|
|
3082
|
+
if (opts.skipStack) {
|
|
3083
|
+
return { stackSkipReason: "--skip-stack" };
|
|
3084
3084
|
}
|
|
3085
|
+
const located = await locateOrCloneRepo({
|
|
3086
|
+
owner,
|
|
3087
|
+
name: repoName,
|
|
3088
|
+
cwd: process.cwd(),
|
|
3089
|
+
allowClone: false,
|
|
3090
|
+
...opts.repoDir ? { explicit: opts.repoDir } : {}
|
|
3091
|
+
});
|
|
3092
|
+
switch (located.kind) {
|
|
3093
|
+
case "found":
|
|
3094
|
+
return { repoPath: located.path };
|
|
3095
|
+
case "explicit-not-a-node-repo":
|
|
3096
|
+
p3.cancel(
|
|
3097
|
+
`--repo-dir ${JSON.stringify(located.path)} doesn't look like a node repo (missing one of: ${NODE_REPO_MARKERS.join(", ")}). Verify the path or drop --repo-dir to let reset search the cwd.`
|
|
3098
|
+
);
|
|
3099
|
+
return process.exit(2);
|
|
3100
|
+
case "clone-disallowed":
|
|
3101
|
+
return { stackSkipReason: `no checkout found at ${process.cwd()}; pass --repo-dir to target one` };
|
|
3102
|
+
case "cloned":
|
|
3103
|
+
case "gh-not-installed":
|
|
3104
|
+
case "clone-failed":
|
|
3105
|
+
return {
|
|
3106
|
+
stackSkipReason: `unexpected outcome ${located.kind} from locateOrCloneRepo with allowClone=false`
|
|
3107
|
+
};
|
|
3108
|
+
default:
|
|
3109
|
+
return assertNever();
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
function renderSnapshotNote(args) {
|
|
3113
|
+
const {
|
|
3114
|
+
region,
|
|
3115
|
+
repoPath,
|
|
3116
|
+
stackSkipReason,
|
|
3117
|
+
runningContainers,
|
|
3118
|
+
plistExists,
|
|
3119
|
+
keyFileExists,
|
|
3120
|
+
launchAgentPaths,
|
|
3121
|
+
opts,
|
|
3122
|
+
wipeData,
|
|
3123
|
+
wipeImages
|
|
3124
|
+
} = args;
|
|
3085
3125
|
let runningContainersLabel;
|
|
3086
3126
|
if (runningContainers === null) {
|
|
3087
3127
|
runningContainersLabel = pc2.dim("unknown");
|
|
@@ -3104,6 +3144,9 @@ var resetCommand = new Command("reset").description(
|
|
|
3104
3144
|
].join("\n"),
|
|
3105
3145
|
"Snapshot"
|
|
3106
3146
|
);
|
|
3147
|
+
}
|
|
3148
|
+
async function confirmReset(args) {
|
|
3149
|
+
const { opts, region, wipeData } = args;
|
|
3107
3150
|
if (wipeData && !opts.dryRun) {
|
|
3108
3151
|
unwrap(
|
|
3109
3152
|
await p3.text({
|
|
@@ -3111,7 +3154,9 @@ var resetCommand = new Command("reset").description(
|
|
|
3111
3154
|
validate: (v) => v === region ? void 0 : `must match the region label exactly`
|
|
3112
3155
|
})
|
|
3113
3156
|
);
|
|
3114
|
-
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
if (!opts.dryRun && !opts.yes) {
|
|
3115
3160
|
const cont = unwrap(
|
|
3116
3161
|
await p3.confirm({
|
|
3117
3162
|
message: "Proceed with reset? (volumes will be preserved)",
|
|
@@ -3123,6 +3168,9 @@ var resetCommand = new Command("reset").description(
|
|
|
3123
3168
|
process.exit(0);
|
|
3124
3169
|
}
|
|
3125
3170
|
}
|
|
3171
|
+
}
|
|
3172
|
+
function buildResetInput(args) {
|
|
3173
|
+
const { opts, repoPath, plistExists, launchAgentPaths, wipeData, wipeImages, removeOrphans } = args;
|
|
3126
3174
|
const stack = repoPath ? {
|
|
3127
3175
|
repoPath,
|
|
3128
3176
|
composeFiles: resolveComposeFiles(repoPath, opts.composeFile),
|
|
@@ -3136,11 +3184,20 @@ var resetCommand = new Command("reset").description(
|
|
|
3136
3184
|
keepKeyFile: opts.keepKeyFile ?? false
|
|
3137
3185
|
} : void 0;
|
|
3138
3186
|
const dockerLogoutInput = !opts.skipDockerLogout ? { registry: opts.registry ?? GHCR_REGISTRY } : void 0;
|
|
3187
|
+
return {
|
|
3188
|
+
...stack ? { stack } : {},
|
|
3189
|
+
...launchAgent ? { launchAgent } : {},
|
|
3190
|
+
...dockerLogoutInput ? { dockerLogout: dockerLogoutInput } : {},
|
|
3191
|
+
dryRun: opts.dryRun ?? false
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
async function runResetWithSpinners(args) {
|
|
3195
|
+
const { opts, region, wipeData, input } = args;
|
|
3139
3196
|
const phaseSpins = /* @__PURE__ */ new Map();
|
|
3140
3197
|
const actingPhases = [];
|
|
3141
|
-
if (stack && !
|
|
3142
|
-
if (launchAgent && !
|
|
3143
|
-
if (
|
|
3198
|
+
if (input.stack && !input.dryRun) actingPhases.push(RESET_PHASES.STOP_STACK);
|
|
3199
|
+
if (input.launchAgent && !input.dryRun) actingPhases.push(RESET_PHASES.LAUNCH_AGENT);
|
|
3200
|
+
if (input.dockerLogout && !input.dryRun) actingPhases.push(RESET_PHASES.DOCKER_LOGOUT);
|
|
3144
3201
|
for (const name of actingPhases) {
|
|
3145
3202
|
const s = p3.spinner();
|
|
3146
3203
|
s.start(`${name}\u2026`);
|
|
@@ -3156,30 +3213,27 @@ var resetCommand = new Command("reset").description(
|
|
|
3156
3213
|
p3.log.info(line);
|
|
3157
3214
|
}
|
|
3158
3215
|
};
|
|
3159
|
-
const report = await runReset(
|
|
3160
|
-
{
|
|
3161
|
-
...stack ? { stack } : {},
|
|
3162
|
-
...launchAgent ? { launchAgent } : {},
|
|
3163
|
-
...dockerLogoutInput ? { dockerLogout: dockerLogoutInput } : {},
|
|
3164
|
-
dryRun: opts.dryRun ?? false
|
|
3165
|
-
},
|
|
3166
|
-
{ ...DEFAULT_DEPS, onPhase: renderPhase }
|
|
3167
|
-
);
|
|
3216
|
+
const report = await runReset(input, { ...DEFAULT_DEPS, onPhase: renderPhase });
|
|
3168
3217
|
for (const [, spin] of phaseSpins) spin.stop(pc2.dim("\u2014 skipped"));
|
|
3218
|
+
reportResetOutcome({ report, opts, wipeData, region });
|
|
3219
|
+
}
|
|
3220
|
+
function reportResetOutcome(args) {
|
|
3221
|
+
const { report, opts, wipeData, region } = args;
|
|
3169
3222
|
const failed = report.phases.filter((ph) => ph.status === "fail").length;
|
|
3170
3223
|
if (failed > 0) {
|
|
3171
3224
|
p3.outro(pc2.red(`${failed} step${failed === 1 ? "" : "s"} failed.`));
|
|
3172
3225
|
process.exit(1);
|
|
3173
|
-
}
|
|
3226
|
+
}
|
|
3227
|
+
if (opts.dryRun) {
|
|
3174
3228
|
p3.outro(pc2.cyan("Dry run complete. Re-run without --dry-run to apply."));
|
|
3175
|
-
|
|
3176
|
-
p3.outro(
|
|
3177
|
-
pc2.green(
|
|
3178
|
-
wipeData ? `Reset complete \u2014 volumes wiped. Re-run \`create-op-node bootstrap --region ${region}\` to start fresh.` : `Reset complete \u2014 volumes preserved. Re-run \`create-op-node bootstrap --region ${region}\` to bring the stack back up.`
|
|
3179
|
-
)
|
|
3180
|
-
);
|
|
3229
|
+
return;
|
|
3181
3230
|
}
|
|
3182
|
-
|
|
3231
|
+
p3.outro(
|
|
3232
|
+
pc2.green(
|
|
3233
|
+
wipeData ? `Reset complete \u2014 volumes wiped. Re-run \`create-op-node bootstrap --region ${region}\` to start fresh.` : `Reset complete \u2014 volumes preserved. Re-run \`create-op-node bootstrap --region ${region}\` to bring the stack back up.`
|
|
3234
|
+
)
|
|
3235
|
+
);
|
|
3236
|
+
}
|
|
3183
3237
|
|
|
3184
3238
|
// src/lib/cosign.ts
|
|
3185
3239
|
var COSIGN_OIDC_ISSUER = "https://token.actions.githubusercontent.com";
|
|
@@ -4620,7 +4674,7 @@ async function looksLikeRegionsRepo(dir) {
|
|
|
4620
4674
|
}
|
|
4621
4675
|
|
|
4622
4676
|
// src/cli.ts
|
|
4623
|
-
var VERSION = "0.10.
|
|
4677
|
+
var VERSION = "0.10.8";
|
|
4624
4678
|
var program = new Command();
|
|
4625
4679
|
program.name("create-op-node").description(
|
|
4626
4680
|
"Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."
|