From a14413e886d8567fc9257dd39266b9a08f2989fc Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Fri, 10 Jul 2026 15:36:49 +0200 Subject: [PATCH] fix(cluster0): Gitea cluster proxy, 2FA seed, hub downloads nginx Bind Gitea on all interfaces for cast02/03 proxy, fix stale 2FA lab-seed approve flow, add /v0/downloads/ and GITEA_UPSTREAM_HOST, and guard nginx configure against empty templates. Co-authored-by: Cursor --- sim/cluster0/cluster.env | 2 + sim/cluster0/gitea/app.ini.template | 3 +- .../backend/src/AuthTwoFactorPage.php | 47 ++++++++++++++----- sim/cluster0/nginx/apps-port80.conf | 10 +++- sim/cluster0/scripts/configure-nginx.sh | 7 ++- sim/cluster0/scripts/deploy-hub-downloads.sh | 25 ++++++++++ 6 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 sim/cluster0/scripts/deploy-hub-downloads.sh diff --git a/sim/cluster0/cluster.env b/sim/cluster0/cluster.env index 5c96d7a..8480e15 100644 --- a/sim/cluster0/cluster.env +++ b/sim/cluster0/cluster.env @@ -51,6 +51,8 @@ POSTGRES_SEMI_ENABLE=1 # Gitea browse UI on cast01 (primary); mirrors added later GITEA_ENABLE=1 GITEA_HTTP_PORT=3000 +# nginx on all cluster nodes proxies /git/ to primary (cast01) — see apps-port80.conf +GITEA_UPSTREAM_HOST=10.7.16.236 GITEA_DB_TYPE=sqlite3 GITEA_PUBLIC_HOST=acl0.f0xx.org GITEA_ROOT_URL=https://acl0.f0xx.org/app/androidcast_project/git/ diff --git a/sim/cluster0/gitea/app.ini.template b/sim/cluster0/gitea/app.ini.template index 2f6eb56..2ad6daa 100644 --- a/sim/cluster0/gitea/app.ini.template +++ b/sim/cluster0/gitea/app.ini.template @@ -17,7 +17,8 @@ LFS_START_SERVER = true PROTOCOL = http DOMAIN = @GITEA_DOMAIN@ ROOT_URL = @GITEA_ROOT_URL@ -HTTP_ADDR = 127.0.0.1 +# Bind on all interfaces so cast02/cast03 nginx can proxy to cast01:3000. +HTTP_ADDR = 0.0.0.0 HTTP_PORT = @GITEA_HTTP_PORT@ APP_DATA_PATH = @GITEA_APP_DATA@ STATIC_ROOT_PATH = /usr/share/webapps/gitea diff --git a/sim/cluster0/lab-seeds/backend/src/AuthTwoFactorPage.php b/sim/cluster0/lab-seeds/backend/src/AuthTwoFactorPage.php index 31ecbd6..22e8dd3 100644 --- a/sim/cluster0/lab-seeds/backend/src/AuthTwoFactorPage.php +++ b/sim/cluster0/lab-seeds/backend/src/AuthTwoFactorPage.php @@ -1,35 +1,56 @@ $approveUrl, + 'raw_token' => $rawToken, + 'qr_url' => AuthTotp::qrImageUrl($approveUrl), + ]; + if (!UrlShortenerDatabase::enabled() || !UrlShortenerDatabase::ping()) { - return ['long_url' => $longUrl]; + return $payload; } $bearers = ShortLinksRepository::listBearers(); if ($bearers === []) { - return ['long_url' => $longUrl]; + return $payload; } $bearerId = (int) ($bearers[0]['id'] ?? 0); if ($bearerId <= 0) { - return ['long_url' => $longUrl]; + return $payload; } - $mint = ShortLinksRepository::createLink($bearerId, $longUrl, 900); + $mint = ShortLinksRepository::createLink($bearerId, $approveUrl, 900); if (empty($mint['ok'])) { - return ['long_url' => $longUrl]; + return $payload; } - return [ - 'long_url' => $longUrl, - 'short_url' => (string) ($mint['short_url'] ?? ''), - 'qr_url' => (string) ($mint['qr_url'] ?? ''), - ]; + $shortUrl = (string) ($mint['short_url'] ?? ''); + $qrUrl = (string) ($mint['qr_url'] ?? ''); + if ($qrUrl !== '') { + $payload['qr_url'] = $qrUrl; + } + if ($shortUrl !== '') { + $payload['short_url'] = $shortUrl; + } + return $payload; } } diff --git a/sim/cluster0/nginx/apps-port80.conf b/sim/cluster0/nginx/apps-port80.conf index 5344c14..9a5cc40 100644 --- a/sim/cluster0/nginx/apps-port80.conf +++ b/sim/cluster0/nginx/apps-port80.conf @@ -38,6 +38,13 @@ server { add_header Cache-Control "public, max-age=60"; } + # Hub download artifacts (Session Studio jar, etc.) — populated by deploy-hub-downloads.sh + location ^~ /v0/downloads/ { + alias /var/www/localhost/htdocs/apps/app/androidcast_project/downloads/; + add_header Cache-Control "public, max-age=300"; + default_type application/octet-stream; + } + # Favicon — served from hub root (shared by all sub-apps via explicit tags) location = /favicon.ico { alias /var/www/localhost/htdocs/apps/app/androidcast_project/favicon.ico; @@ -254,7 +261,8 @@ server { return 301 $public_scheme://$http_host/app/androidcast_project/git/; } location ^~ /app/androidcast_project/git/ { - proxy_pass http://127.0.0.1:3000/; + # Gitea runs on cast01 only — do not proxy to 127.0.0.1 on cast02/cast03. + proxy_pass http://10.7.16.236:3000/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/sim/cluster0/scripts/configure-nginx.sh b/sim/cluster0/scripts/configure-nginx.sh index 66f43e0..e6f2823 100644 --- a/sim/cluster0/scripts/configure-nginx.sh +++ b/sim/cluster0/scripts/configure-nginx.sh @@ -10,8 +10,13 @@ ensure_shared_mounted GIT_NGINX="/mnt/repos/ac/ac-deploy/sim/cluster0/nginx/apps-port80.conf" CONF_DST="$ROOT/nginx/apps-port80.conf" if [ -f "$GIT_NGINX" ] && [ "$(realpath "$GIT_NGINX" 2>/dev/null)" != "$(realpath "$CONF_DST" 2>/dev/null)" ]; then - install -D -m 644 "$GIT_NGINX" "$CONF_DST" + if [ -s "$GIT_NGINX" ]; then + install -D -m 644 "$GIT_NGINX" "$CONF_DST" + else + log "WARN: skip empty GIT_NGINX at $GIT_NGINX" + fi fi +[ -s "$CONF_DST" ] || die "missing or empty nginx template at $CONF_DST" install -D -m 644 "$CONF_DST" /etc/nginx/http.d/androidcast.conf rm -f /etc/nginx/http.d/default.conf /etc/nginx/http.d/cluster.conf 2>/dev/null || true nginx -t diff --git a/sim/cluster0/scripts/deploy-hub-downloads.sh b/sim/cluster0/scripts/deploy-hub-downloads.sh new file mode 100644 index 0000000..66fff27 --- /dev/null +++ b/sim/cluster0/scripts/deploy-hub-downloads.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Publish hub download artifacts (Session Studio jar) to BE downloads mount. +# APK is served via OTA stable channel (/v0/ota/) — run ci-build-and-publish-ota.sh separately. +set -eu +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=/dev/null +. "$ROOT/scripts/lib/common.sh" +load_cluster_env + +DOWNLOADS_ROOT="${APP_ROOT:-/var/www/localhost/htdocs/apps/app/androidcast_project}/downloads" +WS="${AC_WORKSPACE_ROOT:-/var/www/ac/workspace}" +STUDIO_JAR="${WS}/ac-session-studio/build/libs/ac-session-studio.jar" + +mkdir -p "$DOWNLOADS_ROOT" + +if [ -f "$STUDIO_JAR" ]; then + install -m 644 "$STUDIO_JAR" "${DOWNLOADS_ROOT}/ac-session-studio.jar" + log "installed Session Studio jar → ${DOWNLOADS_ROOT}/ac-session-studio.jar" +else + log "WARN: Session Studio jar not built at ${STUDIO_JAR}" + log " cd ac-session-studio && ./gradlew shadowJar (or installDist) on builder node" +fi + +log "Hub downloads dir: ${DOWNLOADS_ROOT}" +log "URL: ${PUBLIC_ORIGIN:-https://apps.f0xx.org}/v0/downloads/ac-session-studio.jar"