Update runner

This commit is contained in:
Chris Chen
2026-06-22 15:53:51 -07:00
parent ef3731ba48
commit a537974edf
10 changed files with 278 additions and 221 deletions
+114
View File
@@ -0,0 +1,114 @@
# Deploy to the Ubuntu VM (all-in-one) — LAN / HTTP
Everything runs on **one Ubuntu VM, one Docker daemon**: Gitea, the container
registry, the build, and the ROLAC runtime. So a single Gitea Actions job (the
`ubuntu` runner) does the whole pipeline — no cross-machine pull, no Windows-runner
quirks.
```
git push main
Gitea (on the VM) ── triggers .gitea/workflows/ci-cd-vm.yml
ubuntu runner (on the VM, same Docker daemon)
dotnet test
docker build ./API + ./APP -> :latest + :<sha>
docker push -> git.golife.love/chrischen/rolac-{api,app}
docker compose up -d (TAG=<sha>)
curl /api/health
browser -> http://<vm-ip>:8080
│ nginx edge (container, 8080->80)
├── / -> app container (Angular static)
└── /api/ -> api container (ASP.NET, :8080)
api ──> existing PostgreSQL @ 192.168.68.55:49154 (not containerized)
```
No TLS yet — plain HTTP on port **8080**. Add Let's Encrypt later (see the Azure
`deploy/` files) or front it with an existing reverse proxy.
---
## One-time setup — on the VM
1. **Deploy dir + secrets:**
```bash
sudo mkdir -p /opt/rolac/nginx/conf.d /opt/rolac/data/api-storage
sudo cp /path/to/repo/deploy/vm/.env.example /opt/rolac/.env
sudo nano /opt/rolac/.env # real DB user/password + JWT_SECRET + APP_ORIGIN
```
Make sure the user the runner executes as can read/write `/opt/rolac`.
2. **Registry token** — in Gitea: Settings → Applications → new token with
`read:package` + `write:package`. Log Docker in once on the VM:
```bash
docker login git.golife.love -u ChrisChen # paste the token
```
3. **Install act_runner on the VM** with the label **`ubuntu`** and access to the
host Docker. The runner must be able to run `dotnet`, `docker`, and
`docker compose`, and reach `/opt/rolac`:
```bash
docker run -d --restart unless-stopped --name rolac-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /opt/rolac:/opt/rolac \
-e GITEA_INSTANCE_URL=https://git.golife.love \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
-e GITEA_RUNNER_LABELS=ubuntu \
gitea/act_runner:latest
```
> The job calls `dotnet test` and `docker build` directly. If act_runner runs in
> a container, that image needs the .NET 8 SDK + docker CLI on PATH. Simplest:
> install act_runner as a **native binary** on the VM (it then uses the host's
> Docker + an installed .NET SDK). Either way the label must be `ubuntu`.
4. **Gitea repo secrets** (Settings → Actions → Secrets):
- `REGISTRY_USER` = `ChrisChen`
- `REGISTRY_TOKEN` = the package token from step 2
5. **Enable Actions** for the repo if needed (Settings → Advanced → Actions).
---
## Day-to-day
`git push` to `main` → `.gitea/workflows/ci-cd-vm.yml`:
**test → build both images → push to registry → sync compose/nginx → `compose up -d` → health check.**
Open `http://<vm-ip>:8080` and log in.
Deploy pins `TAG=<git-sha>`, so the running containers match exactly the commit that
was built (the images already exist in the local Docker, so this is instant).
---
## Manual deploy (no runner yet)
On the VM, from a checkout of the repo:
```bash
docker login git.golife.love -u ChrisChen
docker build -t git.golife.love/chrischen/rolac-api:latest ./API
docker build -t git.golife.love/chrischen/rolac-app:latest ./APP
mkdir -p /opt/rolac/nginx/conf.d /opt/rolac/data/api-storage
cp deploy/vm/docker-compose.yml /opt/rolac/docker-compose.yml
cp deploy/vm/nginx/conf.d/rolac.conf /opt/rolac/nginx/conf.d/rolac.conf
cd /opt/rolac && docker compose up -d
curl -fsS http://localhost:8080/api/health
```
---
## Notes
- **First boot runs DB migrations** against `192.168.68.55` automatically
(`Program.cs` calls `MigrateAsync()` + seed). The VM must reach that host and the
DB user needs DDL rights; back up before the first run.
- **Uploaded files** persist under `/opt/rolac/data/api-storage`.
- **Same Docker daemon for build + run** means `docker compose up` finds the freshly
built `:<sha>` images locally; `docker compose pull` is unnecessary here (but
harmless if you add it).
- To go HTTPS later: switch the edge to ports 80/443 and mount Let's Encrypt certs,
or use the Azure `deploy/` files which already include certbot.
+30
View File
@@ -0,0 +1,30 @@
services:
api:
image: git.golife.love/chrischen/rolac-api:${TAG:-latest}
env_file: .env
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__DefaultConnection: ${DB_CONNECTION}
Jwt__SecretKey: ${JWT_SECRET}
# Same-origin /api means CORS is not triggered by the browser; this is only
# a safety net for direct cross-origin calls / tools. Set to your VM URL.
Cors__AllowedOrigins__0: ${APP_ORIGIN:-http://localhost:8080}
volumes:
- ./data/api-storage:/app/App_Data/storage
restart: unless-stopped
expose: ["8080"]
app:
image: git.golife.love/chrischen/rolac-app:${TAG:-latest}
restart: unless-stopped
expose: ["80"]
nginx:
image: nginx:alpine
# HTTP only on a high port for now (TLS to be added later). Reach the app at
# http://<vm-ip>:8080 on the LAN / wherever the VM is reachable.
ports: ["8080:80"]
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on: [api, app]
restart: unless-stopped
+18
View File
@@ -0,0 +1,18 @@
server {
listen 80;
server_name _;
# API -> api container. The SPA calls same-origin /api/... (environment.prod.ts).
location /api/ {
proxy_pass http://api:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Everything else -> the Angular static app (its own nginx does SPA fallback).
location / {
proxy_pass http://app:80;
}
}
+34
View File
@@ -0,0 +1,34 @@
# Custom Gitea act_runner image for the ROLAC pipeline.
#
# The workflow needs BOTH the .NET SDK (dotnet test) and the Docker CLI
# (docker build / push / compose) in the same execution environment. The stock
# gitea/act_runner image has neither, so we bake them on top of the .NET 8 SDK
# image and copy the act_runner binary in. Registered as label `ubuntu:host`,
# every step runs inside THIS container, which talks to the host Docker daemon
# via the mounted socket.
FROM mcr.microsoft.com/dotnet/sdk:8.0
# Docker CLI + compose plugin, Node.js (JS-based actions like checkout need it),
# git, curl, bash.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl gnupg git bash \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
&& chmod a+r /etc/apt/keyrings/docker.asc \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
> /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/*
# act_runner binary from the official image.
COPY --from=gitea/act_runner:latest /usr/local/bin/act_runner /usr/local/bin/act_runner
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# .runner registration state persists here (mount a volume).
WORKDIR /data
ENTRYPOINT ["/entrypoint.sh"]
+45
View File
@@ -0,0 +1,45 @@
# Gitea act_runner on the VM (Docker Compose)
Runs the CI/CD runner as a container, but with a **custom image** that bundles the
.NET 8 SDK + Docker CLI + Node, because the ROLAC workflow does both `dotnet test`
and `docker build`/`compose`. The stock `gitea/act_runner` image has neither.
It registers with the label **`ubuntu:host`**:
- `ubuntu` = the label name the workflow targets (`runs-on: ubuntu`).
- `:host` = run each step **inside this runner container** (which has the tools),
instead of spawning a separate job container that wouldn't have dotnet/docker.
The container mounts the **host Docker socket** (so build/push/compose act on the
host daemon) and **`/opt/rolac`** at the same path (so compose's relative volumes
resolve), and uses **host networking** (so the deploy step's
`curl http://localhost:8080/api/health` works).
## Setup
1. **Get a runner registration token** in Gitea:
Settings → Actions → Runners → **Create new runner** → copy the token.
(This is the *registration* token — different from the `REGISTRY_TOKEN` repo
secret used for `docker login`.)
2. **Configure + start** (on the VM, from this directory):
```bash
cd deploy/vm/runner
cp .env.example .env
nano .env # paste GITEA_RUNNER_REGISTRATION_TOKEN
docker compose up -d --build
```
3. **Verify** it shows up online in Gitea → Settings → Actions → Runners, with the
`ubuntu` label.
## Notes
- Registration state is stored in `./runner-data/.runner` (a bind mount), so the
runner does **not** re-register on restart. To re-register from scratch, stop the
container and delete `runner-data/`.
- `docker login git.golife.love` for the registry is done by the **workflow** using
the repo secrets `REGISTRY_USER` / `REGISTRY_TOKEN` — you do not need to log in
inside the runner manually.
- Logs: `docker compose logs -f runner`.
- The runner can build/run containers on the host because it shares the host Docker
socket. Treat this runner as privileged — only run trusted workflows on it.
+17
View File
@@ -0,0 +1,17 @@
services:
runner:
build: .
image: rolac-act-runner:latest
restart: unless-stopped
# host networking so the deploy step's `curl http://localhost:8080/api/health`
# reaches the published edge port on the host.
network_mode: host
env_file: .env
volumes:
# talk to the host Docker daemon (build/push/compose all run on the host)
- /var/run/docker.sock:/var/run/docker.sock
# deploy target — must be the SAME path so compose's relative ./data and
# ./nginx volumes resolve to real host paths
- /opt/rolac:/opt/rolac
# persist runner registration so it doesn't re-register on restart
- ./runner-data:/data
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
set -e
# Register once (state stored in /data/.runner, which is a mounted volume so it
# survives restarts). On later starts it just runs the daemon.
if [ ! -f /data/.runner ]; then
echo "Registering runner with ${GITEA_INSTANCE_URL} ..."
act_runner register --no-interactive \
--instance "${GITEA_INSTANCE_URL}" \
--token "${GITEA_RUNNER_REGISTRATION_TOKEN}" \
--name "${GITEA_RUNNER_NAME:-vm-runner}" \
--labels "${GITEA_RUNNER_LABELS:-ubuntu:host}"
fi
exec act_runner daemon