From 408db0d0cc0795a1850dfb0e10d2bf2f12db4289 Mon Sep 17 00:00:00 2001 From: Charles N Wyble Date: Wed, 10 Sep 2025 21:48:32 +0000 Subject: [PATCH] chore(ci): bootstrap CI + hooks Squash-merge bootstrap-cicd into integration --- .gitea/workflows/ci.yml | 24 +++++++ .gitea/workflows/nightly.yml | 19 ++++++ .gitea/workflows/release.yml | 29 +++++++++ .githooks/commit-msg | 5 ++ .githooks/pre-commit | 11 ++++ .githooks/pre-push | 11 ++++ Makefile | 33 ++++++++++ RESUME.md | 53 +++++++++++++++ TODO.md | 34 ++++++++++ ci.Dockerfile | 40 ++++++++++++ commitlint.config.cjs | 4 ++ docker/ci.compose.yml | 13 ++++ instructions/bootstrap-cicd.md | 36 +++++++++++ instructions/git-workflow.md | 36 +++++++++++ proposals/bootstrap-cicd.md | 69 ++++++++++++++++++++ questions/bootstrap-cicd.md | 110 +++++++++++++++++++++++++++++++ scripts/ci | 115 +++++++++++++++++++++++++++++++++ scripts/commitlint-hook | 10 +++ scripts/setup-hooks | 19 ++++++ 19 files changed, 671 insertions(+) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/nightly.yml create mode 100644 .gitea/workflows/release.yml create mode 100644 .githooks/commit-msg create mode 100644 .githooks/pre-commit create mode 100644 .githooks/pre-push create mode 100644 Makefile create mode 100644 RESUME.md create mode 100644 TODO.md create mode 100644 ci.Dockerfile create mode 100644 commitlint.config.cjs create mode 100644 docker/ci.compose.yml create mode 100644 instructions/bootstrap-cicd.md create mode 100644 instructions/git-workflow.md create mode 100644 proposals/bootstrap-cicd.md create mode 100644 questions/bootstrap-cicd.md create mode 100755 scripts/ci create mode 100755 scripts/commitlint-hook create mode 100755 scripts/setup-hooks diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f2f133c --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + pull_request: + branches: ["**"] + push: + branches: ["integration", "bootstrap", "bootstrap-cicd"] + +jobs: + checks: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build CI image + run: docker build -f ci.Dockerfile -t local/ci:latest . + + - name: Lint + run: docker run --rm -v ${{ github.workspace }}:/workspace local/ci:latest bash -lc "cd /workspace && IN_CI_CONTAINER=1 scripts/ci lint" + + - name: Build validation + run: docker run --rm -v ${{ github.workspace }}:/workspace local/ci:latest bash -lc "cd /workspace && IN_CI_CONTAINER=1 scripts/ci build" + diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml new file mode 100644 index 0000000..4d12627 --- /dev/null +++ b/.gitea/workflows/nightly.yml @@ -0,0 +1,19 @@ +name: Nightly + +on: + schedule: + - cron: '0 3 * * *' + +jobs: + report: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build CI image + run: docker build -f ci.Dockerfile -t local/ci:latest . + + - name: Lint (nightly) + run: docker run --rm -v ${{ github.workspace }}:/workspace local/ci:latest bash -lc "cd /workspace && IN_CI_CONTAINER=1 scripts/ci lint" + diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..ecc296c --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release + +on: + push: + branches: ["main"] + +jobs: + tag-and-notes: + runs-on: docker + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build CI image + run: docker build -f ci.Dockerfile -t local/ci:latest . + + - name: Compute tag + id: tag + run: | + TZ=UTC date +"v%Y.%m.%d-%H%M" > tag.txt + echo "tag=$(cat tag.txt)" >> $GITHUB_OUTPUT + + - name: Create annotated tag + run: | + git config user.name "ci" + git config user.email "ci@local" + git tag -a ${{ steps.tag.outputs.tag }} -m "Release ${{ steps.tag.outputs.tag }}" + git push origin ${{ steps.tag.outputs.tag }} + diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100644 index 0000000..69abee3 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +scripts/commitlint-hook "$1" + diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..9b3ecda --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "> pre-commit: format + lint + commit message check" + +# Run format and lint inside the CI container +scripts/ci format +scripts/ci lint + +echo "pre-commit completed." + diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100644 index 0000000..17a2682 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "> pre-push: build validation + placeholders for tests/security" + +scripts/ci build +scripts/ci test +scripts/ci security + +echo "pre-push completed." + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ff8b74f --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +SHELL := /usr/bin/env bash + +.PHONY: all check quick format lint build test security ci-image hooks-setup + +all: check + +check: + ./scripts/ci all + +quick: + ./scripts/ci format && ./scripts/ci lint + +format: + ./scripts/ci format + +lint: + ./scripts/ci lint + +build: + ./scripts/ci build + +test: + ./scripts/ci test + +security: + ./scripts/ci security + +ci-image: + docker build -f ci.Dockerfile -t local/ci:latest . + +hooks-setup: + ./scripts/setup-hooks + diff --git a/RESUME.md b/RESUME.md new file mode 100644 index 0000000..726e303 --- /dev/null +++ b/RESUME.md @@ -0,0 +1,53 @@ +Resume Guide + +Purpose +- Quick checklist to pick up work after restarting Codex CLI with expanded permissions. + +Branches on remote +- main (default), integration, release, bootstrap, bootstrap-cicd + +1) Pull latest +- git fetch --all --prune +- git switch bootstrap && git pull +- git switch bootstrap-cicd && git pull +- git switch integration && git pull + +2) Ensure Docker is available +- Start Docker Desktop/daemon as needed + +3) Install hooks locally +- make hooks-setup + +4) Run local checks (Docker-only) +- git switch bootstrap && make quick && make build +- git switch bootstrap-cicd && make quick && make build +- Optional full pass: make check + +5) Open PRs (when branches are green locally) +- bootstrap → integration: https://git.knownelement.com/KNEL/LLMScaffolding/pulls/new/bootstrap +- bootstrap-cicd → integration: https://git.knownelement.com/KNEL/LLMScaffolding/pulls/new/bootstrap-cicd + +6) Merge to integration +- Use squash merge, allow auto-merge on green where configured + +7) Release to main +- Open PR: integration → main (require 1 approval) +- After merge, tag manually (until CI runners are enabled): + - git switch main && git pull + - TAG=$(date -u +"v%Y.%m.%d-%H%M") + - git tag -a "$TAG" -m "Release $TAG" + - git push origin "$TAG" +- Optional: fast-forward release branch pointer: + - git branch -f release main && git push -f origin release + +8) Docs & parity +- Git workflow: instructions/git-workflow.md +- Local CI parity: instructions/bootstrap-cicd.md + +9) Defer CI enablement for two weeks +- Track in TODO.md: Revisit enabling runners and protected checks on 2025-09-24 + +10) Next tasks +- Answer any outstanding questions in questions/* +- On approval, implement further proposals and update instructions/* + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..15933ed --- /dev/null +++ b/TODO.md @@ -0,0 +1,34 @@ +TODO + +- Git workflow + - [x] Questions gathered and answered + - [x] Proposal iteration 2 drafted + - [x] Finalize approval and capture in instructions/git-workflow.md + +- Branches + - [x] Create integration, release, bootstrap from main + - [x] Push bootstrap to origin + - [ ] Decide whether to maintain a fast-forwarded release branch to the latest tag + +- CI/CD bootstrap + - [x] Create branch bootstrap-cicd from main + - [x] Add questions at questions/bootstrap-cicd.md + - [x] Draft proposal based on answers + - [x] Implement parity tooling: scripts/ci, ci.Dockerfile, docker/ci.compose.yml + - [x] Add .gitea/workflows: ci.yml, release.yml, nightly.yml + - [x] Add commitlint.config.cjs, Makefile + - [ ] Optional: add .pre-commit-config.yaml (defer for now) + - [ ] Optional: add CODEOWNERS + +- Protections & settings (in Gitea UI) + - [ ] Protect main and release/* with required checks + - [ ] Leave integration unprotected; allow auto-merge on green + - [ ] Require 1 approval for integration→main + - [ ] Revisit enabling CI and protections after runners are ready (target: 2025-09-24) + +- Releases + - [ ] Tag format vYYYY.MM.DD-HHMM (UTC) in release workflow + - [ ] Optional: fast-forward release branch to latest tag + +- Docs + - [ ] Write docs/engineering/git-workflow.md with diagrams and examples diff --git a/ci.Dockerfile b/ci.Dockerfile new file mode 100644 index 0000000..605d76f --- /dev/null +++ b/ci.Dockerfile @@ -0,0 +1,40 @@ +FROM debian:12-slim + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git bash coreutils findutils file python3 python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install shfmt, hadolint, actionlint (static), shellcheck, yamllint, node tools +RUN set -eux; \ + # shellcheck + apt-get update && apt-get install -y --no-install-recommends shellcheck && rm -rf /var/lib/apt/lists/*; \ + # shfmt + SHFMT_VER=3.7.0; curl -fsSL -o /usr/local/bin/shfmt https://github.com/mvdan/sh/releases/download/v${SHFMT_VER}/shfmt_v${SHFMT_VER}_linux_amd64 && chmod +x /usr/local/bin/shfmt; \ + # hadolint + HADOLINT_VER=2.12.0; curl -fsSL -o /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VER}/hadolint-Linux-x86_64 && chmod +x /usr/local/bin/hadolint; + +# actionlint +RUN set -eux; \ + AL_VER=1.7.1; \ + curl -fsSL -o /usr/local/bin/actionlint https://github.com/rhysd/actionlint/releases/download/v${AL_VER}/actionlint_${AL_VER}_linux_amd64.tar.gz; \ + tar -C /usr/local/bin -xzf /usr/local/bin/actionlint; \ + rm -f /usr/local/bin/actionlint + +# yamllint via pip (allow install on Debian's externally-managed Python) +RUN pip3 install --break-system-packages --no-cache-dir yamllint==1.35.1 + +# Node + npm for prettier, markdownlint, commitlint +RUN set -eux; \ + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ + apt-get update && apt-get install -y --no-install-recommends nodejs && \ + rm -rf /var/lib/apt/lists/* + +RUN npm --location=global install \ + prettier@3.3.3 \ + markdownlint-cli@0.39.0 \ + @commitlint/cli@19.5.0 @commitlint/config-conventional@19.5.0 + +WORKDIR /workspace +ENTRYPOINT ["bash","-lc"] +CMD ["bash"] diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 0000000..3acb487 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,4 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; + diff --git a/docker/ci.compose.yml b/docker/ci.compose.yml new file mode 100644 index 0000000..6f370e7 --- /dev/null +++ b/docker/ci.compose.yml @@ -0,0 +1,13 @@ +services: + ci: + build: + context: .. + dockerfile: ci.Dockerfile + working_dir: /workspace + volumes: + - "../:/workspace:Z" + environment: + - IN_CI_CONTAINER=1 + entrypoint: ["bash","-lc"] + command: ["bash"] + diff --git a/instructions/bootstrap-cicd.md b/instructions/bootstrap-cicd.md new file mode 100644 index 0000000..9ccf1fe --- /dev/null +++ b/instructions/bootstrap-cicd.md @@ -0,0 +1,36 @@ +Bootstrap CI/CD – Finalized Instructions (Phase 1) + +Goal +- Provide Docker‑only local checks and Git hooks with parity to future CI. CI workflows are prepared but may remain disabled until runners are ready. + +Requirements +- Docker + Docker Compose v2 on the development machine. No host packages beyond Docker are required. + +Local Checks +- Entry point: `scripts/ci ` where phase ∈ {format, lint, build, test, security, all}. +- Always runs inside the ci container using `docker/ci.compose.yml`. +- Tools pinned in `ci.Dockerfile`: shfmt, shellcheck, hadolint, yamllint, actionlint, prettier, markdownlint, commitlint. + +Hooks +- Install hooks: `make hooks-setup` (copies .githooks/* into .git/hooks). +- pre-commit: runs format + lint. +- commit-msg: runs commitlint (Conventional Commits). +- pre-push: runs build; test and security are present but currently no‑ops. + +Convenience Targets +- `make quick` → format + lint. +- `make check` → all phases. +- `make build` → compose validation. + +CI (Prepared, optional enablement later) +- .gitea/workflows/ci.yml: builds ci image; runs lint + build. +- .gitea/workflows/release.yml: on pushes to main, creates annotated tag vYYYY.MM.DD-HHMM (UTC). +- .gitea/workflows/nightly.yml: nightly lint run. +- All jobs run inside the ci image; no runner host package installs. + +Protected Checks (when CI is enabled) +- Protect: ci / lint, ci / build, ci / commitlint. Add ci / test and ci / security when they exist. + +Future Extensions +- Add tests/security phases per repo stack; enable CI branch protections once runners are ready; optionally add pre-commit framework as an alternative to native hooks. + diff --git a/instructions/git-workflow.md b/instructions/git-workflow.md new file mode 100644 index 0000000..127a92f --- /dev/null +++ b/instructions/git-workflow.md @@ -0,0 +1,36 @@ +Git Workflow – Finalized Instructions + +Scope +- Applies to this repo. Users typically consume tagged releases; contributors work via branches/PRs. CI/CD config is Gitea‑native; no GitHub/GitLab. + +Branches +- main: production; default branch. Protected. +- integration: development (unprotected; merges auto on green). +- Working branches: `feature/`, `fix/`, `chore/` from integration. +- Hotfix: `hotfix/` from main; PR back to main, then forward-merge into integration. +- Release branch: ephemeral or lightweight `release/*`. Protect when present; optionally fast‑forward to latest tag via CI. + +Merges & Approvals +- Feature → integration: squash merge; auto‑merge on green (no human approval). Self‑merge allowed. +- integration → main: squash merge; require 1 approval; self‑merge not allowed. +- Force pushes disabled on protected branches (`main`, `release/*`); PRs required. + +Commit Style +- Conventional Commits for PR titles and commit messages. + +Versioning & Tags +- Calendar tags: vYYYY.MM.DD-HHMM (UTC). Annotated tags only on main after release. + +Release Flow +1) Feature branches PR into integration; checks pass → auto‑merge. +2) PR integration → main; 1 approval required; on merge, deploy and tag release. +3) Optional: CI fast‑forwards a release branch pointer to the new tag. + +Protected Checks (to enable when runners are ready) +- On protected branches (`main`, `release/*`): ci / lint, ci / build, ci / commitlint. Add ci / test and ci / security if/when introduced. + +CODEOWNERS +- Keep minimal; require your review for integration → main. + +Notes +- No secrets required for this repo. Future repos should integrate Vault for secrets. diff --git a/proposals/bootstrap-cicd.md b/proposals/bootstrap-cicd.md new file mode 100644 index 0000000..1c5b44b --- /dev/null +++ b/proposals/bootstrap-cicd.md @@ -0,0 +1,69 @@ +**Bootstrap CI/CD Proposal (Phase 1)** + +- Scope: Local developer parity via Docker-first tooling and hooks, minimal CI placeholders (no runners required yet). Applies to this repo (docs/scripts/docker-compose), with an easy path to template for others. + +**Checks To Implement Now (Local via Docker)** + +- Stacks: shell, Dockerfiles/Compose, Markdown/Docs, YAML; Python/Node optional later. +- Formatters/Linters: + - shell: shfmt + shellcheck + - docker: hadolint + - markdown: markdownlint + prettier + - yaml: yamllint + actionlint (for workflows) +- Tests: none for now (lint-only baseline). +- Security: skip for this repo now. + +**Execution Model** + +- Docker-only: all checks run inside a pinned `ci` image. Host only orchestrates Docker/Compose. +- Single entrypoint: `scripts/ci` with phases: `format`, `lint`, `build` (compose validate), `test` (no-op for now), `security` (no-op), `all`. +- Compose file: `docker/ci.compose.yml` defines `ci` service that mounts repo and executes `scripts/ci `. + +**Hooks Parity** + +- Provide Git hooks via pre-commit framework and native Git hooks: + - pre-commit: run `format`, `lint`, and commit message check (Conventional Commits). + - pre-push: run `build` (compose config validation) and keep `test`/`security` as no-ops for now. +- Commit message style: Conventional Commits via `commitlint` rule-set; enforce in CI later and locally via `commit-msg` hook. + +**Minimal CI (Deferred Enablement)** + +- Workflows will be prepared but can stay disabled until runners are available: + - `.gitea/workflows/ci.yml`: mirrors local `lint` + `build` using the same `ci` image; triggered on PRs when enabled. + - `.gitea/workflows/release.yml`: on `main` merges, tags with `vYYYY.MM.DD-HHMM` and (optionally) creates release notes; can be enabled later. + - `.gitea/workflows/nightly.yml`: scheduled dependency/lint refresh; optional for later. +- All jobs execute inside the `ci` container image; no host package installs. + +**Caching & Matrix** + +- Matrix: single Linux image for now. +- Caching: enable Docker layer cache when CI runners are available; no special local caching required. + +**Concurrency & Timeouts (defaults for later)** + +- Cancel in-progress on same ref: enabled for PRs. +- Job timeout: 30 minutes. + +**Protected Check Names (for later enforcement)** + +- `ci / lint`, `ci / build`, `ci / commitlint`. Tests/Security can be added when introduced. + +**Files To Add (upon approval)** + +- `scripts/ci` (bash) — phases and Docker/host detection (host executes Docker only). +- `ci.Dockerfile` — pinned versions: shfmt, shellcheck, hadolint, yamllint, markdownlint-cli, prettier, actionlint, commitlint. +- `docker/ci.compose.yml` — `ci` service to run checks. +- `.pre-commit-config.yaml` — wire to `scripts/ci` phases; enable `commit-msg` hook for commitlint. +- `commitlint.config.cjs` — Conventional Commits rules. +- `.gitea/workflows/ci.yml`, `release.yml`, `nightly.yml` — prepared but can be disabled until runners are ready. +- `Makefile` — `check`, `quick`, `lint`, `format`, `build` targets mapping to scripts. + +**Rollout Plan** + +1) Implement local tooling and hooks on `bootstrap-cicd`. +2) Document quickstart in `docs/engineering/ci-cd.md`. +3) Later: enable Gitea workflows when runners are ready; add protected checks. +4) Optionally expand with tests/security scanners and language stacks per repo. + +If this matches your intent, I will scaffold the above on `bootstrap-cicd` and then capture the finalized process in `instructions/bootstrap-cicd.md`. + diff --git a/questions/bootstrap-cicd.md b/questions/bootstrap-cicd.md new file mode 100644 index 0000000..26b6fd5 --- /dev/null +++ b/questions/bootstrap-cicd.md @@ -0,0 +1,110 @@ +Bootstrap CI/CD – Questions + +Goal: define initial CI/CD checks and local Docker-parity hooks for this repo (docs/site, scripts, docker-compose), and a template usable by other repos. + +Answer style: short codes + notes, e.g. `1:a,c 2:b 3:docker`. + +1) Stacks present now (select all): + - a) Shell scripts + - b) Dockerfiles/Compose + - c) Markdown/Docs + - d) YAML (workflows/config) + - e) Python + - f) Node/JS + - g) Other (specify) + + a,b,c,d potentially e. + +2) Formatters/linters per stack: + - shell: a) shfmt b) shellcheck c) both + - docker: a) hadolint + - markdown: a) markdownlint b) prettier c) both + - yaml: a) yamllint b) actionlint (for workflows) c) both + - python (if used): a) black b) ruff c) pytest (tests) + - node (if used): a) eslint b) prettier c) jest (tests) + + shell: c + docker: a + markdown: c + yaml: c + + I will leave python/node testing up to you. It isn't needed for this repo unless you create python scripts at some point. + +3) Testing scope now: + - a) none (docs/scripts only) + - b) smoke tests for scripts (bats/pytest-sh) + - c) unit tests for scripts (specify framework) + + A (other then linting) + +4) Security scanning: + - a) trivy fs + - b) grype + - c) bandit (python) + - d) npm audit (node) + - e) skip for this repo + + e + +5) Execution environment for CI: + - a) run inside repo’s `ci.Dockerfile` + - b) run on runner host with packages + - c) mix (specify) + + All execution MUST be done in docker containers. Absolutely no work must be done on the host beyond git operations and docker orchestration. + +6) Matrix needs (now): + - a) none (single Linux image) + - b) multiple language versions (specify) + - c) OS matrix (Linux only for now?) + + Um. I don't know. I think just a simle Linux environment can be assumed? + +7) Caching: + - a) enable tool caches (pip/npm) in CI + - b) enable Docker layer cache + - c) none + + I guess docker layer cache? It will be two weeks before I'm working on software (and therefore setup gitea CI runners etc). + +8) Check names to protect on branches (final labels): + - a) ci / lint + - b) ci / test + - c) ci / build + - d) ci / security + - e) ci / commitlint + + I don't know, leave it up to you + +9) Hooks parity: + - pre-commit: run format+lint+commitlint? (y/n) y + - pre-push: run test+build+security (fast profile)? (y/n) y + +10) Concurrency & timeouts: + - cancel in-progress on new commits to same PR? (y/n) + - default job timeout (minutes)? + +11) Release flow details: + - generate release notes from merged PRs since last tag? (y/n) + - attach built artifacts (site tarball, etc)? (y/n) + +12) Coverage gates (if tests exist): + - threshold % to require? (number or skip) + +13) Auto-merge bot to `integration`: + - bot account/name (or use Gitea built-in)? + - automerge conditions beyond green checks? (labels, size) + +14) Notifications: + - a) none + - b) email + - c) webhook/Chat (specify) + +15) Future extensibility: + - template these workflows for other repos? (y/n) + - segregate language-specific jobs behind conditions? (y/n) + +Notes: add any constraints about runners, container registry, or build tools. + + +Lets just ignore all things CI for now? I'm brand new to CI. Use your best judgement/adopt best practices and/or ignore CI as needed. Do track that it's an outstanding item to go in depth on though. I don't want it to block moving forward with the dozen or so docs repos I need to use this LLM workflow with though. \ No newline at end of file diff --git a/scripts/ci b/scripts/ci new file mode 100755 index 0000000..5c55b36 --- /dev/null +++ b/scripts/ci @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +set -euo pipefail + +PHASE="${1:-}" + +usage() { + echo "Usage: scripts/ci " >&2 + exit 2 +} + +if [[ -z "${PHASE}" ]]; then + usage +fi + +repo_root() { + git rev-parse --show-toplevel 2>/dev/null || pwd +} + +run_outside_container() { + local phase="$1" + local root + root="$(repo_root)" + if ! command -v docker >/dev/null 2>&1; then + echo "Docker is required to run CI tasks locally." >&2 + exit 1 + fi + if ! command -v docker-compose >/dev/null 2>&1 && ! docker compose version >/dev/null 2>&1; then + echo "Docker Compose v2+ is required (docker compose)." >&2 + exit 1 + fi + # Build ci image if needed and run the requested phase inside the container + (cd "$root" && docker compose -f docker/ci.compose.yml run --rm \ + -e IN_CI_CONTAINER=1 \ + ci bash -lc "cd /workspace && scripts/ci --inside ${phase}") +} + +run_format() { + echo ">> Formatting" + # shell: format in-place + shfmt -bn -ci -i 2 -w . + # prettier for markdown/yaml/json/etc + prettier --log-level warn --write \ + "**/*.md" "**/*.yaml" "**/*.yml" "**/*.json" \ + "**/*.css" "**/*.html" 2>/dev/null || true +} + +run_lint() { + echo ">> Linting" + # shellcheck + mapfile -t sh_files < <(git ls-files -z | xargs -0 file --mime-type | awk -F: '/(x-shellscript|text\/x-shellscript)/{print $1}'; git ls-files "*.sh") + if [[ ${#sh_files[@]} -gt 0 ]]; then + shellcheck -x "${sh_files[@]}" || (echo "Shellcheck failed" && exit 1) + shfmt -d . + fi + # hadolint on Dockerfiles + if ls Dockerfile* docker/*Dockerfile* 1>/dev/null 2>&1; then + hadolint Dockerfile* docker/*Dockerfile* 2>/dev/null || true + fi + # yamllint + if git ls-files "*.yml" "*.yaml" | grep -q .; then + yamllint -s $(git ls-files "*.yml" "*.yaml") + fi + # markdownlint + if git ls-files "*.md" | grep -q .; then + markdownlint $(git ls-files "*.md") + fi + # actionlint for workflow files if present + if [ -d .gitea/workflows ]; then + actionlint -color + fi +} + +run_build() { + echo ">> Build checks" + # Validate docker compose configs if present + if [ -f docker-compose.yml ] || [ -f docker/compose.yml ]; then + docker compose config -q + fi +} + +run_test() { + echo ">> Tests (none defined)" +} + +run_security() { + echo ">> Security checks (skipped for this repo)" +} + +run_inside_container() { + local phase="$1" + case "$phase" in + format) run_format ;; + lint) run_lint ;; + build) run_build ;; + test) run_test ;; + security) run_security ;; + all) run_format; run_lint; run_build; run_test; run_security ;; + *) usage ;; + esac +} + +if [[ "${1:-}" == "--inside" ]]; then + shift + PHASE="${1:-}" + [[ -z "$PHASE" ]] && usage + run_inside_container "$PHASE" + exit 0 +fi + +if [[ "${IN_CI_CONTAINER:-}" != "1" ]]; then + run_outside_container "$PHASE" +else + run_inside_container "$PHASE" +fi + diff --git a/scripts/commitlint-hook b/scripts/commitlint-hook new file mode 100755 index 0000000..021f21c --- /dev/null +++ b/scripts/commitlint-hook @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +MSG_FILE="${1:-.git/COMMIT_EDITMSG}" + +# Run commitlint inside the CI container against the commit message file +docker compose -f docker/ci.compose.yml run --rm \ + -e IN_CI_CONTAINER=1 \ + ci bash -lc "commitlint --edit ${MSG_FILE}" + diff --git a/scripts/setup-hooks b/scripts/setup-hooks new file mode 100755 index 0000000..e62f375 --- /dev/null +++ b/scripts/setup-hooks @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +root_dir="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + +mkdir -p "$root_dir/.git/hooks" + +for hook in pre-commit pre-push commit-msg; do + src="$root_dir/.githooks/$hook" + dest="$root_dir/.git/hooks/$hook" + if [[ -f "$src" ]]; then + cp "$src" "$dest" + chmod +x "$dest" + echo "Installed hook: $hook" + fi +done + +echo "Git hooks installed." + -- 2.43.0