fix34: устранён import-time баг i18n, усилен SSH tunnel policy и doctor warning
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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-команду:
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user