feat: deploy (Dockerfile, compose) and Gitea Actions CI
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
699d038d1c
commit
ce1baaa706
@@ -0,0 +1,5 @@
|
||||
**/node_modules
|
||||
**/target
|
||||
**/dist
|
||||
.git
|
||||
.superpowers
|
||||
@@ -0,0 +1,80 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, "build/**"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
tools:
|
||||
name: Tool tests and sync-check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- run: npm run test:tools
|
||||
- run: npm run check
|
||||
- run: npm run test:site
|
||||
|
||||
verify-references:
|
||||
name: Verify references (all modules, Rust and TS)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- uses: dtolnay/[email protected]
|
||||
- run: node tools/verify-references.mjs
|
||||
|
||||
coupling-audit:
|
||||
name: Coupling audit on references
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Audit every reference engine
|
||||
run: |
|
||||
set -e
|
||||
for d in course/*/reference/*/; do
|
||||
echo "== $d"
|
||||
node tools/coupling-audit.mjs "$d"
|
||||
done
|
||||
|
||||
site:
|
||||
name: Site build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- run: npm --prefix site ci
|
||||
- run: npm --prefix site run build
|
||||
env:
|
||||
COURSE_HOST: ci.example.invalid
|
||||
|
||||
naive-engine:
|
||||
name: Naive engine tests (Module 0)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
- uses: dtolnay/[email protected]
|
||||
# test_00_real_rules_whirlwind_order is expected to fail: it is marked
|
||||
# #[should_panic] (Rust) / test.fails (TS), so the suite passes.
|
||||
- run: cargo test
|
||||
working-directory: naive-engine/rust
|
||||
- run: npm ci
|
||||
working-directory: naive-engine/ts
|
||||
- run: npm test
|
||||
working-directory: naive-engine/ts
|
||||
- run: npm run typecheck
|
||||
working-directory: naive-engine/ts
|
||||
@@ -3,3 +3,4 @@ target/
|
||||
dist/
|
||||
.DS_Store
|
||||
*.log
|
||||
deploy/.env
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Copy to deploy/.env and fill in.
|
||||
# Public hostname of the course (a DNS A/AAAA record must point at this server).
|
||||
COURSE_HOST=course.example.com
|
||||
# Name of the Docker network nginx-proxy is attached to.
|
||||
PROXY_NETWORK=nginx-proxy
|
||||
@@ -0,0 +1,14 @@
|
||||
# Build context is the repository root:
|
||||
# docker build -f deploy/Dockerfile --build-arg COURSE_HOST=course.example.com .
|
||||
FROM node:22-alpine AS build
|
||||
ARG COURSE_HOST
|
||||
RUN test -n "$COURSE_HOST" || (echo "COURSE_HOST build arg is required" >&2; exit 1)
|
||||
ENV COURSE_HOST=$COURSE_HOST
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN npm --prefix site ci --no-audit --no-fund
|
||||
RUN npm --prefix site run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/site/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,38 @@
|
||||
# Deploying the course site
|
||||
|
||||
The site (`site/`, Astro Starlight) is built into static files and served by
|
||||
`nginx:alpine`. It is meant to run behind a `jwilder/nginx-proxy` container with
|
||||
`acme-companion` (Let's Encrypt), which routes by the `VIRTUAL_HOST` variable.
|
||||
|
||||
## Deploy
|
||||
|
||||
```sh
|
||||
git clone https://gitea.lucaswinther.info/lucasw89/systemic-game-design-course.git
|
||||
cd systemic-game-design-course/deploy
|
||||
cp .env.example .env # set COURSE_HOST (and PROXY_NETWORK if yours differs)
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
- `COURSE_HOST` is the public hostname; point its DNS record at the server first.
|
||||
It is used as the site URL at build time, as `VIRTUAL_HOST`, and as `LETSENCRYPT_HOST`.
|
||||
- The build fails if `COURSE_HOST` is unset, so the placeholder URL never ships.
|
||||
- The container joins the external Docker network `${PROXY_NETWORK:-nginx-proxy}`;
|
||||
it must be the network your nginx-proxy container is on.
|
||||
- To update: `git pull && docker compose up -d --build`.
|
||||
|
||||
Manual build: `docker build -f deploy/Dockerfile --build-arg COURSE_HOST=course.example.com .`
|
||||
(run from the repository root).
|
||||
|
||||
## CI
|
||||
|
||||
`.gitea/workflows/ci.yml` runs on Gitea Actions (needs a runner with the `ubuntu-latest` label):
|
||||
|
||||
| Job | Runs |
|
||||
| --- | --- |
|
||||
| `tools` | `npm run test:tools`, `npm run check` (sync-check), `npm run test:site` |
|
||||
| `verify-references` | `node tools/verify-references.mjs`: every module's reference, Rust and TS, with cumulative tests (needs network for `npm install`; Rust pinned to 1.85.1) |
|
||||
| `coupling-audit` | `tools/coupling-audit.mjs` on every `course/*/reference/*` |
|
||||
| `site` | `npm --prefix site ci && npm --prefix site run build` with a dummy `COURSE_HOST` |
|
||||
| `naive-engine` | Module 0's engine: `cargo test`, `npm test`, `npm run typecheck`. The whirlwind-order test is expected to fail and is marked `should_panic` / `test.fails`, so the suite passes |
|
||||
|
||||
There is no clippy step.
|
||||
@@ -0,0 +1,21 @@
|
||||
services:
|
||||
course:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile
|
||||
args:
|
||||
COURSE_HOST: ${COURSE_HOST:?set COURSE_HOST in deploy/.env}
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "80"
|
||||
environment:
|
||||
VIRTUAL_HOST: ${COURSE_HOST}
|
||||
LETSENCRYPT_HOST: ${COURSE_HOST}
|
||||
VIRTUAL_PORT: "80"
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: ${PROXY_NETWORK:-nginx-proxy}
|
||||
external: true
|
||||
@@ -1,9 +1,11 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import starlight from "@astrojs/starlight";
|
||||
|
||||
// The site is served at the root of its own subdomain. Set COURSE_HOST at build time
|
||||
// (e.g. COURSE_HOST=course.example.com); the default is a placeholder.
|
||||
const host = process.env.COURSE_HOST ?? "course.example.com";
|
||||
// The site is served at the root of its own subdomain. COURSE_HOST is required at build
|
||||
// time (e.g. COURSE_HOST=course.example.com); there is deliberately no default, so a
|
||||
// deploy can never ship with a placeholder URL.
|
||||
const host = process.env.COURSE_HOST;
|
||||
if (!host) throw new Error("COURSE_HOST is not set (e.g. COURSE_HOST=course.example.com)");
|
||||
|
||||
export default defineConfig({
|
||||
site: `https://${host}`,
|
||||
|
||||
Reference in New Issue
Block a user