Add Gitea Actions CI/CD and deploy descriptor
- .gitea/workflows/ci.yaml: test on PR/push to master, deploy on push to master - deploy/docker-compose.yml: 15-puzzle container on the edge network with a persistent data volume - Dockerfile: create /data owned by app for the DATA_FILE volume - README: document deployment, required Actions secrets/variables, and persistence
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
name: CI
|
||||
|
||||
# Tests and deploys the game into the host Docker daemon.
|
||||
#
|
||||
# * pull_request -> master : runs the server test suite (gates the merge).
|
||||
# * push -> master (a merge): runs the tests, then rebuilds the image and
|
||||
# (re)deploys the `15-puzzle` container via deploy/docker-compose.yml.
|
||||
#
|
||||
# The runner is host-mode, so docker / docker compose / git are available
|
||||
# directly and the image is built locally on the host daemon (no registry) —
|
||||
# the same pattern as ../galaxy-game/.gitea/workflows/dev-deploy.yaml.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths-ignore: ['**.md']
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths-ignore: ['**.md']
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Run Go tests
|
||||
# internal/puzzle and cmd/ui pull in desktop Ebitengine, which needs C
|
||||
# libraries (alsa, libX11, Xrandr) that are not installed on the runner
|
||||
# host. We test the server-side packages instead — exactly the code that
|
||||
# runs inside the deployed container (HMAC validation, file repository,
|
||||
# HTTP API). The wasm build is exercised by docker build in the deploy job.
|
||||
run: go test -count=1 ./internal/repo/... ./internal/validator/... ./internal/web-service/...
|
||||
|
||||
deploy:
|
||||
needs: test
|
||||
if: ${{ gitea.event_name == 'push' }}
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build image and (re)deploy container
|
||||
working-directory: deploy
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
ACCESS_CODE: ${{ secrets.ACCESS_CODE }}
|
||||
PROJECT_LINK: ${{ vars.PROJECT_LINK }}
|
||||
DATA_FILE: ${{ vars.DATA_FILE }}
|
||||
SERVER_PORT: ${{ vars.SERVER_PORT }}
|
||||
CONTEXT_ROOT: ${{ vars.CONTEXT_ROOT }}
|
||||
run: docker compose up -d --build --remove-orphans
|
||||
|
||||
- name: Probe the container
|
||||
env:
|
||||
SERVER_PORT: ${{ vars.SERVER_PORT }}
|
||||
CONTEXT_ROOT: ${{ vars.CONTEXT_ROOT }}
|
||||
run: |
|
||||
set -u
|
||||
port="${SERVER_PORT:-8080}"
|
||||
root="${CONTEXT_ROOT:-/}"; root="/${root#/}"; root="${root%/}"
|
||||
url="http://15-puzzle:${port}${root}/puzzle.html"
|
||||
echo "probing ${url}"
|
||||
for i in $(seq 1 15); do
|
||||
if docker run --rm --network edge alpine:3.20 \
|
||||
wget -q -T 5 -O /dev/null "${url}"; then
|
||||
echo "healthy: ${url}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "probe failed for ${url}; recent container logs:"
|
||||
docker logs --tail 50 15-puzzle || true
|
||||
exit 1
|
||||
|
||||
- name: Prune dangling images
|
||||
if: always()
|
||||
run: docker image prune -f
|
||||
@@ -40,6 +40,12 @@ RUN apk add --no-cache tzdata ; \
|
||||
getent group ${GID} || addgroup --gid ${GID} ${TARGET_GROUP} ; \
|
||||
adduser -u ${UID} -g ${TARGET_GROUP} -D -S -h ${APPLICATION_HOME} ${TARGET_USER}
|
||||
WORKDIR ${APPLICATION_HOME}
|
||||
|
||||
# Persisted game-data directory (DATA_FILE lives here). A named volume mounted
|
||||
# at /data inherits this ownership on first use, so the unprivileged app user
|
||||
# can write the temp-file + rename that repo.go performs.
|
||||
RUN mkdir -p /data && chown ${TARGET_USER}:${TARGET_GROUP} /data
|
||||
|
||||
USER ${TARGET_USER}
|
||||
|
||||
COPY --from=builder /tmp/bin/server /usr/bin/server
|
||||
|
||||
@@ -37,6 +37,40 @@ Certain environment variables should be set for the server to start:
|
||||
| `CONTEXT_ROOT` | Server requests URI root path, defaulting to `/` if not set. |
|
||||
| `STATIC_DIR` | Directory where static files are located, defaulting to the current directory if not set. |
|
||||
|
||||
## Deployment
|
||||
|
||||
The repository ships a [Gitea Actions](https://docs.gitea.com/usage/actions/overview)
|
||||
workflow ([`.gitea/workflows/ci.yaml`](.gitea/workflows/ci.yaml)) that runs the
|
||||
server test suite on every pull request to `master`, and on every push to
|
||||
`master` additionally rebuilds the Docker image and (re)deploys the container on
|
||||
the host Docker daemon through [`deploy/docker-compose.yml`](deploy/docker-compose.yml).
|
||||
|
||||
The deploy job runs in host mode: it builds the image locally (no registry) and
|
||||
recreates the `15-puzzle` container attached to the external `edge` Docker
|
||||
network, so a host reverse proxy can route to `15-puzzle:8080`.
|
||||
|
||||
### Required Actions secrets and variables
|
||||
|
||||
Configure these under the repository's *Settings → Actions*:
|
||||
|
||||
| Kind | Name | Example / note |
|
||||
| - | - | - |
|
||||
| Secret | `BOT_TOKEN` | Telegram Bot API token. |
|
||||
| Secret | `ACCESS_CODE` | Pin-code for the Statistics Screen. |
|
||||
| Variable | `PROJECT_LINK` | URL to the project's source code. |
|
||||
| Variable | `DATA_FILE` | `/data/data.json` (lives on the persistent volume). |
|
||||
| Variable | `SERVER_PORT` | `8080`. |
|
||||
| Variable | `CONTEXT_ROOT` | `/`. |
|
||||
|
||||
`STATIC_DIR` is intentionally left unset: the static assets (HTML, `game.wasm`,
|
||||
`wasm_exec.js`) are baked into the image and served from the working directory.
|
||||
|
||||
### Persistence
|
||||
|
||||
Game data is stored on the named Docker volume `15-puzzle-data` mounted at
|
||||
`/data`, so player ratings survive container recreation. The image creates
|
||||
`/data` owned by the unprivileged `app` user so the volume is writable.
|
||||
|
||||
## Credits
|
||||
|
||||
- [Ebitengine](https://github.com/hajimehoshi/ebiten) game engine by Hajime Hoshi.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Deploy descriptor for the 15-puzzle container on the host Docker daemon.
|
||||
# Driven by .gitea/workflows/ci.yaml (`docker compose up -d --build`); env
|
||||
# values are interpolated from Gitea Actions secrets/variables exported by the
|
||||
# deploy job. Joins the external `edge` network so the host reverse proxy can
|
||||
# route to `15-puzzle:8080`.
|
||||
name: 15-puzzle
|
||||
|
||||
services:
|
||||
app:
|
||||
container_name: 15-puzzle
|
||||
image: 15-puzzle:latest
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
BOT_TOKEN: ${BOT_TOKEN:?set BOT_TOKEN}
|
||||
ACCESS_CODE: ${ACCESS_CODE:?set ACCESS_CODE}
|
||||
PROJECT_LINK: ${PROJECT_LINK:?set PROJECT_LINK}
|
||||
DATA_FILE: ${DATA_FILE:-/data/data.json}
|
||||
SERVER_PORT: ${SERVER_PORT:-8080}
|
||||
CONTEXT_ROOT: ${CONTEXT_ROOT:-/}
|
||||
volumes:
|
||||
- data:/data
|
||||
networks:
|
||||
- edge
|
||||
|
||||
volumes:
|
||||
data:
|
||||
name: 15-puzzle-data
|
||||
|
||||
networks:
|
||||
edge:
|
||||
external: true
|
||||
Reference in New Issue
Block a user