Исправлен rollback/firewall lifecycle и shell execution, синхронизирована документация
This commit is contained in:
@@ -10,7 +10,9 @@ HY2XS — production installer/runtime-manager для развёртывания
|
|||||||
- vanilla Hysteria2 устанавливается как pinned binary (version/url/sha256 из metadata install package);
|
- vanilla Hysteria2 устанавливается как pinned binary (version/url/sha256 из metadata install package);
|
||||||
- runtime-конфиг управляется через `/etc/hy2xs/hy2xs.env` и команду `reconfigure`.
|
- runtime-конфиг управляется через `/etc/hy2xs/hy2xs.env` и команду `reconfigure`.
|
||||||
|
|
||||||
В baseline намеренно не входят target-side build, rollback/uninstall orchestration, Telegram bot delivery, port hopping и generic server-manager функции.
|
В baseline намеренно не входят target-side build, standalone rollback/update/uninstall subcommands, Telegram bot delivery, port hopping и generic server-manager функции.
|
||||||
|
|
||||||
|
При этом install/reconfigure содержат bounded rollback для failure-сценариев firewall/systemd/config/smoke.
|
||||||
|
|
||||||
## Структура репозитория
|
## Структура репозитория
|
||||||
|
|
||||||
@@ -64,6 +66,8 @@ apt-get install -y sudo ca-certificates curl iproute2 tar openssl nftables syste
|
|||||||
|
|
||||||
`sudo` является обязательной зависимостью target-хоста. Он используется не для интерактивной установки, а для smoke-проверок прав доступа от имени runtime-пользователей.
|
`sudo` является обязательной зависимостью target-хоста. Он используется не для интерактивной установки, а для smoke-проверок прав доступа от имени runtime-пользователей.
|
||||||
|
|
||||||
|
`sudo` должен быть установлен до запуска `./install.sh`: preflight проверяет его до шага `installDeps`, поэтому deps-стадия не является bootstrap-слоем для `sudo`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./install.sh --non-interactive
|
./install.sh --non-interactive
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ Telegram-бот, backend выдачи ключей, remote profile publishing, b
|
|||||||
- target-side `bun install` / transpile / compile
|
- target-side `bun install` / transpile / compile
|
||||||
- Telegram-бот
|
- Telegram-бот
|
||||||
- backend выдачи remote profiles
|
- backend выдачи remote profiles
|
||||||
- update / rollback / uninstall
|
- standalone update / rollback / uninstall subcommands
|
||||||
|
|
||||||
|
При этом в install/reconfigure допускается bounded rollback для failure-сценариев firewall/systemd/config/smoke.
|
||||||
- Docker как основной способ поставки
|
- Docker как основной способ поставки
|
||||||
- multi-node deployment
|
- multi-node deployment
|
||||||
- сложный control plane
|
- сложный control plane
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
## Оркестратор не умеет
|
## Оркестратор не умеет
|
||||||
|
|
||||||
- upgrade
|
- upgrade
|
||||||
- rollback
|
- standalone rollback subcommands
|
||||||
- uninstall
|
- uninstall
|
||||||
- repair старых неизвестных состояний
|
- repair старых неизвестных состояний
|
||||||
- target-side build
|
- target-side build
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
- Любой конфликт неизвестного старого состояния = stop with error.
|
- Любой конфликт неизвестного старого состояния = stop with error.
|
||||||
- Никакой сложной автомиграции.
|
- Никакой сложной автомиграции.
|
||||||
- Ошибки должны быть текстовыми и пригодными для диагностики.
|
- Ошибки должны быть текстовыми и пригодными для диагностики.
|
||||||
|
- Для install/reconfigure допустим bounded rollback при failure-сценариях firewall/systemd/config/smoke.
|
||||||
|
|
||||||
## CLI baseline
|
## CLI baseline
|
||||||
|
|
||||||
|
|||||||
@@ -79,9 +79,10 @@
|
|||||||
4. UI поставлен из bundled fork
|
4. UI поставлен из bundled fork
|
||||||
5. `post-install.env` отражает фактическое deploy-состояние
|
5. `post-install.env` отражает фактическое deploy-состояние
|
||||||
6. оркестратор зафиксирован как Bun/TypeScript stack и поставляется как готовый install-артефакт
|
6. оркестратор зафиксирован как Bun/TypeScript stack и поставляется как готовый install-артефакт
|
||||||
7. оркестратор не требует update / rollback / uninstall логики
|
7. оркестратор не требует standalone update / rollback / uninstall subcommands
|
||||||
8. Telegram/access layer не требуется для прохождения install acceptance
|
8. bounded rollback в install/reconfigure корректно отрабатывает failure-сценарии firewall/systemd/config/smoke
|
||||||
9. отсутствует production path для port hopping
|
9. Telegram/access layer не требуется для прохождения install acceptance
|
||||||
10. UI не запускается от root
|
10. отсутствует production path для port hopping
|
||||||
11. клиентские endpoint не зависят от request `Host`/`hostname`
|
11. UI не запускается от root
|
||||||
12. production build verify падает, если `config/hy2xs.env` содержит placeholder-значения
|
12. клиентские endpoint не зависят от request `Host`/`hostname`
|
||||||
|
13. production build verify падает, если `config/hy2xs.env` содержит placeholder-значения
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ apt-get install -y sudo ca-certificates curl iproute2 tar openssl nftables syste
|
|||||||
|
|
||||||
`sudo` обязателен для permission smoke-checks от имени runtime-пользователей.
|
`sudo` обязателен для permission smoke-checks от имени runtime-пользователей.
|
||||||
|
|
||||||
|
`sudo` должен быть установлен до запуска `./install.sh`: preflight проверяет его до шага установки зависимостей.
|
||||||
|
|
||||||
## 3. Required open ports
|
## 3. Required open ports
|
||||||
|
|
||||||
- UDP `${HY2XS_HYSTERIA_PORT}`
|
- UDP `${HY2XS_HYSTERIA_PORT}`
|
||||||
|
|||||||
+4
-3
@@ -8,7 +8,7 @@
|
|||||||
- оркестратор: **install-only**, только первичная установка и базовая настройка
|
- оркестратор: **install-only**, только первичная установка и базовая настройка
|
||||||
- стек оркестратора: **Bun + TypeScript**
|
- стек оркестратора: **Bun + TypeScript**
|
||||||
- target-side build: **запрещён**
|
- target-side build: **запрещён**
|
||||||
- update / rollback / uninstall: **вне scope**
|
- standalone update / rollback / uninstall subcommands: **вне scope**
|
||||||
- сборка и упаковка: **отдельный локальный build layer**
|
- сборка и упаковка: **отдельный локальный build layer**
|
||||||
- post-install state: **`/etc/hysteria/post-install.env`**
|
- post-install state: **`/etc/hysteria/post-install.env`**
|
||||||
- клиентский delivery/access layer: **вне baseline этого пакета docs**
|
- клиентский delivery/access layer: **вне baseline этого пакета docs**
|
||||||
@@ -34,8 +34,9 @@
|
|||||||
2. HY2XS admin форкается к себе и поставляется вместе с пакетом.
|
2. HY2XS admin форкается к себе и поставляется вместе с пакетом.
|
||||||
3. Оркестратор пишется на **Bun + TypeScript**.
|
3. Оркестратор пишется на **Bun + TypeScript**.
|
||||||
4. На target нет `npm` / `pnpm` / `yarn` / `bun install` / transpile step.
|
4. На target нет `npm` / `pnpm` / `yarn` / `bun install` / transpile step.
|
||||||
5. На target нет логики update / rollback / uninstall.
|
5. На target нет standalone логики update / rollback / uninstall.
|
||||||
6. Выдача доступа пользователям, Telegram-бот, billing, backend профилей и похожие контуры **не входят** в этот baseline.
|
6. В install/reconfigure есть bounded rollback для failure-сценариев firewall/systemd/config/smoke.
|
||||||
|
7. Выдача доступа пользователям, Telegram-бот, billing, backend профилей и похожие контуры **не входят** в этот baseline.
|
||||||
|
|
||||||
## Состав документов
|
## Состав документов
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ async function markInstallSuccessful(context: InstallContext): Promise<void> {
|
|||||||
await runVisible`chown root:root ${INSTALL_STATE_PATH}`;
|
await runVisible`chown root:root ${INSTALL_STATE_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rollbackFailedInstall(context: InstallContext): Promise<void> {
|
async function rollbackFailedInstall(context: InstallContext, state: { firewallTouched: boolean }): Promise<void> {
|
||||||
await rollbackFirewallNow(context);
|
if (state.firewallTouched) {
|
||||||
|
await rollbackFirewallNow(context);
|
||||||
|
}
|
||||||
await runVisible`systemctl stop hysteria-server hy2xs-admin || true`;
|
await runVisible`systemctl stop hysteria-server hy2xs-admin || true`;
|
||||||
await runVisible`systemctl disable hysteria-server hy2xs-admin || true`;
|
await runVisible`systemctl disable hysteria-server hy2xs-admin || true`;
|
||||||
await runVisible`systemctl reset-failed hysteria-server hy2xs-admin || true`;
|
await runVisible`systemctl reset-failed hysteria-server hy2xs-admin || true`;
|
||||||
@@ -66,6 +68,10 @@ export async function install(options: InstallOptions): Promise<void> {
|
|||||||
throw new Error("missing Hysteria lock metadata in package: hysteria.version/hysteria.url/hysteria.sha256");
|
throw new Error("missing Hysteria lock metadata in package: hysteria.version/hysteria.url/hysteria.sha256");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
firewallTouched: false
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
step("preflight");
|
step("preflight");
|
||||||
await preflight(context);
|
await preflight(context);
|
||||||
@@ -88,6 +94,7 @@ export async function install(options: InstallOptions): Promise<void> {
|
|||||||
await deploySystemd(context);
|
await deploySystemd(context);
|
||||||
step("firewall");
|
step("firewall");
|
||||||
await applyFirewall(context);
|
await applyFirewall(context);
|
||||||
|
state.firewallTouched = true;
|
||||||
step("post-install env");
|
step("post-install env");
|
||||||
await writePostInstallEnv(context);
|
await writePostInstallEnv(context);
|
||||||
step("bootstrap admin secret");
|
step("bootstrap admin secret");
|
||||||
@@ -99,7 +106,7 @@ export async function install(options: InstallOptions): Promise<void> {
|
|||||||
step("mark install successful");
|
step("mark install successful");
|
||||||
await markInstallSuccessful(context);
|
await markInstallSuccessful(context);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await rollbackFailedInstall(context);
|
await rollbackFailedInstall(context, state);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,18 @@ export async function runVisible(command: TemplateStringsArray, ...args: unknown
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function runRawVisible(command: string): Promise<void> {
|
||||||
|
info(`running script:\n${command}`);
|
||||||
|
const process = Bun.spawn(["sh", "-eu", "-c", command], {
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit"
|
||||||
|
});
|
||||||
|
const exitCode = await process.exited;
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(`script failed (${exitCode})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function runHidden(command: TemplateStringsArray, ...args: unknown[]): Promise<void> {
|
export async function runHidden(command: TemplateStringsArray, ...args: unknown[]): Promise<void> {
|
||||||
const rendered = renderCommand(command, args);
|
const rendered = renderCommand(command, args);
|
||||||
const process = Bun.spawn(["sh", "-c", rendered], {
|
const process = Bun.spawn(["sh", "-c", rendered], {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { InstallContext } from "../types/context";
|
import type { InstallContext } from "../types/context";
|
||||||
import { runVisible } from "../lib/process";
|
import { runRawVisible, runVisible } from "../lib/process";
|
||||||
|
|
||||||
async function ensureRuntimeIdentity(user: string, expectedHome: string): Promise<void> {
|
async function ensureRuntimeIdentity(user: string, expectedHome: string): Promise<void> {
|
||||||
const checkCmd = `
|
const checkCmd = `
|
||||||
@@ -23,7 +23,7 @@ else
|
|||||||
useradd --system --home ${expectedHome} --shell /usr/sbin/nologin ${user}
|
useradd --system --home ${expectedHome} --shell /usr/sbin/nologin ${user}
|
||||||
fi
|
fi
|
||||||
`;
|
`;
|
||||||
await runVisible`${checkCmd}`;
|
await runRawVisible(checkCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareFilesystem(context: InstallContext): Promise<void> {
|
export async function prepareFilesystem(context: InstallContext): Promise<void> {
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ export async function rollbackFirewallNow(context: RuntimeContext): Promise<void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await exists("/etc/nftables.d/nftables.conf.existed")) && !(await exists("/etc/nftables.d/hy2xs.nft.existed"))) {
|
||||||
|
info("firewall rollback skipped: no HY2XS rollback markers found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (context.config.firewallStagedApply) {
|
if (context.config.firewallStagedApply) {
|
||||||
await runVisible`systemctl stop hy2xs-fw-rollback || true`;
|
await runVisible`systemctl stop hy2xs-fw-rollback || true`;
|
||||||
await runVisible`systemctl reset-failed hy2xs-fw-rollback || true`;
|
await runVisible`systemctl reset-failed hy2xs-fw-rollback || true`;
|
||||||
@@ -131,6 +136,6 @@ export async function rollbackFirewallNow(context: RuntimeContext): Promise<void
|
|||||||
|
|
||||||
await runVisible`if [ -f /etc/nftables.d/nftables.conf.existed ]; then cp -a /etc/nftables.conf.hy2xs.bak /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi`;
|
await runVisible`if [ -f /etc/nftables.d/nftables.conf.existed ]; then cp -a /etc/nftables.conf.hy2xs.bak /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi`;
|
||||||
await runVisible`if [ -f /etc/nftables.d/hy2xs.nft.existed ]; then cp -a /etc/nftables.d/hy2xs.nft.bak /etc/nftables.d/hy2xs.nft 2>/dev/null || true; else rm -f /etc/nftables.d/hy2xs.nft; fi`;
|
await runVisible`if [ -f /etc/nftables.d/hy2xs.nft.existed ]; then cp -a /etc/nftables.d/hy2xs.nft.bak /etc/nftables.d/hy2xs.nft 2>/dev/null || true; else rm -f /etc/nftables.d/hy2xs.nft; fi`;
|
||||||
await runVisible`nft -f /etc/nftables.conf >/dev/null 2>&1 || true`;
|
await runVisible`if [ -f /etc/nftables.d/nftables.conf.existed ]; then nft -f /etc/nftables.conf >/dev/null 2>&1 || true; else nft flush ruleset >/dev/null 2>&1 || true; fi`;
|
||||||
await runVisible`rm -f ${FW_BACKUP_FILES}`;
|
await runVisible`rm -f ${FW_BACKUP_FILES}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user