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"