diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b220708 --- /dev/null +++ b/.dockerignore @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..abea2e6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7f4759b..f5e7751 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,10 +4,19 @@ on: push: branches: - 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: build_and_deploy: 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: - name: Checkout code @@ -35,5 +44,10 @@ jobs: sudo docker compose up -d --build --force-recreate echo "Deployment complete." + - name: Remove environment file + if: always() + working-directory: ${{ github.workspace }} + run: rm -f .env + - name: Clean up old Docker images run: sudo docker image prune -f diff --git a/Dockerfile b/Dockerfile index 92180b1..8a5dff4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,14 +17,14 @@ RUN mkdir src && \ 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) -RUN cargo build --release && \ +RUN cargo build --locked --release && \ rm -rf src target/release/deps/dsec_bot* # Copy the actual source code COPY src ./src # Build the actual application -RUN cargo build --release +RUN cargo build --locked --release # Runtime stage - use a minimal image FROM debian:bookworm-slim