32 Commits

Author SHA1 Message Date
Gan, Jimmy e3f6a2c32e Revert "fix(ci): use Docker bridge gateway IP for checkout in dev CI"
This reverts commit 0ee8d6cdab.
2026-06-04 18:07:50 +08:00
Gan, Jimmy 0ee8d6cdab 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.
2026-06-04 17:58:38 +08:00
Gan, Jimmy ab44c2956e fix(ci): remove --cache-from flag - Podman caches layers automatically
Deploy Dashboard (Main) / Frontend Tests (push) Successful in 50s
Deploy Dashboard (Main) / Backend Tests (push) Failing after 23m28s
Deploy Dashboard (Main) / Build Docker Image (push) Has been skipped
Deploy Dashboard (Main) / Deploy to Production (push) Failing after 14m58s
2026-06-04 17:52:26 +08:00
Gan, Jimmy 3c5ec3cdd2 feat(ci): build dashboard images on VPS (SSD) instead of NAS HDD
Deploy Dashboard (Main) / Backend Tests (push) Has been cancelled
Deploy Dashboard (Main) / Build Docker Image (push) Has been cancelled
Deploy Dashboard (Main) / Deploy to Production (push) Has been cancelled
Deploy Dashboard (Main) / Frontend Tests (push) Has been cancelled
- 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.
2026-06-04 17:49:43 +08:00
Gan, Jimmy 03b515aa1d fix(ci): switch tests from VPS host runner to NAS ubuntu-latest
Deploy Dashboard (Main) / Frontend Tests (push) Successful in 54s
Deploy Dashboard (Main) / Backend Tests (push) Successful in 14m32s
Deploy Dashboard (Main) / Deploy to Production (push) Failing after 15m6s
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 <noreply@anthropic.com>
2026-06-04 11:42:16 +08:00
Gan, Jimmy bd9cb67b14 fix(ci): replace python3 with sed/grep in deploy step containers
Deploy Dashboard (Main) / Deploy to Production (push) Has been cancelled
Deploy Dashboard (Main) / Backend Tests (push) Has been cancelled
Deploy Dashboard (Main) / Frontend Tests (push) Has been cancelled
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 <noreply@anthropic.com>
2026-06-04 11:22:41 +08:00
Gan, Jimmy 56fd761618 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 <noreply@anthropic.com>
2026-06-04 11:07:23 +08:00
Gan, Jimmy 3989e281c0 fix(ci): use explicit venv/bin/python instead of activate+python3
Deploy Dashboard (Main) / Deploy to Production (push) Has been cancelled
Deploy Dashboard (Main) / Frontend Tests (push) Has been cancelled
Deploy Dashboard (Main) / Backend Tests (push) Has been cancelled
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 <noreply@anthropic.com>
2026-06-04 10:59:55 +08:00
Gan, Jimmy 90546b6c9a 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 <noreply@anthropic.com>
2026-06-04 10:39:02 +08:00
Gan, Jimmy 3f0bcd2b7f chore: remove CI trigger file
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Successful in 1m0s
2026-06-04 10:38:09 +08:00
Gan, Jimmy f2fa2daaa8 fix(ci): create default .env in CI if missing before compose validate
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Has been cancelled
The CI container may not have /volume1/docker/claude-dev/.env mounted,
causing 'docker compose config' to fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:36:50 +08:00
Gan, Jimmy 00364114d5 Use python -m pytest instead of pytest binary in CI
Venv symlinks break when cache is used across different environments
(VPS host vs NAS Docker). python -m pytest is resilient because it
loads pytest as a module, not via a shebang-based script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:00:24 +08:00
Gan, Jimmy e3ac6344f6 fix(ci): use Docker bridge gateway IP 172.21.0.1:3300 for checkout
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Failing after 1m4s
host.docker.internal not resolving in act containers on Synology Docker.
The gateway IP 172.21.0.1 routes to the host where Gitea is now
bound to 0.0.0.0:3300.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:00:12 +08:00
Gan, Jimmy 92c1f29843 Merge main into dev: resolve CI checkout URL conflict
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Failing after 25s
Use host.docker.internal:3300 with ubuntu-latest runner.
Gitea port now bound to 0.0.0.0:3300 so Docker containers
can reach it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:58:23 +08:00
Gan, Jimmy d42a5422db chore: trigger claude-dev CI pipeline 2026-06-04 09:57:56 +08:00
Gan, Jimmy 85ca387877 fix(ci): use host.docker.internal:3300 for checkout after Gitea port fix
Changed Gitea HTTP port binding from 127.0.0.1:3300 to 0.0.0.0:3300
so Docker containers can reach Gitea via host.docker.internal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:53:07 +08:00
Gan, Jimmy bb9a4c3977 Create /nas-dashboard directory before syncing compose file
Docker volume mounts may not auto-create mount points inside the
job container. Use mkdir -p to ensure the target directory exists
before copying files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:50:36 +08:00
Gan, Jimmy 761f5f562b fix(ci): use nas:host runner with localhost:3300 for claude-dev CI
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Has been cancelled
The act Docker containers on the NAS runner cannot reach Gitea
on the gitea_gitea network despite the runner config. Switching
to the 'nas' runner label which uses host mode — jobs run directly
on the NAS host where localhost:3300 reaches Gitea.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:49:30 +08:00
Gan, Jimmy 10e48461d4 fix(ci): use Gitea container IP for checkout in claude-dev workflow
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Failing after 16m58s
Replaces 'gitea:3000' with '172.21.0.4:3000' to bypass DNS
resolution issues in act job containers. Also removed --dns
override from runner-config.yaml which was breaking Docker's
embedded DNS and container name resolution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:52:34 +08:00
Gan, Jimmy d29c2c3a13 Validate cached venv by testing pytest, not just python binary
The cached venv's python binary may work but tool symlinks (pytest, etc.)
break when restored in a different environment. Test pytest specifically
to catch this.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:50:31 +08:00
Gan, Jimmy 951d68f022 Validate cached venv before use (test python binary works)
Deploy Dashboard (Main) / Frontend Tests (push) Has been cancelled
Deploy Dashboard (Main) / Deploy to Production (push) Has been cancelled
Deploy Dashboard (Main) / Backend Tests (push) Has been cancelled
Cached venvs from NAS Docker containers have broken symlinks when
restored on the VPS host, causing "pytest: command not found".
Add a functional check that venv/bin/python runs before accepting
the cache as valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:42:24 +08:00
Gan, Jimmy c8b87561a8 feat(claude-dev): mount full /volume1 and NAS root into container
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Failing after 12m12s
Added bind mounts:
- /volume1:/volume1 — full Synology data volume
- /:/nas-root — host root filesystem (system config, crontab, etc.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:25:14 +08:00
Gan, Jimmy 6fce702820 fix(claude-dev): add env_file to docker-compose for ANTHROPIC env vars
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Has been cancelled
Deploy Dashboard (Dev) / Backend Tests (push) Failing after 11m13s
Deploy Dashboard (Dev) / Frontend Tests (push) Failing after 15m4s
Deploy Dashboard (Dev) / Deploy to Dev (push) Has been skipped
The NAS deployment already had this line; syncing back to source
so future CI deploys don't wipe it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:16:57 +08:00
Gan, Jimmy 6ed875ae81 Move test jobs to VPS runner, keep deploy on NAS
Backend and frontend tests now run on `runs-on: vps` (server2 at
158.101.140.85, host mode) with checkout via Tailscale IP. Deploy
job stays on `runs-on: nas` with gitea_gitea network for Docker
socket access. This eliminates the HDD bottleneck and Docker
network/DNS issues that plagued the all-NAS pipeline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:09:38 +08:00
Gan, Jimmy aa19085538 Add container network: gitea_gitea to backend/frontend test jobs
Job containers were created on isolated bridge networks that couldn't
reach Gitea at any address. Add workflow-level container blocks to
backend-tests and frontend-tests (matching deploy job) so all job
containers are on the gitea network and can resolve "gitea" via
Docker's embedded DNS. Revert checkout URLs back to gitea:3000.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 03:59:54 +08:00
Gan, Jimmy fa2d96d58a Use NAS LAN IP for checkout (192.168.31.221:3300)
host.docker.internal doesn't resolve in job containers despite the
runner config setting --add-host. Use the NAS LAN IP directly,
which is reachable from any Docker bridge network.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 03:56:21 +08:00
Gan, Jimmy 9e72e8abdf Use host.docker.internal:3300 for checkout instead of gitea:3000
Job containers are on their own bridge networks and can't reach
172.21.0.4 (Gitea's IP on gitea_gitea network). Use the Docker host
gateway with port 3300 (which maps to Gitea container's 3000) instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 03:53:25 +08:00
Gan, Jimmy ab24bd8526 Add gitea hosts entry before checkout steps to work around DNS override
Runner's --dns options bypass Docker's embedded DNS resolver, so job
containers can't resolve the "gitea" hostname even when connected to the
gitea_gitea network. Add /etc/hosts entry pointing gitea to 172.21.0.4
before each checkout step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 03:34:43 +08:00
Gan, Jimmy fccdf64435 Increase pip install timeout from 300s to 600s for cold-cache HDD slowness
Cold venv installs on the NAS HDD exceed 300s, causing the backend
tests install step to be killed by timeout. Double the timeout so the
first or fallback pip mirror attempt can complete before the timeout
fires.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 01:34:00 +08:00
Gan, Jimmy 1e695011ac Add TRUSTED_PROXIES to docker-compose environment to override config.py default
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:30:44 +08:00
Gan, Jimmy 789e4124be Update TRUSTED_PROXIES to include Docker bridge gateway 172.17.0.1
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:12:48 +08:00
Gan, Jimmy c27dfbfe35 Merge branch fix-ci-deployment into main 2026-05-27 22:24:18 +08:00
6 changed files with 100 additions and 136 deletions
+5 -1
View File
@@ -22,7 +22,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
run: | run: |
if [ ! -d .git ]; then if [ ! -d .git ]; then
git clone --branch "${GITHUB_REF_NAME}" --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . git clone --branch "${GITHUB_REF_NAME}" --depth=1 "http://172.21.0.1:3300/${GITHUB_REPOSITORY}.git" .
fi fi
- name: Resolve image tags - name: Resolve image tags
@@ -124,6 +124,10 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
export DOCKER_API_VERSION=1.43 export DOCKER_API_VERSION=1.43
# Ensure .env exists (CI container may not have it mounted)
if [ ! -f /volume1/docker/claude-dev/.env ]; then
printf 'ANTHROPIC_API_KEY=ci\nANTHROPIC_BASE_URL=https://www.bytecatcode.org\nANTHROPIC_MODEL=ci\nANTHROPIC_SMALL_FAST_MODEL=ci\nCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1\nCLAUDE_DEV_IMAGE_TAG=latest\n' > /volume1/docker/claude-dev/.env
fi
if ! docker compose -f /volume1/docker/claude-dev/docker-compose.yml config >/dev/null; then if ! docker compose -f /volume1/docker/claude-dev/docker-compose.yml config >/dev/null; then
echo "❌ docker-compose config validation failed" echo "❌ docker-compose config validation failed"
docker compose -f /volume1/docker/claude-dev/docker-compose.yml config docker compose -f /volume1/docker/claude-dev/docker-compose.yml config
+32 -37
View File
@@ -157,10 +157,41 @@ jobs:
npm run test:coverage -- --reporter=verbose --run --pool=forks --poolOptions.forks.maxForks=2 || true 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)" 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: deploy-dev:
name: Deploy to Dev name: Deploy to Dev
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [backend-tests, frontend-tests] needs: [build-image-dev, frontend-tests]
env: env:
SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum
container: container:
@@ -175,42 +206,6 @@ jobs:
git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" .
fi 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 - name: Sync runtime compose file
run: | run: |
mkdir -p /volume1/docker/nas-dashboard mkdir -p /volume1/docker/nas-dashboard
+57 -97
View File
@@ -22,7 +22,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
run: | run: |
if [ ! -d .git ]; then 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 fi
- name: Setup Python - name: Setup Python
@@ -55,32 +55,32 @@ jobs:
cd dashboard/backend cd dashboard/backend
if [ "${{ steps.cache-python.outputs.cache-hit }}" = "true" ]; then if [ "${{ steps.cache-python.outputs.cache-hit }}" = "true" ]; then
echo "Restoring venv from cache $CACHE_KEY..." echo "Restoring venv from cache $CACHE_KEY..."
if cp -a $CACHE_DIR/venv . && [ -f venv/bin/activate ]; then if cp -a $CACHE_DIR/venv . && [ -f venv/bin/activate ] && venv/bin/python -m pytest --version >/dev/null 2>&1; then
echo "Cache restored successfully" echo "Cache restored successfully"
else else
echo "Cache corrupted, installing fresh..." echo "Cache corrupted, installing fresh..."
rm -rf venv rm -rf venv
python3 -m venv venv python3 -m venv venv
. venv/bin/activate . venv/bin/activate
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements.txt || \
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements.txt || \
pip install -r requirements.txt pip install -r requirements.txt
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements-dev.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements-dev.txt || \
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements-dev.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements-dev.txt || \
pip install -r requirements-dev.txt pip install -r requirements-dev.txt
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR pytest-timeout pytest-cov || pip install pytest-timeout pytest-cov timeout 600 pip install --cache-dir=$PIP_CACHE_DIR pytest-timeout pytest-cov || pip install pytest-timeout pytest-cov
fi fi
else else
echo "Installing fresh dependencies..." echo "Installing fresh dependencies..."
python3 -m venv venv python3 -m venv venv
. venv/bin/activate . venv/bin/activate
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements.txt || \
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements.txt || \
pip install -r requirements.txt pip install -r requirements.txt
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements-dev.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --retries 3 --timeout 30 -r requirements-dev.txt || \
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements-dev.txt || \ timeout 600 pip install --cache-dir=$PIP_CACHE_DIR --index-url https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn -r requirements-dev.txt || \
pip install -r requirements-dev.txt pip install -r requirements-dev.txt
timeout 300 pip install --cache-dir=$PIP_CACHE_DIR pytest-timeout pytest-cov || pip install pytest-timeout pytest-cov timeout 600 pip install --cache-dir=$PIP_CACHE_DIR pytest-timeout pytest-cov || pip install pytest-timeout pytest-cov
echo "Saving venv to cache $CACHE_KEY..." echo "Saving venv to cache $CACHE_KEY..."
cp -a venv $CACHE_DIR/ || echo "Warning: cache save failed" cp -a venv $CACHE_DIR/ || echo "Warning: cache save failed"
fi fi
@@ -89,7 +89,7 @@ jobs:
run: | run: |
cd dashboard/backend cd dashboard/backend
. venv/bin/activate . venv/bin/activate
pytest tests/ -v --timeout=60 \ python -m pytest tests/ -v --timeout=60 \
--cov=. --cov-report=xml --cov-report=term \ --cov=. --cov-report=xml --cov-report=term \
--junit-xml=test-results.xml \ --junit-xml=test-results.xml \
--cov-fail-under=49 --cov-fail-under=49
@@ -102,13 +102,13 @@ jobs:
frontend-tests: frontend-tests:
name: Frontend Tests name: Frontend Tests
runs-on: ubuntu-latest runs-on: vps
steps: steps:
- name: Checkout repository - name: Checkout repository
run: | run: |
if [ ! -d .git ]; then 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 fi
- name: Setup Node.js - name: Setup Node.js
@@ -168,17 +168,44 @@ jobs:
fi fi
echo "✅ Frontend tests passed" 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)..."
# Podman caches layers automatically; no --cache-from needed
podman build -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: deploy:
name: Deploy to Production name: Deploy to Production
runs-on: ubuntu-latest runs-on: nas
if: always() if: always() && needs.build-image.result == 'success'
needs: [backend-tests, frontend-tests] needs: [build-image, frontend-tests]
env: env:
SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum SECRET_KEY: test-secret-key-for-ci-environment-32chars-minimum
GITEA_DB_PASSWORD: nyM3P4v1aYAsT7zfUtdguImNVHgFltCKCY/OjzWZVRE= GITEA_DB_PASSWORD: nyM3P4v1aYAsT7zfUtdguImNVHgFltCKCY/OjzWZVRE=
container: container:
network: gitea_gitea network: gitea_gitea
options: --add-host=host.docker.internal:host-gateway --dns 192.168.31.1 --dns 8.8.8.8
volumes: volumes:
- /var/packages/ContainerManager/target/usr/bin/docker:/usr/bin/docker - /var/packages/ContainerManager/target/usr/bin/docker:/usr/bin/docker
- /volume1/docker/nas-dashboard:/nas-dashboard - /volume1/docker/nas-dashboard:/nas-dashboard
@@ -189,60 +216,10 @@ jobs:
git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" . git clone --depth=1 "http://gitea:3000/${GITHUB_REPOSITORY}.git" .
fi 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 - name: Sync runtime compose file
run: cp dashboard/docker-compose.yml /nas-dashboard/docker-compose.yml run: |
mkdir -p /nas-dashboard
cp dashboard/docker-compose.yml /nas-dashboard/docker-compose.yml
- name: Deploy and verify - name: Deploy and verify
run: | run: |
@@ -261,30 +238,7 @@ jobs:
if ! docker container inspect nas-dashboard >/dev/null 2>&1; then if ! docker container inspect nas-dashboard >/dev/null 2>&1; then
echo "Compose failed, stripping networks and retrying..." echo "Compose failed, stripping networks and retrying..."
docker rm -f docker-socket-proxy nas-dashboard 2>/dev/null || true docker rm -f docker-socket-proxy nas-dashboard 2>/dev/null || true
python3 -c " sed '/^networks:/,$d' /nas-dashboard/docker-compose.yml > /nas-dashboard/prod-ci.yml
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)
"
docker compose -f /nas-dashboard/prod-ci.yml up -d --pull never 2>&1 || true docker compose -f /nas-dashboard/prod-ci.yml up -d --pull never 2>&1 || true
fi fi
@@ -349,7 +303,13 @@ jobs:
# Verify network connections # Verify network connections
echo "=== 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 # Wait for container to be healthy
echo "Waiting for container to be healthy..." echo "Waiting for container to be healthy..."
+4
View File
@@ -7,9 +7,13 @@ services:
stdin_open: true stdin_open: true
tty: true tty: true
working_dir: /repos/nas-tools working_dir: /repos/nas-tools
env_file:
- /volume1/docker/claude-dev/.env
volumes: volumes:
- /volume1/repos:/repos - /volume1/repos:/repos
- /volume1/docker/claude-dev/claude-config:/root/.claude - /volume1/docker/claude-dev/claude-config:/root/.claude
- /volume1/docker/claude-dev/claude-config/.claude.json:/root/.claude.json - /volume1/docker/claude-dev/claude-config/.claude.json:/root/.claude.json
- /volume1/docker/claude-dev/gitea_token:/root/.gitea_token:ro - /volume1/docker/claude-dev/gitea_token:/root/.gitea_token:ro
- /volume1/docker/claude-dev/bashrc:/root/.bashrc - /volume1/docker/claude-dev/bashrc:/root/.bashrc
- /volume1:/volume1
- /:/nas-root
+1 -1
View File
@@ -103,7 +103,7 @@ if not TRANSMISSION_USER or not TRANSMISSION_PASS:
# Networking configs # Networking configs
LAN_IP_RANGES = os.environ.get("LAN_IP_RANGES", "192.168.31.0/24").split(",") LAN_IP_RANGES = os.environ.get("LAN_IP_RANGES", "192.168.31.0/24").split(",")
TRUSTED_PROXIES = os.environ.get("TRUSTED_PROXIES", "127.0.0.1,::1,172.21.0.1").split(",") TRUSTED_PROXIES = os.environ.get("TRUSTED_PROXIES", "127.0.0.1,::1,172.21.0.1,172.17.0.1").split(",")
def _parse_ip(ip_str: str): def _parse_ip(ip_str: str):
+1
View File
@@ -44,6 +44,7 @@ services:
- SECRET_KEY=${SECRET_KEY} - SECRET_KEY=${SECRET_KEY}
- ADMIN_USER=jimmy - ADMIN_USER=jimmy
- ADMIN_PASSWORD_HASH=${ADMIN_PASSWORD_HASH} - ADMIN_PASSWORD_HASH=${ADMIN_PASSWORD_HASH}
- TRUSTED_PROXIES=127.0.0.1,::1,172.21.0.1,172.17.0.1
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-} - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
- TELEGRAM_PROXY=${TELEGRAM_PROXY:-} - TELEGRAM_PROXY=${TELEGRAM_PROXY:-}