Продакшн-фиксы install/reconfigure: rollback, firewall lifecycle, preflight и runbook
This commit is contained in:
@@ -1,16 +1,22 @@
|
||||
import type { ReconfigureContext, ReconfigureOptions } from "../types/context";
|
||||
import { readText, writeText } from "../lib/fs";
|
||||
import { exists, readText, writeText } from "../lib/fs";
|
||||
import { info, step } from "../lib/log";
|
||||
import { parseRuntimeEnv, renderRuntimeEnv } from "../config/env";
|
||||
import { preflight } from "../steps/preflight";
|
||||
import { generateConfig } from "../steps/config";
|
||||
import { deploySystemd } from "../steps/systemd";
|
||||
import { applyFirewall } from "../steps/firewall";
|
||||
import { applyFirewall, cancelFirewallRollback, rollbackFirewallNow } from "../steps/firewall";
|
||||
import { writePostInstallEnv } from "../steps/env";
|
||||
import { smoke } from "../steps/smoke";
|
||||
import { runVisible } from "../lib/process";
|
||||
import { readInstalledHysteriaVersion, readPackageValue } from "../lib/packageMeta";
|
||||
|
||||
const INSTALL_STATE_PATH = "/var/lib/hy2xs/install-state.json";
|
||||
|
||||
type InstallState = {
|
||||
installed?: boolean;
|
||||
};
|
||||
|
||||
async function backupCurrentState(): Promise<void> {
|
||||
await runVisible`mkdir -p /etc/hy2xs/backups`;
|
||||
await runVisible`cp -a /etc/hysteria/config.yaml /etc/hy2xs/backups/config.yaml.bak 2>/dev/null || true`;
|
||||
@@ -42,6 +48,36 @@ async function rollbackCurrentState(): Promise<void> {
|
||||
await runVisible`systemctl restart hysteria-server hy2xs-admin || true`;
|
||||
}
|
||||
|
||||
async function ensureInstallStateExists(): Promise<void> {
|
||||
if (!(await exists(INSTALL_STATE_PATH))) {
|
||||
throw new Error(`install state marker is missing: ${INSTALL_STATE_PATH}. Run install first.`);
|
||||
}
|
||||
|
||||
const raw = await readText(INSTALL_STATE_PATH);
|
||||
let parsed: InstallState;
|
||||
try {
|
||||
parsed = JSON.parse(raw) as InstallState;
|
||||
} catch {
|
||||
throw new Error(`invalid install state marker format: ${INSTALL_STATE_PATH}`);
|
||||
}
|
||||
if (!parsed.installed) {
|
||||
throw new Error(`install state marker does not indicate successful installation: ${INSTALL_STATE_PATH}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function warnBootstrapDrift(nextConfigRaw: string): Promise<void> {
|
||||
if (!(await exists("/etc/hy2xs/hy2xs.env"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prev = parseRuntimeEnv(await readText("/etc/hy2xs/hy2xs.env"));
|
||||
const next = parseRuntimeEnv(nextConfigRaw);
|
||||
if (prev.adminInitialPassword !== next.adminInitialPassword || prev.adminConPass !== next.adminConPass) {
|
||||
info("warning: Admin bootstrap fields are install-only and will not rotate existing credentials.");
|
||||
info("warning: Use a dedicated password rotation flow in application/account layer.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function reconfigure(options: ReconfigureOptions): Promise<void> {
|
||||
const configRaw = await readText(options.sourceConfigPath);
|
||||
const config = parseRuntimeEnv(configRaw);
|
||||
@@ -58,6 +94,9 @@ export async function reconfigure(options: ReconfigureOptions): Promise<void> {
|
||||
|
||||
step("preflight");
|
||||
await preflight(context);
|
||||
step("install state marker");
|
||||
await ensureInstallStateExists();
|
||||
await warnBootstrapDrift(configRaw);
|
||||
|
||||
if (options.dryRun) {
|
||||
info("reconfigure dry-run: validated config and execution graph");
|
||||
@@ -86,8 +125,11 @@ export async function reconfigure(options: ReconfigureOptions): Promise<void> {
|
||||
await writePostInstallEnv(context);
|
||||
step("smoke checks");
|
||||
await smoke(context);
|
||||
step("finalize firewall rollback guard");
|
||||
await cancelFirewallRollback(context);
|
||||
} catch (error) {
|
||||
info("reconfigure failed, rollback in progress");
|
||||
await rollbackFirewallNow(context);
|
||||
await rollbackCurrentState();
|
||||
throw error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user