Skip to content

Cập nhật giao diện MergeOS dashboard #106

Cập nhật giao diện MergeOS dashboard

Cập nhật giao diện MergeOS dashboard #106

Workflow file for this run

name: Build and deploy
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: deploy-master
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.mod
- name: Test backend
working-directory: backend
run: go test ./...
- name: Build backend
working-directory: backend
run: |
mkdir -p dist
go build -o dist/mergeos ./cmd/mergeos
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
cache-dependency-path: |
frontend/package-lock.json
admin/package-lock.json
scan/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Build frontend
working-directory: frontend
run: npm run build
- name: Install admin dependencies
working-directory: admin
run: npm ci
- name: Build admin
working-directory: admin
run: npm run build
- name: Install scan dependencies
working-directory: scan
run: npm ci
- name: Build scan
working-directory: scan
run: npm run build
- name: Package release
run: |
mkdir -p release/frontend/dist
mkdir -p release/admin/dist
mkdir -p release/scan/dist
cp backend/dist/mergeos release/mergeos
cp -R frontend/dist/. release/frontend/dist/
cp -R admin/dist/. release/admin/dist/
cp -R scan/dist/. release/scan/dist/
cp frontend/server.js frontend/package.json frontend/package-lock.json release/frontend/
cp admin/server.js admin/package.json admin/package-lock.json release/admin/
tar -czf mergeos-release.tar.gz -C release .
- name: Upload release artifact
uses: actions/upload-artifact@v7
with:
name: mergeos-release
path: mergeos-release.tar.gz
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
steps:
- name: Download release artifact
uses: actions/download-artifact@v8
with:
name: mergeos-release
- name: Validate deploy secrets
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
ADMIN_EMAIL: ${{ secrets.ADMIN_EMAIL }}
ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }}
DEV_PAYMENT_ENABLED: ${{ secrets.DEV_PAYMENT_ENABLED }}
MERGEOS_GOOGLE_CLIENT_ID: ${{ secrets.MERGEOS_GOOGLE_CLIENT_ID }}
MERGEOS_GOOGLE_CLIENT_SECRET: ${{ secrets.MERGEOS_GOOGLE_CLIENT_SECRET }}
run: |
test -n "$DEPLOY_HOST"
test -n "$DEPLOY_USER"
test -n "$DEPLOY_PASSWORD"
test -n "$DEPLOY_PORT"
test -n "$ADMIN_EMAIL"
test -n "$ADMIN_PASSWORD"
test -n "$MERGEOS_GOOGLE_CLIENT_ID"
test -n "$MERGEOS_GOOGLE_CLIENT_SECRET"
- name: Prepare deploy directory
uses: appleboy/ssh-action@v1.0.3
env:
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
password: ${{ secrets.DEPLOY_PASSWORD }}
port: ${{ secrets.DEPLOY_PORT }}
envs: DEPLOY_PATH
script: |
set -e
APP_DIR="${DEPLOY_PATH:-$HOME/mergeos}"
mkdir -p "$APP_DIR"
- name: Upload release to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
password: ${{ secrets.DEPLOY_PASSWORD }}
port: ${{ secrets.DEPLOY_PORT }}
source: mergeos-release.tar.gz
target: ${{ secrets.DEPLOY_PATH }}
- name: Activate release
uses: appleboy/ssh-action@v1.0.3
env:
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
ADMIN_EMAIL: ${{ secrets.ADMIN_EMAIL }}
ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }}
DEV_PAYMENT_ENABLED: ${{ secrets.DEV_PAYMENT_ENABLED }}
DEV_PAYMENT_CODE: ${{ secrets.DEV_PAYMENT_CODE }}
MERGEOS_GITHUB_TOKEN: ${{ secrets.MERGEOS_GITHUB_TOKEN }}
GITHUB_OWNER: ${{ secrets.GITHUB_OWNER }}
GITHUB_OWNER_TYPE: ${{ secrets.GITHUB_OWNER_TYPE }}
MERGEOS_GITHUB_APP_ID: ${{ secrets.MERGEOS_GITHUB_APP_ID }}
MERGEOS_GITHUB_APP_CLIENT_ID: ${{ secrets.MERGEOS_GITHUB_APP_CLIENT_ID }}
MERGEOS_GITHUB_APP_CLIENT_SECRET: ${{ secrets.MERGEOS_GITHUB_APP_CLIENT_SECRET }}
MERGEOS_GITHUB_OAUTH_CLIENT_ID: ${{ secrets.MERGEOS_GITHUB_OAUTH_CLIENT_ID }}
MERGEOS_GITHUB_OAUTH_CLIENT_SECRET: ${{ secrets.MERGEOS_GITHUB_OAUTH_CLIENT_SECRET }}
MERGEOS_GOOGLE_CLIENT_ID: ${{ secrets.MERGEOS_GOOGLE_CLIENT_ID }}
MERGEOS_GOOGLE_CLIENT_SECRET: ${{ secrets.MERGEOS_GOOGLE_CLIENT_SECRET }}
GEMINI_API_KEYS: ${{ secrets.GEMINI_API_KEYS }}
GEMINI_REVIEW_MODEL: ${{ secrets.GEMINI_REVIEW_MODEL }}
GEMINI_REVIEW_WEBHOOK_SECRET: ${{ secrets.GEMINI_REVIEW_WEBHOOK_SECRET }}
GEMINI_REVIEW_MAX_PATCH_BYTES: ${{ secrets.GEMINI_REVIEW_MAX_PATCH_BYTES }}
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
password: ${{ secrets.DEPLOY_PASSWORD }}
port: ${{ secrets.DEPLOY_PORT }}
envs: DEPLOY_PATH,ADMIN_EMAIL,ADMIN_PASSWORD,DEV_PAYMENT_ENABLED,DEV_PAYMENT_CODE,MERGEOS_GITHUB_TOKEN,GITHUB_OWNER,GITHUB_OWNER_TYPE,MERGEOS_GITHUB_APP_ID,MERGEOS_GITHUB_APP_CLIENT_ID,MERGEOS_GITHUB_APP_CLIENT_SECRET,MERGEOS_GITHUB_OAUTH_CLIENT_ID,MERGEOS_GITHUB_OAUTH_CLIENT_SECRET,MERGEOS_GOOGLE_CLIENT_ID,MERGEOS_GOOGLE_CLIENT_SECRET,GEMINI_API_KEYS,GEMINI_REVIEW_MODEL,GEMINI_REVIEW_WEBHOOK_SECRET,GEMINI_REVIEW_MAX_PATCH_BYTES
script: |
set -e
APP_DIR="${DEPLOY_PATH:-$HOME/mergeos}"
cd "$APP_DIR"
rm -rf frontend
rm -rf admin
rm -rf scan
tar -xzf mergeos-release.tar.gz
chmod +x mergeos
DATABASE_URL_VALUE=""
if command -v sudo >/dev/null 2>&1; then
wait_for_apt() {
if ! command -v fuser >/dev/null 2>&1; then
return 0
fi
for _ in $(seq 1 60); do
if ! sudo -n fuser /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; then
return 0
fi
sleep 2
done
sudo -n fuser /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock
return 1
}
apt_update() {
wait_for_apt
sudo -n apt-get update
}
apt_install() {
wait_for_apt
sudo -n env DEBIAN_FRONTEND=noninteractive apt-get install -y "$@"
}
DB_NAME="mergeos"
DB_USER="mergeos"
DB_PASSWORD_FILE="$APP_DIR/.postgres-password"
if ! command -v psql >/dev/null 2>&1 || ! command -v openssl >/dev/null 2>&1; then
apt_update
apt_install postgresql openssl
fi
if command -v systemctl >/dev/null 2>&1; then
sudo -n systemctl enable postgresql >/dev/null 2>&1 || true
sudo -n systemctl start postgresql
else
sudo -n service postgresql start
fi
if [ ! -s "$DB_PASSWORD_FILE" ]; then
umask 077
openssl rand -hex 32 > "$DB_PASSWORD_FILE"
fi
DB_PASSWORD="$(tr -d '\r\n' < "$DB_PASSWORD_FILE")"
sudo -n -u postgres psql -v ON_ERROR_STOP=1 <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${DB_USER}') THEN
CREATE ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}';
ELSE
ALTER ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
SQL
if ! sudo -n -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname = '${DB_NAME}'" | grep -q 1; then
sudo -n -u postgres createdb -O "$DB_USER" "$DB_NAME"
fi
sudo -n -u postgres psql -v ON_ERROR_STOP=1 -d "$DB_NAME" -c "ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};"
DATABASE_URL_VALUE="postgres://${DB_USER}:${DB_PASSWORD}@127.0.0.1:5432/${DB_NAME}?sslmode=disable"
fi
if command -v systemctl >/dev/null 2>&1; then
SERVICE_DIR="$HOME/.config/systemd/user"
SERVICE_FILE="$SERVICE_DIR/mergeos.service"
mkdir -p "$SERVICE_DIR"
loginctl enable-linger "$USER" 2>/dev/null || true
cat > "$SERVICE_FILE" <<SERVICE
[Unit]
Description=MergeOS production service
After=network.target
[Service]
Type=simple
WorkingDirectory=$APP_DIR
Environment=MERGEOS_ENV=production
Environment=PORT=8080
Environment=DATABASE_URL=$DATABASE_URL_VALUE
Environment=ADMIN_EMAIL=$ADMIN_EMAIL
Environment=ADMIN_PASSWORD=$ADMIN_PASSWORD
Environment=DEV_PAYMENT_ENABLED=$DEV_PAYMENT_ENABLED
Environment=DEV_PAYMENT_CODE=$DEV_PAYMENT_CODE
Environment=GITHUB_TOKEN=$MERGEOS_GITHUB_TOKEN
Environment=GITHUB_OWNER=$GITHUB_OWNER
Environment=GITHUB_OWNER_TYPE=$GITHUB_OWNER_TYPE
Environment=GITHUB_APP_ID=$MERGEOS_GITHUB_APP_ID
Environment=GITHUB_APP_CLIENT_ID=$MERGEOS_GITHUB_APP_CLIENT_ID
Environment=GITHUB_APP_CLIENT_SECRET=$MERGEOS_GITHUB_APP_CLIENT_SECRET
Environment=GITHUB_OAUTH_CLIENT_ID=$MERGEOS_GITHUB_OAUTH_CLIENT_ID
Environment=GITHUB_OAUTH_CLIENT_SECRET=$MERGEOS_GITHUB_OAUTH_CLIENT_SECRET
Environment=GOOGLE_CLIENT_ID=$MERGEOS_GOOGLE_CLIENT_ID
Environment=GOOGLE_CLIENT_SECRET=$MERGEOS_GOOGLE_CLIENT_SECRET
Environment=GEMINI_API_KEYS=$GEMINI_API_KEYS
Environment=GEMINI_REVIEW_MODEL=$GEMINI_REVIEW_MODEL
Environment=GEMINI_REVIEW_WEBHOOK_SECRET=$GEMINI_REVIEW_WEBHOOK_SECRET
Environment=GEMINI_REVIEW_MAX_PATCH_BYTES=$GEMINI_REVIEW_MAX_PATCH_BYTES
ExecStart=$APP_DIR/mergeos
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
SERVICE
systemctl --user daemon-reload
systemctl --user enable mergeos.service
systemctl --user restart mergeos.service
systemctl --user is-active --quiet mergeos.service
else
pkill -f "$APP_DIR/mergeos" || true
MERGEOS_ENV=production PORT=8080 DATABASE_URL="$DATABASE_URL_VALUE" ADMIN_EMAIL="$ADMIN_EMAIL" ADMIN_PASSWORD="$ADMIN_PASSWORD" DEV_PAYMENT_ENABLED="$DEV_PAYMENT_ENABLED" DEV_PAYMENT_CODE="$DEV_PAYMENT_CODE" GITHUB_TOKEN="$MERGEOS_GITHUB_TOKEN" GITHUB_OWNER="$GITHUB_OWNER" GITHUB_OWNER_TYPE="$GITHUB_OWNER_TYPE" GITHUB_APP_ID="$MERGEOS_GITHUB_APP_ID" GITHUB_APP_CLIENT_ID="$MERGEOS_GITHUB_APP_CLIENT_ID" GITHUB_APP_CLIENT_SECRET="$MERGEOS_GITHUB_APP_CLIENT_SECRET" GITHUB_OAUTH_CLIENT_ID="$MERGEOS_GITHUB_OAUTH_CLIENT_ID" GITHUB_OAUTH_CLIENT_SECRET="$MERGEOS_GITHUB_OAUTH_CLIENT_SECRET" GOOGLE_CLIENT_ID="$MERGEOS_GOOGLE_CLIENT_ID" GOOGLE_CLIENT_SECRET="$MERGEOS_GOOGLE_CLIENT_SECRET" GEMINI_API_KEYS="$GEMINI_API_KEYS" GEMINI_REVIEW_MODEL="$GEMINI_REVIEW_MODEL" GEMINI_REVIEW_WEBHOOK_SECRET="$GEMINI_REVIEW_WEBHOOK_SECRET" GEMINI_REVIEW_MAX_PATCH_BYTES="$GEMINI_REVIEW_MAX_PATCH_BYTES" nohup "$APP_DIR/mergeos" > "$APP_DIR/mergeos.log" 2> "$APP_DIR/mergeos.err.log" &
fi
if command -v sudo >/dev/null 2>&1; then
NODE_MAJOR=22
if ! command -v node >/dev/null 2>&1 || ! node -e "process.exit(Number(process.versions.node.split('.')[0]) >= 22 ? 0 : 1)"; then
apt_update
apt_install ca-certificates curl gnupg
sudo -n install -d -m 0755 /etc/apt/keyrings
curl -fsSL "https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key" | gpg --dearmor | sudo -n tee /etc/apt/keyrings/nodesource.gpg >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | sudo -n tee /etc/apt/sources.list.d/nodesource.list >/dev/null
apt_update
apt_install nodejs
fi
cd "$APP_DIR/frontend"
node -v
npm -v
npm ci --omit=dev
cd "$APP_DIR"
if command -v systemctl >/dev/null 2>&1; then
SERVICE_DIR="$HOME/.config/systemd/user"
FRONTEND_SERVICE_FILE="$SERVICE_DIR/mergeos-frontend.service"
NPM_BIN="$(command -v npm)"
mkdir -p "$SERVICE_DIR"
cat > "$FRONTEND_SERVICE_FILE" <<SERVICE
[Unit]
Description=MergeOS SSR frontend
After=network.target mergeos.service
[Service]
Type=simple
WorkingDirectory=$APP_DIR/frontend
Environment=MERGEOS_ENV=production
Environment=NODE_ENV=production
Environment=FRONTEND_HOST=127.0.0.1
Environment=FRONTEND_PORT=8081
Environment=API_TARGET=http://127.0.0.1:8080
ExecStart=$NPM_BIN run production
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
SERVICE
systemctl --user daemon-reload
systemctl --user enable mergeos-frontend.service
systemctl --user restart mergeos-frontend.service
systemctl --user is-active --quiet mergeos-frontend.service
else
pkill -f "$APP_DIR/frontend/server.js" || true
cd "$APP_DIR/frontend"
MERGEOS_ENV=production NODE_ENV=production FRONTEND_HOST=127.0.0.1 FRONTEND_PORT=8081 API_TARGET=http://127.0.0.1:8080 nohup node server.js --mode production --prod > "$APP_DIR/frontend.log" 2> "$APP_DIR/frontend.err.log" &
cd "$APP_DIR"
fi
cd "$APP_DIR/admin"
npm ci --omit=dev
cd "$APP_DIR"
if command -v systemctl >/dev/null 2>&1; then
SERVICE_DIR="$HOME/.config/systemd/user"
ADMIN_SERVICE_FILE="$SERVICE_DIR/mergeos-admin.service"
mkdir -p "$SERVICE_DIR"
cat > "$ADMIN_SERVICE_FILE" <<SERVICE
[Unit]
Description=MergeOS SSR admin
After=network.target mergeos.service
[Service]
Type=simple
WorkingDirectory=$APP_DIR/admin
Environment=MERGEOS_ENV=production
Environment=NODE_ENV=production
Environment=ADMIN_FRONTEND_HOST=127.0.0.1
Environment=ADMIN_FRONTEND_PORT=8082
Environment=API_TARGET=http://127.0.0.1:8080
ExecStart=$NPM_BIN run production
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
SERVICE
systemctl --user daemon-reload
systemctl --user enable mergeos-admin.service
systemctl --user restart mergeos-admin.service
systemctl --user is-active --quiet mergeos-admin.service
else
pkill -f "$APP_DIR/admin/server.js" || true
cd "$APP_DIR/admin"
MERGEOS_ENV=production NODE_ENV=production ADMIN_FRONTEND_HOST=127.0.0.1 ADMIN_FRONTEND_PORT=8082 API_TARGET=http://127.0.0.1:8080 nohup node server.js --mode production --prod > "$APP_DIR/admin.log" 2> "$APP_DIR/admin.err.log" &
cd "$APP_DIR"
fi
if ! command -v nginx >/dev/null 2>&1; then
apt_update
apt_install nginx
fi
if ! command -v certbot >/dev/null 2>&1; then
apt_update
apt_install certbot python3-certbot-nginx
fi
sudo -n tee /etc/nginx/sites-available/mergeos >/dev/null <<NGINX
server {
listen 80;
listen [::]:80;
server_name scan.mergeos.shop;
root $APP_DIR/scan/dist;
index index.html;
client_max_body_size 30m;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
location /assets/ {
try_files \$uri =404;
access_log off;
expires 1h;
}
location / {
try_files \$uri \$uri/ /index.html;
}
}
server {
listen 80;
listen [::]:80;
server_name mergeos.shop _;
root $APP_DIR/frontend/dist/client;
index index.html;
client_max_body_size 30m;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
location /assets/ {
try_files \$uri =404;
access_log off;
expires 1h;
}
location / {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
server {
listen 80;
listen [::]:80;
server_name uta.mergeos.shop;
root $APP_DIR/admin/dist/client;
index index.html;
client_max_body_size 30m;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
location /assets/ {
try_files \$uri =404;
access_log off;
expires 1h;
}
location / {
proxy_pass http://127.0.0.1:8082;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
NGINX
sudo -n ln -sf /etc/nginx/sites-available/mergeos /etc/nginx/sites-enabled/mergeos
sudo -n rm -f /etc/nginx/sites-enabled/default
sudo -n nginx -t
sudo -n systemctl enable nginx
sudo -n systemctl restart nginx
CERTBOT_CONTACT_ARGS="--register-unsafely-without-email"
sudo -n certbot --nginx --non-interactive --agree-tos $CERTBOT_CONTACT_ARGS --redirect --keep-until-expiring --expand -d mergeos.shop -d uta.mergeos.shop -d scan.mergeos.shop
sudo -n nginx -t
sudo -n systemctl restart nginx
sudo -n systemctl is-active --quiet nginx
fi