Полный production-фикс fix30/fix30.1: auth, smoke, bootstrap, preflight, rollback и валидация PATCH
This commit is contained in:
@@ -92,6 +92,7 @@ func UpdatePeer(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
peerUpdateDto.Id = &id
|
||||||
if peerUpdateDto.Name != nil && *peerUpdateDto.Name != "" && service.ExistPeerName(*peerUpdateDto.Name, id) {
|
if peerUpdateDto.Name != nil && *peerUpdateDto.Name != "" && service.ExistPeerName(*peerUpdateDto.Name, id) {
|
||||||
vo.Fail(fmt.Sprintf("name %s already exists", *peerUpdateDto.Name), c)
|
vo.Fail(fmt.Sprintf("name %s already exists", *peerUpdateDto.Name), c)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func validateField[T interface{}](c *gin.Context, field T) (T, error) {
|
|||||||
bindErr = c.ShouldBindQuery(&field)
|
bindErr = c.ShouldBindQuery(&field)
|
||||||
} else if c.Request.Method == http.MethodPost ||
|
} else if c.Request.Method == http.MethodPost ||
|
||||||
c.Request.Method == http.MethodPut ||
|
c.Request.Method == http.MethodPut ||
|
||||||
|
c.Request.Method == http.MethodPatch ||
|
||||||
c.Request.Method == http.MethodDelete {
|
c.Request.Method == http.MethodDelete {
|
||||||
bindErr = c.ShouldBindJSON(&field)
|
bindErr = c.ShouldBindJSON(&field)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dao
|
package dao
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -63,6 +64,9 @@ func InitSql(port string) error {
|
|||||||
if err := ensureSecureBootstrapAdmin(); err != nil {
|
if err := ensureSecureBootstrapAdmin(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := ensureSecureBootstrapPeer(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if _, err := getOrCreateJwtSecret(); err != nil {
|
if _, err := getOrCreateJwtSecret(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -143,6 +147,124 @@ func ensureSecureBootstrapAdmin() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureSecureBootstrapPeer() error {
|
||||||
|
bootstrapSecret := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_CON_PASS"))
|
||||||
|
if bootstrapSecret == "" {
|
||||||
|
generated, err := util.RandomString(24)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bootstrapSecret = generated
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := GetPeer("auth_id = ?", "bootstrap-admin-peer"); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := GetPeer("name = ?", "bootstrap-admin-peer"); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secretDigest, err := buildPeerSecretDigest(bootstrapSecret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := GetPeer("secret_digest = ?", secretDigest); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
secretEncrypted, err := encryptBootstrapPeerSecret(bootstrapSecret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "bootstrap-admin-peer"
|
||||||
|
authID := "bootstrap-admin-peer"
|
||||||
|
remark := "bootstrap peer seeded from HY2XS_ADMIN_CON_PASS"
|
||||||
|
quota := int64(0)
|
||||||
|
download := int64(0)
|
||||||
|
upload := int64(0)
|
||||||
|
expires := int64(0)
|
||||||
|
maxDevices := int64(3)
|
||||||
|
disabled := int64(0)
|
||||||
|
bannedUntil := int64(0)
|
||||||
|
lastConnection := int64(0)
|
||||||
|
|
||||||
|
peer := entity.Peer{
|
||||||
|
Name: &name,
|
||||||
|
Remark: &remark,
|
||||||
|
AuthId: &authID,
|
||||||
|
SecretDigest: &secretDigest,
|
||||||
|
SecretEncrypted: &secretEncrypted,
|
||||||
|
QuotaBytes: "a,
|
||||||
|
DownloadBytes: &download,
|
||||||
|
UploadBytes: &upload,
|
||||||
|
ExpiresAt: &expires,
|
||||||
|
MaxDevices: &maxDevices,
|
||||||
|
Disabled: &disabled,
|
||||||
|
BannedUntil: &bannedUntil,
|
||||||
|
LastConnectionAt: &lastConnection,
|
||||||
|
}
|
||||||
|
_, saveErr := SavePeer(peer)
|
||||||
|
return saveErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPeerSecretDigest(rawSecret string) (string, error) {
|
||||||
|
secretKey, err := getOrCreatePeerSecretDigestKey()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return util.HmacSHA256Hex(rawSecret, secretKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrCreatePeerSecretEncryptionKey() ([]byte, error) {
|
||||||
|
raw, err := getOrCreateConfigKey(constant.PeerSecretEncryptionKey, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decoded, decErr := util.DecodeBase64Key(raw, 32)
|
||||||
|
if decErr == nil {
|
||||||
|
return decoded, nil
|
||||||
|
}
|
||||||
|
plain := []byte(strings.TrimSpace(raw))
|
||||||
|
if len(plain) < 32 {
|
||||||
|
return nil, errors.New("invalid peer secret encryption key")
|
||||||
|
}
|
||||||
|
plain = plain[:32]
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(plain)
|
||||||
|
if err := UpdateConfig([]string{constant.PeerSecretEncryptionKey}, map[string]interface{}{"value": encoded}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptBootstrapPeerSecret(rawSecret string) (string, error) {
|
||||||
|
key, err := getOrCreatePeerSecretEncryptionKey()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return util.EncryptAESGCM(rawSecret, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrCreateConfigKey(key string, size int) (string, error) {
|
||||||
|
cfg, err := GetConfig("key = ?", key)
|
||||||
|
if err == nil && cfg.Value != nil && strings.TrimSpace(*cfg.Value) != "" {
|
||||||
|
return strings.TrimSpace(*cfg.Value), nil
|
||||||
|
}
|
||||||
|
raw, genErr := util.RandomString(size)
|
||||||
|
if genErr != nil {
|
||||||
|
return "", genErr
|
||||||
|
}
|
||||||
|
value := raw
|
||||||
|
remark := key
|
||||||
|
if _, saveErr := SaveConfig(entity.Config{Key: &key, Value: &value, Remark: &remark}); saveErr != nil {
|
||||||
|
if updErr := UpdateConfig([]string{key}, map[string]interface{}{"value": value}); updErr != nil {
|
||||||
|
return "", updErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
func runMigrations() error {
|
func runMigrations() error {
|
||||||
if tx := sqliteDB.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
if tx := sqliteDB.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||||
version TEXT PRIMARY KEY,
|
version TEXT PRIMARY KEY,
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ func resolvePublicEndpoint() (string, int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Hysteria2Auth(conPass string) (int64, string, error) {
|
func Hysteria2Auth(conPass string) (int64, string, error) {
|
||||||
if !Hysteria2IsRunning() {
|
|
||||||
return 0, "", errors.New("hysteria2 is not running")
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
secretDigest, digestErr := PeerSecretDigest(conPass)
|
secretDigest, digestErr := PeerSecretDigest(conPass)
|
||||||
if digestErr != nil {
|
if digestErr != nil {
|
||||||
|
|||||||
@@ -177,13 +177,16 @@ export async function install(options: InstallOptions): Promise<void> {
|
|||||||
await advanceInstallState(context, "installing");
|
await advanceInstallState(context, "installing");
|
||||||
state.lastPhase = "installing";
|
state.lastPhase = "installing";
|
||||||
step("preflight");
|
step("preflight");
|
||||||
await preflight(context);
|
await preflight(context, { requireCapabilities: false });
|
||||||
stepDone("preflight");
|
stepDone("preflight");
|
||||||
await advanceInstallState(context, "preflight_ok");
|
await advanceInstallState(context, "preflight_ok");
|
||||||
state.lastPhase = "preflight_ok";
|
state.lastPhase = "preflight_ok";
|
||||||
step("system dependencies");
|
step("system dependencies");
|
||||||
await installDeps(context);
|
await installDeps(context);
|
||||||
stepDone("system dependencies");
|
stepDone("system dependencies");
|
||||||
|
step("preflight capabilities");
|
||||||
|
await preflight(context, { requireCapabilities: true });
|
||||||
|
stepDone("preflight capabilities");
|
||||||
await advanceInstallState(context, "deps_ok");
|
await advanceInstallState(context, "deps_ok");
|
||||||
state.lastPhase = "deps_ok";
|
state.lastPhase = "deps_ok";
|
||||||
step("filesystem");
|
step("filesystem");
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ async function rollbackCurrentState(): Promise<void> {
|
|||||||
|
|
||||||
await runVisible`if [ -f /etc/hy2xs/backups/hy2xs.env.existed ]; then cp -a /etc/hy2xs/backups/hy2xs.env.bak /etc/hy2xs/hy2xs.env 2>/dev/null || true; else rm -f /etc/hy2xs/hy2xs.env; fi`;
|
await runVisible`if [ -f /etc/hy2xs/backups/hy2xs.env.existed ]; then cp -a /etc/hy2xs/backups/hy2xs.env.bak /etc/hy2xs/hy2xs.env 2>/dev/null || true; else rm -f /etc/hy2xs/hy2xs.env; fi`;
|
||||||
await runVisible`if [ -f /etc/hy2xs/backups/post-install.env.existed ]; then cp -a /etc/hy2xs/backups/post-install.env.bak /etc/hysteria/post-install.env 2>/dev/null || true; else rm -f /etc/hysteria/post-install.env; fi`;
|
await runVisible`if [ -f /etc/hy2xs/backups/post-install.env.existed ]; then cp -a /etc/hy2xs/backups/post-install.env.bak /etc/hysteria/post-install.env 2>/dev/null || true; else rm -f /etc/hysteria/post-install.env; fi`;
|
||||||
await runVisible`if [ -f /etc/hy2xs/backups/nftables.conf.existed ]; then cp -a /etc/hy2xs/backups/nftables.conf.bak /etc/nftables.conf 2>/dev/null || true; fi`;
|
await runVisible`if [ -f /etc/hy2xs/backups/nftables.conf.existed ]; then cp -a /etc/hy2xs/backups/nftables.conf.bak /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi`;
|
||||||
await runVisible`if [ -f /etc/hy2xs/backups/hy2xs.nft.existed ]; then cp -a /etc/hy2xs/backups/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/hy2xs/backups/hy2xs.nft.existed ]; then cp -a /etc/hy2xs/backups/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`nft -f /etc/nftables.conf >/dev/null 2>&1 || true`;
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ type AssertPlatformOptions = {
|
|||||||
distro: "debian";
|
distro: "debian";
|
||||||
supportedVersions: number[];
|
supportedVersions: number[];
|
||||||
architectures: Array<"amd64">;
|
architectures: Array<"amd64">;
|
||||||
|
requireSystemdRun?: boolean;
|
||||||
|
requireNftables?: boolean;
|
||||||
|
requireOpenSsl3?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function assertPlatform(options: AssertPlatformOptions): Promise<void> {
|
export async function assertPlatform(options: AssertPlatformOptions): Promise<void> {
|
||||||
@@ -29,16 +32,16 @@ export async function assertPlatform(options: AssertPlatformOptions): Promise<vo
|
|||||||
`required capability missing: systemd (${profile.capabilityDetails.systemdReason}; pid1=${profile.capabilityDetails.pid1}; state=${profile.capabilityDetails.systemdState})`
|
`required capability missing: systemd (${profile.capabilityDetails.systemdReason}; pid1=${profile.capabilityDetails.pid1}; state=${profile.capabilityDetails.systemdState})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!profile.capabilities.systemdRun) {
|
if (options.requireSystemdRun !== false && !profile.capabilities.systemdRun) {
|
||||||
fail("required capability missing: systemd-run");
|
fail("required capability missing: systemd-run");
|
||||||
}
|
}
|
||||||
if (!profile.capabilities.nftables) {
|
if (options.requireNftables !== false && !profile.capabilities.nftables) {
|
||||||
fail("required capability missing: nft");
|
fail("required capability missing: nft");
|
||||||
}
|
}
|
||||||
if (!profile.capabilities.nftAtomicReplace) {
|
if (options.requireNftables !== false && !profile.capabilities.nftAtomicReplace) {
|
||||||
fail("required capability missing: nft atomic replace");
|
fail("required capability missing: nft atomic replace");
|
||||||
}
|
}
|
||||||
if (!profile.capabilities.openssl3) {
|
if (options.requireOpenSsl3 !== false && !profile.capabilities.openssl3) {
|
||||||
fail("required capability missing: OpenSSL 3.x runtime");
|
fail("required capability missing: OpenSSL 3.x runtime");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { fail, info } from "../lib/log";
|
|||||||
import { run } from "../lib/process";
|
import { run } from "../lib/process";
|
||||||
import { assertPlatform } from "../platform/assert";
|
import { assertPlatform } from "../platform/assert";
|
||||||
|
|
||||||
|
type PreflightOptions = {
|
||||||
|
requireCapabilities?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function isNoDnsRecords(error: unknown): boolean {
|
function isNoDnsRecords(error: unknown): boolean {
|
||||||
return (
|
return (
|
||||||
typeof error === "object" &&
|
typeof error === "object" &&
|
||||||
@@ -41,8 +45,13 @@ async function isUnitActive(unit: string): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function preflight(context: RuntimeContext): Promise<void> {
|
export async function preflight(context: RuntimeContext, options?: PreflightOptions): Promise<void> {
|
||||||
const isReconfigure = context.mode === "reconfigure";
|
const isReconfigure = context.mode === "reconfigure";
|
||||||
|
const requireCapabilities = options?.requireCapabilities ?? true;
|
||||||
|
|
||||||
|
const needsFirewallCapabilities = !context.options.skipFirewall &&
|
||||||
|
context.config.firewallMode !== "off" &&
|
||||||
|
context.config.firewallMode !== "external";
|
||||||
|
|
||||||
if (process.getuid?.() !== 0) {
|
if (process.getuid?.() !== 0) {
|
||||||
fail("installer must run as root");
|
fail("installer must run as root");
|
||||||
@@ -51,7 +60,10 @@ export async function preflight(context: RuntimeContext): Promise<void> {
|
|||||||
await assertPlatform({
|
await assertPlatform({
|
||||||
distro: "debian",
|
distro: "debian",
|
||||||
supportedVersions: [13],
|
supportedVersions: [13],
|
||||||
architectures: ["amd64"]
|
architectures: ["amd64"],
|
||||||
|
requireSystemdRun: requireCapabilities,
|
||||||
|
requireNftables: requireCapabilities && needsFirewallCapabilities,
|
||||||
|
requireOpenSsl3: requireCapabilities
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(await fileExists(`${context.options.packageDir}/systemd/hy2xs-admin.service`))) {
|
if (!(await fileExists(`${context.options.packageDir}/systemd/hy2xs-admin.service`))) {
|
||||||
|
|||||||
@@ -144,23 +144,27 @@ export async function smoke(context: RuntimeContext): Promise<void> {
|
|||||||
}
|
}
|
||||||
await runVisible`! ss -H -ltn | grep -q '\[::\]:${context.config.uiPort} '`;
|
await runVisible`! ss -H -ltn | grep -q '\[::\]:${context.config.uiPort} '`;
|
||||||
await runVisible`! ss -H -lun | grep -q '\[::\]:${context.config.hysteriaPort} '`;
|
await runVisible`! ss -H -lun | grep -q '\[::\]:${context.config.hysteriaPort} '`;
|
||||||
|
const missingTokenAuthCode = await runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||||
|
if (missingTokenAuthCode.trim() !== "403") {
|
||||||
|
throw new Error(`unexpected auth status without machine token: ${missingTokenAuthCode}`);
|
||||||
|
}
|
||||||
const invalidAuthResponse = await retry(
|
const invalidAuthResponse = await retry(
|
||||||
"auth invalid credentials",
|
"auth invalid credentials",
|
||||||
5,
|
5,
|
||||||
1000,
|
1000,
|
||||||
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`,
|
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' 'http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth?access_token=${context.config.hysteriaTrafficStatsSecret}'`,
|
||||||
(response) => /"ok"\s*:\s*false/.test(response),
|
(response) => /"ok"\s*:\s*false/.test(response),
|
||||||
(response, error) => new Error(`unexpected auth response for invalid credentials: ${response ?? String(error)}`),
|
(response, error) => new Error(`unexpected auth response for invalid credentials: ${response ?? String(error)}`),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i += 1) {
|
for (let i = 0; i < 10; i += 1) {
|
||||||
const response = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
const response = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' 'http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth?access_token=${context.config.hysteriaTrafficStatsSecret}'`;
|
||||||
if (!/"ok"\s*:\s*false/.test(response)) {
|
if (!/"ok"\s*:\s*false/.test(response)) {
|
||||||
throw new Error(`unexpected auth response during rate-limit smoke: ${response}`);
|
throw new Error(`unexpected auth response during rate-limit smoke: ${response}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const invalidTypeAuthCode = await runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
const invalidTypeAuthCode = await runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' 'http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth?access_token=${context.config.hysteriaTrafficStatsSecret}'`;
|
||||||
if (invalidTypeAuthCode.trim() !== "400") {
|
if (invalidTypeAuthCode.trim() !== "400") {
|
||||||
throw new Error(`unexpected auth status for tx as string: ${invalidTypeAuthCode}`);
|
throw new Error(`unexpected auth status for tx as string: ${invalidTypeAuthCode}`);
|
||||||
}
|
}
|
||||||
@@ -175,7 +179,7 @@ export async function smoke(context: RuntimeContext): Promise<void> {
|
|||||||
"auth valid credentials",
|
"auth valid credentials",
|
||||||
10,
|
10,
|
||||||
1000,
|
1000,
|
||||||
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${adminConPass}","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`,
|
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${adminConPass}","tx":0}' 'http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth?access_token=${context.config.hysteriaTrafficStatsSecret}'`,
|
||||||
(response) => /"ok"\s*:\s*true/.test(response),
|
(response) => /"ok"\s*:\s*true/.test(response),
|
||||||
(response, error) => new Error(`unexpected auth response for valid credentials: ${response ?? String(error)}`),
|
(response, error) => new Error(`unexpected auth response for valid credentials: ${response ?? String(error)}`),
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ HY2_TLS_KEY_PATH={{TLS_KEY_PATH}}
|
|||||||
HY2_LISTEN_HOST={{HYSTERIA_BIND_HOST}}
|
HY2_LISTEN_HOST={{HYSTERIA_BIND_HOST}}
|
||||||
HY2_PORT={{HYSTERIA_PORT}}
|
HY2_PORT={{HYSTERIA_PORT}}
|
||||||
HY2_AUTH_MODE=http
|
HY2_AUTH_MODE=http
|
||||||
HY2_AUTH_URL=http://127.0.0.1:{{UI_PORT}}/hui/hysteria2/auth
|
HY2_AUTH_URL=http://127.0.0.1:{{UI_PORT}}/hui/hysteria2/auth?access_token={{HYSTERIA_API_SECRET}}
|
||||||
HY2_TRAFFIC_STATS_LISTEN={{HYSTERIA_API_HOST}}:{{HYSTERIA_API_PORT}}
|
HY2_TRAFFIC_STATS_LISTEN={{HYSTERIA_API_HOST}}:{{HYSTERIA_API_PORT}}
|
||||||
HY2_OBFS_TYPE=salamander
|
HY2_OBFS_TYPE=salamander
|
||||||
HY2_OBFS_PASSWORD={{HYSTERIA_OBFS_PASSWORD}}
|
HY2_OBFS_PASSWORD={{HYSTERIA_OBFS_PASSWORD}}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ listen: {{HYSTERIA_BIND_HOST}}:{{HYSTERIA_PORT}}
|
|||||||
auth:
|
auth:
|
||||||
type: http
|
type: http
|
||||||
http:
|
http:
|
||||||
url: http://127.0.0.1:{{UI_PORT}}/hui/hysteria2/auth
|
url: http://127.0.0.1:{{UI_PORT}}/hui/hysteria2/auth?access_token={{HYSTERIA_API_SECRET}}
|
||||||
insecure: {{AUTH_INSECURE}}
|
insecure: {{AUTH_INSECURE}}
|
||||||
|
|
||||||
obfs:
|
obfs:
|
||||||
|
|||||||
@@ -20,5 +20,12 @@ run_fix20_acceptance_subset() {
|
|||||||
|
|
||||||
log_step "Acceptance: docs matrix markers"
|
log_step "Acceptance: docs matrix markers"
|
||||||
grep -q 'Fix20 production matrix' docs/11-testing-and-acceptance.md || fail "acceptance: fix20 matrix section missing"
|
grep -q 'Fix20 production matrix' docs/11-testing-and-acceptance.md || fail "acceptance: fix20 matrix section missing"
|
||||||
}
|
|
||||||
|
|
||||||
|
log_step "Acceptance: machine auth URL in templates"
|
||||||
|
grep -q '/hui/hysteria2/auth?access_token={{HYSTERIA_API_SECRET}}' "$package_dir/templates/hysteria/config.yaml.tpl" || fail "acceptance: machine token missing in hysteria auth URL template"
|
||||||
|
grep -q '^HY2_AUTH_URL=http://127.0.0.1:{{UI_PORT}}/hui/hysteria2/auth?access_token={{HYSTERIA_API_SECRET}}$' "$package_dir/templates/env/post-install.env.tpl" || fail "acceptance: machine token missing in post-install HY2_AUTH_URL"
|
||||||
|
|
||||||
|
log_step "Acceptance: smoke auth checks are tokenized"
|
||||||
|
grep -q 'unexpected auth status without machine token' orchestrator/src/steps/smoke.ts || fail "acceptance: missing 403 negative smoke for auth without machine token"
|
||||||
|
grep -q 'hysteria2/auth?access_token=\${context.config.hysteriaTrafficStatsSecret}' orchestrator/src/steps/smoke.ts || fail "acceptance: smoke auth URL is not tokenized"
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ verify_archive() {
|
|||||||
printf '%s\n' "$env_content" | grep -q 'replace-with-your-domain.example' && fail "packaged hy2xs.env contains placeholder domain"
|
printf '%s\n' "$env_content" | grep -q 'replace-with-your-domain.example' && fail "packaged hy2xs.env contains placeholder domain"
|
||||||
printf '%s\n' "$env_content" | grep -q 'replace-with-your-email@example.com' && fail "packaged hy2xs.env contains placeholder email"
|
printf '%s\n' "$env_content" | grep -q 'replace-with-your-email@example.com' && fail "packaged hy2xs.env contains placeholder email"
|
||||||
|
|
||||||
|
local hysteria_tpl
|
||||||
|
hysteria_tpl="$(tar -xOzf "$archive" hy2xs-install/templates/hysteria/config.yaml.tpl)"
|
||||||
|
printf '%s\n' "$hysteria_tpl" | grep -q '/hui/hysteria2/auth?access_token={{HYSTERIA_API_SECRET}}' || fail "hysteria auth template must include machine access_token"
|
||||||
|
|
||||||
|
local post_install_tpl
|
||||||
|
post_install_tpl="$(tar -xOzf "$archive" hy2xs-install/templates/env/post-install.env.tpl)"
|
||||||
|
printf '%s\n' "$post_install_tpl" | grep -q '^HY2_AUTH_URL=http://127.0.0.1:{{UI_PORT}}/hui/hysteria2/auth?access_token={{HYSTERIA_API_SECRET}}$' || fail "post-install env template must include machine access_token in HY2_AUTH_URL"
|
||||||
|
|
||||||
local tmp
|
local tmp
|
||||||
tmp="$(mktemp -d)"
|
tmp="$(mktemp -d)"
|
||||||
tar -xzf "$archive" -C "$tmp"
|
tar -xzf "$archive" -C "$tmp"
|
||||||
@@ -65,7 +73,7 @@ verify_archive() {
|
|||||||
[ -x "$tmp/hy2xs-install/orchestrator/hy2xs-orchestrator" ] || fail "orchestrator is not executable"
|
[ -x "$tmp/hy2xs-install/orchestrator/hy2xs-orchestrator" ] || fail "orchestrator is not executable"
|
||||||
[ -x "$tmp/hy2xs-install/ui/hy2xs-admin/hy2xs-admin" ] || fail "hy2xs-admin is not executable"
|
[ -x "$tmp/hy2xs-install/ui/hy2xs-admin/hy2xs-admin" ] || fail "hy2xs-admin is not executable"
|
||||||
|
|
||||||
"$tmp/hy2xs-install/orchestrator/hy2xs-orchestrator" status --package-dir "$tmp/hy2xs-install" >/dev/null 2>&1 || true
|
"$tmp/hy2xs-install/orchestrator/hy2xs-orchestrator" status --package-dir "$tmp/hy2xs-install" >/dev/null 2>&1 || fail "orchestrator status sanity check failed"
|
||||||
|
|
||||||
local meta
|
local meta
|
||||||
meta="$(cat "$tmp/hy2xs-install/metadata/package.env")"
|
meta="$(cat "$tmp/hy2xs-install/metadata/package.env")"
|
||||||
|
|||||||
Reference in New Issue
Block a user