create-op-node 0.10.7 → 0.10.9
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 +319 -226
- 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
|
-
} else {
|
|
2906
|
-
const result2 = await deps.composeDown({
|
|
2907
|
-
files: input.stack.composeFiles,
|
|
2908
|
-
cwd: input.stack.repoPath,
|
|
2909
|
-
...input.stack.envFile ? { envFile: input.stack.envFile } : {},
|
|
2910
|
-
wipeVolumes: input.stack.wipeVolumes,
|
|
2911
|
-
removeOrphans: input.stack.removeOrphans,
|
|
2912
|
-
...input.stack.wipeImages ? { removeImages: "all" } : {}
|
|
2913
|
-
});
|
|
2914
|
-
if (result2.ok) {
|
|
2915
|
-
const bits = [
|
|
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
|
+
};
|
|
2927
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" };
|
|
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
|
-
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
status: "fail",
|
|
2949
|
-
detail: `${fail?.step}: ${fail?.reason ?? "unknown"}`
|
|
2950
|
-
});
|
|
2951
|
-
}
|
|
2939
|
+
};
|
|
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
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";
|
|
@@ -3374,36 +3428,47 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
|
|
|
3374
3428
|
phases.push(ph);
|
|
3375
3429
|
deps.onPhase?.(ph);
|
|
3376
3430
|
};
|
|
3431
|
+
await verifyTlsPhase(input, deps, push);
|
|
3432
|
+
await verifyHealthPhase(input, deps, push);
|
|
3433
|
+
await verifyGraphqlPhase(input, deps, push);
|
|
3434
|
+
await verifyCloudflarePhase(input, deps, push);
|
|
3435
|
+
await verifyCosignPhase(input, deps, push);
|
|
3436
|
+
return { phases };
|
|
3437
|
+
}
|
|
3438
|
+
async function verifyTlsPhase(input, deps, push) {
|
|
3377
3439
|
const tls2 = await deps.tls({ host: input.apiHost });
|
|
3378
3440
|
if (!tls2.ok) {
|
|
3379
3441
|
push({ name: "TLS handshake", status: "fail", detail: tls2.reason });
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
push({ name: "TLS handshake", status: "ok", detail: line });
|
|
3391
|
-
}
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
const line = `subject=${tls2.subject}, issuer=${tls2.issuer}, ${formatExpiry(tls2.daysToExpiry)}`;
|
|
3445
|
+
if (tls2.daysToExpiry < input.certWarnDays) {
|
|
3446
|
+
push({
|
|
3447
|
+
name: "TLS handshake",
|
|
3448
|
+
status: "warn",
|
|
3449
|
+
detail: tls2.daysToExpiry < 0 ? line : `${line} (< warn threshold ${input.certWarnDays}d)`
|
|
3450
|
+
});
|
|
3451
|
+
return;
|
|
3392
3452
|
}
|
|
3393
|
-
|
|
3394
|
-
|
|
3453
|
+
push({ name: "TLS handshake", status: "ok", detail: line });
|
|
3454
|
+
}
|
|
3455
|
+
async function verifyHealthPhase(input, deps, push) {
|
|
3456
|
+
const health = await deps.http({ url: `https://${input.apiHost}/health` });
|
|
3395
3457
|
if (health.ok) {
|
|
3396
3458
|
push({ name: "GET /health", status: "ok", detail: `HTTP ${health.status}` });
|
|
3397
3459
|
} else {
|
|
3398
3460
|
push({ name: "GET /health", status: "fail", detail: health.reason });
|
|
3399
3461
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3462
|
+
}
|
|
3463
|
+
async function verifyGraphqlPhase(input, deps, push) {
|
|
3464
|
+
const gql = await deps.graphql({ url: `https://${input.apiHost}/api` });
|
|
3402
3465
|
if (gql.ok) {
|
|
3403
3466
|
push({ name: "GraphQL { __typename }", status: "ok", detail: `typename=${gql.typename}` });
|
|
3404
3467
|
} else {
|
|
3405
3468
|
push({ name: "GraphQL { __typename }", status: "fail", detail: gql.reason });
|
|
3406
3469
|
}
|
|
3470
|
+
}
|
|
3471
|
+
async function verifyCloudflarePhase(input, deps, push) {
|
|
3407
3472
|
const cfFields = [
|
|
3408
3473
|
["--cf-token", input.cf?.token],
|
|
3409
3474
|
["--cf-account-id", input.cf?.accountId],
|
|
@@ -3416,54 +3481,57 @@ async function runVerify(input, deps = DEFAULT_DEPS2) {
|
|
|
3416
3481
|
status: "skipped",
|
|
3417
3482
|
detail: "pass --cf-token + --cf-account-id + --tunnel-id to enable"
|
|
3418
3483
|
});
|
|
3419
|
-
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
if (cfSet.length < cfFields.length) {
|
|
3420
3487
|
const missing = cfFields.filter(([, v]) => v === void 0 || v === "").map(([name]) => name).join(", ");
|
|
3421
3488
|
push({
|
|
3422
3489
|
name: "Cloudflare Tunnel",
|
|
3423
3490
|
status: "warn",
|
|
3424
3491
|
detail: `partial CF config \u2014 missing ${missing}; check skipped`
|
|
3425
3492
|
});
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
const tun = await deps.tunnel({
|
|
3496
|
+
token: input.cf.token,
|
|
3497
|
+
accountId: input.cf.accountId,
|
|
3498
|
+
tunnelId: input.cf.tunnelId
|
|
3499
|
+
});
|
|
3500
|
+
if (!tun.ok) {
|
|
3501
|
+
push({ name: "Cloudflare Tunnel", status: "fail", detail: tun.reason });
|
|
3502
|
+
} else if (tun.connections === 0) {
|
|
3503
|
+
push({
|
|
3504
|
+
name: "Cloudflare Tunnel",
|
|
3505
|
+
status: "warn",
|
|
3506
|
+
detail: `status=${tun.status}, 0 connections \u2014 cloudflared on the Studio appears offline`
|
|
3507
|
+
});
|
|
3426
3508
|
} else {
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3509
|
+
push({
|
|
3510
|
+
name: "Cloudflare Tunnel",
|
|
3511
|
+
status: "ok",
|
|
3512
|
+
detail: `${tun.connections} connections, status=${tun.status}`
|
|
3431
3513
|
});
|
|
3432
|
-
if (!tun.ok) {
|
|
3433
|
-
push({ name: "Cloudflare Tunnel", status: "fail", detail: tun.reason });
|
|
3434
|
-
} else if (tun.connections === 0) {
|
|
3435
|
-
push({
|
|
3436
|
-
name: "Cloudflare Tunnel",
|
|
3437
|
-
status: "warn",
|
|
3438
|
-
detail: `status=${tun.status}, 0 connections \u2014 cloudflared on the Studio appears offline`
|
|
3439
|
-
});
|
|
3440
|
-
} else {
|
|
3441
|
-
push({
|
|
3442
|
-
name: "Cloudflare Tunnel",
|
|
3443
|
-
status: "ok",
|
|
3444
|
-
detail: `${tun.connections} connections, status=${tun.status}`
|
|
3445
|
-
});
|
|
3446
|
-
}
|
|
3447
3514
|
}
|
|
3515
|
+
}
|
|
3516
|
+
async function verifyCosignPhase(input, deps, push) {
|
|
3448
3517
|
if (input.images.length === 0) {
|
|
3449
3518
|
push({
|
|
3450
3519
|
name: "cosign verify",
|
|
3451
3520
|
status: "skipped",
|
|
3452
3521
|
detail: "pass --image <ref> (repeatable) to enable"
|
|
3453
3522
|
});
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
}
|
|
3460
|
-
|
|
3461
|
-
}
|
|
3462
|
-
|
|
3463
|
-
}
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
for (const image of input.images) {
|
|
3526
|
+
const cos = await deps.cosign({ image });
|
|
3527
|
+
if (cos.ok) {
|
|
3528
|
+
push({ name: `cosign verify ${image}`, status: "ok", detail: "signature valid" });
|
|
3529
|
+
} else if (cos.skipped) {
|
|
3530
|
+
push({ name: `cosign verify ${image}`, status: "skipped", detail: cos.reason });
|
|
3531
|
+
} else {
|
|
3532
|
+
push({ name: `cosign verify ${image}`, status: "fail", detail: cos.reason });
|
|
3464
3533
|
}
|
|
3465
3534
|
}
|
|
3466
|
-
return { phases };
|
|
3467
3535
|
}
|
|
3468
3536
|
function readTokenFile(path) {
|
|
3469
3537
|
const raw = readFileSync(path, "utf8").trim();
|
|
@@ -3488,6 +3556,53 @@ var verifyCommand = new Command("verify").description(
|
|
|
3488
3556
|
new Option("--show-skipped", "Include skipped phases in the summary").default(false)
|
|
3489
3557
|
).action(async (opts) => {
|
|
3490
3558
|
p3.intro(pc2.bgCyan(pc2.black(" create-op-node verify ")));
|
|
3559
|
+
const domain = await resolveDomain(opts);
|
|
3560
|
+
const certWarnDays = resolveCertWarnDays(opts);
|
|
3561
|
+
const cfToken = resolveCfToken(opts);
|
|
3562
|
+
const apiHost = opts.apiHost ?? `api.${domain}`;
|
|
3563
|
+
const images = opts.image ?? [];
|
|
3564
|
+
const totalPhases = 4 + (images.length === 0 ? 1 : images.length);
|
|
3565
|
+
const report = await runVerifyWithSpinner({
|
|
3566
|
+
apiHost,
|
|
3567
|
+
certWarnDays,
|
|
3568
|
+
cfToken,
|
|
3569
|
+
opts,
|
|
3570
|
+
images,
|
|
3571
|
+
totalPhases
|
|
3572
|
+
});
|
|
3573
|
+
renderVerifySummary(report, { opts, totalPhases });
|
|
3574
|
+
reportVerifyOutcome(report);
|
|
3575
|
+
});
|
|
3576
|
+
function phaseIcon2(ph) {
|
|
3577
|
+
switch (ph.status) {
|
|
3578
|
+
case "ok":
|
|
3579
|
+
return pc2.green("\u2713");
|
|
3580
|
+
case "warn":
|
|
3581
|
+
return pc2.yellow("\u26A0");
|
|
3582
|
+
case "skipped":
|
|
3583
|
+
return pc2.dim("\xB7");
|
|
3584
|
+
case "fail":
|
|
3585
|
+
return pc2.red("\u2717");
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
function phaseColor(status) {
|
|
3589
|
+
switch (status) {
|
|
3590
|
+
case "ok":
|
|
3591
|
+
return pc2.green;
|
|
3592
|
+
case "warn":
|
|
3593
|
+
return pc2.yellow;
|
|
3594
|
+
case "skipped":
|
|
3595
|
+
return pc2.dim;
|
|
3596
|
+
case "fail":
|
|
3597
|
+
return pc2.red;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function formatPhaseLine(prefix, ph) {
|
|
3601
|
+
const icon = phaseIcon2(ph);
|
|
3602
|
+
const colorize = phaseColor(ph.status);
|
|
3603
|
+
return colorize(`${icon} ${prefix}: ${ph.detail}`);
|
|
3604
|
+
}
|
|
3605
|
+
async function resolveDomain(opts) {
|
|
3491
3606
|
const domain = opts.domain ? opts.domain : unwrap(
|
|
3492
3607
|
await p3.text({
|
|
3493
3608
|
message: "Public domain of the node?",
|
|
@@ -3499,28 +3614,33 @@ var verifyCommand = new Command("verify").description(
|
|
|
3499
3614
|
p3.cancel(`--domain ${JSON.stringify(domain)} doesn't look like a domain.`);
|
|
3500
3615
|
process.exit(2);
|
|
3501
3616
|
}
|
|
3617
|
+
return domain;
|
|
3618
|
+
}
|
|
3619
|
+
function resolveCertWarnDays(opts) {
|
|
3502
3620
|
const rawDays = opts.certWarnDays ?? "14";
|
|
3503
3621
|
const certWarnDays = Number.parseInt(rawDays, 10);
|
|
3504
3622
|
if (!Number.isFinite(certWarnDays) || certWarnDays < 0 || !/^\d+$/.test(rawDays)) {
|
|
3505
3623
|
p3.cancel(`--cert-warn-days must be a non-negative integer (got ${JSON.stringify(rawDays)}).`);
|
|
3506
3624
|
process.exit(2);
|
|
3507
3625
|
}
|
|
3626
|
+
return certWarnDays;
|
|
3627
|
+
}
|
|
3628
|
+
function resolveCfToken(opts) {
|
|
3508
3629
|
if (opts.cfToken && opts.cfTokenFile) {
|
|
3509
3630
|
p3.cancel("Pass either --cf-token or --cf-token-file, not both.");
|
|
3510
3631
|
process.exit(2);
|
|
3511
3632
|
}
|
|
3512
|
-
|
|
3513
|
-
if (!
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
}
|
|
3633
|
+
if (opts.cfToken) return opts.cfToken;
|
|
3634
|
+
if (!opts.cfTokenFile) return void 0;
|
|
3635
|
+
try {
|
|
3636
|
+
return readTokenFile(opts.cfTokenFile);
|
|
3637
|
+
} catch (err) {
|
|
3638
|
+
p3.cancel(err.message);
|
|
3639
|
+
process.exit(2);
|
|
3520
3640
|
}
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
const
|
|
3641
|
+
}
|
|
3642
|
+
async function runVerifyWithSpinner(args) {
|
|
3643
|
+
const { apiHost, certWarnDays, cfToken, opts, images, totalPhases } = args;
|
|
3524
3644
|
let phaseIndex = 0;
|
|
3525
3645
|
let activeSpin = null;
|
|
3526
3646
|
const renderPhase = (ph) => {
|
|
@@ -3529,10 +3649,7 @@ var verifyCommand = new Command("verify").description(
|
|
|
3529
3649
|
activeSpin?.stop(formatPhaseLine(prefix, ph));
|
|
3530
3650
|
activeSpin = null;
|
|
3531
3651
|
};
|
|
3532
|
-
const deps = {
|
|
3533
|
-
...DEFAULT_DEPS2,
|
|
3534
|
-
onPhase: renderPhase
|
|
3535
|
-
};
|
|
3652
|
+
const deps = { ...DEFAULT_DEPS2, onPhase: renderPhase };
|
|
3536
3653
|
activeSpin = p3.spinner();
|
|
3537
3654
|
activeSpin.start("Running checks\u2026");
|
|
3538
3655
|
const report = await runVerify(
|
|
@@ -3549,6 +3666,10 @@ var verifyCommand = new Command("verify").description(
|
|
|
3549
3666
|
deps
|
|
3550
3667
|
);
|
|
3551
3668
|
activeSpin?.stop("Done.");
|
|
3669
|
+
return report;
|
|
3670
|
+
}
|
|
3671
|
+
function renderVerifySummary(report, args) {
|
|
3672
|
+
const { opts, totalPhases } = args;
|
|
3552
3673
|
const visible = opts.showSkipped ?? false ? report.phases : report.phases.filter((ph) => ph.status !== "skipped");
|
|
3553
3674
|
const lines = visible.map((ph) => {
|
|
3554
3675
|
const idx = report.phases.indexOf(ph) + 1;
|
|
@@ -3559,47 +3680,19 @@ var verifyCommand = new Command("verify").description(
|
|
|
3559
3680
|
lines.push(pc2.dim(`\xB7 ${skippedCount} phase${skippedCount === 1 ? "" : "s"} skipped (run with --show-skipped to see them)`));
|
|
3560
3681
|
}
|
|
3561
3682
|
p3.note(lines.join("\n"), "Summary");
|
|
3683
|
+
}
|
|
3684
|
+
function reportVerifyOutcome(report) {
|
|
3562
3685
|
const summary = summarize(report);
|
|
3563
|
-
if (summary.ok) {
|
|
3564
|
-
if (summary.warned === 0) {
|
|
3565
|
-
p3.outro(pc2.green("All checks passed."));
|
|
3566
|
-
} else {
|
|
3567
|
-
p3.outro(pc2.yellow(`Passed with ${summary.warned} warning${summary.warned === 1 ? "" : "s"}.`));
|
|
3568
|
-
}
|
|
3569
|
-
} else {
|
|
3686
|
+
if (!summary.ok) {
|
|
3570
3687
|
p3.outro(pc2.red(`${summary.failed} check${summary.failed === 1 ? "" : "s"} failed.`));
|
|
3571
3688
|
process.exit(1);
|
|
3572
3689
|
}
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
return pc2.green("\u2713");
|
|
3578
|
-
case "warn":
|
|
3579
|
-
return pc2.yellow("\u26A0");
|
|
3580
|
-
case "skipped":
|
|
3581
|
-
return pc2.dim("\xB7");
|
|
3582
|
-
case "fail":
|
|
3583
|
-
return pc2.red("\u2717");
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
function phaseColor(status) {
|
|
3587
|
-
switch (status) {
|
|
3588
|
-
case "ok":
|
|
3589
|
-
return pc2.green;
|
|
3590
|
-
case "warn":
|
|
3591
|
-
return pc2.yellow;
|
|
3592
|
-
case "skipped":
|
|
3593
|
-
return pc2.dim;
|
|
3594
|
-
case "fail":
|
|
3595
|
-
return pc2.red;
|
|
3690
|
+
if (summary.warned === 0) {
|
|
3691
|
+
p3.outro(pc2.green("All checks passed."));
|
|
3692
|
+
} else {
|
|
3693
|
+
p3.outro(pc2.yellow(`Passed with ${summary.warned} warning${summary.warned === 1 ? "" : "s"}.`));
|
|
3596
3694
|
}
|
|
3597
3695
|
}
|
|
3598
|
-
function formatPhaseLine(prefix, ph) {
|
|
3599
|
-
const icon = phaseIcon2(ph);
|
|
3600
|
-
const colorize = phaseColor(ph.status);
|
|
3601
|
-
return colorize(`${icon} ${prefix}: ${ph.detail}`);
|
|
3602
|
-
}
|
|
3603
3696
|
|
|
3604
3697
|
// src/lib/region-schema.json
|
|
3605
3698
|
var region_schema_default = {
|
|
@@ -4620,7 +4713,7 @@ async function looksLikeRegionsRepo(dir) {
|
|
|
4620
4713
|
}
|
|
4621
4714
|
|
|
4622
4715
|
// src/cli.ts
|
|
4623
|
-
var VERSION = "0.10.
|
|
4716
|
+
var VERSION = "0.10.9";
|
|
4624
4717
|
var program = new Command();
|
|
4625
4718
|
program.name("create-op-node").description(
|
|
4626
4719
|
"Interactive bootstrap for an Opus Populi federation node.\nCloudflare infrastructure \u2192 Mac Studio \u2192 live public API."
|