Skip to content

Latest commit

 

History

History
344 lines (260 loc) · 11.3 KB

File metadata and controls

344 lines (260 loc) · 11.3 KB

DEMO_BRINGUP.md — stand up the trial on demo.ece.mcmaster.ca

Goal: get the web app live on the VM so Brennan can test it over the campus network by June 9. Architecture: plain HTTP, single origin via nginx on :80 (no TLS — decided with IT; see doc/it-install-requirements.md).

 Brennan's browser ──HTTP :80──► nginx ──┬─►  /        → Next.js  127.0.0.1:3000
 http://demo.ece.mcmaster.ca             ├─►  /api/*   → FastAPI  127.0.0.1:8000
                                         └─►  /healthz → FastAPI  127.0.0.1:8000

Backend + frontend listen on localhost only; nginx is the one public door. Auth is the MacID shim (no SSO yet) — fine for a trial; the only "secret" is a non-secret username.


0. Fill these in once (used throughout)

export REPO_DIR="$HOME/ta-course-match"          # where the code lives on the VM
export HOSTNAME_PUBLIC="demo.ece.mcmaster.ca"    # the URL Brennan visits
export SERVICE_USER="$(whoami)"                  # the account Ron gave you (has sudo)
export ADMIN_MACIDS="brennan,$(whoami)"          # comma-separated; Brennan MUST be here to be admin
export DEMO_CYCLE="mock-2025-26"                 # solver-ready demo cycle shipped in the repo

