177 lines
7.7 KiB
YAML
177 lines
7.7 KiB
YAML
name: Deploy to Server
|
|
|
|
on:
|
|
push:
|
|
branches: [ main ]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on:
|
|
- ubuntu-latest
|
|
- self-hosted
|
|
steps:
|
|
- name: Locate node/npm and fix PATH
|
|
run: |
|
|
set +e
|
|
echo "--- initial PATH ---"
|
|
echo "$PATH"
|
|
echo
|
|
echo "--- find node/npm binaries (maxdepth 6, 15s timeout) ---"
|
|
timeout 15 find / \( -path /proc -o -path /sys \) -prune -o -type f \( -name node -o -name npm \) -print 2>/dev/null | head -n 30 || true
|
|
echo
|
|
echo "--- common locations ---"
|
|
for d in /usr/local/bin /usr/bin /opt/node/bin /root/.nvm/versions/node/*/bin /home/*/.nvm/versions/node/*/bin /snap/bin /opt/homebrew/bin; do
|
|
[ -x "$d/node" ] && echo "FOUND node in $d: $($d/node -v 2>/dev/null)"
|
|
[ -x "$d/npm" ] && echo "FOUND npm in $d: $($d/npm -v 2>/dev/null)"
|
|
done
|
|
echo
|
|
echo "--- choose first available node/npm dir and write to $GITHUB_ENV ---"
|
|
NODE_DIR=""
|
|
for d in /usr/local/bin /usr/bin /opt/node/bin /opt/homebrew/bin /snap/bin; do
|
|
[ -x "$d/node" ] && [ -x "$d/npm" ] && { NODE_DIR="$d"; break; }
|
|
done
|
|
if [ -z "$NODE_DIR" ]; then
|
|
d=$(dirname "$(find / -path /proc -prune -o -type f -name node -print 2>/dev/null | head -n 1)" 2>/dev/null)
|
|
[ -n "$d" ] && [ -x "$d/node" ] && [ -x "$d/npm" ] && NODE_DIR="$d"
|
|
fi
|
|
if [ -z "$NODE_DIR" ]; then
|
|
for candidate in /root/.nvm/versions/node/*/bin /home/*/.nvm/versions/node/*/bin; do
|
|
[ -d "$candidate" ] && [ -x "$candidate/node" ] && [ -x "$candidate/npm" ] && { NODE_DIR="$candidate"; break; }
|
|
done
|
|
fi
|
|
if [ -z "$NODE_DIR" ]; then
|
|
echo "FAIL: could not locate node/npm on this runner. Install node 20 on the runner host first."
|
|
exit 1
|
|
fi
|
|
echo "SELECTED NODE_DIR=$NODE_DIR"
|
|
echo "PATH=$NODE_DIR:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" >> "$GITHUB_ENV"
|
|
echo
|
|
echo "--- verify ---"
|
|
export "PATH=$NODE_DIR:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
|
|
which node && node -v
|
|
which npm && npm -v
|
|
which git && git --version | head -n 1
|
|
which curl && which ssh && which scp && (docker --version || docker-compose --version || true)
|
|
|
|
- name: Prepare workspace
|
|
working-directory: /tmp
|
|
run: |
|
|
rm -rf volunteer-deploy
|
|
mkdir -p /tmp/volunteer-deploy
|
|
|
|
- name: Checkout (git)
|
|
working-directory: /tmp/volunteer-deploy
|
|
run: |
|
|
if [ -d src ]; then rm -rf src; fi
|
|
mkdir src && cd src
|
|
git clone --depth 1 -b main https://gitea.fse-media.group/Henry_Du/VolunteerManage-for-byw.git .
|
|
|
|
- name: Configure npm mirror
|
|
working-directory: /tmp/volunteer-deploy/src
|
|
run: |
|
|
echo "registry=https://registry.npmmirror.com" > ~/.npmrc
|
|
npm config set registry https://registry.npmmirror.com
|
|
|
|
- name: Build client (native)
|
|
working-directory: /tmp/volunteer-deploy/src/client
|
|
run: |
|
|
npm install --no-audit --no-fund
|
|
npm run build
|
|
|
|
- name: Build server (native)
|
|
working-directory: /tmp/volunteer-deploy/src/server
|
|
run: |
|
|
npm install --no-audit --no-fund
|
|
npm run build
|
|
|
|
- name: Install server production deps
|
|
working-directory: /tmp/volunteer-deploy/src/server
|
|
run: |
|
|
rm -rf node_modules
|
|
npm install --omit=dev --no-audit --no-fund
|
|
|
|
- name: Prepare deploy package
|
|
working-directory: /tmp/volunteer-deploy
|
|
run: |
|
|
set -e
|
|
echo "--- verify build artifacts exist ---"
|
|
ls -la src/client/dist >/dev/null 2>&1 || (echo "ERROR: src/client/dist missing, client build failed"; ls -la src/client; exit 1)
|
|
ls -la src/server/dist >/dev/null 2>&1 || (echo "ERROR: src/server/dist missing, server build failed"; ls -la src/server; exit 1)
|
|
ls -la src/server/node_modules >/dev/null 2>&1 || (echo "ERROR: src/server/node_modules missing"; exit 1)
|
|
echo
|
|
echo "--- prepare deploy-pkg directory tree ---"
|
|
rm -rf deploy-pkg
|
|
mkdir -p deploy-pkg/app/client/dist \
|
|
deploy-pkg/app/server/dist \
|
|
deploy-pkg/app/server/node_modules \
|
|
deploy-pkg/app/server/src/db
|
|
echo
|
|
echo "--- copy files ---"
|
|
cp -r src/client/dist/. deploy-pkg/app/client/dist/
|
|
cp -r src/server/dist/. deploy-pkg/app/server/dist/
|
|
cp -r src/server/node_modules/. deploy-pkg/app/server/node_modules/
|
|
cp src/server/src/db/schema.sql deploy-pkg/app/server/src/db/schema.sql
|
|
echo
|
|
echo "--- result tree ---"
|
|
find deploy-pkg -maxdepth 5 -type d | sort
|
|
echo
|
|
echo "--- generate docker-compose.yml ---"
|
|
cat > deploy-pkg/docker-compose.yml <<'EOF'
|
|
services:
|
|
volunteer:
|
|
image: node:20-alpine
|
|
container_name: volunteer-manage
|
|
restart: unless-stopped
|
|
working_dir: /app/server
|
|
command: ["node", "dist/index.js"]
|
|
ports:
|
|
- "3000:3000"
|
|
environment:
|
|
- NODE_ENV=production
|
|
- PORT=3000
|
|
- JWT_SECRET=${JWT_SECRET:-volunteer-manage-secret-key-2026}
|
|
volumes:
|
|
- /opt/volunteer/data:/app/server/data:z
|
|
- /opt/volunteer/app:/app:z
|
|
EOF
|
|
echo
|
|
echo "compose file:"
|
|
cat deploy-pkg/docker-compose.yml
|
|
|
|
- name: Prepare SSH key
|
|
env:
|
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
run: |
|
|
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
|
rm -f ~/.ssh/deploy_key ~/.ssh/known_hosts
|
|
printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
printf 'Host *\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n IdentityFile ~/.ssh/deploy_key\n' > ~/.ssh/config
|
|
chmod 600 ~/.ssh/config
|
|
ssh -V
|
|
|
|
- name: Deploy via SSH
|
|
env:
|
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
|
SSH_PORT: ${{ secrets.SSH_PORT || 22 }}
|
|
SSH_USER: ${{ secrets.SSH_USER }}
|
|
working-directory: /tmp/volunteer-deploy
|
|
run: |
|
|
PORT="${SSH_PORT:-22}"
|
|
echo "Deploy target: $SSH_USER@$SSH_HOST:$PORT"
|
|
ssh -p $PORT $SSH_USER@$SSH_HOST "mkdir -p /opt/volunteer/app /opt/volunteer/data"
|
|
scp -P $PORT -r deploy-pkg/app/* $SSH_USER@$SSH_HOST:/opt/volunteer/app/
|
|
scp -P $PORT deploy-pkg/docker-compose.yml $SSH_USER@$SSH_HOST:/opt/volunteer/docker-compose.yml
|
|
ssh -p $PORT $SSH_USER@$SSH_HOST "cd /opt/volunteer && (docker compose down || docker-compose down || true) && (docker compose up -d --force-recreate || docker-compose up -d --force-recreate) && sleep 5 && (docker compose ps || docker-compose ps)"
|
|
|
|
- name: Health check
|
|
env:
|
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
|
SSH_PORT: ${{ secrets.SSH_PORT || 22 }}
|
|
SSH_USER: ${{ secrets.SSH_USER }}
|
|
run: |
|
|
PORT="${SSH_PORT:-22}"
|
|
ssh -p $PORT $SSH_USER@$SSH_HOST "for i in 1 2 3 4 5 6 7 8 9 10 11 12; do if curl -fsS http://localhost:3000/u > /dev/null; then echo 'Service OK'; exit 0; fi; echo Wait...; sleep 3; done; echo '--- Service FAIL, diagnose ---'; (docker compose ps || docker-compose ps); echo '--- container logs last 30 ---'; (docker compose logs --tail 30 || docker-compose logs --tail 30); exit 1"
|
|
|