fix34: устранён import-time баг i18n, усилен SSH tunnel policy и doctor warning

This commit is contained in:
2026-05-09 12:34:47 +05:00
parent b2639da71c
commit 2e8e88bccc
6 changed files with 158 additions and 9 deletions
+10 -4
View File
@@ -1,10 +1,15 @@
import { createI18n } from "vue-i18n";
import { useAppStore } from "@/store/modules/app";
const appStore = useAppStore();
import defaultSettings from "@/settings";
import enLocale from "./package/en";
import ruLocale from "./package/ru";
function initialLocale(): string {
if (typeof window === "undefined") {
return defaultSettings.language || "ru";
}
return window.localStorage.getItem("language") || defaultSettings.language || "ru";
}
const messages = {
ru: {
...ruLocale,
@@ -16,7 +21,8 @@ const messages = {
const i18n = createI18n({
legacy: false,
locale: appStore.language,
locale: initialLocale(),
fallbackLocale: "ru",
messages: messages,
globalInjection: true,
});
+1 -3
View File
@@ -7,8 +7,6 @@ import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: false }); // Индикатор загрузки
const permissionStore = usePermissionStoreHook();
// Разрешённые маршруты
const whiteList = ["/login", "/register"];
@@ -37,6 +35,7 @@ router.beforeEach(async (to, from, next) => {
} else {
try {
const { roles, forcePasswordChange } = await adminStore.getAdminInfo();
const permissionStore = usePermissionStoreHook();
const accessRoutes = permissionStore.generateRoutes(roles);
accessRoutes.forEach((route) => {
router.addRoute(route);
@@ -68,4 +67,3 @@ router.beforeEach(async (to, from, next) => {
router.afterEach(() => {
NProgress.done();
});
+44
View File
@@ -107,6 +107,50 @@ curl -sS \
- совпадает ли `HUI_INSTALL_DIR` с реальностью
- не сломан ли bind host / port
### Admin UI access via SSH tunnel
Production-модель для UI: `HY2XS_UI_BIND_HOST=127.0.0.1`, внешний доступ к `8080/tcp` не открывается.
Доступ оператора выполняется через SSH local forwarding.
Windows-команда туннеля:
```bash
ssh -p 2323 \
-i C:\Users\kirap\.ssh\id_ed25519_uk1 \
-N \
-L 127.0.0.1:8080:127.0.0.1:8080 \
root@185.156.108.141
```
После запуска открыть `http://127.0.0.1:8080/#/login`.
Если SSH-туннель не поднимается (`administratively prohibited`), проверить effective SSH policy:
```bash
sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) '
```
Рекомендуемый фрагмент hardening `sshd_config`:
```sshconfig
Port 2323
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
AllowTcpForwarding local
PermitOpen 127.0.0.1:8080 localhost:8080
GatewayPorts no
X11Forwarding no
AllowAgentForwarding no
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
```
### Hysteria скачалась, но не стартует
Проверить:
- валиден ли config
+45 -1
View File
@@ -99,7 +99,51 @@ hy2xs-orchestrator doctor --package-dir /usr/local/lib/hy2xs/package --config /e
Команда выполняет preflight + smoke как post-install/post-reboot validation.
## 13. Secret-safe config sharing
## 13. Admin UI access via SSH tunnel
Production policy: UI остаётся loopback-only (`HY2XS_UI_BIND_HOST=127.0.0.1`), внешний доступ к `8080/tcp` не открывается.
Операторский доступ выполняется через SSH local forwarding.
Windows tunnel command:
```bash
ssh -p 2323 \
-i C:\Users\kirap\.ssh\id_ed25519_uk1 \
-N \
-L 127.0.0.1:8080:127.0.0.1:8080 \
root@185.156.108.141
```
Open in browser: `http://127.0.0.1:8080/#/login`.
Если туннель падает с `administratively prohibited`, проверить effective sshd-конфиг:
```bash
sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) '
```
Recommended sshd hardening fragment:
```sshconfig
Port 2323
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
AllowTcpForwarding local
PermitOpen 127.0.0.1:8080 localhost:8080
GatewayPorts no
X11Forwarding no
AllowAgentForwarding no
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
```
## 14. Secret-safe config sharing
Для передачи конфигов в тикеты/чаты используйте встроенную redaction-команду:
+55 -1
View File
@@ -1,10 +1,61 @@
import type { ReconfigureContext, ReconfigureOptions } from "../types/context";
import { readText } from "../lib/fs";
import { setOperationContext, step, stepDone } from "../lib/log";
import { info, setOperationContext, step, stepDone } from "../lib/log";
import { parseRuntimeEnv } from "../config/env";
import { preflight } from "../steps/preflight";
import { smoke } from "../steps/smoke";
import { readInstalledHysteriaVersion, readPackageValue } from "../lib/packageMeta";
import { run } from "../lib/process";
function hasPermitOpenForLocalUi(value: string): boolean {
const normalized = value.trim().toLowerCase();
if (!normalized) {
return false;
}
if (normalized === "any") {
return true;
}
return normalized.split(/\s+/).includes("127.0.0.1:8080") || normalized.split(/\s+/).includes("localhost:8080");
}
async function checkSshForwardingForLocalUi(uiBindHost: string): Promise<void> {
if (uiBindHost !== "127.0.0.1") {
return;
}
try {
const sshdConfigText = await run`sshd -T`;
const lines = sshdConfigText.split(/\r?\n/);
const effective = new Map<string, string>();
for (const line of lines) {
const normalized = line.trim();
if (!normalized) {
continue;
}
const separator = normalized.indexOf(" ");
if (separator <= 0) {
continue;
}
const key = normalized.slice(0, separator).trim();
const value = normalized.slice(separator + 1).trim();
effective.set(key, value);
}
const allowTcpForwarding = (effective.get("allowtcpforwarding") || "").toLowerCase();
const permitOpen = effective.get("permitopen") || "";
const forwardingEnabled = allowTcpForwarding === "yes" || allowTcpForwarding === "all" || allowTcpForwarding === "local";
const permitOpenValid = hasPermitOpenForLocalUi(permitOpen);
if (!forwardingEnabled || !permitOpenValid) {
info(
"WARNING: UI is local-only, but SSH local forwarding is disabled or restricted (allowtcpforwarding/permitopen). Verify sshd policy for 127.0.0.1:8080 tunnel access."
);
}
} catch {
info("WARNING: unable to read effective sshd config via `sshd -T`; skipping SSH forwarding advisory check.");
}
}
export async function doctor(options: ReconfigureOptions): Promise<void> {
setOperationContext(`doctor-${Date.now().toString(36)}`);
@@ -24,6 +75,9 @@ export async function doctor(options: ReconfigureOptions): Promise<void> {
step("doctor preflight");
await preflight(context);
stepDone("doctor preflight");
await checkSshForwardingForLocalUi(context.config.uiBindHost);
step("doctor smoke");
await smoke(context);
stepDone("doctor smoke");
+3
View File
@@ -40,6 +40,9 @@ run_fix20_acceptance_subset() {
log_step "Acceptance: bootstrap peer can pass auth smoke"
grep -q 'quota := int64(-1)' apps/dao/sqlite.go || fail "acceptance: bootstrap peer quota must be unlimited (-1), otherwise install auth smoke fails"
log_step "Acceptance: frontend i18n does not touch Pinia at module import"
! grep -q 'useAppStore' apps/frontend/src/lang/index.ts || fail "acceptance: lang/index.ts must not import/use Pinia store"
log_step "Acceptance: env rendering maps machine token and fails on unresolved placeholders"
grep -q 'HYSTERIA_API_SECRET: context.config.hysteriaTrafficStatsSecret' orchestrator/src/steps/env.ts || fail "acceptance: writePostInstallEnv must pass HYSTERIA_API_SECRET"
grep -q 'template render failed: unresolved placeholders' orchestrator/src/lib/fs.ts || fail "acceptance: renderTemplate must fail on unresolved placeholders"