-
Notifications
You must be signed in to change notification settings - Fork 0
Add CI auto-deploy with selective app restarts #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" | ||
| cd "$REPO_ROOT" | ||
|
|
||
| export PATH="/usr/local/go/bin:${PATH:-}" | ||
|
|
||
| BRANCH="${1:-main}" | ||
| FORCE_APPS="${DEPLOY_FORCE_APPS:-0}" | ||
|
|
||
| OLD_SHA="$(git rev-parse HEAD)" | ||
| log() { | ||
| echo "$*" >&2 | ||
| } | ||
|
|
||
| log "Fetching and updating ${BRANCH} (was ${OLD_SHA:0:7})..." | ||
| git fetch origin "$BRANCH" | ||
| git checkout "$BRANCH" | ||
| git pull origin "$BRANCH" | ||
| NEW_SHA="$(git rev-parse HEAD)" | ||
| log "Now at ${NEW_SHA:0:7}" | ||
|
|
||
| DETERMINE_ARGS=("$OLD_SHA" "$NEW_SHA") | ||
| if [[ "$FORCE_APPS" == "1" ]]; then | ||
| DETERMINE_ARGS=(--force-apps "${DETERMINE_ARGS[@]}") | ||
| fi | ||
|
|
||
| eval "$(bash ./.github/scripts/determine-deploy-targets.sh "${DETERMINE_ARGS[@]}")" | ||
|
|
||
| log "Building tools..." | ||
| make tools | ||
|
|
||
| for app in $DEPLOY_APPS; do | ||
| log "Building ${app}..." | ||
| make "$app" | ||
| done | ||
|
|
||
| for app in $DEPLOY_APPS; do | ||
| if systemctl list-unit-files "${app}.service" --no-legend 2>/dev/null | grep -q "${app}.service"; then | ||
| log "Restarting ${app}..." | ||
| sudo systemctl restart "${app}" | ||
| else | ||
| log "Skipping ${app} restart (no systemd unit)" | ||
| fi | ||
| done | ||
|
|
||
| log "Deploy complete (${NEW_SHA:0:7})" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| #!/usr/bin/env bash | ||
| # Decide which Services binaries need rebuilding between two git refs. | ||
| # | ||
| # Tools are always rebuilt. Apps (hermes, atlas, zeus) are rebuilt only when | ||
| # changed files touch their source tree or Go dependency closure. | ||
| # | ||
| # Usage: | ||
| # determine-deploy-targets.sh [--force-apps] [BASE_REF] [HEAD_REF] | ||
| # | ||
| # Prints shell assignments to stdout: | ||
| # DEPLOY_TOOLS=1 | ||
| # DEPLOY_APPS="hermes atlas" | ||
| # | ||
| # Logs analysis details to stderr. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" | ||
| cd "$REPO_ROOT" | ||
|
|
||
| FORCE_APPS=0 | ||
| POSITIONAL=() | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --force-apps) | ||
| FORCE_APPS=1 | ||
| shift | ||
| ;; | ||
| -*) | ||
| echo "Unknown option: $1" >&2 | ||
| exit 1 | ||
| ;; | ||
| *) | ||
| POSITIONAL+=("$1") | ||
| shift | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| BASE_REF="${POSITIONAL[0]:-HEAD~1}" | ||
| HEAD_REF="${POSITIONAL[1]:-HEAD}" | ||
|
|
||
| ALL_APPS=(hermes atlas zeus) | ||
| DEPLOY_APPS=() | ||
|
|
||
| log() { | ||
| echo "$*" >&2 | ||
| } | ||
|
|
||
| app_dep_dirs() { | ||
| local app="$1" | ||
| go list -deps -f '{{.Dir}}' "./apps/${app}/" | grep "^${REPO_ROOT}/" || true | ||
| } | ||
|
|
||
| file_affects_app() { | ||
| local file="$1" | ||
| local app="$2" | ||
| local abs="${REPO_ROOT}/${file}" | ||
|
|
||
| [[ "$file" == apps/${app}/* ]] && return 0 | ||
|
|
||
| local dir | ||
| while IFS= read -r dir; do | ||
| [[ -z "$dir" ]] && continue | ||
| if [[ "$abs" == "${dir}/"* ]] || [[ "$abs" == "$dir" ]]; then | ||
| return 0 | ||
| fi | ||
| done < <(app_dep_dirs "$app") | ||
|
|
||
| return 1 | ||
| } | ||
|
|
||
| rebuild_all_apps() { | ||
| local reason="$1" | ||
| log "Rebuilding all apps: ${reason}" | ||
| DEPLOY_APPS=("${ALL_APPS[@]}") | ||
| } | ||
|
|
||
| if [[ "$FORCE_APPS" -eq 1 ]]; then | ||
| rebuild_all_apps "forced" | ||
| elif [[ "$BASE_REF" == "0000000000000000000000000000000000000000" ]]; then | ||
| rebuild_all_apps "no previous commit (force-push or first deploy)" | ||
| else | ||
| CHANGED_FILES=() | ||
| while IFS= read -r file; do | ||
| [[ -n "$file" ]] && CHANGED_FILES+=("$file") | ||
| done < <(git diff --name-only "$BASE_REF" "$HEAD_REF") | ||
|
|
||
| if [[ ${#CHANGED_FILES[@]} -eq 0 ]]; then | ||
| log "No changed files between ${BASE_REF} and ${HEAD_REF}" | ||
| else | ||
| log "Changed files (${#CHANGED_FILES[@]}):" | ||
| for file in "${CHANGED_FILES[@]}"; do | ||
| log " - ${file}" | ||
| done | ||
|
|
||
| for file in "${CHANGED_FILES[@]}"; do | ||
| case "$file" in | ||
| go.mod | go.sum | Makefile) | ||
| rebuild_all_apps "${file} changed" | ||
| break | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| if [[ ${#DEPLOY_APPS[@]} -eq 0 ]]; then | ||
| for app in "${ALL_APPS[@]}"; do | ||
| for file in "${CHANGED_FILES[@]}"; do | ||
| if file_affects_app "$file" "$app"; then | ||
| log "${app}: affected by ${file}" | ||
| DEPLOY_APPS+=("$app") | ||
| break | ||
| fi | ||
| done | ||
| done | ||
| fi | ||
| fi | ||
| fi | ||
|
|
||
| log "Deploy plan: tools=always apps=${DEPLOY_APPS[*]:-(none)}" | ||
|
|
||
| echo "DEPLOY_TOOLS=1" | ||
| if [[ ${#DEPLOY_APPS[@]} -eq 0 ]]; then | ||
| echo 'DEPLOY_APPS=""' | ||
| else | ||
| echo "DEPLOY_APPS=\"${DEPLOY_APPS[*]}\"" | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| curl -L https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg >/dev/null | ||
| echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list | ||
| sudo apt update | ||
| sudo apt-get install cloudflared | ||
| cloudflared --version |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| name: Manually Deploy Services to Prod | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Check if user is a repo admin | ||
| run: | | ||
| if [[ $(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission" | jq -r '.permission') != "admin" ]]; then | ||
| echo "Only repo admins can run this action." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.25" | ||
|
|
||
| - name: Download dependencies | ||
| run: go mod download | ||
|
|
||
| - name: Verify build | ||
| run: | | ||
| go build -o /dev/null ./apps/atlas/ | ||
| go build -o /dev/null ./apps/hermes/ | ||
| go build -o /dev/null ./apps/zeus/ | ||
|
|
||
| - name: Determine deploy targets | ||
| run: bash ./.github/scripts/determine-deploy-targets.sh --force-apps | ||
|
|
||
| - name: Install cloudflared | ||
| run: ./.github/scripts/install-cloudflared.sh | ||
|
|
||
| - name: Load SSH Key | ||
| uses: shimataro/ssh-key-action@v2 | ||
| with: | ||
| key: ${{ secrets.VPS_SSH_PRIVATE_KEY }} | ||
| known_hosts: ${{ secrets.VPS_SSH_KNOWN_HOSTS }} | ||
|
|
||
| - name: Deploy Services | ||
| env: | ||
| TUNNEL_SERVICE_TOKEN_ID: ${{ secrets.CF_GHA_CLIENT_ID }} | ||
| TUNNEL_SERVICE_TOKEN_SECRET: ${{ secrets.CF_GHA_CLIENT_SECRET }} | ||
| run: | | ||
| branch_ref="${{ github.ref }}" | ||
| branch_name="${branch_ref#refs/heads/}" | ||
| ssh -o ProxyCommand="cloudflared access ssh --hostname %h" root@ssh.raidhub.io \ | ||
| "cd /RaidHub/Services && DEPLOY_FORCE_APPS=1 bash ./.github/scripts/deploy-services.sh \"$branch_name\"" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| name: Deploy to Prod | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| deploy: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: "1.25" | ||
|
|
||
| - name: Download dependencies | ||
| run: go mod download | ||
|
|
||
| - name: Verify build | ||
| run: | | ||
| go build -o /dev/null ./apps/atlas/ | ||
| go build -o /dev/null ./apps/hermes/ | ||
| go build -o /dev/null ./apps/zeus/ | ||
|
|
||
|
Comment on lines
+25
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The deploy workflow will fail because Suggested FixIn the Prompt for AI Agent |
||
| - name: Determine deploy targets | ||
| run: | | ||
| bash ./.github/scripts/determine-deploy-targets.sh \ | ||
| "${{ github.event.before }}" "${{ github.sha }}" | ||
|
|
||
| - name: Install cloudflared | ||
| run: ./.github/scripts/install-cloudflared.sh | ||
|
|
||
| - name: Load SSH Key | ||
| uses: shimataro/ssh-key-action@v2 | ||
| with: | ||
| key: ${{ secrets.VPS_SSH_PRIVATE_KEY }} | ||
| known_hosts: ${{ secrets.VPS_SSH_KNOWN_HOSTS }} | ||
|
|
||
| - name: Deploy Services | ||
| env: | ||
| TUNNEL_SERVICE_TOKEN_ID: ${{ secrets.CF_GHA_CLIENT_ID }} | ||
| TUNNEL_SERVICE_TOKEN_SECRET: ${{ secrets.CF_GHA_CLIENT_SECRET }} | ||
| run: | | ||
| ssh -o ProxyCommand="cloudflared access ssh --hostname %h" root@ssh.raidhub.io \ | ||
| "cd /RaidHub/Services && bash ./.github/scripts/deploy-services.sh main" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The
branch_namevariable, derived fromgithub.ref, is used unsafely in arunblock, creating a command injection vulnerability if the branch name contains shell metacharacters.Severity: HIGH
Suggested Fix
Pass the branch name to the script via an environment variable instead of direct string interpolation. Set
branch_namein theenvcontext of the step, and then reference it as$BRANCH_NAMEwithin the script. This prevents the shell from interpreting metacharacters in the branch name during expansion. For example:env: { BRANCH_NAME: ${{ github.ref_name }} }and then use$BRANCH_NAMEin therunscript. Usinggithub.ref_nameis also safer as it only contains the branch/tag name.Prompt for AI Agent
Did we get this right? 👍 / 👎 to inform future reviews.