Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6e0edbc00 | |||
| 2b180be926 | |||
| 1bf1f7563f | |||
| 988a941b41 |
@@ -0,0 +1,47 @@
|
|||||||
|
name: Debug DB
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
debug:
|
||||||
|
runs-on: mac
|
||||||
|
steps:
|
||||||
|
- name: Setup SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
printf '%s' "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2_deploy.pem
|
||||||
|
chmod 600 ~/.ssh/ec2_deploy.pem
|
||||||
|
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Query DB
|
||||||
|
env:
|
||||||
|
EC2_HOST: ${{ secrets.EC2_HOST }}
|
||||||
|
EC2_USER: ${{ secrets.EC2_USER }}
|
||||||
|
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} bash << EOF
|
||||||
|
echo "=== Docker containers ==="
|
||||||
|
docker ps --format "{{.Names}}\t{{.Status}}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Databases ==="
|
||||||
|
docker exec jiu_mysql mysql -uroot -p${DB_PASSWORD} -e "SHOW DATABASES;" 2>/dev/null \
|
||||||
|
|| echo "jiu_mysql container not found or DB_PASSWORD wrong"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Tables in jiu_db ==="
|
||||||
|
docker exec jiu_mysql mysql -uroot -p${DB_PASSWORD} jiu_db -e "SHOW TABLES;" 2>/dev/null
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== shops ==="
|
||||||
|
docker exec jiu_mysql mysql -uroot -p${DB_PASSWORD} jiu_db -e "SELECT id, name, code FROM shops;" 2>/dev/null
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== users ==="
|
||||||
|
docker exec jiu_mysql mysql -uroot -p${DB_PASSWORD} jiu_db -e "SELECT id, shop_id, username, role, is_active FROM users;" 2>/dev/null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Cleanup SSH key
|
||||||
|
if: always()
|
||||||
|
run: rm -f ~/.ssh/ec2_deploy.pem
|
||||||
@@ -36,6 +36,13 @@ jobs:
|
|||||||
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
|
FORGEJO_URL: ${{ secrets.FORGEJO_URL }}
|
||||||
run: |
|
run: |
|
||||||
tar -czf web.tar.gz -C client/build web
|
tar -czf web.tar.gz -C client/build web
|
||||||
|
tar -czf configs.tar.gz \
|
||||||
|
deploy/nginx-jiu.conf \
|
||||||
|
deploy/jiu.service \
|
||||||
|
deploy/production.env.template \
|
||||||
|
deploy/setup-ec2.sh \
|
||||||
|
deploy/docker-compose.yml \
|
||||||
|
deploy/docker-compose.jiu.yml
|
||||||
VERSION="v$(date +%Y%m%d.%H%M)"
|
VERSION="v$(date +%Y%m%d.%H%M)"
|
||||||
COMMIT="${{ gitea.sha }}"
|
COMMIT="${{ gitea.sha }}"
|
||||||
COMMIT_MSG=$(git log -1 --pretty="%s" HEAD)
|
COMMIT_MSG=$(git log -1 --pretty="%s" HEAD)
|
||||||
@@ -53,6 +60,8 @@ jobs:
|
|||||||
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@backend/jiu-server"
|
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@backend/jiu-server"
|
||||||
curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
|
curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@web.tar.gz"
|
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@web.tar.gz"
|
||||||
|
curl -sf -X POST "${FORGEJO_URL}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
|
||||||
|
-H "Authorization: token ${FORGEJO_TOKEN}" -F "attachment=@configs.tar.gz"
|
||||||
echo "Release ${VERSION} created"
|
echo "Release ${VERSION} created"
|
||||||
|
|
||||||
- name: Setup SSH
|
- name: Setup SSH
|
||||||
@@ -72,6 +81,8 @@ jobs:
|
|||||||
client/build/web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-web-new/
|
client/build/web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-web-new/
|
||||||
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem" \
|
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem" \
|
||||||
web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-marketing-new/
|
web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-marketing-new/
|
||||||
|
scp -O -i ~/.ssh/ec2_deploy.pem deploy/nginx-jiu.conf \
|
||||||
|
${EC2_USER}@${EC2_HOST}:/tmp/nginx-jiu.conf
|
||||||
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} << 'ENDSSH'
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} << 'ENDSSH'
|
||||||
sudo systemctl stop jiu
|
sudo systemctl stop jiu
|
||||||
cp /tmp/jiu-server /opt/jiu/backend/jiu-server
|
cp /tmp/jiu-server /opt/jiu/backend/jiu-server
|
||||||
@@ -87,7 +98,8 @@ jobs:
|
|||||||
mkdir -p /opt/jiu/marketing
|
mkdir -p /opt/jiu/marketing
|
||||||
rsync -a --delete /tmp/jiu-marketing-new/ /opt/jiu/marketing/
|
rsync -a --delete /tmp/jiu-marketing-new/ /opt/jiu/marketing/
|
||||||
rm -rf /tmp/jiu-marketing-new
|
rm -rf /tmp/jiu-marketing-new
|
||||||
sudo nginx -s reload
|
sudo cp /tmp/nginx-jiu.conf /etc/nginx/conf.d/jiu.conf
|
||||||
|
sudo nginx -t && sudo nginx -s reload
|
||||||
ENDSSH
|
ENDSSH
|
||||||
|
|
||||||
- name: Cleanup SSH key
|
- name: Cleanup SSH key
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
name: Reset DB
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
shops:
|
||||||
|
description: '写入的门店(逗号分隔,如 S001,S002,S003)'
|
||||||
|
required: true
|
||||||
|
default: 'S001,S002,S003'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
reset:
|
||||||
|
runs-on: mac
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
printf '%s' "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2_deploy.pem
|
||||||
|
chmod 600 ~/.ssh/ec2_deploy.pem
|
||||||
|
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Reset database & reseed
|
||||||
|
env:
|
||||||
|
EC2_HOST: ${{ secrets.EC2_HOST }}
|
||||||
|
EC2_USER: ${{ secrets.EC2_USER }}
|
||||||
|
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||||
|
SHOPS: ${{ github.event.inputs.shops }}
|
||||||
|
run: |
|
||||||
|
# 1. 清库重建
|
||||||
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} \
|
||||||
|
"docker exec jiu_mysql mysql -uroot -p${DB_PASSWORD} -e 'DROP DATABASE IF EXISTS jiu_db; CREATE DATABASE jiu_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'"
|
||||||
|
echo "✓ Database reset"
|
||||||
|
|
||||||
|
# 2. 重启 jiu-server,AutoMigrate 重建表结构
|
||||||
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} \
|
||||||
|
"sudo systemctl restart jiu"
|
||||||
|
echo "✓ jiu-server restarted, waiting for AutoMigrate..."
|
||||||
|
|
||||||
|
# 3. 等待健康检查通过
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} \
|
||||||
|
"curl -sf http://localhost:8080/health" && break
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "✓ Health check passed"
|
||||||
|
|
||||||
|
# 4. 写入 seed 数据
|
||||||
|
for SHOP in $(echo "$SHOPS" | tr ',' ' '); do
|
||||||
|
FILE="backend/seeds/${SHOP}.sql"
|
||||||
|
if [ ! -f "$FILE" ]; then
|
||||||
|
echo "⚠️ $FILE not found, skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "→ Seeding $SHOP ..."
|
||||||
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} \
|
||||||
|
"docker exec -i jiu_mysql mysql -uroot -p${DB_PASSWORD} jiu_db" \
|
||||||
|
< "$FILE"
|
||||||
|
echo "✓ $SHOP done"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Cleanup SSH key
|
||||||
|
if: always()
|
||||||
|
run: rm -f ~/.ssh/ec2_deploy.pem
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Seed DB
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
shops:
|
||||||
|
description: '要写入的门店(逗号分隔,如 S001,S002,S003)'
|
||||||
|
required: true
|
||||||
|
default: 'S001,S002,S003'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
seed:
|
||||||
|
runs-on: mac
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
printf '%s' "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2_deploy.pem
|
||||||
|
chmod 600 ~/.ssh/ec2_deploy.pem
|
||||||
|
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Seed databases
|
||||||
|
env:
|
||||||
|
EC2_HOST: ${{ secrets.EC2_HOST }}
|
||||||
|
EC2_USER: ${{ secrets.EC2_USER }}
|
||||||
|
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||||
|
SHOPS: ${{ github.event.inputs.shops }}
|
||||||
|
run: |
|
||||||
|
for SHOP in $(echo "$SHOPS" | tr ',' ' '); do
|
||||||
|
FILE="backend/seeds/${SHOP}.sql"
|
||||||
|
if [ ! -f "$FILE" ]; then
|
||||||
|
echo "⚠️ $FILE not found, skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "→ Seeding $SHOP ..."
|
||||||
|
ssh -i ~/.ssh/ec2_deploy.pem ${EC2_USER}@${EC2_HOST} \
|
||||||
|
"docker exec -i jiu_mysql mysql -uroot -p${DB_PASSWORD} jiu_db" \
|
||||||
|
< "$FILE"
|
||||||
|
echo "✓ $SHOP done"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Cleanup SSH key
|
||||||
|
if: always()
|
||||||
|
run: rm -f ~/.ssh/ec2_deploy.pem
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
name: DB Backup
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 18 * * *' # UTC 18:00 = 北京时间 02:00
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
backup:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Setup SSH
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2.pem
|
|
||||||
chmod 600 ~/.ssh/ec2.pem
|
|
||||||
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Dump MySQL to NAS
|
|
||||||
env:
|
|
||||||
EC2_HOST: ${{ secrets.EC2_HOST }}
|
|
||||||
EC2_USER: ${{ secrets.EC2_USER }}
|
|
||||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
BACKUP_DIR=/volume1/backups/jiu-db
|
|
||||||
mkdir -p $BACKUP_DIR
|
|
||||||
FILENAME="jiu_db_$(date +%Y%m%d_%H%M%S).sql.gz"
|
|
||||||
ssh -i ~/.ssh/ec2.pem ${EC2_USER}@${EC2_HOST} \
|
|
||||||
"docker exec jiu_mysql mysqldump -uroot -p${DB_PASSWORD} jiu_db" \
|
|
||||||
| gzip > ${BACKUP_DIR}/${FILENAME}
|
|
||||||
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
|
|
||||||
echo "Saved: ${BACKUP_DIR}/${FILENAME}"
|
|
||||||
|
|
||||||
- name: Cleanup SSH key
|
|
||||||
if: always()
|
|
||||||
run: rm -f ~/.ssh/ec2.pem
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches-ignore: [main]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: backend/go.mod
|
|
||||||
cache-dependency-path: backend/go.sum
|
|
||||||
|
|
||||||
- name: Go tests
|
|
||||||
working-directory: backend
|
|
||||||
run: go test ./...
|
|
||||||
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Flutter tests
|
|
||||||
working-directory: client
|
|
||||||
run: flutter test
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
name: Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: backend/go.mod
|
|
||||||
cache-dependency-path: backend/go.sum
|
|
||||||
|
|
||||||
- name: Go tests
|
|
||||||
working-directory: backend
|
|
||||||
run: go test ./...
|
|
||||||
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Flutter tests
|
|
||||||
working-directory: client
|
|
||||||
run: flutter test
|
|
||||||
|
|
||||||
- name: Build backend (linux/amd64)
|
|
||||||
working-directory: backend
|
|
||||||
run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o jiu-server .
|
|
||||||
|
|
||||||
- name: Build Flutter Web
|
|
||||||
working-directory: client
|
|
||||||
run: flutter build web --release --dart-define=BASE_URL=https://jiu.51yanmei.com --dart-define=PUBLIC_URL=https://jiu.51yanmei.com
|
|
||||||
|
|
||||||
- name: Setup SSH
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/ec2.pem
|
|
||||||
chmod 600 ~/.ssh/ec2.pem
|
|
||||||
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Deploy to EC2
|
|
||||||
env:
|
|
||||||
EC2_HOST: ${{ secrets.EC2_HOST }}
|
|
||||||
EC2_USER: ${{ secrets.EC2_USER }}
|
|
||||||
run: |
|
|
||||||
scp -i ~/.ssh/ec2.pem backend/jiu-server ${EC2_USER}@${EC2_HOST}:/tmp/jiu-server
|
|
||||||
rsync -avz --delete -e "ssh -i ~/.ssh/ec2.pem" \
|
|
||||||
client/build/web/ ${EC2_USER}@${EC2_HOST}:/tmp/jiu-web-new/
|
|
||||||
ssh -i ~/.ssh/ec2.pem ${EC2_USER}@${EC2_HOST} << 'ENDSSH'
|
|
||||||
sudo systemctl stop jiu
|
|
||||||
cp /tmp/jiu-server /opt/jiu/backend/jiu-server
|
|
||||||
chmod +x /opt/jiu/backend/jiu-server
|
|
||||||
sudo systemctl start jiu
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
curl -sf http://localhost:8080/health && break
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
rm -rf /opt/jiu/web-old
|
|
||||||
mv /opt/jiu/web /opt/jiu/web-old 2>/dev/null || true
|
|
||||||
mv /tmp/jiu-web-new /opt/jiu/web
|
|
||||||
sudo nginx -s reload
|
|
||||||
ENDSSH
|
|
||||||
|
|
||||||
- name: Cleanup SSH key
|
|
||||||
if: always()
|
|
||||||
run: rm -f ~/.ssh/ec2.pem
|
|
||||||
+5
-26
@@ -22,44 +22,23 @@ server {
|
|||||||
proxy_read_timeout 30s;
|
proxy_read_timeout 30s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Flutter 管理端(/app/ 子路径)
|
# Flutter 管理端(仅 /app/ 前缀,不污染根路径)
|
||||||
location /app/ {
|
location /app/ {
|
||||||
alias /opt/jiu/web/;
|
alias /opt/jiu/web/;
|
||||||
index index.html;
|
index index.html;
|
||||||
try_files $uri $uri/ /app/index.html;
|
try_files $uri $uri/ /app/index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Flutter 公开商品扫码页(/product/:id → Flutter app)
|
# 扫码页(/scan/<id> → scan.html,JS 从 URL 读取 id)
|
||||||
location /product/ {
|
|
||||||
alias /opt/jiu/web/;
|
|
||||||
try_files $uri /app/index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 营销站点 — 精确路径
|
|
||||||
location = / {
|
|
||||||
root /opt/jiu/marketing;
|
|
||||||
try_files /index.html =404;
|
|
||||||
}
|
|
||||||
location ~ ^/(docs|download)(\.html)?$ {
|
|
||||||
root /opt/jiu/marketing;
|
|
||||||
try_files /$1.html =404;
|
|
||||||
}
|
|
||||||
location /features/ {
|
|
||||||
root /opt/jiu/marketing;
|
|
||||||
try_files $uri $uri.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 营销站点 — 扫码 HTML 页(保留旧链接兼容)
|
|
||||||
location /scan/ {
|
location /scan/ {
|
||||||
root /opt/jiu/marketing;
|
root /opt/jiu/marketing;
|
||||||
try_files /scan.html =404;
|
try_files /scan.html =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 营销静态资源(CSS / SVG / 图片等)
|
# 营销站点兜底(根路径及所有其他路径)
|
||||||
location /assets/ {
|
location / {
|
||||||
root /opt/jiu/marketing;
|
root /opt/jiu/marketing;
|
||||||
expires 7d;
|
try_files $uri $uri.html $uri/index.html /index.html;
|
||||||
add_header Cache-Control "public";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user