From dd70b4299aad39ad2641dfc9414848d478a6be73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=A0=E7=9A=84=E7=94=A8=E6=88=B7=E5=90=8D?= <你的邮箱> Date: Tue, 4 Nov 2025 15:24:38 +0800 Subject: [PATCH] Add GitHub Actions deployment workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add automated deployment workflow configuration - Configure CI/CD pipeline for bc-netts-energy 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy.yaml | 169 ++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..71bdb2a --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,169 @@ +name: Deploy + +on: + push: + branches: ["main"] + workflow_dispatch: + +env: + APP_NAME: bc-netts-energy + BINARY_NAME: netts-energy + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + env: + SERVICE_PORT: ${{ secrets.SERVICE_PORT }} + NETTS_API_KEY: ${{ secrets.NETTS_API_KEY }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: Go module cache + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Run unit tests + run: go test ./... + + - name: Build binary + run: | + mkdir -p build + go build -o build/${{ env.BINARY_NAME }} ./cmd/server + + - name: Upload binary to server + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.SSH_HOST }} + port: ${{ secrets.SSH_PORT }} + username: ${{ secrets.SSH_USER }} + password: ${{ secrets.SSH_PASSWORD }} + source: build/${{ env.BINARY_NAME }} + target: /tmp + + - name: Deploy on remote host + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + port: ${{ secrets.SSH_PORT }} + username: ${{ secrets.SSH_USER }} + password: ${{ secrets.SSH_PASSWORD }} + script_stop: true + command_timeout: 30m + envs: APP_NAME,BINARY_NAME,SERVICE_PORT,GITHUB_SHA,NETTS_API_KEY + script: | + set -euo pipefail + + APP_DIR="/opt/${APP_NAME}" + RELEASE_ROOT="${APP_DIR}/releases" + RELEASE_PATH="${RELEASE_ROOT}/${GITHUB_SHA}" + CURRENT_LINK="${APP_DIR}/current" + SHARED_DIR="${APP_DIR}/shared" + BINARY_PATH="${RELEASE_PATH}/${BINARY_NAME}" + REMOTE_TMP="/tmp/${BINARY_NAME}" + + mkdir -p "${RELEASE_ROOT}" "${SHARED_DIR}" + + if [ ! -f "${REMOTE_TMP}" ]; then + echo "Binary ${REMOTE_TMP} not found on remote host." >&2 + exit 1 + fi + + mkdir -p "${RELEASE_PATH}" + mv "${REMOTE_TMP}" "${BINARY_PATH}" + chmod +x "${BINARY_PATH}" + + SERVICE_NAME="${APP_NAME}.service" + SERVICE_EXISTS=0 + if systemctl list-unit-files "${SERVICE_NAME}" >/dev/null 2>&1; then + SERVICE_EXISTS=1 + if systemctl is-active --quiet "${SERVICE_NAME}"; then + systemctl stop "${SERVICE_NAME}" + fi + fi + + TARGET_PORT="${SERVICE_PORT:-8080}" + if command -v ss >/dev/null 2>&1; then + LISTEN_CMD="ss -ltn" + elif command -v netstat >/dev/null 2>&1; then + LISTEN_CMD="netstat -ltn" + else + echo "Neither ss nor netstat is available to check port usage." >&2 + rm -rf "${RELEASE_PATH}" + if [ "${SERVICE_EXISTS}" = "1" ]; then + systemctl start "${SERVICE_NAME}" + fi + exit 1 + fi + + if ${LISTEN_CMD} | awk '{print $4}' | grep -q ":${TARGET_PORT}$"; then + echo "Port ${TARGET_PORT} is already in use. Deployment aborted." >&2 + rm -rf "${RELEASE_PATH}" + if [ "${SERVICE_EXISTS}" = "1" ]; then + systemctl start "${SERVICE_NAME}" + fi + exit 1 + fi + + if [ ! -f "${SHARED_DIR}/config.yaml" ]; then + echo "Missing config at ${SHARED_DIR}/config.yaml. Deployment aborted." >&2 + rm -rf "${RELEASE_PATH}" + if [ "${SERVICE_EXISTS}" = "1" ]; then + systemctl start "${SERVICE_NAME}" + fi + exit 1 + fi + + if [ -z "${NETTS_API_KEY:-}" ]; then + echo "NETTS_API_KEY is not provided. Deployment aborted." >&2 + rm -rf "${RELEASE_PATH}" + if [ "${SERVICE_EXISTS}" = "1" ]; then + systemctl start "${SERVICE_NAME}" + fi + exit 1 + fi + + if [ ! -f "/etc/systemd/system/${SERVICE_NAME}" ]; then + cat < "/etc/systemd/system/${SERVICE_NAME}" +[Unit] +Description=BC Netts Energy Service +After=network.target + +[Service] +Type=simple +WorkingDirectory=${APP_DIR}/current +ExecStart=${APP_DIR}/current/${BINARY_NAME} -config ${SHARED_DIR}/config.yaml +Restart=on-failure +RestartSec=5 +EnvironmentFile=-/opt/${APP_NAME}/shared/.env + +[Install] +WantedBy=multi-user.target +EOF + systemctl daemon-reload + systemctl enable "${SERVICE_NAME}" + else + systemctl daemon-reload + fi + + ln -sfn "${RELEASE_PATH}" "${CURRENT_LINK}" + + ENV_FILE="${SHARED_DIR}/.env" + cat < "${ENV_FILE}" +NETTS_API_KEY=${NETTS_API_KEY} +EOF + chmod 600 "${ENV_FILE}" + + systemctl restart "${SERVICE_NAME}" + systemctl --no-pager status "${SERVICE_NAME}"