Update runner
This commit is contained in:
@@ -1,77 +0,0 @@
|
|||||||
name: ci-cd-nas
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Runs on the DEV PC runner (label `builder`): Docker Desktop + .NET SDK.
|
|
||||||
# DS220+ (Celeron J4025 / 2GB RAM) cannot build these images, so all the heavy
|
|
||||||
# work (test, dotnet publish, ng build) happens here, then images are pushed
|
|
||||||
# to the Gitea registry on the NAS.
|
|
||||||
build-push:
|
|
||||||
# Label is registered on the dev PC as `windows:host`; runs-on matches the
|
|
||||||
# label NAME (before the colon). `:host` means it runs directly on the PC,
|
|
||||||
# using its installed Docker Desktop + .NET SDK (no container).
|
|
||||||
runs-on: windows
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
# Windows PowerShell (always present). NOTE: do NOT use `shell: bash`
|
|
||||||
# here — act_runner in Windows host mode mislocates the generated .sh
|
|
||||||
# script ("No such file or directory"). PowerShell avoids that bug.
|
|
||||||
shell: powershell
|
|
||||||
env:
|
|
||||||
REGISTRY: git.golife.love/chrischen
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Test API
|
|
||||||
run: dotnet test API/ROLAC.API.Tests/ROLAC.API.Tests.csproj -c Release
|
|
||||||
|
|
||||||
- name: Registry login
|
|
||||||
run: '"${{ secrets.REGISTRY_TOKEN }}" | docker login git.golife.love -u "${{ secrets.REGISTRY_USER }}" --password-stdin'
|
|
||||||
|
|
||||||
- name: Build images
|
|
||||||
run: |
|
|
||||||
docker build -t "$env:REGISTRY/rolac-api:latest" -t "$env:REGISTRY/rolac-api:${{ github.sha }}" ./API
|
|
||||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
||||||
docker build -t "$env:REGISTRY/rolac-app:latest" -t "$env:REGISTRY/rolac-app:${{ github.sha }}" ./APP
|
|
||||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
||||||
|
|
||||||
- name: Push images
|
|
||||||
run: |
|
|
||||||
docker push --all-tags "$env:REGISTRY/rolac-api"
|
|
||||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
||||||
docker push --all-tags "$env:REGISTRY/rolac-app"
|
|
||||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
||||||
|
|
||||||
# Runs on the NAS runner (label `nas`): host Docker socket mounted and
|
|
||||||
# /volume1/docker/rolac bind-mounted at the same path. Deploy ONLY — it just
|
|
||||||
# pulls the freshly-built images and (re)starts the stack. No building here.
|
|
||||||
deploy:
|
|
||||||
needs: build-push
|
|
||||||
runs-on: nas
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: sh
|
|
||||||
env:
|
|
||||||
DEPLOY_DIR: /volume1/docker/rolac
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Registry login
|
|
||||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.golife.love -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Sync compose + nginx to deploy dir
|
|
||||||
run: |
|
|
||||||
mkdir -p "$DEPLOY_DIR/nginx/conf.d" "$DEPLOY_DIR/data/api-storage"
|
|
||||||
cp deploy/nas/docker-compose.yml "$DEPLOY_DIR/docker-compose.yml"
|
|
||||||
cp deploy/nas/nginx/conf.d/rolac.conf "$DEPLOY_DIR/nginx/conf.d/rolac.conf"
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
run: |
|
|
||||||
cd "$DEPLOY_DIR"
|
|
||||||
export TAG=${{ github.sha }}
|
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
sleep 5
|
|
||||||
curl -fsS http://localhost:8080/api/health
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
name: ci-cd-vm
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
# Everything lives on the same Ubuntu VM (Gitea, the registry, the build, and the
|
||||||
|
# runtime share one Docker daemon), so a single job on the `ubuntu` runner does
|
||||||
|
# test -> build -> push -> deploy. No cross-machine pull is needed; deploy reuses
|
||||||
|
# the images just built in the local Docker.
|
||||||
|
jobs:
|
||||||
|
ci-cd:
|
||||||
|
runs-on: ubuntu
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
REGISTRY: git.golife.love/chrischen
|
||||||
|
DEPLOY_DIR: /opt/rolac
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Test API
|
||||||
|
run: dotnet test API/ROLAC.API.Tests/ROLAC.API.Tests.csproj -c Release
|
||||||
|
|
||||||
|
- name: Registry login
|
||||||
|
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.golife.love -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Build images
|
||||||
|
run: |
|
||||||
|
docker build -t "$REGISTRY/rolac-api:latest" -t "$REGISTRY/rolac-api:${{ github.sha }}" ./API
|
||||||
|
docker build -t "$REGISTRY/rolac-app:latest" -t "$REGISTRY/rolac-app:${{ github.sha }}" ./APP
|
||||||
|
|
||||||
|
- name: Push images
|
||||||
|
run: |
|
||||||
|
docker push --all-tags "$REGISTRY/rolac-api"
|
||||||
|
docker push --all-tags "$REGISTRY/rolac-app"
|
||||||
|
|
||||||
|
- name: Sync compose + nginx to deploy dir
|
||||||
|
run: |
|
||||||
|
mkdir -p "$DEPLOY_DIR/nginx/conf.d" "$DEPLOY_DIR/data/api-storage"
|
||||||
|
cp deploy/vm/docker-compose.yml "$DEPLOY_DIR/docker-compose.yml"
|
||||||
|
cp deploy/vm/nginx/conf.d/rolac.conf "$DEPLOY_DIR/nginx/conf.d/rolac.conf"
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
cd "$DEPLOY_DIR"
|
||||||
|
export TAG=${{ github.sha }}
|
||||||
|
docker compose up -d
|
||||||
|
sleep 5
|
||||||
|
curl -fsS http://localhost:8080/api/health
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
# Deploy to Synology NAS (Container Manager) — LAN / HTTP
|
|
||||||
|
|
||||||
Target: run the ROLAC stack on a Synology **DS220+** (Celeron J4025 / 2GB RAM),
|
|
||||||
reachable on the LAN at `http://<nas-ip>:8080`. Images are **built on the dev PC**
|
|
||||||
(the NAS is too weak to compile — Angular's build alone can need >2GB RAM), pushed to
|
|
||||||
the **Gitea registry** on the NAS, and the NAS only **pulls + restarts** the containers.
|
|
||||||
|
|
||||||
```
|
|
||||||
push main
|
|
||||||
dev PC ───────────────► Gitea (NAS)
|
|
||||||
(runner: builder) │ triggers .gitea/workflows/ci-cd-nas.yml
|
|
||||||
test + build + push ─────────┤
|
|
||||||
▼
|
|
||||||
NAS runner (label: nas) ── deploy only ──┐
|
|
||||||
▼
|
|
||||||
browser (LAN) -> http://<nas-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)
|
|
||||||
```
|
|
||||||
|
|
||||||
Why this split: DS220+ can comfortably **run** these lightweight containers
|
|
||||||
(nginx + precompiled .NET + static files) but cannot **build** them. So building
|
|
||||||
(test, `dotnet publish`, `ng build`) runs on the dev PC; the NAS just pulls.
|
|
||||||
|
|
||||||
Differences vs the Azure plan: no TLS/certbot, edge on **8080** (DSM owns 80/443),
|
|
||||||
reuse the LAN database, deploy via the on-NAS runner (no SSH).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Two runners, two jobs
|
|
||||||
|
|
||||||
| Job | `runs-on` | Where | Does |
|
|
||||||
|-----|-----------|-------|------|
|
|
||||||
| `build-push` | `windows` | **dev PC** | test → build both images → push to registry |
|
|
||||||
| `deploy` | `nas` | **NAS** | pull images → `docker compose up -d` → health check |
|
|
||||||
|
|
||||||
> The dev-PC runner is registered with the label `windows:host` — `runs-on` matches
|
|
||||||
> the label NAME (`windows`); `:host` is the run mode (executes directly on the PC,
|
|
||||||
> not in a container, so it uses Docker Desktop + the installed .NET SDK).
|
|
||||||
|
|
||||||
`deploy` has `needs: build-push`, so it only runs after the build succeeds.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## One-time setup — DEV PC (the `windows` runner) ✅ already done
|
|
||||||
|
|
||||||
The dev PC runs act_runner natively with the label `windows:host`, using its
|
|
||||||
installed Docker Desktop + .NET 8 SDK. The workflow's `build-push` job targets
|
|
||||||
`runs-on: windows`. Requirements (for reference):
|
|
||||||
|
|
||||||
- Docker Desktop running, and `docker` on PATH.
|
|
||||||
- .NET 8 SDK on PATH (`dotnet test` runs on this machine).
|
|
||||||
- The `build-push` job uses `shell: powershell`. Do **not** switch it to
|
|
||||||
`shell: bash`: act_runner in Windows host mode mislocates the generated `.sh`
|
|
||||||
script (`/bin/bash: .../1.sh: No such file or directory`). PowerShell avoids it.
|
|
||||||
|
|
||||||
## One-time setup — NAS (the `nas` runner)
|
|
||||||
|
|
||||||
1. **Deploy dir + secrets** (via SSH or File Station):
|
|
||||||
```bash
|
|
||||||
mkdir -p /volume1/docker/rolac/nginx/conf.d /volume1/docker/rolac/data/api-storage
|
|
||||||
cp /path/to/repo/deploy/nas/.env.example /volume1/docker/rolac/.env
|
|
||||||
# edit /volume1/docker/rolac/.env -> real DB user/password + JWT_SECRET + APP_ORIGIN
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Registry token** — in Gitea: Settings → Applications → new token with
|
|
||||||
`read:package` + `write:package`. Log the NAS Docker in once:
|
|
||||||
```bash
|
|
||||||
docker login git.golife.love -u ChrisChen # paste the token
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Install act_runner on the NAS** (Container Manager → Registry → `gitea/act_runner`,
|
|
||||||
or `docker run`). It must:
|
|
||||||
- mount the host Docker socket: `-v /var/run/docker.sock:/var/run/docker.sock`
|
|
||||||
- mount the deploy dir at the same path: `-v /volume1/docker/rolac:/volume1/docker/rolac`
|
|
||||||
- register with the label **`nas`** (this is what `runs-on: nas` targets).
|
|
||||||
```bash
|
|
||||||
docker run -d --restart unless-stopped --name rolac-runner \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-v /volume1/docker/rolac:/volume1/docker/rolac \
|
|
||||||
-e GITEA_INSTANCE_URL=https://git.golife.love \
|
|
||||||
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
|
|
||||||
-e GITEA_RUNNER_LABELS=nas \
|
|
||||||
gitea/act_runner:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## One-time setup — Gitea repo
|
|
||||||
|
|
||||||
1. **Secrets** (Settings → Actions → Secrets):
|
|
||||||
- `REGISTRY_USER` = `ChrisChen`
|
|
||||||
- `REGISTRY_TOKEN` = the package token (with `write:package`)
|
|
||||||
2. **Enable Actions** for the repo if not already (Settings → Advanced → Actions).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Day-to-day
|
|
||||||
|
|
||||||
`git push` to `main` → `.gitea/workflows/ci-cd-nas.yml`:
|
|
||||||
|
|
||||||
1. **dev PC** (`builder`): `dotnet test` → build `rolac-api` + `rolac-app`
|
|
||||||
(tags `:latest` and `:<git-sha>`) → push to `git.golife.love/chrischen/*`.
|
|
||||||
2. **NAS** (`nas`): sync compose/nginx → `TAG=<git-sha> docker compose pull` →
|
|
||||||
`docker compose up -d` → `curl /api/health`.
|
|
||||||
|
|
||||||
Open `http://<nas-ip>:8080` and log in.
|
|
||||||
|
|
||||||
Deploy pins `TAG=<git-sha>` (not `latest`), so the NAS always runs exactly the image
|
|
||||||
this commit produced and `compose pull` forces a fresh fetch.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Manual fallback (no runners yet)
|
|
||||||
|
|
||||||
From the dev PC (Docker Desktop + `docker login git.golife.love`):
|
|
||||||
```powershell
|
|
||||||
# repo root — build + push both images (tags :latest and :<git-sha>)
|
|
||||||
.\deploy\build-push.ps1
|
|
||||||
```
|
|
||||||
Then on the NAS:
|
|
||||||
```bash
|
|
||||||
cd /volume1/docker/rolac
|
|
||||||
docker compose pull
|
|
||||||
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). Make sure the DB user has DDL rights;
|
|
||||||
back up before the first run.
|
|
||||||
- **Bind-mount paths**: the NAS runner runs compose at `/volume1/docker/rolac` on the
|
|
||||||
host (socket-mounted), so `./nginx/conf.d` and `./data` resolve to real NAS paths —
|
|
||||||
that's why the runner mounts that dir at the *same* path.
|
|
||||||
- **Uploaded files** persist under `/volume1/docker/rolac/data/api-storage`.
|
|
||||||
- **DS220+ runs, never builds.** Keep all compilation on the dev PC / a beefier runner.
|
|
||||||
- To expose beyond the LAN later, put it behind DSM's reverse proxy (Application Portal)
|
|
||||||
or switch to the Azure `deploy/` files with certbot.
|
|
||||||
@@ -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.
|
||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
ConnectionStrings__DefaultConnection: ${DB_CONNECTION}
|
ConnectionStrings__DefaultConnection: ${DB_CONNECTION}
|
||||||
Jwt__SecretKey: ${JWT_SECRET}
|
Jwt__SecretKey: ${JWT_SECRET}
|
||||||
# Same-origin /api means CORS is not triggered by the browser; this is only
|
# 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 NAS URL.
|
# a safety net for direct cross-origin calls / tools. Set to your VM URL.
|
||||||
Cors__AllowedOrigins__0: ${APP_ORIGIN:-http://localhost:8080}
|
Cors__AllowedOrigins__0: ${APP_ORIGIN:-http://localhost:8080}
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/api-storage:/app/App_Data/storage
|
- ./data/api-storage:/app/App_Data/storage
|
||||||
@@ -21,7 +21,8 @@ services:
|
|||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
# DSM already uses 80/443, so the edge is published on 8080 (HTTP, LAN only).
|
# 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"]
|
ports: ["8080:80"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
||||||
@@ -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"]
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user