From a5a4d79a327e7130581bba222b2fc952b961dc1e Mon Sep 17 00:00:00 2001 From: safavimohadeseh78 Date: Sun, 25 Oct 2026 12:52:27 +0330 Subject: [PATCH] chore: add a manual workflow for offline npm caches CI can download Linux dependencies so the server can build images without npm. Co-authored-by: Cursor --- .gitea/workflows/offline-deps.yml | 159 ++++++++++++++++++++++++++++++ offline/SERVER.md | 26 +++++ offline/backend.Dockerfile | 52 ++++++++++ offline/frontend.Dockerfile | 29 ++++++ 4 files changed, 266 insertions(+) create mode 100644 .gitea/workflows/offline-deps.yml create mode 100644 offline/SERVER.md create mode 100644 offline/backend.Dockerfile create mode 100644 offline/frontend.Dockerfile diff --git a/.gitea/workflows/offline-deps.yml b/.gitea/workflows/offline-deps.yml new file mode 100644 index 0000000..5d8aa36 --- /dev/null +++ b/.gitea/workflows/offline-deps.yml @@ -0,0 +1,159 @@ +name: Offline npm caches + +on: + workflow_dispatch: + +env: + NODE_VERSION: '22.18.0' + FRONTEND_SHA: c53a67831db98ea10f1aa168fcab80cca97134ed + BACKEND_SHA: 866be223956ef5d3961b0d835b36adedfeb35c82 + +jobs: + caches: + name: offline npm caches + runs-on: ubuntu-latest + timeout-minutes: 40 + steps: + - name: Checkout workflow repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Compiler tools for native smoke + run: | + set -euo pipefail + if ! command -v python3 >/dev/null || ! command -v make >/dev/null || ! command -v g++ >/dev/null; then + sudo apt-get update + sudo apt-get install -y --no-install-recommends python3 make g++ + fi + + - name: Extract pinned sources + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/src/frontend" "$RUNNER_TEMP/src/backend" + git archive "$FRONTEND_SHA" | tar -x -C "$RUNNER_TEMP/src/frontend" + git clone --depth 1 --no-checkout https://get.feep.ir/gold_land/gold_land_server.git "$RUNNER_TEMP/backend-git" + git -C "$RUNNER_TEMP/backend-git" fetch --depth 1 origin "$BACKEND_SHA" + git -C "$RUNNER_TEMP/backend-git" checkout FETCH_HEAD + git -C "$RUNNER_TEMP/backend-git" archive HEAD | tar -x -C "$RUNNER_TEMP/src/backend" + test "$(git -C "$RUNNER_TEMP/backend-git" rev-parse HEAD)" = "$BACKEND_SHA" + test -f "$RUNNER_TEMP/src/frontend/package-lock.json" + test -f "$RUNNER_TEMP/src/backend/package-lock.json" + + - name: Fill empty npm caches and prove offline installs + run: | + set -euo pipefail + node_dir="$(dirname "$(dirname "$(readlink -f "$(command -v node)")")")" + if [ ! -f "$node_dir/include/node/node.h" ]; then + echo "Node headers are not in $node_dir. node-gyp would download them outside the npm cache." + exit 1 + fi + export npm_config_nodedir="$node_dir" + export npm_config_build_from_source=true + echo "CI npm_config_nodedir=$node_dir" + node -v + uname -s + uname -m + + fill_and_prove() { + name="$1" + src="$2" + cache="$3" + shift 3 + rm -rf "$cache" + mkdir -p "$cache" + echo "online npm ci: $name" + npm ci --cache "$cache" --no-audit --no-fund --prefix "$src" + clean="$RUNNER_TEMP/clean/$name" + rm -rf "$clean" + mkdir -p "$clean" + tar -C "$src" --exclude node_modules -cf - . | tar -C "$clean" -xf - + test ! -d "$clean/node_modules" + echo "offline npm ci: $name" + npm ci --offline --cache "$cache" --no-audit --no-fund --prefix "$clean" + (cd "$clean" && npm run build) + if [ "$name" = "backend" ]; then + (cd "$clean" && node -e "const Database=require('better-sqlite3'); const db=new Database(':memory:'); const row=db.prepare('SELECT 1 AS ok').get(); if(row.ok!==1) process.exit(1); console.log('sqlite-smoke-ok')") + fi + } + + fill_and_prove backend "$RUNNER_TEMP/src/backend" "$RUNNER_TEMP/cache/backend" + fill_and_prove frontend "$RUNNER_TEMP/src/frontend" "$RUNNER_TEMP/cache/frontend" + + if [ -d "$HOME/.cache/node-gyp" ]; then + echo "node-gyp wrote $HOME/.cache/node-gyp outside the npm cache" + exit 1 + fi + + python3 - <<'PY' + import os + root=os.environ["RUNNER_TEMP"]+"/cache" + needles=(":_authToken","_password=","BEGIN PRIVATE KEY","BEGIN RSA PRIVATE KEY") + for dirpath, _, files in os.walk(root): + for name in files: + if name in {".npmrc","_auth"}: + raise SystemExit(f"credential file in npm cache: {os.path.join(dirpath, name)}") + path=os.path.join(dirpath, name) + try: + data=open(path,"rb").read() + except OSError: + continue + text=data.decode("utf-8","ignore") + for needle in needles: + if needle in text: + raise SystemExit(f"credential marker in npm cache: {path}") + print("npm cache credential scan ok") + PY + + - name: Pack archive + run: | + set -euo pipefail + out="$RUNNER_TEMP/offline-pack" + rm -rf "$out" + mkdir -p "$out/offline-deps" + cp -a "$RUNNER_TEMP/cache/backend" "$out/offline-deps/backend-npm-cache" + cp -a "$RUNNER_TEMP/cache/frontend" "$out/offline-deps/frontend-npm-cache" + cp offline/backend.Dockerfile offline/frontend.Dockerfile offline/SERVER.md "$out/offline-deps/" + node_dir="$(dirname "$(dirname "$(readlink -f "$(command -v node)")")")" + python3 - <process.exit(r.ok?0:1)).catch(()=>process.exit(1))" + +CMD ["node", "dist/main.js"] diff --git a/offline/frontend.Dockerfile b/offline/frontend.Dockerfile new file mode 100644 index 0000000..142c24a --- /dev/null +++ b/offline/frontend.Dockerfile @@ -0,0 +1,29 @@ +# syntax=docker/dockerfile:1 +# Offline frontend image. Build context is the frontend source tree +# plus ./offline-npm-cache. nginx.conf must be in that context. + +FROM node:22.18.0-bookworm AS build + +WORKDIR /app + +ARG VITE_API_URL=/api +ENV VITE_API_URL=${VITE_API_URL} + +COPY offline-npm-cache /offline-npm-cache +COPY package.json package-lock.json ./ + +RUN npm ci --offline --cache /offline-npm-cache --no-audit --no-fund \ + && rm -rf /offline-npm-cache /root/.npm + +COPY . . + +RUN npm run build + +FROM nginx:1.27-alpine AS production + +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"]