diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6afd19d..92590d7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,113 +1,65 @@ -name: Deploy +name: Build & Deploy (Nix) on: push: - branches: - - main + branches: [main] workflow_dispatch: -permissions: - contents: read - packages: write - -concurrency: - group: deploy-production - cancel-in-progress: true - -env: - IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/booster-role - jobs: - deploy: + build-and-deploy: runs-on: ubuntu-latest environment: orangevps steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Set up Bun - uses: oven-sh/setup-bun@v2 + - uses: DeterminateSystems/nix-installer-action@v16 with: - bun-version: latest + determinate: false + extra-conf: | + sandbox = false + accept-flake-config = true - - name: Install dependencies - run: bun install - - - name: Type check - run: bun run typecheck - - - name: Run tests - run: bun test - - - name: Lint - run: bun run lint - - - name: Set lowercase image name - run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> "$GITHUB_ENV" - - - name: Log in to GHCR - uses: docker/login-action@v3 + - uses: DeterminateSystems/magic-nix-cache-action@v8 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + use-flakehub: false - - name: Build and push image - uses: docker/build-push-action@v6 - with: - context: . - push: true - tags: | - ${{ env.IMAGE_NAME }}:latest - ${{ env.IMAGE_NAME }}:${{ github.sha }} - - - name: Deploy on VPS - uses: appleboy/ssh-action@v1.2.0 + - name: Build & Deploy env: - IMAGE_NAME: ${{ env.IMAGE_NAME }} - GHCR_USERNAME: ${{ github.actor }} - GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} - DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }} - DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - BOOSTER_ROLE_ANCHOR_ROLE_ID: ${{ secrets.BOOSTER_ROLE_ANCHOR_ROLE_ID }} - BOOSTER_ELIGIBILITY_ROLE_ID: ${{ secrets.BOOSTER_ELIGIBILITY_ROLE_ID }} - BOOSTER_GREETING_CHANNEL_ID: ${{ secrets.BOOSTER_GREETING_CHANNEL_ID }} - LOG_LEVEL: ${{ secrets.LOG_LEVEL }} - with: - host: ${{ secrets.VPS_HOST }} - username: ${{ secrets.VPS_USER }} - key: ${{ secrets.VPS_SSH_KEY }} - envs: IMAGE_NAME,GHCR_USERNAME,GHCR_TOKEN,DISCORD_TOKEN,DISCORD_CLIENT_ID,DISCORD_GUILD_ID,DATABASE_URL,BOOSTER_ROLE_ANCHOR_ROLE_ID,BOOSTER_ELIGIBILITY_ROLE_ID,BOOSTER_GREETING_CHANNEL_ID,LOG_LEVEL - script: | + VPS_HOST: ${{ secrets.VPS_HOST }} + VPS_USER: ${{ secrets.VPS_USER }} + VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }} + run: | + set -eu + STORE_PATH=$(nix build . --impure --option sandbox false --no-link --print-out-paths | tail -1) + echo "Store path: $STORE_PATH" + + key_file=$(mktemp /tmp/deploy-key.XXXXXX) + printf '%s\n' "$VPS_SSH_KEY" > "$key_file" + chmod 600 "$key_file" + sed -i 's/\r$//' "$key_file" + ssh-keygen -y -f "$key_file" >/dev/null 2>&1 || { echo "SSH key invalid"; exit 1; } + + export NIX_SSHOPTS="-i $key_file -o StrictHostKeyChecking=no" + nix copy --to "ssh://${VPS_USER}@${VPS_HOST}" "$STORE_PATH" 2>&1 + + ssh -i "$key_file" -o StrictHostKeyChecking=no "${VPS_USER}@${VPS_HOST}" " set -eu - mkdir -p /opt/booster-role - cd /opt/booster-role - - cat > .env < docker-compose.yml </dev/null || true + systemctl restart booster-role + for i in \$(seq 1 30); do + systemctl is-active --quiet booster-role && break + sleep 1 + done + systemctl is-active booster-role || { + echo \"=== SERVICE FAILED — journal ===\" + journalctl -u booster-role -n 40 --no-pager + exit 1 + } + systemctl status booster-role --no-pager 2>&1 | head -8 + " 2>&1 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..617b2f3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1785301185, + "narHash": "sha256-eoS3KQTO0aPWXZvIaRbRAzSSHW3l5wdMFXtT1ISfoKA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9bc02893134c733dd85de46ee4fb2fac696b5529", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8b3c8f8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,49 @@ +{ + description = "Booster Role Discord Bot (Nix build)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + packages.default = pkgs.stdenvNoCC.mkDerivation { + pname = "booster-role"; + version = "0.1.0"; + src = ./.; + + nativeBuildInputs = [ pkgs.bun ]; + + buildPhase = '' + export HOME="$TMPDIR" + bun install --frozen-lockfile + bun run typecheck + bun test + ''; + + installPhase = '' + mkdir -p $out/bin $out/lib/booster-role + cp -r package.json bun.lock tsconfig.json drizzle.config.ts $out/lib/booster-role/ + cp -r src $out/lib/booster-role/src + cp -r node_modules $out/lib/booster-role/node_modules + cat > $out/bin/booster-role << WRAPPER +#!${pkgs.runtimeShell} +cd $out/lib/booster-role +export NODE_ENV=production +${pkgs.bun}/bin/bun run db:migrate +exec ${pkgs.bun}/bin/bun run src/index.ts +WRAPPER + chmod +x $out/bin/booster-role + ''; + }; + + devShells.default = pkgs.mkShell { + buildInputs = [ pkgs.bun ]; + }; + }); +}