SEC-01: add pull-request CI and stop the deploy leaving DOT_ENV on the VPS

A merge to main runs the merged commit as root on the club VPS through the
self-hosted runner, and this repo has no PR CI, no required review and no
status check.

- .github/workflows/ci.yml: fmt / clippy / build --locked / test on
  ubuntu-latest, contents: read, both actions pinned by commit SHA. Never
  the VPS.
- deploy.yml: keep push to main, add workflow_dispatch, permissions
  contents: read, environment: production, and an if: always() step that
  removes the .env the deploy writes into the workspace.
- .dockerignore: keep that .env, target/ and .git out of the build context.
- Dockerfile: --locked on both cargo build --release lines.

environment: production gates nothing until required reviewers are configured
on the environment itself, and the ruleset still requires zero approving
reviews. Both are owner-only and listed in the pull request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam Limbu 2026-08-27 09:40:51 +10:00
parent 88e50b1dc4
commit 9932bdcecb
4 changed files with 107 additions and 2 deletions

9
.dockerignore Normal file
View file

@ -0,0 +1,9 @@
# Keep the docker build context to what the Dockerfile actually COPYs
# (Cargo.toml, Cargo.lock, src). deploy.yml writes the DOT_ENV secret to .env in
# the workspace before running `docker compose up --build`; without this file
# that secret is tarred into the build context and handed to the docker daemon
# on every deploy.
.env
.env.*
target/
.git

82
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,82 @@
# Format, lint, build and test on every pull request.
#
# This must run on a GitHub-hosted runner and never on the club VPS. The runner
# deploy.yml uses lives on the club's own server and executes whatever a workflow
# tells it to, as root, so nothing triggered by a pull request may be pointed at
# it. Read that as a rule for this file, not a property of the repository: this
# repo is public, forking is on, and fork pull requests only need approval from a
# first-time contributor, so a fork that brings its own workflow can still reach
# that runner. See SEC-01.
#
# SEC-01: point the protect-main ruleset's required status check at this job's
# name ("fmt / clippy / build / test") so a red build blocks the merge that
# deploys.
name: CI
on:
pull_request:
# A newer push to the same pull request makes an in-flight run irrelevant.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
check:
name: fmt / clippy / build / test
runs-on: ubuntu-latest
# A cold build of this dependency tree (serenity, reqwest, image, exr) in
# release, then clippy, then test, is slow on a 4-vCPU hosted runner. This is
# a guard against a hung job, not a target.
timeout-minutes: 45
steps:
# Third-party and first-party actions alike are pinned to a full commit
# SHA: a tag is mutable and can be repointed at new code by whoever owns
# the action.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Caches written by a pull_request run are scoped to that pull request, so
# this pays off across pushes to the same branch rather than across branches.
- name: Cache cargo registry and build artifacts
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
# actions/runner-images documents rustfmt for ubuntu-24.04 but not clippy (it
# lists clippy only under ubuntu-22.04), while the image actually in use does
# ship it. Adding both components is a no-op when they are present and keeps
# this job working if that undocumented extra ever goes away. Print the
# versions too: clippy's lint set moves between releases, so knowing which one
# ran is what explains a lint that appeared from nowhere.
- name: Toolchain
run: |
rustup component add clippy rustfmt
cargo --version
cargo fmt --version
cargo clippy --version
- name: Format
run: cargo fmt --all -- --check
# Kept ahead of clippy and test: those resolve dependencies and would
# refresh Cargo.lock in place, so a stale lockfile would slip past
# --locked if they ran first.
- name: Build
run: cargo build --locked --release
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Test
run: cargo test

View file

@ -4,10 +4,19 @@ on:
push: push:
branches: branches:
- main - main
# Manual re-run, for redeploying after a rollback or re-creating the
# workspace .env that the cleanup step below now removes.
workflow_dispatch:
permissions:
contents: read
jobs: jobs:
build_and_deploy: build_and_deploy:
runs-on: [self-hosted, linux] runs-on: [self-hosted, linux]
# SEC-01: this gates nothing until required reviewers are configured on the
# "production" environment itself. See the pull request description.
environment: production
steps: steps:
- name: Checkout code - name: Checkout code
@ -35,5 +44,10 @@ jobs:
sudo docker compose up -d --build --force-recreate sudo docker compose up -d --build --force-recreate
echo "Deployment complete." echo "Deployment complete."
- name: Remove environment file
if: always()
working-directory: ${{ github.workspace }}
run: rm -f .env
- name: Clean up old Docker images - name: Clean up old Docker images
run: sudo docker image prune -f run: sudo docker image prune -f

View file

@ -17,14 +17,14 @@ RUN mkdir src && \
echo 'fn main() { println!("Dummy main for dependency caching"); }' > src/main.rs echo 'fn main() { println!("Dummy main for dependency caching"); }' > src/main.rs
# Build dependencies (this layer will be cached unless Cargo.toml/Cargo.lock changes) # Build dependencies (this layer will be cached unless Cargo.toml/Cargo.lock changes)
RUN cargo build --release && \ RUN cargo build --locked --release && \
rm -rf src target/release/deps/dsec_bot* rm -rf src target/release/deps/dsec_bot*
# Copy the actual source code # Copy the actual source code
COPY src ./src COPY src ./src
# Build the actual application # Build the actual application
RUN cargo build --release RUN cargo build --locked --release
# Runtime stage - use a minimal image # Runtime stage - use a minimal image
FROM debian:bookworm-slim FROM debian:bookworm-slim