d50a7fd049
The runner executes jobs inside a container that ships no docker client, so the publish job died on its first command: /var/run/act/workflow/1: line 3: docker: command not found act_runner does mount the host's socket into job containers, so only the client is missing. Both image jobs now fetch the static binary when it is absent and then check that the socket answers, because "no client" and "no daemon" are different problems and the log should say which one it hit. The docker build-test job needed the same treatment — it is skipped on tags, so it had never reached that command either. Verified in a container without a docker client: with the socket mounted the step installs the client and builds the web image; without it the step fails with the runner-configuration message instead of a confusing connection error.
201 lines
7.0 KiB
YAML
201 lines
7.0 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
tags: ["v*"]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
env:
|
|
NODE_VERSION: "20"
|
|
PNPM_VERSION: "9.15.4"
|
|
# 1.85 is the floor: dependencies now ship edition 2024, which older
|
|
# Cargo refuses to even parse. Keep this in step with the toolchain
|
|
# pinned in apps/daemon/Dockerfile.
|
|
RUST_TOOLCHAIN: "1.97"
|
|
# Static client only; the daemon comes from the socket the runner mounts.
|
|
DOCKER_CLI_VERSION: "29.7.1"
|
|
|
|
jobs:
|
|
# --- Lint + TypeScript Check ---
|
|
lint:
|
|
name: Lint & Type Check
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: pnpm
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
|
|
- name: TypeScript check (shared)
|
|
run: pnpm --filter @source/shared build
|
|
|
|
- name: TypeScript check (database)
|
|
run: pnpm --filter @source/database build
|
|
|
|
- name: TypeScript check (API)
|
|
run: pnpm --filter @source/api build
|
|
|
|
- name: TypeScript check (Web)
|
|
run: pnpm --filter @source/web build
|
|
|
|
- name: Lint
|
|
run: pnpm lint
|
|
|
|
- name: Format check
|
|
run: pnpm format:check
|
|
|
|
# --- Rust Daemon ---
|
|
daemon:
|
|
name: Daemon Build & Test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Self-hosted act runners run as root in a container that has no sudo,
|
|
# while GitHub-hosted runners need it. Pick whichever exists.
|
|
- name: Install protoc
|
|
run: |
|
|
SUDO=""
|
|
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
|
$SUDO apt-get update
|
|
$SUDO apt-get install -y protobuf-compiler
|
|
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: apps/daemon
|
|
|
|
- name: Check
|
|
working-directory: apps/daemon
|
|
run: cargo check
|
|
|
|
- name: Test
|
|
working-directory: apps/daemon
|
|
run: cargo test
|
|
|
|
- name: Clippy
|
|
working-directory: apps/daemon
|
|
run: cargo clippy -- -D warnings || true
|
|
|
|
# --- Docker Build Test ---
|
|
docker:
|
|
name: Docker Build
|
|
runs-on: ubuntu-latest
|
|
needs: [lint, daemon]
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# The runner executes jobs inside a container that has no docker CLI,
|
|
# while act_runner mounts the host's socket at /var/run/docker.sock.
|
|
# Install just the client when it is missing, then prove the socket is
|
|
# actually reachable — the two failure modes look nothing alike and the
|
|
# message should say which one happened.
|
|
- name: Ensure docker CLI
|
|
run: |
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
url="https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_CLI_VERSION}.tgz"
|
|
if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" -o /tmp/docker.tgz
|
|
else wget -qO /tmp/docker.tgz "$url"; fi
|
|
tar -xzf /tmp/docker.tgz -C /usr/local/bin --strip-components=1 docker/docker
|
|
fi
|
|
docker --version
|
|
docker version >/dev/null 2>&1 || {
|
|
echo "The docker socket is not reachable from this job."
|
|
echo "act_runner must mount it: leave container.docker_host empty in its config.yaml."
|
|
exit 1
|
|
}
|
|
|
|
- name: Build API image
|
|
run: docker build -f apps/api/Dockerfile -t gamepanel-api:ci .
|
|
|
|
- name: Build Web image
|
|
run: docker build -f apps/web/Dockerfile -t gamepanel-web:ci .
|
|
|
|
- name: Build Daemon image
|
|
run: docker build -f apps/daemon/Dockerfile -t gamepanel-daemon:ci .
|
|
|
|
# --- Publish images (tags only) ---
|
|
#
|
|
# docker-compose.panel.yml deploys from these images, so the stack can be
|
|
# installed on a server that has no checkout of this repository — that is
|
|
# what a control panel needs.
|
|
#
|
|
# Plain `docker build` + `docker push` on purpose: no buildx or bake, so the
|
|
# job runs on the same self-hosted runner as the build test above.
|
|
#
|
|
# Requires a REGISTRY_TOKEN secret with package write scope. The registry is
|
|
# this Gitea instance's own container registry; the panel pulls from it.
|
|
publish:
|
|
name: Publish images
|
|
runs-on: ubuntu-latest
|
|
needs: [lint, daemon]
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
REGISTRY: gits.hibna.com.tr/hibna
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# The runner executes jobs inside a container that has no docker CLI,
|
|
# while act_runner mounts the host's socket at /var/run/docker.sock.
|
|
# Install just the client when it is missing, then prove the socket is
|
|
# actually reachable — the two failure modes look nothing alike and the
|
|
# message should say which one happened.
|
|
- name: Ensure docker CLI
|
|
run: |
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
url="https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_CLI_VERSION}.tgz"
|
|
if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" -o /tmp/docker.tgz
|
|
else wget -qO /tmp/docker.tgz "$url"; fi
|
|
tar -xzf /tmp/docker.tgz -C /usr/local/bin --strip-components=1 docker/docker
|
|
fi
|
|
docker --version
|
|
docker version >/dev/null 2>&1 || {
|
|
echo "The docker socket is not reachable from this job."
|
|
echo "act_runner must mount it: leave container.docker_host empty in its config.yaml."
|
|
exit 1
|
|
}
|
|
|
|
- name: Registry login
|
|
run: |
|
|
printf '%s' "${{ secrets.REGISTRY_TOKEN }}" |
|
|
docker login gits.hibna.com.tr -u "${{ github.actor }}" --password-stdin
|
|
|
|
- name: Resolve tag
|
|
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV"
|
|
|
|
# The API Dockerfile carries the migration runner as its own stage; it
|
|
# has to be pushed as a separate image because docker-compose.panel.yml
|
|
# runs it as a one-shot service before the API starts.
|
|
- name: API + migrate
|
|
run: |
|
|
docker build -f apps/api/Dockerfile -t "$REGISTRY/gamepanel-api:$TAG" .
|
|
docker build -f apps/api/Dockerfile --target migrate -t "$REGISTRY/gamepanel-migrate:$TAG" .
|
|
docker push "$REGISTRY/gamepanel-api:$TAG"
|
|
docker push "$REGISTRY/gamepanel-migrate:$TAG"
|
|
|
|
# VITE_API_URL is baked in at build time: the SPA calls /api on its own
|
|
# origin, which the image's nginx proxies to the api service.
|
|
- name: Web
|
|
run: |
|
|
docker build -f apps/web/Dockerfile --build-arg VITE_API_URL=/api -t "$REGISTRY/gamepanel-web:$TAG" .
|
|
docker push "$REGISTRY/gamepanel-web:$TAG"
|
|
|
|
- name: Daemon
|
|
run: |
|
|
docker build -f apps/daemon/Dockerfile -t "$REGISTRY/gamepanel-daemon:$TAG" .
|
|
docker push "$REGISTRY/gamepanel-daemon:$TAG"
|