Replace brennan with his real MacID and add any co-admins. Anyone not in this list (and not in the imported cycle's instructor/student tables) gets a 401.


1. Verify what IT installed (fail fast)

java -version      # expect 17.x  (Timefold's JVM solver core)
node -v            # expect v22.x or v24.x  (NOT v20 — EOL)
npm -v
uv --version
git --version

If any are missing, stop and go back to IT — the rest won't work without them.


2. Get the code

git clone https://github.com/NooriDan/ta-course-match.git "$REPO_DIR"
cd "$REPO_DIR"

(If IT pre-placed it, just cd "$REPO_DIR" and git pull.)


3. Backend dependencies

cd "$REPO_DIR"
uv sync --extra web      # creates .venv/ with tcm, tcm-web, alembic console scripts

This pins Python 3.12 automatically (via .python-version) and installs Timefold + FastAPI. No system Python needed.


4. Solver smoke test — confirm Python + Java + Timefold all work before building anything else

cd "$REPO_DIR"
.venv/bin/tcm validate "$DEMO_CYCLE"
.venv/bin/tcm solve    "$DEMO_CYCLE" --time-limit-seconds 20

validate should pass; solve should run the JVM and write a result under results/$DEMO_CYCLE/. If this fails with a JVM/Java error, fix that now — nothing downstream will solve. (This is the HANDOFF canary, run early.)


5. Initialize the database (SQLite — fine for a demo)

cd "$REPO_DIR"
.venv/bin/alembic upgrade head      # creates data/tcm.sqlite3 at schema head (migrations 0001–0007)

We leave TCM_DB_URL unset → bundled SQLite. Good enough for ~120 trial users; switch to Postgres only when this goes past the demo.


6. Runtime environment file

Create /etc/tcm/tcm.env — read by the backend service:

sudo mkdir -p /etc/tcm
sudo tee /etc/tcm/tcm.env >/dev/null <<EOF
# --- Auth shim (no SSO yet). ENVIRONMENT MUST be dev/test/ci or the boot
#     guard refuses to start while TCM_AUTH_MODE=shim. "dev" is what unlocks
#     the shim; it does NOT mean hot-reload (TCM_RELOAD stays 0). ---
ENVIRONMENT=dev
TCM_AUTH_MODE=shim
TCM_ADMIN_MACIDS=${ADMIN_MACIDS}

# --- Bind localhost only; nginx is the public door ---
TCM_HOST=127.0.0.1
TCM_PORT=8000
TCM_RELOAD=0
TCM_CORS_ORIGINS=http://${HOSTNAME_PUBLIC}

# --- JVM heap for Timefold (2 GiB suits ECE scale) ---
JAVA_OPTS=-Xmx2g
# systemd has a minimal PATH; pin JAVA_HOME so the service can find the JVM.
JAVA_HOME=$(dirname "$(dirname "$(readlink -f "$(which java)")")")
EOF

cat /etc/tcm/tcm.env      # sanity-check the expanded values (esp. JAVA_HOME)

7. Build the frontend

NEXT_PUBLIC_API_BASE_URL is baked in at build time — the browser uses it to reach the API. With single-origin nginx it's just the public hostname:

cd "$REPO_DIR/frontend"
npm ci
NEXT_PUBLIC_API_BASE_URL="http://${HOSTNAME_PUBLIC}" npm run build

⚠️ If you ever change the hostname, you must rebuild — the URL is compiled in. ⚠️ Small VM? next build can spike to ~1–2 GiB RAM. If it OOM-kills, add swap (sudo fallocate -l 2G /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile) or build locally and rsync the frontend/.next dir up.


8. Run both services under systemd (survives logout + reboot)

Backend:

sudo tee /etc/systemd/system/tcm-web.service >/dev/null <<EOF
[Unit]
Description=tcm-web (FastAPI backend)
After=network.target

[Service]
User=${SERVICE_USER}
WorkingDirectory=${REPO_DIR}
EnvironmentFile=/etc/tcm/tcm.env
ExecStart=${REPO_DIR}/.venv/bin/tcm-web
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF

Frontend:

sudo tee /etc/systemd/system/tcm-frontend.service >/dev/null <<EOF
[Unit]
Description=tcm-frontend (Next.js)
After=network.target tcm-web.service

[Service]
User=${SERVICE_USER}
WorkingDirectory=${REPO_DIR}/frontend
Environment=NODE_ENV=production
ExecStart=/usr/bin/npm run start -- -H 127.0.0.1 -p 3000
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF

Enable + start:

sudo systemctl daemon-reload
sudo systemctl enable --now tcm-web tcm-frontend
sudo systemctl status tcm-web tcm-frontend --no-pager

Both should be active (running). Tail logs with journalctl -u tcm-web -f (or tcm-frontend).


9. nginx reverse proxy (single origin, :80, plain HTTP)

sudo tee /etc/nginx/sites-available/tcm >/dev/null <<EOF
server {
    listen 80;
    server_name ${HOSTNAME_PUBLIC};

    # CV uploads (10 MiB) + cycle-import payloads (50 MiB) — nginx default is 1 MiB
    client_max_body_size 50m;

    # SSE: live solve-progress stream. Must NOT buffer; long-lived.
    location ~ ^/api/cycles/.+/runs/.+/events\$ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host \$host;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
    }

    # API + health -> backend
    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host \$host;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }
    location = /healthz {
        proxy_pass http://127.0.0.1:8000;
    }

    # Everything else -> Next.js frontend
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host \$host;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
    }
}
EOF

sudo ln -sf /etc/nginx/sites-available/tcm /etc/nginx/sites-enabled/tcm
sudo rm -f /etc/nginx/sites-enabled/default     # drop the "Welcome to nginx" default
sudo nginx -t                                   # config syntax check
sudo systemctl reload nginx

