{"id":51529309,"url":"https://github.com/noobping/ci","last_synced_at":"2026-07-09T01:01:12.939Z","repository":{"id":353617483,"uuid":"1219951339","full_name":"noobping/ci","owner":"noobping","description":"small Git-native CI runner","archived":false,"fork":false,"pushed_at":"2026-04-24T17:09:25.000Z","size":244,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-24T18:28:28.104Z","etag":null,"topics":["ci","git","tool","workflow-automation"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noobping.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T11:45:00.000Z","updated_at":"2026-04-24T17:09:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/noobping/ci","commit_stats":null,"previous_names":["noobping/ci"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/noobping/ci","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noobping%2Fci","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noobping%2Fci/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noobping%2Fci/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noobping%2Fci/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noobping","download_url":"https://codeload.github.com/noobping/ci/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noobping%2Fci/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35282899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","git","tool","workflow-automation"],"created_at":"2026-07-09T01:01:12.156Z","updated_at":"2026-07-09T01:01:12.932Z","avatar_url":"https://github.com/noobping.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n![License](https://img.shields.io/badge/license-MIT-blue.svg)\n[![Release](https://github.com/noobping/ci/actions/workflows/release.yml/badge.svg)](https://github.com/noobping/ci/actions/workflows/release.yml)\n\n# ci\n\n`ci` is a small Git-native CI runner and build tool.\n\nInstall it into a local or bare Git repository to run like Git hooks, or use it directly for builds. It runs workflows from `.ci`, and can also execute GitHub/Gitea workflow files locally.\n\nNo daemon. No server. No web UI.\n\n`ci` uses what the host already has: Podman, Docker, and Git when available. If Git is missing but Podman or Docker is present, it can run Git from a (configurable) container image instead.\n\nIf Git can run a hook, Git can run `ci`.\n\n## Build from source\n\n```sh\ncargo build --release\n```\n\n## Commands\n\nCore commands:\n\n- `run`: run one or more workflows.\n- `list`: list discovered workflows.\n- `install`: install managed Git hooks.\n- `uninstall`: remove managed Git hooks.\n- `update`: refresh the installed runner.\n- `hook`: run as a Git hook entrypoint.\n- `status`: validate repo, config, hooks, runtimes, cache, and store state.\n- `explain`: show why an event or workflow matched.\n- `schema`: print JSON Schema for config and workflow files.\n- `clean`: export or keep recorded artifacts from run manifests.\n- `completion`: generate shell completion scripts.\n- `man`: generate `man1` pages.\n- `init`: create `.ci/build.yml`.\n- `self`: print information about the current `ci` binary.\n- `other`: compare the installed repository runner with the current `ci` binary.\n\nIf the first command does not match a built-in command, `ci` treats it as a workflow name. For example, `ci build` is equivalent to `ci run build`.\n\nArguments after the `build` workflow name are forwarded to the detected build step:\n\n```sh\nci run build --no-default-features\nci build --features sqlite\nci build -- --dry-run\nci build --no-dry-run -- --dry-run\n```\n\nFor native YAML build workflows, `ci` looks for a step named `build`, then for a recognizable build command such as `cargo build`, and appends the arguments to that step. Known `ci` options keep their `ci` meaning before `--`, so `ci build --dry-run` previews the workflow. Put build-command flags after `--` when a flag name overlaps with `ci`. The space-joined argument string is also available to scripts as `CI_WORKFLOW_ARGS`.\n\nGlobal output controls:\n\n```sh\nci --verbose build\nci -vv build\nci -vvv build\nci --quiet build\n```\n\n`--verbose`/`-v` shows command traces; repeat it as `-vv` and `-vvv` for extra runner detail. `--quiet`/`-q` hides normal output, warnings, and workflow script output, but still shows critical errors. Verbose and quiet modes are passed to supported runner-owned commands, such as Git; quiet mode uses `--quiet` when supported and falls back to `--silent` for commands that use that spelling.\n\nOutput levels:\n\n| Level | How to enable | Behavior |\n| --- | --- | --- |\n| Quiet | `-q`, `--quiet`, or `quiet: true` | Hides normal output, warnings, verbose logs, and workflow script output. Critical `ci` errors are still shown. |\n| Normal | default | Shows normal info, warnings, critical errors, and workflow script output. |\n| Verbose 1 | `-v`, `--verbose` | Adds command traces and runner verbose messages; asks supported runner-owned commands to be verbose. |\n| Verbose 2 | `-vv` | Adds runner detail such as Git execution mode, step shell/workdir, container image/platform, and package container build paths. |\n| Verbose 3+ | `-vvv` | Adds deeper detail such as Git working directory, environment variable counts, and generated container base/tag info. Higher levels currently behave like `-vvv`. |\n\nConfiguration precedence is:\n\n```text\nCLI flags \u003e workflow fields \u003e workflow defaults \u003e project config \u003e user config \u003e system config \u003e auto-detect\n```\n\n## Script-friendly list output\n\n`ci list` keeps the aligned human-readable layout when writing to a terminal.\n\nWhen `stdout` is redirected or piped, `ci list` automatically switches to porcelain output:\n\n```sh\nci list | cut -f1\nci list \u003e workflows.tsv\n```\n\nPorcelain output is tab-separated:\n\n```text\nname\u003cTAB\u003eprovider\u003cTAB\u003ekind\u003cTAB\u003epath\n```\n\nUse `--porcelain` to force that format on a terminal, or `--no-porcelain` to keep the aligned layout even when piping or redirecting output.\n\n## Workflow sources\n\nIf a repository has no workflows, `ci` auto-detects a basic `build` workflow for supported stacks. Rust, Node.js, Go, Python packages, Maven, Gradle, and .NET projects get a default build command; when the host tool is not available, that generated workflow uses the matching container automatically.\n\nChoose a stack explicitly when auto-detection is not enough:\n\n```sh\nci build --tech rust\nci build -t node\nci build --type golang\nci build --tech-stack py\n```\n\nWorkflow sources:\n\n- Native YAML: `.ci/build.yml`, `.ci/test.yaml`\n- Native executable: `.ci/build.sh`\n- Directory metadata: `.ci/release/workflow.yml`\n- Containerfile workflow: `.ci/image/Containerfile`\n- GitHub Actions: `.github/workflows/*.yml`\n- Gitea Actions: `.gitea/workflows/*.yml`\n\nExecutable scripts:\n\n```sh\n.ci/build.sh\n```\n\nNative YAML workflow files:\n\n```yaml\nname: build\ndefaults:\n  tech: rust\n  container:\n    arch:\n      - x64\n      - arm64\n    components:\n      - cargo-fmt\n      - cargo-clippy\n    packages:\n      - htop\non:\n  - manual\n  - pre-push\nsteps:\n  - name: Checkout\n    uses: checkout\n    with:\n      submodules: recursive\n  - name: Format\n    run: cargo fmt --check\n    readonly: true\n  - name: Test\n    run: cargo test --all\n  - name: Build\n    run: cargo build --release\n```\n\nNative `.ci/*.yml` steps also support first-class conditions:\n\n- `if: success` or `if: success()`: run when the current step path is still successful. This is the default when `if` is omitted.\n- `if: failure` or `if: failure()`: run after the previous executed step failed.\n- `if: always` or `if: always()`: run regardless of the previous step result.\n- `if: arch(x64)` or `if: arch x64`: true when the selected execution architecture matches; aliases such as `amd64` and `linux/amd64` are normalized, comma-separated values are accepted, and `arch(host)` matches only the host machine's native architecture.\n- `if: exists(cargo)` or `if: exists cargo`: true when a repo-relative path exists, or when a bare command exists on `PATH`; `has(...)` and `is(...)` are aliases, and their parentheses are optional too.\n- `if: exists(path:Cargo.toml)`: true when a repo-relative or absolute file/directory path exists.\n- `if: exists(file:Cargo.toml)` / `if: exists(dir:src)`: true only for files or directories.\n- `if: exists(cmd:cargo)`: true when an executable command exists; `command:`, `exe:`, and `executable:` are aliases.\n- `if: exists(env:USE_DEBUG)`: true when a workflow/step env var is set, or when the host environment provides it.\n- `if: missing(cargo)` or `if: missing cargo`: inverse existence check; `not(...)` is an alias. The same optional target prefixes work with `missing(...)`.\n- `if: is exists(Cargo.toml)` / `if: not missing Cargo.toml`: word forms are accepted for readable existence checks.\n- When a workflow/container default is set, native `run:` steps use that container by default. Use `container: false` on a step that intentionally targets the host, such as installing files under `~`.\n\nNative workflows can require other workflows with `needs:`. Dependencies run before the selected workflow, even when they would not otherwise match the current event. `requires:`, `depends:`, and `dependencies:` are accepted aliases.\n\n```yaml\nname: release-bin\non: [post-receive, manual]\nneeds: build\nsteps:\n  - run: install -Dm0755 \"dist/ci-linux-$CI_ARCH\" \"public/ci-linux-$CI_ARCH\"\n```\n\nThis repository uses that to keep the workflows small:\n\n- `check`: format, lint, and test\n- `build`: depends on `check`, builds release binaries, and writes `dist/ci-linux-$CI_ARCH`\n- `install-local`: depends on `build` and installs the host binary under `~/.local/bin`\n- `aur`: depends on `build`, generates `.SRCINFO`, and commits AUR metadata when available\n\nTo add a fallback step after a failure and still let the workflow recover, mark the failing step with `continue-on-error: true`.\n\n```yaml\nsteps:\n  - name: Build with host cargo\n    run: cargo build --release\n    continue-on-error: true\n  - name: Build with toolbox cargo\n    if: \"failure \u0026\u0026 exists(flatpak-spawn) \u0026\u0026 exists(toolbox)\"\n    run: flatpak-spawn --host toolbox run cargo build --release\n```\n\nNative `.ci/*.yml` steps can also use built-in `uses:` or `use:` action sources. `name:` is only the display name, so built-ins can still have custom labels.\n\n- `checkout`: restore tracked files to `HEAD`, with optional `with.submodules: true|recursive`\n- `submodules`: force `git submodule update --init --recursive`\n- `cache`: restore and save cache paths using `with.key` and `with.path`\n- `upload-artifact`: store artifacts using `with.name` and `with.path`\n- `download-artifact`: restore artifacts using `with.name` and optional `with.path`\n- `export`: copy `source`/`src`/`from` paths to `destination`/`dest`/`to`; multiple sources use the destination as a directory, while a single source can use an exact file path; set `replace: true` or `overwrite: true` to replace an existing target\n- `link`: create symlinks from `source`/`src`/`from` to `destination`/`dest`/`to`; multiple sources use the destination as a directory, while a single source can use an exact link path; set `replace: true` or `overwrite: true` to replace an existing target\n- `commit`: stage paths and create a commit with `message`/`msg`; without paths it stages all changes with `git add -A`; staged paths may use `path`, `paths`, `file`, `files`, `pattern`, `patterns`, `source`, `src`, or `from`; set `staged: true` to commit only already staged changes\n- `sync`: pull and push the current branch, or use `mirror: true` with `source`/`src`/`from` and `destination`/`dest`/`to` remotes\n- `clean`: run `git clean -fd` by default; `ignored: true` maps to `git clean -fdx`, `ignored: only` maps to `git clean -fdX`, `purge: true` runs `git fetch --all --prune`, `cargo: true` runs `cargo clean`, `path` or `paths` removes repo-relative targets, and native `.ci/*.yml` steps may extend the cleanup with an inline `run:` block\n- `podman`: run an inline Bash script with a `podman` function backed by the selected container runtime; auto/runtime selection still prefers Podman, then Docker, and Docker runs strip Podman SELinux relabel mount options such as `:Z`/`:z`\n\n`export` handles files and build outputs. `commit` and `sync` are separate repository actions.\n\n```yaml\nsteps:\n  - use: checkout\n  - name: Clean ignored build outputs\n    use: clean\n  - use: clean\n    ignored: only\n  - use: export\n    src:\n      - target/release/ci\n      - README.md\n    dest: dist\n    replace: true\n  - use: commit\n    message: \"ci: update generated outputs\"\n  - name: Generate AUR metadata\n    run: makepkg --printsrcinfo \u003e .SRCINFO\n  - use: commit\n    paths:\n      - PKGBUILD\n      - .SRCINFO\n    message: \"aur: update package metadata\"\n  - use: sync\n    strategy: rebase\n  - name: Run Butane in a container\n    use: podman\n    shell: bash\n    run: |\n      podman run --rm -v \"$PWD:/work:Z\" -w /work quay.io/coreos/butane:release --help\n  - use: clean\n    purge: true\n    cargo: true\n    run: |\n      rm -rf dist coverage\n  - name: Use local cargo\n    if: exists(cargo)\n    run: cargo test\n  - name: Fallback to toolbox cargo\n    if: missing(cargo)\n    run: toolbox run cargo test\n  - name: Only if a file exists\n    if: exists(path:Cargo.toml)\n    run: cat Cargo.toml\n  - name: Only if HOME is set\n    if: exists(env:HOME)\n    run: printf '%s\\n' \"$HOME\"\n```\n\nContainerfile workflow and GitHub/Gitea examples are covered below. `ci` prefers `podman`, then `flatpak-spawn --host podman` when running inside Flatpak, then falls back to `docker`. Git commands can use the host binary, `flatpak-spawn --host git`, a custom command wrapper, or fall back to `docker.io/alpine/git:latest` in `auto`/`alias` mode.\n\n## Config\n\nOptional config lives in:\n\n```text\n/etc/ci.yml\n/etc/ci/config.yml\n~/.config/ci/config.yml\n.ci/config.yml\n```\n\nThe same `.yaml` filenames are also accepted. Config is loaded in order from system, user, then project config, so project config wins. `--config path/to/file.yml` uses that file for normal config while still keeping system/user `policy` and `locked` sections.\n\nSupported defaults include shell, quiet output, fail-fast, tech stack, architecture, container settings, container runtime, git mode/command/image, default install mode, recursive checkout, default branch allowlist, artifact store, and actions cache. `quiet: true` hides normal output, warnings, and workflow script output, while still showing critical errors. By default, `.github/workflows` and `.gitea/workflows` are discovered only in bare repositories; set `other_workflows: true` or `other_workflows: false` to override that while keeping native `.ci` workflows enabled.\n\nExample:\n\n```yaml\nquiet: true\nfail_fast: true\ntech: rust\narch:\n  - x64\n  - arm64\n\ncontainer:\n  arch:\n    - x64\n    - arm64\n  components:\n    - cargo-fmt\n    - cargo-clippy\n  packages:\n    - htop\n  env:\n    RUST_BACKTRACE: \"1\"\n  volumes:\n    - ~/.cache/my-project:/cache\n\ngit_mode: auto\ngit_image: docker.io/alpine/git:latest\ninstall_mode: copy\nrecursive_checkout: true\nother_workflows: false\n\nlocked:\n  install_mode: copy\n\nbranches:\n  allow:\n    - main\n    - develop\n\nworkflows:\n  build:\n    branches:\n      allow:\n        - main\n        - develop\n\nhooks:\n  pre-push:\n    branches:\n      allow:\n        - main\n```\n\nIn `.ci/config.yml`, default fields can be written directly at the top level; wrapping them in `defaults:` is still accepted. In workflow files, `defaults:` can set workflow defaults such as `tech`, `container`, `execution`, `branches`, `artifacts`, and `env`; direct workflow fields override those defaults. Unknown YAML keys are rejected so misspelled fields fail early.\n\n`git_mode` accepts `auto`, `host`, `flatpak`, `custom`, or `alias`. In `auto` mode, `ci` detects Flatpak and uses `flatpak-spawn --host git` when available, then falls back to host `git` or the configured Git container image. Use `git_mode: custom` with `git_command` to force a wrapper; `git-command: \"flatpak-spawn --host git\"` and YAML lists are both accepted. The `--git-command` flag accepts the same command string.\n\n```yaml\ngit_mode: custom\ngit_command:\n  - flatpak-spawn\n  - --host\n  - git\n```\n\n`install_mode` accepts `link` or `copy` and is used when `ci install` is run without `--mode`/`-m`; the CLI flag wins over normal config. Put defaults under `policy:` or `locked:` to apply them after normal config and CLI flags. For policy values, system config is strongest, then user config, then project config, so `/etc/ci.yml` can enforce a managed default such as:\n\n```yaml\nlocked:\n  install_mode: copy\n```\n\n`--arch` accepts comma-separated values and can be repeated, so `--arch x64,arm64` and `--arch x64 --arch arm64` are equivalent. `arch` accepts either one value or a YAML list and is also used as the default `container.arch` when the container arch list is omitted. Architecture is an execution setting. The selected execution architecture is exposed as `CI_ARCH`; the host machine architecture is exposed as `CI_HOST_ARCH`. Native YAML workflows can run inside a generated container with config-level `container`, workflow `defaults.container`, workflow-level `container`, or a selected tech stack; workflow-level settings override the defaults. `container_runtime: auto` prefers direct Podman, then host Podman through `flatpak-spawn --host` inside Flatpak, then Docker. Explicit `podman` and `docker` settings also use the matching host runtime through `flatpak-spawn --host` when needed. Use `-c`/`--container` to force a generated container for native workflows, or `-C`/`--no-container` to ignore configured native containers and run native steps on the host. `tech`, `type`, `tech-stack`, and `container.type` accept `auto`, `general`, `rust`, `node`, `go`, `python`, `maven`, `gradle`, and `dotnet`; common aliases such as `npm`, `js`, `golang`, `py`, and `.net` are accepted. Omitted/`auto` detects the stack from project files and step commands, then falls back to a general Debian image. Rust containers support `components`, installed with `rustup component add`; `cargo-fmt` maps to `rustfmt` and `cargo-clippy` maps to `clippy`. Container `env`, `volumes`, `workdir`, and `readonly` are passed to native container runs. `container.readonly: true` mounts `/work` read-only; a native step can override it with `readonly: false` or opt into it with `readonly: true`. `read-only` and `read_only` are accepted aliases. When a container workflow, native container workflow, or action does not set `container.platform`, `ci` maps the selected arch to a podman/docker platform such as `linux/amd64` or `linux/arm64`.\n\nPrint JSON Schema for editor integration or validation tooling:\n\n```sh\nci schema config\nci schema workflow\n```\n\nWorkflow-local defaults:\n\n```yaml\nname: build\ndefaults:\n  tech: node\n  container:\n    packages:\n      - git\n  env:\n    NODE_ENV: production\n\nsteps:\n  - run: npm ci\n  - run: npm run build --if-present\n```\n\n## Tech Stacks And Containers\n\n`tech`, `type`, `tech-stack`, and `container.type` accept:\n\n- `auto`\n- `general`\n- `rust`\n- `node`\n- `go`\n- `python`\n- `maven`\n- `gradle`\n- `dotnet`\n\nCommon aliases such as `npm`, `js`, `golang`, `py`, `.net`, and `csharp` are accepted.\n\nDefault images:\n\n- Rust: `docker.io/library/rust:latest`\n- Node.js: `docker.io/library/node:22-bookworm-slim`\n- Go: `docker.io/library/golang:latest`\n- Python: `docker.io/library/python:3`\n- Maven: `docker.io/library/maven:latest`\n- Gradle: `docker.io/library/gradle:latest`\n- .NET: `mcr.microsoft.com/dotnet/sdk:latest`\n- General: `docker.io/library/debian:stable-slim`\n\nForce or disable containers for native workflows:\n\n```sh\nci run --container build\nci run --no-container build\nci run -c build\nci run -C build\n```\n\nRun for multiple container architectures:\n\n```yaml\ncontainer:\n  arch:\n    - x64\n    - arm64\n```\n\nIf `container.arch` is set and no `--arch` override is provided, native container workflows run once per listed architecture.\n\nMount `/work` read-only for check-only container steps:\n\n```yaml\ncontainer:\n  image: docker.io/library/rust:latest\nsteps:\n  - run: cargo fmt --check\n    readonly: true\n  - run: cargo build --release\n    readonly: false\n```\n\nUse `container.readonly: true` to make read-only the workflow default. Step-level `readonly` overrides it.\n\nNative containers use stack-aware cache mounts under `.git/ci/container-cache` for common dependency caches such as Cargo, npm, Go modules, pip, Maven, Gradle, and NuGet. Build output paths such as `target/` stay in the repository mount so later `export` steps can see them.\n\nRun a single native `run:` step in its own image:\n\n```yaml\nsteps:\n  - name: Node lint\n    container: docker.io/library/node:22-bookworm-slim\n    run: npm test\n\n  - name: Tool check\n    container:\n      file: .ci/tools.Containerfile\n      image: localhost/my-project-tools\n      env:\n        TOOL_MODE: strict\n      volumes:\n        - /tmp:/tmp/ci-tools\n      packages:\n        - shellcheck\n    run: tool check\n```\n\nStep-level `packages` and Rust `components` build a generated image for that step, using the step `image` as the base image. `container: false` still keeps a step on the host. `ci run --no-container` disables workflow and step containers for native YAML workflows.\n\n## Containerfile Examples\n\nUse a custom image for native workflow steps:\n\n```Dockerfile\n# Containerfile.ci\nFROM docker.io/library/rust:latest\nRUN rustup component add rustfmt clippy\nRUN apt-get update \\\n    \u0026\u0026 apt-get install -y --no-install-recommends pkg-config \\\n    \u0026\u0026 rm -rf /var/lib/apt/lists/*\n```\n\n```sh\npodman build -t localhost/my-project-ci -f Containerfile.ci .\n```\n\n```yaml\nname: build\ncontainer:\n  image: localhost/my-project-ci\n  arch:\n    - x64\n    - arm64\n\nsteps:\n  - run: cargo fmt --check\n  - run: cargo test --all\n  - run: cargo build --release\n```\n\nUse a Containerfile as a workflow:\n\n```Dockerfile\n# .ci/image/Containerfile\nFROM docker.io/library/rust:latest\nWORKDIR /work\nCOPY . .\nRUN cargo test --all\nRUN cargo build --release\n```\n\nOptional metadata for that workflow:\n\n```yaml\n# .ci/image/workflow.yml\non:\n  - manual\n  - pre-push\ncontainer:\n  arch:\n    - x64\n    - arm64\n```\n\nRun it:\n\n```sh\nci run image\n```\n\nContainerfile workflows build with the repository root as context. Put checks in `RUN` instructions or in the image entrypoint.\n\n## Actions compatibility\n\n`ci` discovers `.github/workflows/*.yml` and `.gitea/workflows/*.yml` and runs a broad local subset including:\n\n- `on`, `env`, `defaults.run`\n- `jobs`, `needs`, `strategy.matrix`\n- `if`, `working-directory`, `continue-on-error`\n- `job.container`, `services`\n- `steps.run`, `steps.uses`\n\nBuilt-in shims exist for:\n\n- `actions/checkout`\n- `actions/cache`\n- `actions/upload-artifact`\n- `actions/download-artifact`\n\nExample:\n\n```yaml\nname: self-host\non:\n  - push\njobs:\n  test:\n    runs-on: local\n    container: docker.io/library/rust:latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: cargo test --all\n```\n\n## Systemd Integration\n\n`ci` does not need a daemon, but systemd can run it on a timer.\n\n`~/.config/systemd/user/my-project-ci.service`:\n\n```ini\n[Unit]\nDescription=Run ci build for my-project\n\n[Service]\nType=oneshot\nWorkingDirectory=%h/Projects/my-project\nExecStart=%h/.local/bin/ci --quiet run build\n```\n\n`~/.config/systemd/user/my-project-ci.timer`:\n\n```ini\n[Unit]\nDescription=Run ci build for my-project periodically\n\n[Timer]\nOnBootSec=5min\nOnUnitActiveSec=1h\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n```\n\nEnable it:\n\n```sh\nsystemctl --user daemon-reload\nsystemctl --user enable --now my-project-ci.timer\n```\n\nFor a system service, use an absolute `WorkingDirectory` and an absolute `ExecStart` path.\n\n## Environment\n\nWorkflow steps receive:\n\n- `CI=true`\n- `CI_TOOL=ci`\n- `CI_EVENT`\n- `CI_HOOK`\n- `CI_ARCH`\n- `CI_HOST_ARCH`\n- `CI_PLATFORM`\n- `CI_REPO`\n- `CI_GIT_DIR`\n- `CI_WORKFLOW`\n- `CI_WORKFLOW_PATH`\n- `CI_WORKFLOW_DIR`\n- `CI_RUN_ID`\n- `CI_PROVIDER`\n- `CI_HOOK_ARGS`\n- `CI_BRANCH` when available\n\nGitHub/Gitea compatibility variables such as `GITHUB_ACTIONS`, `GITHUB_REF`, `GITHUB_REF_NAME`, `GITEA_REF`, and `GITEA_REF_NAME` are also populated when applicable.\n\nUse expression syntax in YAML inputs:\n\n```yaml\nto: ~/.local/bin/ci.${{ env.CI_ARCH }}\n```\n\nUse shell syntax inside `run:`:\n\n```yaml\nrun: echo \"$CI_ARCH\"\n```\n\n## Install modes\n\n### Link mode\n\n```sh\nci install -m link\n```\n\nCreates an arch-specific symlink such as `.git/ci/run.x64` to the currently running `ci` binary. Link mode is a single-current-executable install: managed hooks are direct symlinks to that runner, and reinstalling with link mode removes other managed `run.\u003carch\u003e` files before rewriting hooks back to the current machine.\n\n`--source` is for copy installs; link mode always links the repository runner to the current `ci` executable.\n\n### Copy mode\n\n```sh\nci install -m copy\n```\n\nCopies the currently running `ci` binary into an arch-specific path such as `.git/ci/run.x64`.\n\nWhen no `--source` is set, copy mode installs only the current machine's architecture. That means you can run the same install once on an x64 machine and once on an arm64 machine to populate both `.git/ci/run.x64` and `.git/ci/run.arm64` without extra flags.\n\nWith one installed architecture, managed hooks are direct symlinks such as `.git/hooks/pre-push -\u003e ../ci/run.x64`. When copy mode installs or detects multiple `.git/ci/run.\u003carch\u003e` binaries, managed hooks become small scripts that select the matching runner from `uname -m`.\n\nCopy installs can use per-architecture sources:\n\n```sh\nci --arch x64,arm64 install -m copy -s 'dist/ci-linux-{arch}'\n```\n\nThat installs `dist/ci-linux-x64` to `.git/ci/run.x64` and `dist/ci-linux-arm64` to `.git/ci/run.arm64`.\n\n## Update\n\n```sh\nci update\nci update ~/Projects/myproject\nci update -a\nci update --all ~/Projects\nci update -r ~/Projects\nci update --recursive\nci other\n```\n\nFor link mode, this refreshes the runner symlink. For copy mode, this copies the current binary again. `ci update -s 'dist/ci-linux-{arch}'` uses the same per-architecture source template as install. Managed hooks are refreshed as direct symlinks when one runner is installed, or selector scripts when multiple runners are installed.\n\nUse `ci update PATH` to update another repository directly. Use `ci update --all [PATH]` to update Git repositories directly in that directory, without descending further. Use `ci update -r [PATH]` or `ci update --recursive [PATH]` to search recursively. Repositories without an installed `.git/ci/run...` binary are skipped.\n\n`ci other` prints the host-architecture runner installed under `.git/ci`, hashes it against the currently running `ci`, and reports `same`, `update-needed`, or `missing`.\n\n## Status and explain\n\n```sh\nci status\nci explain build --arch x64 --tech rust\nci explain pre-push\n```\n\n`ci explain` prints the selected architectures, container type/image/platform, matching reason, and native step conditions. `ci status` checks the configured architecture list and warns when a non-host architecture may need binfmt/qemu support.\n\n## Completion and man pages\n\nGenerate bash completion to stdout:\n\n```sh\nci completion bash\n```\n\nInstall bash completion locally:\n\n```sh\nmkdir -p ~/.local/share/bash-completion/completions\nci completion bash --output ~/.local/share/bash-completion/completions/ci\n```\n\nGenerate `man1` pages:\n\n```sh\nci man -d ./target/man\n```\n\nInstall them locally:\n\n```sh\nmkdir -p ~/.local/share/man/man1\nci man -d ~/.local/share/man/man1\n```\n\n`ci man --dir`/`-d` writes `ci.1` and one page per subcommand.\n\n## Remove\n\n```sh\nci uninstall\n```\n\nOnly removes hooks that contain the `managed-by: ci` marker or point at a managed `.git/ci/run...` target.\n\nUse:\n\n```sh\nci uninstall --restore\n```\n\nto restore backed-up hooks named `hook-name.ci-backup`.\n\n## Hook behavior\n\n`ci` can run as:\n\n```sh\nci hook pre-commit\n```\n\nor be invoked directly as a Git hook.\n\nWith one installed runner, installed hooks are symlinks to the runner:\n\n```sh\n.git/hooks/pre-push -\u003e ../ci/run.x64\n```\n\nWith multiple installed runners, each hook is a small managed script that selects the arch-specific runner and calls:\n\n```sh\n../ci/run.$ci_arch hook \u003chook-name\u003e \"$@\"\n```\n\n## Artifacts\n\nSuccessful runs are recorded under:\n\n```text\n.git/ci/runs/\n.git/ci/artifacts/\n```\n\nNative workflows can declare:\n\n```yaml\nartifacts:\n  paths:\n    - target/release/ci\n  mode: keep\n```\n\nArtifacts can later be exported with:\n\n```sh\nci clean -m move -d ./ci-artifacts\n```\n\nBuild outputs can also be copied during a workflow:\n\n```yaml\nsteps:\n  - use: export\n    if: exists(from)\n    from: target/release/ci\n    to: dist/ci\n    replace: true\n```\n\n## Troubleshooting\n\nInspect discovery and matching:\n\n```sh\nci list\nci explain build\nci explain pre-push\nci status\n```\n\nUse `--verbose` for command traces, `-vv`/`-vvv` for extra runner detail, and `--quiet` for low-noise hooks or timers:\n\n```sh\nci --verbose build\nci -vv build\nci --quiet run --event pre-push build\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoobping%2Fci","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoobping%2Fci","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoobping%2Fci/lists"}