Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,25 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false

- name: Install static analysis tools
run: sudo apt-get update && sudo apt-get install -y shellcheck

- name: Check shell scripts
run: shellcheck scripts/*.sh examples/project/scripts/ci/*.sh
run: shellcheck scripts/*.sh examples/project/scripts/ci/*.sh templates/config-repository/scripts/*.sh

- name: Build and inspect inert prototype
run: scripts/validate.sh

- name: Validate public configuration template
run: |
python3 templates/config-repository/scripts/test_policy.py
templates/config-repository/scripts/validate.sh
templates/config-repository/scripts/validate.sh --config templates/config-repository/examples/multi-host/fleet.json
temporary_directory="$(mktemp -d)"
templates/config-repository/scripts/init.sh \
--organization test-company \
--project test-app \
--output "${temporary_directory}/fleet.json"
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Never commit credentials or real deployment configuration. See [SECURITY.md](SEC

- [Architecture](docs/ARCHITECTURE.md)
- [Controller decision record](docs/adr/0001-actions-scale-set-client.md)
- [Public engine / private configuration decision](docs/adr/0002-public-engine-private-configuration.md)
- [Experimental deployment prototype](docs/DEPLOYMENT-PROTOTYPE.md)
- [Live pilot runbook](docs/LIVE-PILOT.md)
- [Host maintenance standard](docs/HOST-MAINTENANCE.md)
Expand All @@ -98,6 +99,13 @@ Never commit credentials or real deployment configuration. See [SECURITY.md](SEC
- [Agent instructions](AGENTS.md)
- [Third-party notices](THIRD_PARTY_NOTICES.md)

### Configuration template

- [Public configuration-repository scaffold](templates/config-repository/README.md)
- [Configuration-template milestone](https://github.com/RandomDevelopment/ci-fleet/issues/12)

The scaffold is validated in this repository before being published as the standalone `ci-fleet-config-template` GitHub template. It contains fictional data only; generated organization configuration should normally be private.

### Copyable examples

- [Experimental read-only workflow](examples/workflows/experimental-smoke.yml.example)
Expand Down
38 changes: 38 additions & 0 deletions docs/adr/0002-public-engine-private-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# ADR 0002: Public delivery engine with private organization configuration

- Status: Accepted
- Date: 2026-07-13
- Tracks: [#12](https://github.com/RandomDevelopment/ci-fleet/issues/12)

## Context

ci-fleet is expanding from runner lifecycle management into a container delivery standard. The reusable engine benefits from public documentation, review, examples, and an Unlicense release. Real deployments contain organization-specific topology and policy that should not be published. Neither public nor private Git repositories are appropriate secret stores.

## Decision

Keep ci-fleet public. Publish a separate public `ci-fleet-config-template` that users generate into private configuration repositories. Random Development will generate `rd-delivery-config` from the same public template used by everyone else.

```mermaid
flowchart LR
F[public ci-fleet engine] --> T[public config template]
T --> R[private RD configuration]
T --> U[another user's private configuration]
R --> P[private project repositories]
U --> X[their private project repositories]
```

The public engine owns schemas, reusable workflows, generic controller/deployment code, validation, examples, and policy. A private configuration repository owns real repository mappings, logical host groups, environment policies, capacity, image names, and internal operating notes.

Secret values remain in GitHub Environments, host-local root-owned files, or an external secret manager. Configuration may declare required secret names but never their values.

## Consequences

- Public reusable workflows must be called at immutable commit SHAs.
- Public repositories never receive access to self-hosted runners.
- Examples use fictional organizations, domains, repositories, and hosts.
- Real private configuration is validated against the same public schema.
- Publishing the controller does not weaken its security boundary; credentials, runner-group policy, environment protection, and host isolation remain authoritative.

## Rollback

The configuration template is additive. Organizations can stop consuming it without changing runner lifecycle code. Random Development can keep its configuration private or migrate it to another configuration system while retaining the public delivery standards.
7 changes: 7 additions & 0 deletions templates/config-repository/.github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
45 changes: 45 additions & 0 deletions templates/config-repository/.github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Validate configuration

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: config-validation-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
# This public template must never receive access to self-hosted runners.
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false

- name: Validate shell and Python syntax
run: |
bash -n scripts/init.sh scripts/validate.sh
python3 -m py_compile scripts/init.py scripts/validate.py scripts/test_policy.py

- name: Prove forbidden configurations fail closed
run: python3 scripts/test_policy.py

- name: Validate reference configurations
run: |
./scripts/validate.sh
./scripts/validate.sh --config examples/multi-host/fleet.json

- name: Prove initialized configurations pass strict policy
run: |
temporary_directory="$(mktemp -d)"
./scripts/init.sh \
--organization test-company \
--project test-app \
--output "${temporary_directory}/fleet.json"
14 changes: 14 additions & 0 deletions templates/config-repository/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.env
.env.*
!.env.example
*.key
*.pem
*.p12
*.pfx
*.keystore
credentials/
secrets/
private/
*.local.json
__pycache__/
*.pyc
23 changes: 23 additions & 0 deletions templates/config-repository/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Agent instructions

## Purpose

This repository maps projects to CI pools, deployment environments, logical host groups, and container images. It never stores secret values.

## Required verification

Before committing configuration changes, run:

```bash
./scripts/validate.sh --strict
```

## Hard rules

- Never add real `.env` files, credentials, tokens, private keys, cookies, or passwords.
- Do not weaken `public_repositories: false` for Docker-socket runner pools.
- Production environments must require approval and must not deploy automatically.
- CI runner hosts and application deployment hosts are separate roles.
- Image promotion uses immutable digests; do not rebuild separately for production.
- Reusable workflows must be pinned to a full reviewed commit SHA.
- Update schema and validator together.
24 changes: 24 additions & 0 deletions templates/config-repository/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>
75 changes: 75 additions & 0 deletions templates/config-repository/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# ci-fleet configuration template

This is the public, secret-free starting point for an organization's private `ci-fleet` configuration repository. It records which trusted projects may use each CI pool, which logical deployment environments exist, and the standardized commands every project must expose.

It does **not** contain runner registration tokens, deploy credentials, private keys, host addresses, or `.env` files.

```mermaid
flowchart LR
E[Public ci-fleet engine] -->|pinned reusable workflow| P[Private project repos]
C[Private config created from this template] -->|logical policy| P
P -->|fast / full| R[Trusted Docker CI pool]
P -->|approved image digest| D[Development hosts]
P -->|manual approval + image digest| X[Production hosts]
S[GitHub Environments / host secret store] -. secret values .-> P

classDef public fill:#dff4ff,stroke:#1570a6,color:#102a43
classDef private fill:#fff3cd,stroke:#9a6700,color:#3d2b00
class E public
class C,P,R,D,X,S private
```

## Start a private organization configuration

1. Create a **private** repository from this public template.
2. Clone it and initialize the first project:

```bash
./scripts/init.sh --organization your-org --project your-app
```

3. Edit `fleet.json` to add the organization's real logical mappings.
4. Run the strict policy check:

```bash
./scripts/validate.sh --strict
```

5. Configure secret **values** in GitHub Environments or the deployment host's secret manager. The repository stores only names such as `DEPLOY_AUTH`.

The initializer refuses to replace a configured file unless `--force` is explicit. Run `./scripts/init.sh --help` for repository, registry, label, and output options.

## Hard rules

- Public repositories never receive access to the trusted self-hosted runner pool.
- Every project implements exactly `./scripts/ci/run.sh fast` and `./scripts/ci/run.sh full`; each command runs the project's own Docker-defined test environment.
- CI runner pools and deployment host groups are separate trust roles.
- Production deployment is manual and requires GitHub Environment approval.
- Reusable workflows and third-party actions are pinned to immutable commits.
- Configuration contains logical identifiers only. Secret values, private host details, and credentials never enter Git.
- Promoted artifacts are container image digests; production does not rebuild a different image.

`fleet.schema.json` provides editor completion and structural documentation. `scripts/validate.py` is the authoritative dependency-free policy check, including relationships JSON Schema cannot express clearly.

## Repository map

| Path | Purpose |
|---|---|
| `fleet.json` | Fictional, valid reference configuration to initialize or replace |
| `fleet.schema.json` | JSON Schema draft 2020-12 editor contract |
| `scripts/init.sh` | Safe first-project initializer |
| `scripts/validate.sh` | Structural, policy, and secret-boundary validation |
| `scripts/test_policy.py` | Regression tests proving unsafe configurations fail closed |
| `examples/multi-host/fleet.json` | Fictional two-project, multi-host topology |
| `SECURITY.md` | Secret handling and vulnerability reporting |
| `AGENTS.md` | Non-negotiable rules for humans and coding agents |

## Public and private boundary

| Safe in this public template | Belongs in the private config repo | Belongs only in a secret store |
|---|---|---|
| Schema, validator, fictional examples | Real repository names and logical host-group names | Tokens, passwords, private keys |
| Standard CI entrypoint names | Environment policy and allowed repository lists | Host addresses and SSH material |
| Reusable workflow references | Required secret **names** | `.env` contents and app credentials |

The public engine and this template use the [Unlicense](LICENSE). See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) before copying third-party material into a derived repository.
9 changes: 9 additions & 0 deletions templates/config-repository/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Security policy

This repository stores deployment relationships and policy, not credentials.

Never commit passwords, tokens, private keys, GitHub App keys, SSH keys, TLS keys, production `.env` files, database connection strings containing credentials, cookies, or cloud credentials. A private repository is not a secret manager.

Configuration may list required secret **names**, such as `DATABASE_URL`, because their values live in GitHub Environments, root-owned host files, or an external secret manager.

If a secret is committed, revoke or rotate it immediately before removing it from Git history. Treat deletion from the latest commit as insufficient.
7 changes: 7 additions & 0 deletions templates/config-repository/THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Third-party notices

No third-party source code is vendored in this template.

The generated configuration is designed to interoperate with GitHub Actions, Docker Engine, Docker Compose, and ci-fleet. Those projects retain their respective licenses.

Original template files are released under the Unlicense in `LICENSE`.
74 changes: 74 additions & 0 deletions templates/config-repository/examples/multi-host/fleet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"$schema": "../../fleet.schema.json",
"schema_version": 1,
"organization": {
"slug": "sample-company",
"registry": "ghcr.io/sample-company",
"delivery_engine": "RandomDevelopment/ci-fleet",
"workflow_ref_policy": "immutable-commit"
},
"runner_pools": {
"trusted-ci": {
"routing_labels": ["docker-ci"],
"allowed_repositories": ["sample-company/api", "sample-company/web"],
"public_repositories": false,
"max_concurrent_jobs": 4
}
},
"host_groups": {
"development-east": {
"role": "deployment",
"environment_class": "development"
},
"staging-east": {
"role": "deployment",
"environment_class": "staging"
},
"production-primary": {
"role": "deployment",
"environment_class": "production"
}
},
"environments": {
"development": {
"host_group": "development-east",
"automatic": true,
"requires_approval": false,
"required_secret_names": ["DEPLOY_AUTH"]
},
"staging": {
"host_group": "staging-east",
"automatic": false,
"requires_approval": true,
"required_secret_names": ["DEPLOY_AUTH"]
},
"production": {
"host_group": "production-primary",
"automatic": false,
"requires_approval": true,
"required_secret_names": ["DEPLOY_AUTH"]
}
},
"projects": {
"api": {
"repository": "sample-company/api",
"image": "ghcr.io/sample-company/api",
"ci_pool": "trusted-ci",
"ci_entrypoints": {
"fast": "./scripts/ci/run.sh fast",
"full": "./scripts/ci/run.sh full"
},
"deployments": ["development", "staging", "production"]
},
"web": {
"repository": "sample-company/web",
"image": "ghcr.io/sample-company/web",
"ci_pool": "trusted-ci",
"ci_entrypoints": {
"fast": "./scripts/ci/run.sh fast",
"full": "./scripts/ci/run.sh full"
},
"deployments": ["development", "production"]
}
}
}
Loading