Jaypore CI

> Jaypore CI: Minimal, Offline, Local CI system.
Log | Files | Refs | README | LICENSE

commit 93b9f6689441b680d72a109f39fb3cf02d104b81
parent 522f246bc616fdd5d8fefb415717d4d46a2b2071
Author: Arjoonn Sharma <arjoonn@midpathsoftware.com>
Date:   Sat, 21 Mar 2026 19:24:22 +0000

Better docs (!13)

Reviewed-on: https://gitea.midpathsoftware.com/midpath/jayporeci/pulls/13
Co-authored-by: Arjoonn Sharma <arjoonn@midpathsoftware.com>
Co-committed-by: Arjoonn Sharma <arjoonn@midpathsoftware.com>

Diffstat:
MREADME.md | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mscripts/build_site.sh | 4++++
Asshot.png | 0
3 files changed, 125 insertions(+), 45 deletions(-)

diff --git a/README.md b/README.md @@ -6,9 +6,12 @@ - [Install](#install) - [Config](#config) + - [Scheduling with crontab](#scheduling-with-crontab) - [Environment Vars](#environment-vars) - [Example workflow](#example-workflow) - [How it works](#how-it-works) + - [Viewing results](#viewing-results) + - [Sharing results with teammates](#sharing-results-with-teammates) - [FAQ / Needs / Wants / Todos](#faq-needs-wants-todos) - [Examples](#examples) - [Golang Lint Build Test](#golang-lint-build-test) @@ -31,98 +34,172 @@ ## Install +Download the binary for your platform from [www.jayporeci.in](https://www.jayporeci.in). +Available builds: + +| Platform | Binary | +|----------|--------| +| Linux x86-64 | `git-jci-linux-amd64` | +| Linux ARM64 | `git-jci-linux-arm64` | +| Linux ARMv7 | `git-jci-linux-armv7` | +| Linux 32-bit | `git-jci-linux-386` | +| Windows x86-64 | `git-jci-windows-amd64.exe` | +| Windows ARM64 | `git-jci-windows-arm64.exe` | + +> macOS is not currently offered as a pre-built binary. You can build from source with `go build ./cmd/git-jci`. + +After downloading, move the binary to somewhere on your `PATH`: + ```bash -# Download an appropriate binary from www.jayporeci.in -# After that move it to some location on your PATH -sudo mv git-jci /usr/local/bin/ +sudo mv git-jci-linux-amd64 /usr/local/bin/git-jci ``` -The binary is fully static (no dependencies) and works on most systems. If you are having issues, please contact us. -Once installed, git will automatically find it as a subcommand and you can start using it via `git jci run` +The binary is fully static (no dependencies) and works on most systems. Once installed, git will automatically find it as a subcommand and you can use it via `git jci`. ## Config -Create a `.jci` folder in your repository. You can place a `run.sh` file and a `crontab` file in it. - -> Make sure that run.sh is executable! +Create a `.jci` folder in your repository and add a `run.sh` file. Optionally add a `crontab` file for scheduled runs. ``` .jci/ -├── crontab -└── run.sh +├── run.sh # required: defines your CI pipeline +└── crontab # optional: schedule recurring runs +``` + +**Make `run.sh` executable:** + +```bash +chmod +x .jci/run.sh ``` -You can put anything in `run.sh`. Call a python program / run docker commands / replace it with a binary from a rust project that does something else entirely! +`run.sh` can be anything ; a shell script, a wrapper that calls a Python program, a binary, a docker-compose invocation ; whatever you like. -`crontab` is used to schedule things. You can run things like midnight tests / builds, SLA checks, repo auto-commits, repo time trackers etc. +### Scheduling with crontab +`.jci/crontab` uses standard 5-field cron syntax with two optional keyword arguments: + +``` +# SCHEDULE [branch:BRANCH] [name:NAME] +0 0 * * * # midnight, current branch +0 0 * * * branch:main # midnight, main branch +*/15 * * * * name:quick-check # every 15 min, named job +0 0 * * * branch:main name:nightly # named midnight build on main +``` + +After editing `.jci/crontab`, run `git jci cron sync` to install the entries into your system's crontab. Cron jobs run with the repository root as the working directory and have access to all three `JCI_*` environment variables. ## Environment Vars -Your `run.sh` script has access to: +Your `run.sh` script receives these environment variables: | Variable | Description | |----------|-------------| | `JCI_COMMIT` | Full commit hash | -| `JCI_REPO_ROOT` | Repository root path | -| `JCI_OUTPUT_DIR` | Output directory for artifacts | +| `JCI_REPO_ROOT` | Absolute path to the repository root | +| `JCI_OUTPUT_DIR` | Absolute path to the output directory for this run | + +All three are always set. The script's initial working directory is `JCI_REPO_ROOT`. Any files written to `JCI_OUTPUT_DIR` become CI artifacts , browsable and downloadable via `git jci web`. The output directory is a temporary directory that is cleaned up after its contents are stored in git. -The script runs with `cwd` set to `JCI_OUTPUT_DIR`. Any files created there become CI artifacts. +> **Non-zero exit = failure.** If `run.sh` exits with a non-zero code, the run is marked as failed (`✗ err`). The artifacts are still saved and viewable. ## Example workflow ```bash -cd repo-dir && git status # enter the repository and check the working tree -git add -A # stage every modified, deleted, or new file -git commit -m "..." # record the staged changes in a new commit -git jci run # execute .jci/run.sh manually and capture artifacts for this commit. You could also use git hooks to run this automatically on commit. -git jci web # launch the local viewer to inspect the latest CI results -git jci push # push the commit's CI artifacts to the default remote -git jci pull # fetch updated CI artifacts from the remote -git jci prune # delete CI refs for commits that no longer exist locally -git jci cron ls # list cron jobs that are there in .jci/crontab -git jci cron sync # sync local machine's crontab with the current contents of .jci/crontab +# 1. Enter the repository +cd my-project + +# 2. Create the CI script (only needed once) +mkdir -p .jci +cat > .jci/run.sh << 'EOF' +#!/usr/bin/env bash +set -euo pipefail +cd "$JCI_REPO_ROOT" +echo "Running tests..." +go test ./... | tee "$JCI_OUTPUT_DIR/test-results.txt" +EOF +chmod +x .jci/run.sh + +# 3. Commit and run CI +git add -A +git commit -m "add CI" +git jci run # execute .jci/run.sh and store results for this commit + +# 4. View results locally (opens at http://localhost:8000) +git jci web + +# 5. Share results with teammates +git jci push # push CI refs to origin +git jci pull # fetch CI refs pushed by teammates + +# 6. Maintenance +git jci prune # dry-run: show what would be removed +git jci prune --commit # actually delete CI refs for gone commits +git jci prune --commit --older-than=14d # also delete refs older than 14 days + +# 7. Cron +git jci cron ls # list scheduled jobs in .jci/crontab +git jci cron sync # install/update entries in the system crontab ``` +You can also trigger `git jci run` automatically via [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) so CI runs on every commit without any manual step. + ## How it works -CI results are stored as git tree objects under the `refs/jci/` namespace. -This keeps them separate from your regular branches and tags, but still -part of the git repository. +CI results are stored as git tree objects under the `refs/jci/` namespace (or `refs/jci-runs/` when a commit has multiple runs). This keeps them completely separate from your regular branches and tags while still being part of the repository. -- Results are not checked out to the working directory -- They can be pushed/pulled like any other refs +- Results are **never** checked out to the working directory +- They are pushed and pulled exactly like any other git refs but only when you explicitly run `git jci push` / `git jci pull`. A plain `git push` does **not** send them. - They are garbage collected when the original commit is gone (via `prune`) -- Each commit's CI output is stored as a separate commit object +- Each run produces a `run.output.txt` artifact plus whatever else your `run.sh` writes to `JCI_OUTPUT_DIR` + +### Viewing results + +`git jci web` starts a local web server at **http://localhost:8000**. It shows all commits on your branches, their CI status, and lets you browse and download artifacts. Clicking a commit shows its output files in a three-panel layout: + +![git jci web screenshot](sshot.png) + +You can pass a custom port: `git jci web 9000`. + +### Sharing results with teammates + +```bash +# You: after running CI +git jci push # pushes refs/jci/* to origin + +# Teammate: to see your results +git jci pull # fetches refs/jci/* from origin +git jci web # now shows your CI results too +``` + +CI refs are pushed to and pulled from `origin` by default. You can specify a different remote: `git jci push my-remote`. ## FAQ / Needs / Wants / Todos - [x] Complex pipeline definitions - - `run.sh` can be an executable. Do whatever you like! + - `run.sh` can be any executable. Do whatever you like! - [x] Artifacts - - Anything in `JCI_OUTPUT_DIR` is an artifact! + - Anything written to `JCI_OUTPUT_DIR` is an artifact, browsable via `git jci web`. - [x] Debug CI locally - - Just execute `run.sh` locally. + - Just run `.jci/run.sh` directly. Set the three env vars yourself to simulate a CI environment. - [x] Automate unit, integration, and end-to-end test suites on every commit - Link [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) and run CI whenever you want. - - For integration / end to end, I like to use a docker-compose that will compose up services / databases etc, and one container is a test driver like [locust](https://locust.io/). + - For integration / end to end, I like to use a docker-compose that composes up services / databases etc, with one container acting as a test driver like [locust](https://locust.io/). - [x] Run linting and static analysis to enforce coding standards - Link [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) and run CI whenever you want. - [x] Produce code coverage reports and surface regressions - - Outputs are placed in `JCI_OUTPUT_DIR`. We can put HTML coverage reports here if needed and view via CI browser. - - For regressions, the `run.sh` can commit examples created by things like + - Write HTML coverage reports to `JCI_OUTPUT_DIR` and view them via `git jci web`. + - For regressions, `run.sh` can commit example files created by tools like [hypothesis](https://hypothesis.readthedocs.io/en/latest/) back to the - repo. This ensures that next runs will use those examples and test for - regresssions. + repo. This ensures that next runs will use those examples and test for regressions. - [x] Build, package, and archive release artifacts across target platforms - I like building a docker image, building stuff inside that, then publishing. - - Refer to the [scripts/](http://127.0.0.1:8000/releases/files.html) files for examples on how to build/render etc. + - See [`.jci/run.sh`](.jci/run.sh) in this repository for a real-world cross-compile + publish example. - [x] Perform dependency and source code security scans (SCA/SAST) - I like to run [Truffle Hog](https://github.com/trufflesecurity/trufflehog) to prevent accidental leaks. - [x] Generate documentation sites and preview environments for review - This Jaypore CI site itself is generated and published via CI. - [x] Schedule recurring workflows (cron-style) for maintenance tasks - - I run a nightly build via cron to ensure that I catch any dependency failures / security breaks. See [.jci/crontab](.jci/crontab.html) for an example. + - I run a nightly build via cron to ensure I catch dependency failures / security breaks. See [.jci/crontab](.jci/crontab) for an example. - [x] Notify developers and stakeholders when CI statuses change or regress - As part of our scripts, we can call telegram / slack / email APIs and inform devs of changes. - [ ] Built-in secrets management with masking, rotation, and per-environment scoping @@ -138,7 +215,6 @@ part of the git repository. - [ ] Deployment environments with history, approvals, and promotion policies - [ ] First-class integration with observability / error tracking tools (e.g., Sentry) - [ ] Ecosystem of reusable actions/tasks with versioned catalogs and templates - - This is already there? Not sure if this is something we even need to solve? - [ ] Validate infrastructure-as-code changes and deployment pipelines via dry runs @@ -638,7 +714,7 @@ run_js=false run_go=false if [ -z "$changed_files" ]; then - echo "No diff detected (first commit or shallow clone) — running all sub-pipelines." + echo "No diff detected (first commit or shallow clone) running all sub-pipelines." run_python=true run_js=true run_go=true @@ -681,7 +757,7 @@ if $run_python; then echo "Running: flake8 06-sub-pipelines/python-app/" flake8 06-sub-pipelines/python-app/ 2>&1 && echo "Lint: PASS" || echo "Lint: FAIL" else - echo "No Python linter found (ruff/flake8) — checking syntax only." + echo "No Python linter found (ruff/flake8) checking syntax only." find 06-sub-pipelines/python-app -name '*.py' -exec python3 -m py_compile {} + 2>&1 \ && echo "Syntax check: PASS" || echo "Syntax check: FAIL" fi diff --git a/scripts/build_site.sh b/scripts/build_site.sh @@ -26,6 +26,10 @@ mkdir -p "${BUILD_DIR}" head README.md | grep '>' > description 2>/dev/null || echo "jci" > description ) +# Regenerate the README table of contents before building the site +echo "Regenerating README table of contents..." +bash "${REPO_DIR}/scripts/generate_readme_index.sh" "${REPO_DIR}/README.md" + cp -r "${REPO_DIR}/bin" "${REPO_DIR}/binaries" cp -r "${REPO_DIR}/bin" "${PUBLIC_DIR}/binaries" (cd "${BUILD_DIR}" && pwd && stagit -u /releases/git "${REPO_DIR}") diff --git a/sshot.png b/sshot.png Binary files differ.