name: Update Demo Server on: release: types: [published] workflow_dispatch: inputs: tag: description: 'Release tag to deploy (e.g., v5.1.28)' required: true type: string permissions: contents: read jobs: update-demo: # Only run for stable releases (not pre-releases) or manual dispatch if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false runs-on: ubuntu-latest steps: - name: Resolve target tag id: target run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then TAG="${{ inputs.tag }}" else TAG="${{ github.event.release.tag_name }}" fi echo "tag=$TAG" >> "$GITHUB_OUTPUT" - name: Skip if not latest published release id: gate if: github.event_name == 'release' env: GH_TOKEN: ${{ github.token }} run: | TARGET="${{ steps.target.outputs.tag }}" LATEST=$(gh api "repos/${{ github.repository }}/releases?per_page=100" --jq 'map(select(.draft == false and .prerelease == false and (.tag_name | test("^v5\\.1\\.[0-9]+$"))))[0].tag_name') echo "Target tag: $TARGET" echo "Latest published v5.1 tag: $LATEST" if [ "$TARGET" != "$LATEST" ]; then echo "skip=true" >> "$GITHUB_OUTPUT" echo "Release is not the latest v5.1 stable release; skipping demo update." else echo "skip=false" >> "$GITHUB_OUTPUT" fi - name: Check release type if: steps.gate.outputs.skip != 'true' run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "Manual deployment triggered for: ${{ inputs.tag }}" else echo "Release: ${{ github.event.release.tag_name }}" echo "Prerelease: ${{ github.event.release.prerelease }}" echo "Updating demo server to latest stable v5.1 release..." fi - name: Wait for release assets if: steps.gate.outputs.skip != 'true' run: | TAG="${{ steps.target.outputs.tag }}" echo "Waiting for release assets to be available..." MAX_ATTEMPTS=30 ATTEMPT=0 while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do echo "Checking for assets (attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS)..." # Check if checksums.txt and linux-amd64 tarball exist CHECKSUMS_STATUS=$(curl -sL -o /dev/null -w "%{http_code}" \ "https://github.com/rcourtman/Pulse/releases/download/${TAG}/checksums.txt") TARBALL_STATUS=$(curl -sL -o /dev/null -w "%{http_code}" \ "https://github.com/rcourtman/Pulse/releases/download/${TAG}/pulse-${TAG}-linux-amd64.tar.gz") echo "checksums.txt: $CHECKSUMS_STATUS, tarball: $TARBALL_STATUS" if [ "$CHECKSUMS_STATUS" = "200" ] && [ "$TARBALL_STATUS" = "200" ]; then echo "✅ Release assets are available!" exit 0 fi ATTEMPT=$((ATTEMPT + 1)) if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then echo "Assets not ready yet, waiting 10 seconds..." sleep 10 fi done echo "❌ Timeout waiting for release assets" exit 1 - name: Setup SSH if: steps.gate.outputs.skip != 'true' run: | mkdir -p ~/.ssh echo "${{ secrets.DEMO_SERVER_SSH_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H ${{ secrets.DEMO_SERVER_HOST }} >> ~/.ssh/known_hosts - name: Check current demo version if: steps.gate.outputs.skip != 'true' id: current run: | TARGET="${{ steps.target.outputs.tag }}" TARGET_STRIPPED="${TARGET#v}" CURRENT=$(ssh -i ~/.ssh/id_ed25519 ${{ secrets.DEMO_SERVER_USER }}@${{ secrets.DEMO_SERVER_HOST }} \ "curl -s http://localhost:7655/api/version | jq -r .version") echo "Current demo version: ${CURRENT}" if [ "$CURRENT" = "$TARGET" ] || [ "$CURRENT" = "$TARGET_STRIPPED" ] || [ "v${CURRENT}" = "$TARGET" ]; then echo "skip_current=true" >> "$GITHUB_OUTPUT" echo "Demo server already on target version; skipping update." else echo "skip_current=false" >> "$GITHUB_OUTPUT" fi - name: Update demo server if: steps.gate.outputs.skip != 'true' && steps.current.outputs.skip_current != 'true' run: | TAG="${{ steps.target.outputs.tag }}" # Use set -o pipefail to ensure curl errors aren't masked by bash ssh -i ~/.ssh/id_ed25519 ${{ secrets.DEMO_SERVER_USER }}@${{ secrets.DEMO_SERVER_HOST }} \ "set -o pipefail && curl -fsSL https://raw.githubusercontent.com/rcourtman/Pulse/release/5.1/install.sh | sudo bash -s -- --version $TAG" - name: Verify update if: steps.gate.outputs.skip != 'true' && steps.current.outputs.skip_current != 'true' run: | # Wait a moment for service to restart sleep 5 # Check version endpoint VERSION=$(ssh -i ~/.ssh/id_ed25519 ${{ secrets.DEMO_SERVER_USER }}@${{ secrets.DEMO_SERVER_HOST }} \ "curl -s http://localhost:7655/api/version | jq -r .version") echo "Demo server is now running version: $VERSION" # Verify version matches target TAG="${{ steps.target.outputs.tag }}" TAG_STRIPPED="${TAG#v}" if [ "$VERSION" != "$TAG" ] && [ "$VERSION" != "$TAG_STRIPPED" ]; then echo "::error::Version mismatch! Expected $TAG but got $VERSION" exit 1 fi # Verify mock mode is active by authenticating and checking node count # Uses $HOME for cookie file since /tmp may not be writable by all users NODES=$(ssh -i ~/.ssh/id_ed25519 ${{ secrets.DEMO_SERVER_USER }}@${{ secrets.DEMO_SERVER_HOST }} \ 'curl -s -c $HOME/.demo-cookies.txt http://localhost:7655/api/login -X POST -H "Content-Type: application/json" -d "{\"username\":\"demo\",\"password\":\"demo\"}" > /dev/null && curl -s -b $HOME/.demo-cookies.txt http://localhost:7655/api/state | jq -r ".nodes | length" && rm -f $HOME/.demo-cookies.txt') echo "Mock nodes detected: $NODES" if [ "$NODES" -ge 1 ]; then echo "✅ Demo server successfully updated and verified!" else echo "⚠️ Demo server updated but mock mode may not be active" exit 1 fi - name: Cleanup SSH key if: always() && steps.gate.outputs.skip != 'true' run: rm -f ~/.ssh/id_ed25519