10. Open the firewall (if IT hasn't already)

sudo ufw allow 80/tcp   # Ubuntu/Debian
# RHEL/firewalld:  sudo firewall-cmd --permanent --add-service=http && sudo firewall-cmd --reload

Confirm with IT that the campus firewall also lets port 80 reach the VM — the host firewall is only half the path.


11. Smoke test the live stack

curl -s   http://localhost/healthz                 # -> {"status":"ok"}
curl -sI  http://localhost/                         # -> 200 (Next.js homepage)
curl -si  http://localhost/api/cycles               # -> 401 (shim rejects: no MacID)
curl -s   http://localhost/api/cycles -H "X-MacID: $(whoami)"   # -> [] (admin, empty list)

Then from your laptop (not the VM), confirm it's reachable on the network:

curl -sI http://demo.ece.mcmaster.ca/             # -> 200

12. Seed the demo cycle

Import mock-2025-26 (119 students, 26 courses, 294 rankings — pre-populated, ready to solve):

curl -X POST http://localhost/api/cycles/import \
  -H "X-MacID: $(whoami)" \
  -H "Content-Type: application/json" \
  -d "{\"cycle\":\"${DEMO_CYCLE}\",\"input_dir\":\"${REPO_DIR}/data/${DEMO_CYCLE}\"}"

The import endpoint only accepts input_dir paths under <repo>/data/ — the shipped cycle already lives there, so this just works. A 201 means success; re-check GET /api/cycles and you'll see the cycle listed.

Web-path solve canary: sign in as admin (next step), open the cycle, and run the solver from the dashboard. A run that reaches completed with a score confirms the full browser → API → JVM → DB round-trip.


13. Hand off to Brennan

  1. Send him http://demo.ece.mcmaster.ca.
  2. He lands on the dev sign-in screen → enters his MacID (the one you put in ADMIN_MACIDS) → he's in as admin.
  3. He can browse the imported mock-2025-26 cycle, run/inspect a solve, view rankings, etc.
  4. Instructors/students can only sign in after their cycle is imported (their MacID has to exist in the cycle's tables) — for the demo, the mock-2025-26 roster is already loaded, so any of those MacIDs work too.

14. Operations

Task Command
Tail backend logs journalctl -u tcm-web -f
Tail frontend logs journalctl -u tcm-frontend -f
Restart everything sudo systemctl restart tcm-web tcm-frontend
Pull a code update cd $REPO_DIR && git pull && uv sync --extra web && .venv/bin/alembic upgrade head
Rebuild FE after update cd $REPO_DIR/frontend && npm ci && NEXT_PUBLIC_API_BASE_URL="http://$HOSTNAME_PUBLIC" npm run build && sudo systemctl restart tcm-frontend
Re-import / add a cycle drop CSVs under $REPO_DIR/data/<cycle>/, re-run the step 12 curl with that cycle name
Back up the demo copy data/tcm.sqlite3 + data/<cycle>/cvs/ (CVs live on disk, not in the DB)

15. Troubleshooting

Symptom Cause → Fix
Backend won't start, log says "Refusing to boot: TCM_AUTH_MODE=shim only allowed when ENVIRONMENT is dev/test/ci" ENVIRONMENT isn't dev. Fix /etc/tcm/tcm.env, systemctl restart tcm-web.
Solve fails only as a service (works from CLI in step 4) with a JVM-not-found error JAVA_HOME wrong/empty in the env file. Recompute: dirname $(dirname $(readlink -f $(which java))), put it in /etc/tcm/tcm.env, restart.
Browser loads the page but all data calls fail / CORS error NEXT_PUBLIC_API_BASE_URL was wrong at build time, or TCM_CORS_ORIGINS ≠ the public origin. Rebuild FE (step 7) and check the env file.
Live solve progress bar stalls / SSE not updating nginx is buffering the stream. Confirm the …/events regex location block is present and nginx -t && systemctl reload nginx.
CV upload or cycle import returns 413 client_max_body_size 50m; missing from the nginx server block.
next build killed / OOM small VM — add swap or build locally and rsync .next (see step 7 note).
Brennan gets 401 on sign-in his MacID isn't in TCM_ADMIN_MACIDS (and the cycle isn't imported). Fix the env file, restart, re-import.

Owner: Danial (dnoorizadeh@gmail.com). Surface-area reference: HANDOFF.md. Install list for IT: doc/it-install-requirements.md.