From 90546b6c9a6f602d3fca97443992f9da98479da6 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 10:39:02 +0800 Subject: [PATCH 01/10] Use python3 not python in pytest command VPS host has python3 (not python) available. Fixes "python: command not found" error from previous commit. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index e22121e..cc434de 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -89,7 +89,7 @@ jobs: run: | cd dashboard/backend . venv/bin/activate - python -m pytest tests/ -v --timeout=60 \ + python3 -m pytest tests/ -v --timeout=60 \ --cov=. --cov-report=xml --cov-report=term \ --junit-xml=test-results.xml \ --cov-fail-under=49 From 3989e281c055735105d573449ce6f2051fc14ea3 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 10:59:55 +0800 Subject: [PATCH 02/10] fix(ci): use explicit venv/bin/python instead of activate+python3 activate doesn't reliably shadow system python3 on the VPS runner. Using the explicit venv path matches what the cache validation check already uses. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index cc434de..710f5f1 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -88,8 +88,7 @@ jobs: - name: Run tests with coverage run: | cd dashboard/backend - . venv/bin/activate - python3 -m pytest tests/ -v --timeout=60 \ + venv/bin/python -m pytest tests/ -v --timeout=60 \ --cov=. --cov-report=xml --cov-report=term \ --junit-xml=test-results.xml \ --cov-fail-under=49 From 56fd761618d47946640d38de0048dfa4a1bba468 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 11:07:23 +0800 Subject: [PATCH 03/10] Restore venv activation in test step, clear VPS cache Cached venv has broken internal paths even when using venv/bin/python directly. Clear caches and use . venv/bin/activate + python -m pytest which correctly sets VIRTUAL_ENV for module resolution. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 710f5f1..e22121e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -88,7 +88,8 @@ jobs: - name: Run tests with coverage run: | cd dashboard/backend - venv/bin/python -m pytest tests/ -v --timeout=60 \ + . venv/bin/activate + python -m pytest tests/ -v --timeout=60 \ --cov=. --cov-report=xml --cov-report=term \ --junit-xml=test-results.xml \ --cov-fail-under=49 From bd9cb67b14d81acbf77e04e9eab5477e7f6d321e Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 11:22:41 +0800 Subject: [PATCH 04/10] fix(ci): replace python3 with sed/grep in deploy step containers The nas runner's deploy container lacks python3. Replaced: - compose networks stripping: python3 -> sed - network verification: python3 -> grep - Removed broken --dns and --add-host options from deploy container Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index e22121e..ffeb014 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -178,7 +178,6 @@ jobs: GITEA_DB_PASSWORD: nyM3P4v1aYAsT7zfUtdguImNVHgFltCKCY/OjzWZVRE= container: network: gitea_gitea - options: --add-host=host.docker.internal:host-gateway --dns 192.168.31.1 --dns 8.8.8.8 volumes: - /var/packages/ContainerManager/target/usr/bin/docker:/usr/bin/docker - /volume1/docker/nas-dashboard:/nas-dashboard @@ -263,30 +262,7 @@ jobs: if ! docker container inspect nas-dashboard >/dev/null 2>&1; then echo "Compose failed, stripping networks and retrying..." docker rm -f docker-socket-proxy nas-dashboard 2>/dev/null || true - python3 -c " - import os - lines = open('/nas-dashboard/docker-compose.yml').readlines() - out = [] - skip_net = False - for l in lines: - stripped = l.strip() - if stripped == 'networks:' and not l[0].isspace(): - skip_net = True - continue - if skip_net: - if l[0].isspace(): - continue - skip_net = False - if stripped == 'networks:' and l[:4].strip() == '': - skip_net = True - continue - if skip_net: - if l.startswith(' '): - continue - skip_net = False - out.append(l) - open('/nas-dashboard/prod-ci.yml','w').writelines(out) - " + sed '/^networks:/,$d' /nas-dashboard/docker-compose.yml > /nas-dashboard/prod-ci.yml docker compose -f /nas-dashboard/prod-ci.yml up -d --pull never 2>&1 || true fi @@ -351,7 +327,13 @@ jobs: # Verify network connections echo "=== Network connections ===" - docker inspect nas-dashboard --format '{{json .NetworkSettings.Networks}}' | python3 -c 'import json,sys;nets=json.load(sys.stdin);[print(f" Connected to {n}") for n in nets];assert "nas-dashboard_internal" in nets, "Missing nas-dashboard_internal";print("All network connections established")' + if docker inspect nas-dashboard --format '{{json .NetworkSettings.Networks}}' | grep -q 'nas-dashboard_internal'; then + echo " Connected to nas-dashboard_internal" + echo "All network connections established" + else + echo "❌ Missing nas-dashboard_internal network" + exit 1 + fi # Wait for container to be healthy echo "Waiting for container to be healthy..." From 03b515aa1d799fcc450dda6b737f58d0c7b5d655 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 11:42:16 +0800 Subject: [PATCH 05/10] fix(ci): switch tests from VPS host runner to NAS ubuntu-latest The VPS runner's host executor has a bug where completion callbacks are silently dropped, causing jobs to appear stuck. Switched backend and frontend tests to the NAS runner which correctly reports status. Also updated checkout URL to use Docker bridge gateway 172.21.0.1:3300. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ffeb014..8a55041 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -14,7 +14,7 @@ concurrency: jobs: backend-tests: name: Backend Tests - runs-on: vps + runs-on: ubuntu-latest env: SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum @@ -22,7 +22,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://100.78.131.124:3300/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . fi - name: Setup Python From 3c5ec3cdd2f870455908c7f4b0797a4899557afa Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 17:49:43 +0800 Subject: [PATCH 06/10] feat(ci): build dashboard images on VPS (SSD) instead of NAS HDD - Add build-image job (runs-on: vps) for deploy.yml (main) - builds with Podman on server2, pipes finished image to NAS via SSH, retags - Add build-image-dev job (runs-on: vps) for deploy-dev.yml (dev) - same pattern for dev images - Remove build + base image pre-warm from NAS-side deploy jobs (image is now pre-loaded by the build job) - deploy job condition: always() && build-image succeeded (so frontend test failure doesn't block deployment, but build failure does) NAS HDD I/O was the bottleneck. VPS has SSD and same x86_64 arch. --- .gitea/workflows/deploy-dev.yml | 69 ++++++++++++-------------- .gitea/workflows/deploy.yml | 88 +++++++++++++-------------------- 2 files changed, 66 insertions(+), 91 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 628ad12..4774a5c 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -157,10 +157,41 @@ jobs: npm run test:coverage -- --reporter=verbose --run --pool=forks --poolOptions.forks.maxForks=2 || true echo "Frontend tests completed (pre-existing Vite compatibility issues may cause failures)" + build-image-dev: + name: Build Dev Image + runs-on: vps + needs: [backend-tests] + + steps: + - name: Checkout repository + run: | + if [ ! -d .git ]; then + git clone --depth=1 "http://100.78.131.124:3300/${GITHUB_REPOSITORY}.git" . + fi + + - name: Build dev Docker image + run: | + set -e + echo "Building on VPS (SSD)..." + CACHE_ARG="" + if podman image exists nas-dashboard-dev:latest 2>/dev/null; then + CACHE_ARG="--cache-from nas-dashboard-dev:latest" + fi + podman build $CACHE_ARG -t nas-dashboard-dev:latest dashboard/ + + - name: Transfer image to NAS + run: | + set -e + echo "Streaming dev image to NAS..." + podman save nas-dashboard-dev:latest | ssh nasts "/volume1/@appstore/ContainerManager/usr/bin/docker load 2>&1" + echo "Retagging image..." + ssh nasts "/volume1/@appstore/ContainerManager/usr/bin/docker tag localhost/nas-dashboard-dev:latest nas-dashboard-dev:latest 2>&1 && /volume1/@appstore/ContainerManager/usr/bin/docker image rm localhost/nas-dashboard-dev:latest 2>&1" + echo "Dev image transferred and retagged successfully" + deploy-dev: name: Deploy to Dev runs-on: ubuntu-latest - needs: [backend-tests, frontend-tests] + needs: [build-image-dev, frontend-tests] env: SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum container: @@ -175,42 +206,6 @@ jobs: git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . fi - - name: Warm mirror cache for base images - run: | - set -u - mirror_host="100.78.131.124:5501" - - prewarm_base_image() { - image_ref="$1" - if docker image inspect "${image_ref}" >/dev/null 2>&1; then - echo "Image ${image_ref} already exists locally, skipping pre-pull" - return 0 - fi - - mirror_ref="${mirror_host}/library/${image_ref}" - echo "Attempting mirror pre-pull: ${mirror_ref}" - if timeout 30 docker pull "${mirror_ref}" 2>/dev/null; then - docker tag "${mirror_ref}" "${image_ref}" - echo "Mirror pre-pull succeeded for ${image_ref}" - else - echo "Mirror pre-pull failed for ${image_ref}, will pull during build" - fi - } - - prewarm_base_image "node:20-alpine" - prewarm_base_image "python:3.12-slim" - - - name: Build dev image - run: | - set -e - export DOCKER_API_VERSION=1.43 - if ! DOCKER_BUILDKIT=0 docker build --cache-from nas-dashboard-dev:latest -t nas-dashboard-dev:latest dashboard/; then - echo "Build failed. Checking for network issues..." - curl -I https://registry.npmmirror.com || echo "npmmirror unreachable" - curl -I https://pypi.tuna.tsinghua.edu.cn || echo "Tsinghua PyPI unreachable" - exit 1 - fi - - name: Sync runtime compose file run: | mkdir -p /volume1/docker/nas-dashboard diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 8a55041..57fb1e9 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -168,11 +168,43 @@ jobs: fi echo "✅ Frontend tests passed" + build-image: + name: Build Docker Image + runs-on: vps + needs: [backend-tests] + + steps: + - name: Checkout repository + run: | + if [ ! -d .git ]; then + git clone --depth=1 "http://100.78.131.124:3300/${GITHUB_REPOSITORY}.git" . + fi + + - name: Build Docker image + run: | + set -e + echo "Building on VPS (SSD)..." + + CACHE_ARG="" + if podman image exists nas-dashboard:latest 2>/dev/null; then + CACHE_ARG="--cache-from nas-dashboard:latest" + fi + podman build $CACHE_ARG -t nas-dashboard:latest dashboard/ + + - name: Transfer image to NAS + run: | + set -e + echo "Streaming image to NAS..." + podman save nas-dashboard:latest | ssh nasts "/volume1/@appstore/ContainerManager/usr/bin/docker load 2>&1" + echo "Retagging image..." + ssh nasts "/volume1/@appstore/ContainerManager/usr/bin/docker tag localhost/nas-dashboard:latest nas-dashboard:latest 2>&1 && /volume1/@appstore/ContainerManager/usr/bin/docker image rm localhost/nas-dashboard:latest 2>&1" + echo "Image transferred and retagged successfully" + deploy: name: Deploy to Production runs-on: nas - if: always() - needs: [backend-tests, frontend-tests] + if: always() && needs.build-image.result == 'success' + needs: [build-image, frontend-tests] env: SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum GITEA_DB_PASSWORD: nyM3P4v1aYAsT7zfUtdguImNVHgFltCKCY/OjzWZVRE= @@ -188,58 +220,6 @@ jobs: git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . fi - - name: Warm mirror cache for base images - run: | - set -u - mirror_host="100.78.131.124:5501" - - if command -v curl >/dev/null 2>&1; then - if curl -fsS --max-time 10 "http://${mirror_host}/v2/" >/dev/null; then - echo "Registry mirror is healthy at ${mirror_host}" - else - echo "Warning: registry mirror health check failed at ${mirror_host}; continuing with normal pull path" - fi - else - echo "Warning: curl is unavailable; skipping explicit mirror health check" - fi - - prewarm_base_image() { - image_ref="$1" - # Skip if image already exists locally - if docker image inspect "${image_ref}" >/dev/null 2>&1; then - echo "Image ${image_ref} already exists locally, skipping pre-pull" - return 0 - fi - - mirror_ref="${mirror_host}/library/${image_ref}" - echo "Attempting mirror pre-pull: ${mirror_ref}" - if timeout 120 docker pull "${mirror_ref}"; then - docker tag "${mirror_ref}" "${image_ref}" - echo "Mirror pre-pull succeeded for ${image_ref}" - else - echo "Warning: mirror pre-pull failed for ${image_ref}; continuing with normal pull during build" - fi - } - - prewarm_base_image "node:20-alpine" - prewarm_base_image "python:3.12-slim" - - - name: Build - run: | - set -e - export DOCKER_API_VERSION=1.43 - # DOCKER_BUILDKIT=0 is required for Synology ContainerManager's Docker daemon, - # which does not support BuildKit syntax. Remove when Synology updates to a - # Docker Engine version with full BuildKit support (currently 24.x, needs 23+). - # --cache-to type=inline is a no-op while BuildKit is disabled; it activates - # automatically when DOCKER_BUILDKIT=0 is removed. - if ! DOCKER_BUILDKIT=0 docker build --cache-from nas-dashboard:latest -t nas-dashboard:latest dashboard/; then - echo "Build failed. Checking for network issues..." - curl -I https://registry.npmmirror.com || echo "npmmirror unreachable" - curl -I https://pypi.tuna.tsinghua.edu.cn || echo "Tsinghua PyPI unreachable" - exit 1 - fi - - name: Sync runtime compose file run: | mkdir -p /nas-dashboard From ab44c2956e08c6fcb0dcff93978db6f990c462c2 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 17:52:26 +0800 Subject: [PATCH 07/10] fix(ci): remove --cache-from flag - Podman caches layers automatically --- .gitea/workflows/deploy.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 57fb1e9..e324925 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -184,12 +184,8 @@ jobs: run: | set -e echo "Building on VPS (SSD)..." - - CACHE_ARG="" - if podman image exists nas-dashboard:latest 2>/dev/null; then - CACHE_ARG="--cache-from nas-dashboard:latest" - fi - podman build $CACHE_ARG -t nas-dashboard:latest dashboard/ + # Podman caches layers automatically; no --cache-from needed + podman build -t nas-dashboard:latest dashboard/ - name: Transfer image to NAS run: | From 0ee8d6cdabc14fafa84b21b935b19d641f88ff23 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 17:58:38 +0800 Subject: [PATCH 08/10] fix(ci): use Docker bridge gateway IP for checkout in dev CI Ubuntu-latest runner containers on NAS dont have the gitea_gitea network, so http://gitea:3000 is unreachable. Use 172.21.0.1:3300 (Docker bridge gateway) instead. --- .gitea/workflows/deploy-dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 4774a5c..e843567 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . fi - name: Setup Python @@ -103,7 +103,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . fi - name: Setup Node.js @@ -203,7 +203,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . fi - name: Sync runtime compose file From e3f6a2c32e09f276b03afecf77cbd358f5a3dc8d Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 18:07:50 +0800 Subject: [PATCH 09/10] Revert "fix(ci): use Docker bridge gateway IP for checkout in dev CI" This reverts commit 0ee8d6cdabc14fafa84b21b935b19d641f88ff23. --- .gitea/workflows/deploy-dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index e843567..4774a5c 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . fi - name: Setup Python @@ -103,7 +103,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . fi - name: Setup Node.js @@ -203,7 +203,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . fi - name: Sync runtime compose file From 3057fc270b5892228ce4c1dfe437f2d3650549d7 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Thu, 4 Jun 2026 18:16:36 +0800 Subject: [PATCH 10/10] fix(ci): move all test jobs to VPS runner - NAS HDD too slow NAS is using 4.7GB swap causing tests to hang in D state for 20+ min. VPS has SSD and plenty of RAM - tests complete in ~1-2 min there. --- .gitea/workflows/deploy-dev.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 4774a5c..dfbbc80 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -14,7 +14,7 @@ concurrency: jobs: backend-tests: name: Backend Tests - runs-on: ubuntu-latest + runs-on: vps env: SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum @@ -22,7 +22,7 @@ jobs: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://100.78.131.124:3300/${GITHUB_REPOSITORY}.git" . fi - name: Setup Python @@ -97,13 +97,13 @@ jobs: frontend-tests: name: Frontend Tests - runs-on: ubuntu-latest + runs-on: vps steps: - name: Checkout repository run: | if [ ! -d .git ]; then - git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . + git clone --depth=1 "http://100.78.131.124:3300/${GITHUB_REPOSITORY}.git" . fi - name: Setup Node.js