diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..e73b206 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,39 @@ +name: CI/CD - Linter, formatter and tests + +on: + push: + branches: + - main + pull_request: + types: + - opened + - synchronize + +jobs: + lint-format-and-test: + runs-on: [self-hosted] + timeout-minutes: 10 + + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Write env file + run: > + printf 'DATABASE_URL="postgresql://ci:ci@localhost:5432/ci"\n' > + packages/db/.env + + - name: Generate Prisma client + run: pnpm --filter @t09-project-2/db run db:generate + + - name: Check formatting + run: pnpm check + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck diff --git a/.github/workflows/deploy-to-vm.yaml b/.github/workflows/deploy-to-vm.yaml new file mode 100644 index 0000000..91ef565 --- /dev/null +++ b/.github/workflows/deploy-to-vm.yaml @@ -0,0 +1,180 @@ +# Generated by GLM 5.3 NVFP4 + +# Deploys the app to the group VM via its self-hosted runner. +# +# One-time VM setup (run once as an admin): +# 1. Node.js >= 22 and pnpm installed system-wide (so systemd can find them): +# sudo npm install -g pnpm@10 +# 2. Sudoers entry so the runner can manage the services (sudo visudo): +# ALL=(root) NOPASSWD: /usr/bin/install, /usr/bin/tee, /usr/bin/systemctl +# 3. PostgreSQL on localhost (pnpm db:setup, see README). +# 4. Apache reverse proxy so the frontend is reachable at [baseurl]/project2 +# (the Nitro server already serves the app under /project2): +# sudo a2enmod proxy_http +# sudo tee /etc/apache2/conf-available/gspotter.conf > /dev/null <<'EOF' +# ProxyPreserveHost On +# ProxyPass /project2 http://localhost:3000/project2 +# ProxyPassReverse /project2 http://localhost:3000/project2 +# EOF +# sudo a2enconf gspotter && sudo systemctl reload apache2 +# Any old static build left in /var/www/html/project2 can be removed. +# +# Result: GraphQL backend on port 3001, SSR frontend on port 3000 (proxied to +# [baseurl]/project2 by Apache), both running as systemd services that restart +# on every deploy and on boot. + +name: CI/CD - Deploy to VM + +on: + workflow_run: + workflows: ['CI/CD - Linter, formatter and tests'] + types: [completed] + branches: + - main + +permissions: + contents: read + +concurrency: + group: production-deploy + cancel-in-progress: false + +env: + # Persistent location of the deployed app on the VM + DEPLOY_ROOT: /srv/gspotter + # Public URL of the VM, no trailing slash (baked into the frontend build) + PUBLIC_BASE_URL: http://CHANGEME + # Connection string for the VM's PostgreSQL (see pnpm db:setup) + DATABASE_URL: postgresql://gspot:gspotter@localhost:5432/gspot + BACKEND_PORT: 3001 + FRONTEND_PORT: 3000 + +jobs: + deploy: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: [self-hosted] + timeout-minutes: 20 + + steps: + - name: Check out source + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Check prerequisites + run: | + set -euo pipefail + for cmd in node pnpm rsync; do + command -v "$cmd" > /dev/null || { + echo "::error::$cmd is not installed on the runner" + exit 1 + } + done + [[ "$PUBLIC_BASE_URL" != "http://it2810-09.idi.ntnu.no/" ]] || { + echo "::error::Set PUBLIC_BASE_URL to the VM's public URL" + exit 1 + } + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Write env files + run: | + printf 'DATABASE_URL="%s"\n' "$DATABASE_URL" > apps/backend/.env + cp apps/backend/.env packages/db/.env + + - name: Generate Prisma client + run: pnpm --filter @t09-project-2/db run db:generate + + - name: Build frontend + run: pnpm --filter @t09-project-2/frontend run build + env: + VITE_GRAPHQL_URL: ${{ env.PUBLIC_BASE_URL }}:${{ env.BACKEND_PORT }}/ + + - name: Create deploy root + run: sudo install -d -o "$(id -un)" -g "$(id -gn)" "$DEPLOY_ROOT" + + - name: Sync app to deploy root + run: | + rsync -a --delete \ + --exclude .git \ + --exclude node_modules \ + ./ "$DEPLOY_ROOT/" + + - name: Install dependencies in deploy root + run: pnpm -C "$DEPLOY_ROOT" install --frozen-lockfile + + - name: Apply database schema + run: pnpm -C "$DEPLOY_ROOT" --filter @t09-project-2/db run db:push + + - name: Install and restart services + run: | + sudo tee /etc/systemd/system/gspotter-backend.service > /dev/null < /dev/null < createContext(), }) diff --git a/apps/frontend/src/components/Map.tsx b/apps/frontend/src/components/Map.tsx index 45ecdfb..8137d86 100644 --- a/apps/frontend/src/components/Map.tsx +++ b/apps/frontend/src/components/Map.tsx @@ -3,17 +3,22 @@ import { MapContainer, Marker, Popup, TileLayer } from 'react-leaflet' export default function Map() { return (
- - - - - Guinness!!! - - - + + + + + Guinness!!! + + +
- ) -} \ No newline at end of file + ) +} diff --git a/apps/frontend/src/routes/__root.tsx b/apps/frontend/src/routes/__root.tsx index 709e4ad..4119982 100644 --- a/apps/frontend/src/routes/__root.tsx +++ b/apps/frontend/src/routes/__root.tsx @@ -49,7 +49,12 @@ function RootDocument({ children }: { children: React.ReactNode }) {