From d652bfeae0a719c8880d3889a97cf120458e163a Mon Sep 17 00:00:00 2001 From: Romamo Date: Thu, 8 Oct 2026 12:53:19 +0300 Subject: [PATCH] Polish the GitHub page: README hero, guides index, community files The README opens with the pitch, a diagram of the gate, a short quickstart and a guide index; the long walkthroughs moved verbatim to docs/getting-started.md. Adds issue forms (bug, feature, pilot request), a pull request template, SUPPORT.md and the Contributor Covenant 2.1, and aligns the PyPI summary and keywords with the project's description. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FS8hJ94DCmHAsqa4LHiywn --- .github/ISSUE_TEMPLATE/bug_report.yml | 42 ++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 21 + .github/ISSUE_TEMPLATE/pilot.yml | 45 ++ .github/PULL_REQUEST_TEMPLATE.md | 11 + .github/SUPPORT.md | 8 + CHANGELOG.md | 5 + CODE_OF_CONDUCT.md | 83 ++++ README.md | 472 ++++++--------------- docs/assets/logo.svg | 4 + docs/getting-started.md | 149 +++++++ pyproject.toml | 3 +- 12 files changed, 519 insertions(+), 332 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/pilot.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/SUPPORT.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 docs/assets/logo.svg create mode 100644 docs/getting-started.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..0c37b3c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,42 @@ +name: Bug report +description: Something Cloudfall did is wrong, or it reported success it could not prove +labels: [bug] +body: + - type: markdown + attributes: + value: | + Never paste real hostnames, addresses, credentials or secret values. Replace them with synthetic ones such as `h1` and `203.0.113.10`. + - type: input + id: version + attributes: + label: Cloudfall version + description: The output of `cloudfall --version`, or the commit you run + validations: + required: true + - type: textarea + id: command + attributes: + label: Command + description: The exact command you ran + render: console + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual + description: What happened instead. Include the JSON document Cloudfall printed and its exit code + render: json + validations: + required: true + - type: input + id: target + attributes: + label: Target + description: Debian version and provider of the server, if the bug involves one diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..39c37f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Question or idea + url: https://github.com/cloudfall-dev/cloudfall/discussions + about: Ask how something works or share an idea in Discussions + - name: Security vulnerability + url: https://github.com/cloudfall-dev/cloudfall/security/advisories/new + about: Report privately; never in a public issue diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..00afc86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,21 @@ +name: Feature request +description: A capability Cloudfall should have +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What you are trying to do and what stops you today + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposal + description: How you would like Cloudfall to handle it + - type: textarea + id: alternatives + attributes: + label: Alternatives + description: What you do now instead diff --git a/.github/ISSUE_TEMPLATE/pilot.yml b/.github/ISSUE_TEMPLATE/pilot.yml new file mode 100644 index 0000000..edfb4c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pilot.yml @@ -0,0 +1,45 @@ +name: Pilot request +description: Move an application off a PaaS onto servers you own, with help from the maintainers +title: "Pilot: " +labels: [pilot] +body: + - type: markdown + attributes: + value: | + Pilots get hands-on help with a migration in exchange for feedback. This issue is public: describe your setup in general terms and leave out names, hostnames and credentials. + - type: dropdown + id: source + attributes: + label: Where it runs today + options: + - Render + - Heroku + - Fly.io + - Railway + - Vercel + - Own servers, managed by hand or Ansible + - Other + validations: + required: true + - type: textarea + id: stack + attributes: + label: Stack + description: Services, databases, workers and cron jobs, roughly + validations: + required: true + - type: input + id: hosts + attributes: + label: Servers + description: How many servers you run, or expect to run after the move + - type: input + id: bill + attributes: + label: Monthly hosting bill + description: A rough range is enough + - type: input + id: contact + attributes: + label: How to reach you + description: Optional. Leave it empty and we will reply here diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a01a5b7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## What and why + + + +## Checklist + +- [ ] `uv run ruff check .`, `uv run mypy`, `uv run pytest` pass +- [ ] `uv run cloudfall config validate --project config/examples` passes +- [ ] `uv run ansible-lint engine/ansible` passes, if the engine changed +- [ ] A `CHANGELOG.md` entry under `[Unreleased]`, if users will notice +- [ ] Fixtures and docs are synthetic: no real hostnames, addresses or credentials diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..6c8a96d --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,8 @@ +# Getting help + +- **How do I…?** Ask in [Discussions](https://github.com/cloudfall-dev/cloudfall/discussions) under Q&A +- **Something is broken.** Open a [bug report](https://github.com/cloudfall-dev/cloudfall/issues/new?template=bug_report.yml) with the command, the JSON Cloudfall printed and its exit code +- **Moving off a PaaS?** Open a [pilot request](https://github.com/cloudfall-dev/cloudfall/issues/new?template=pilot.yml) +- **A security issue.** Report it privately as described in [SECURITY.md](../SECURITY.md) + +Start with the [getting started guide](../docs/getting-started.md) and the [proving runs](../docs/proving-runs/): most questions about what Cloudfall does on a real server are answered there. diff --git a/CHANGELOG.md b/CHANGELOG.md index e9bb404..bfb0f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,11 @@ Notable changes to Cloudfall. The format follows ### Changed +- The README opens with what Cloudfall is, a diagram of the gate, a short + quickstart and a guide index; the long walkthroughs moved to + [docs/getting-started.md](docs/getting-started.md). The repository has issue + forms (bug, feature, pilot request), a pull request template, a support + page and a code of conduct, and the PyPI summary matches the project's description - The repository moved to the `cloudfall-dev` organization: github.com/cloudfall-dev/cloudfall. The package metadata, the README, the site and `REPOSITORY_URL` point there; the old diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..97ddcbf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [conduct@cloudfall.dev](mailto:conduct@cloudfall.dev). All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/README.md b/README.md index c073746..d874979 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,30 @@ -# Cloudfall +

+ Cloudfall +

+ +

Cloudfall

+ +

+ Your servers, run by an agent. With a record that answers why. +

+ +

+ CI + PyPI + Python + License: AGPL-3.0 + Discussions +

+ +

+ Website · + Getting started · + Migrate from Render · + Roadmap · + Manifesto +

+ +--- Cloudfall is the operator's record for self-hosted servers run by an AI agent. The agent decides; Ansible executes; Cloudfall says what the agent may do, @@ -7,388 +33,172 @@ step and every decision an audit entry: what the fleet looked like, what was proposed, who approved, what verify reported. Cloudfall never reports success it cannot prove. Open source, plain Debian, no Kubernetes. -It is built for teams with one to twenty servers and no platform hire: -founders leaving a PaaS whose bill crossed $1,000 a month, and anyone whose -servers are already run by Claude Code, Codex or another agent. Today it -moves your applications and databases onto one or two Hetzner-class servers -you own (hardened baseline, monitoring, PostgreSQL and Redis with backups -that provably restore, health-gated deploys with rollback, a real DNS -cutover) and an always-on operator runs them there with a receipt for every -action. The wedge use case is a migration from Render; see the -[roadmap](ROADMAP.md) for the milestone plan and the -[manifesto](MANIFESTO.md) for the principles behind the project. - -Where it is going: your existing Ansible inventory and playbooks as the -only config, with each playbook exposed to the agent as a declared tool. -That design is written up and not yet built; see -[Direction](#direction-your-ansible-not-ours) below. +```mermaid +flowchart LR + A["AI agent
(Claude Code, Codex, MCP)"] -->|proposes| G{"Cloudfall
policy gate"} + H["Human"] -.->|approves what
can't be undone| G + G -->|allowed| X["Ansible"] + X --> S["Your Debian servers"] + S -->|observed state| V["Verify"] + V --> R[("Receipt:
evidence, proposal,
approval, outcome")] + R -.->|earned autonomy| G +``` + +## Who it is for + +Teams with one to twenty servers and no platform hire: founders leaving a +PaaS whose bill crossed $1,000 a month, and anyone whose servers are already +run by Claude Code, Codex or another agent. Today Cloudfall moves your +applications and databases onto one or two Hetzner-class servers you own +(hardened baseline, monitoring, PostgreSQL and Redis with backups that +provably restore, health-gated deploys with rollback, a real DNS cutover), +and an always-on operator runs them there with a receipt for every action. > **Proven live, not promised.** Everything implemented has run on > disposable Hetzner Cloud Debian 13 servers: an application actually hosted -> on Render was cut over with its data and a real TTL-lowered DNS flip on an -> owned domain, a deliberately bad release was rolled back automatically -> along the way, an induced failure was remediated by the operator first -> with a single human approval and then autonomously under declared policy, +> on Render was cut over with its data and a real TTL-lowered DNS flip, a bad +> release was rolled back automatically, an induced failure was remediated +> first with one human approval and then autonomously under declared policy, > a timer-driven drill proved a real backup restorable, and an alert crossed > the public network to a second machine. The entire migration was driven by > an AI agent through the MCP server alone. Full reports: -> [proving-runs](docs/proving-runs/). The one layer not yet exercised live -> is bare-metal RAID/storage provisioning (see the [roadmap](ROADMAP.md)) +> [proving-runs](docs/proving-runs/) ## Why Cloudfall - **A record that answers "why did the agent do that".** Every deploy, remediation and drill leaves a receipt; every operator decision records - the evidence it was based on, the proposal, the approval and the outcome. - An agent's own log says a tool was called. This says what the fleet looked - like when it was + the evidence, the proposal, the approval and the outcome. An agent's own + log says a tool was called. This says what the fleet looked like when it was - **Verified, not just ran.** Status is derived from validated observations, - `cloudfall audit` reports drift between declared config and observed - servers with distinct exit codes, deploys are gated on health and rolled - back when it fails. Cloudfall never reports success it cannot prove -- **Autonomy earned from the record.** The always-on operator watches - declared alerts and audited drift, remediates what its receipt history - proves reversible under declared policy, and asks you only about what - can't be undone -- **AI-agent native.** Agents operate through a stable CLI and Python API - with structured JSON results instead of inventing shell commands over SSH. - `cloudfall mcp serve` exposes read-only evidence tools freely; a deploy, - rollback, restart or migration only returns its plan until called again - with `yes`, and approving a recorded operation stays a person's command -- **The bill buys computers, not margins.** A steady $1,000/mo cloud bill is - mostly margin on commodity compute and managed databases. On servers you - own, that difference becomes runway, every year -- **Good-enough databases, with proof.** Managed PostgreSQL is mostly - insurance. Cloudfall keeps the coverage and drops the premium: loopback - PostgreSQL and Redis with receipted backups and timer-driven restore - drills that prove a real backup restorable instead of assuming it -- **Boring on purpose.** Applications run as native systemd services with - artifact releases and symlink rollback, behind nginx, on stock Debian. Any - Linux admin can take over the server without Cloudfall installed - -## Architecture - -Three modules with strict boundaries: - -- [`config/`](config/README.md) — the config: declarative YAML resources and - their JSON Schemas -- [`sdk/`](sdk/README.md) — the `cloudfall` CLI and Python API used by - agents and tooling -- [`engine/`](engine/README.md) — internal execution machinery (Ansible-based) - -Rules: the config contains no execution logic, the CLI reads and validates -the config without Ansible internals, the engine never silently rewrites the -config, and entry points call the CLI rather than Ansible directly. Mutating -operations execute through the engine's command-line and playbook contracts, -never its internals. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for the -full architecture and the long-term fleet vision. - -What you run all of this against is a **project**: one directory, usually a -private repository, holding your fleet (servers, server types, SSH public -keys), your applications (applications, components, services, domains), the -declarations that operate them (alert rules, operator policies, logging -stacks), and a `pyproject.toml` pinning the Cloudfall commit it runs with. -Every `cloudfall` command runs inside a project: the current directory when -it is one, or the directory named by `--project` or by the -`CLOUDFALL_PROJECT` environment variable. Relative paths, including the -`tmp/` defaults, resolve against the project, as if the command had been -started there. - -## Status - -Cloudfall is pre-1.0 and honestly labeled. Implemented today, and proven -live on disposable Debian targets: - -- Typed YAML config validated against JSON Schemas, deterministic Ansible - inventory rendering, read-only server inspection, and - config-versus-observed drift audit with distinct exit codes -- Hardened Debian baseline, UTC time, nftables firewall, and a guarded - Loki/Grafana/Alloy logging stack with declared alert rules and - notification channels over mTLS -- A service catalog (loopback PostgreSQL, Redis, and Nginx/TLS sites) with - sops/age secret rendering, receipted backups, and a timer-driven restore - drill with external alert delivery -- Health-gated deploys with artifact releases and symlink rollback, plus an - evidence-derived operations dashboard -- Render blueprint and live-API importers, guided data migration, and the - resumable `cloudfall migrate` orchestrator with explicit cutover steps -- The `cloudfall mcp serve` agent server and the always-on operator with receipted - proposals and policy-bounded autonomy - -The one implemented layer not yet validated live is bare-metal RAID/storage -provisioning. Next up is the fleet-density track: enforced resource sharing, -application mobility with node drain, PostgreSQL point-in-time recovery, and -the demand-gated failover formation, alongside broader catalog breadth. See -the [roadmap](ROADMAP.md) for the milestone plan. - -## Direction: your Ansible, not ours - -Most teams that would use Cloudfall already run Ansible. The next step is -to read their inventory as the only config instead of Cloudfall's own -resource documents, keep Cloudfall's declarations under one `cloudfall` key -in their `group_vars` and `host_vars`, and expose each of their playbooks to -the agent as a declared operation with a risk level, a verify step and an -audit entry. Ansible is the hand, the agent is the brain, Cloudfall is the -conscience. The design, its build order and what it deliberately leaves out -are in [docs/brownfield-design.md](docs/brownfield-design.md). None of it -is implemented yet; everything below this line is. + `cloudfall audit` reports drift with distinct exit codes, and deploys are + gated on health and rolled back when it fails +- **Autonomy earned from the record.** The operator remediates what its + receipt history proves reversible under declared policy, and asks you only + about what can't be undone +- **AI-agent native.** A stable CLI and Python API with structured JSON + results, and `cloudfall mcp serve` for agents. Read-only evidence tools are + free; a deploy, rollback, restart or migration returns its plan until + called again with `yes` +- **The bill buys computers, not margins.** On servers you own, the margin + on commodity compute and managed databases becomes runway +- **Good-enough databases, with proof.** Loopback PostgreSQL and Redis with + receipted backups and restore drills that prove a backup restorable +- **Boring on purpose.** Native systemd services, artifact releases with + symlink rollback, nginx, stock Debian. Any Linux admin can take over the + server without Cloudfall installed ## Quickstart -Cloudfall is on PyPI. The only prerequisite is -[`uv`](https://docs.astral.sh/uv/): it provisions the required Python 3.14 -interpreter and all dependencies automatically, so you do not need Python -3.14 preinstalled. +The only prerequisite is [`uv`](https://docs.astral.sh/uv/); it provisions +Python 3.14 and every dependency. ```console uv tool install cloudfall cloudfall --help ``` -The reference resource set lives in this repository, so validating it means -a clone: - -```console -git clone https://github.com/cloudfall-dev/cloudfall.git -cd cloudfall -uv sync -uv run cloudfall config validate --project config/examples -``` - -The command validates every YAML config document against the v1 JSON Schemas -and then checks cross-resource references. Successful and failed results are -emitted as structured JSON. - -Show the non-secret platform inventory and render it as deterministic Ansible -JSON: - -```console -uv run cloudfall inventory show --project config/examples -uv run cloudfall-engine inventory render --project config/examples -``` - -### Start your own project - -Your servers, applications, evidence, and any playbooks of your own belong in -a project: a directory of its own, kept private. Cloudfall's wheel bundles the -schema catalog and the Ansible engine, so the project needs no checkout of -this repository. `cloudfall init` is the first command, and it pins the -project to the released Cloudfall that ran it. Without a directory argument -it initializes the current directory, which must be empty: +Start a project (a directory of its own, kept private), declare your SSH key +and first server, then validate and audit: ```console uvx cloudfall init my-project cd my-project uv sync -``` - -The project holds one directory per resource kind (`servers/`, -`server-types/`, `ssh-public-keys/`, `applications/`, `components/`, -`services/`, `domains/`, `alert-rules/`, `operator-policies/`, -`logging-stacks/`), a `secrets/` directory with a `.sops.yaml` template for -its encrypted fragments, a README with the next steps (pass `--description` -to say what the project manages), an `AGENTS.md` stating the terms on which -an AI agent operates the project (see below), a `.gitignore` for the -runtime `tmp/` directory where evidence and receipts land, a fresh git -repository, and a `pyproject.toml`: - -```toml -[project] -name = "my-project" -version = "0" -requires-python = ">=3.14" -dependencies = ["cloudfall=="] - -[tool.uv] -package = false -``` - -Bump that version and run `uv sync` to move the project to a newer -Cloudfall. To run an unreleased Cloudfall in a project, point the dependency -at a checkout or a commit with uv's own -[`[tool.uv.sources]`](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources); -Cloudfall itself only ever pins a release. - -Only the kind directories are read as resources, so playbooks, roles, docs, -and tooling files may live anywhere else in the project. Commands find the -project on their own: the current directory when it is one, else -`--project`, else `CLOUDFALL_PROJECT`. Declare your SSH key and your first -server with `cloudfall add`; the server's type is created from the bundled -Debian 13 baseline when it does not exist yet, and every written file is -plain YAML you can edit (the reference set under `config/examples/` shows -every kind). Then validate, render, inspect, and audit from inside the -project: - -```console -uv run cloudfall add ssh-key ~/.ssh/id_ed25519.pub --owner roman +uv run cloudfall add ssh-key ~/.ssh/id_ed25519.pub --owner you uv run cloudfall add server h1 --address 203.0.113.10 uv run cloudfall config validate -uv run cloudfall-engine inventory render --output tmp/ansible-inventory.json -uv run cloudfall-engine playbook run inspect --inventory tmp/ansible-inventory.json --extra-vars cloudfall_inspect_output_directory=$PWD/tmp/observed -uv run cloudfall audit --observed tmp/observed -uv run cloudfall-engine playbook run playbooks/proxy.yml --roles roles ``` -Bundled playbooks are addressed by name (`cloudfall-engine playbook list` -shows them); project playbooks by path, with `--roles` directories searched -before the bundled roles. To move a project to a newer Cloudfall commit, bump -`rev` and run `uv sync`. - -### The agent contract - -Humans and AI agents both edit the resources and both run the CLI, with a -human approving anything that changes a server. `cloudfall init` writes -that arrangement down as the project's `AGENTS.md`, the operating contract -an agent started in the directory (Claude Code, Codex, or `cloudfall mcp serve`) -reads first: the canonical `uv run cloudfall` invocation and project -resolution, every command sorted by effect (changes nothing on servers, -writes project files, changes servers) with the gate each server-changing -command demands, the JSON-on-stdout, JSON-error-on-stderr output contract -and exit codes, what the agent may read under `tmp/`, the rule that secret -values are never read or written by an agent, and what is committed. A -one-line `CLAUDE.md` points Claude Code at it. The classification is -generated from the same catalog the tests check against the CLI, so a new -command cannot ship unclassified. +`cloudfall init` also writes an `AGENTS.md`: the contract an agent started +in the project reads first, with every command sorted by effect and the gate +each server-changing command demands. The full walkthrough, from inspection +and drift audit to the live dashboard, is in +[Getting started](docs/getting-started.md). ## Migrate from Render -Map a `render.yaml` blueprint onto Cloudfall config: - ```console uv run cloudfall import render render.yaml --application acme --server h1 +uv run cloudfall migrate --build acme-api=main # show the plan +uv run cloudfall migrate --build acme-api=main --yes # run it ``` -Web, private, and worker services become `Component` resources (workers use -a service-active health gate instead of a fabricated HTTP check), managed -PostgreSQL becomes a `Service` with application-owned peer-authentication -databases, and custom domains become TLS-required `Domain` routes. Each web -component receives an explicit listen port written as `PORT` into an -environment file rather than into any resource — the config never contains -secret values. The full walkthrough, including the Render-to-Cloudfall name -mapping, is in the [Render migration guide](docs/render-migration-guide.md). - -The importer never guesses silently. `IMPORT-REPORT.md` records everything -that was not imported (cron, static, and container services today), every -assumption it made, and every action required before cutover, including data -migration and DNS steps. Merge the emitted fragment with your servers and -server types, validate, then drive the whole migration with one resumable -plan: - -```console -uv run cloudfall migrate --build acme-api=main -uv run cloudfall migrate --build acme-api=main --yes -``` - -Without `--yes` the command shows the plan; with it, the plan executes step -by step — baseline, services, artifact builds, health-gated deployments, -HTTP routes, a DNS-verification pause at the cutover moment, TLS issuance, -and a final evidence pass that requires a compliant audit and healthy -routes. Progress persists after every step, so a failed step or the DNS -pause resumes exactly where it stopped. - -Managed-database contents follow the same guided path: save the source -connection URL into a local file and either run `cloudfall data copy` -directly (it shows the plan until you add `--yes`) or add `--data =` to the migration plan. The -engine dumps on the target host, restores over the peer-authenticated -socket, refuses non-empty target databases, verifies per-table row counts, -and writes a `DataMigrationReceipt`. The same path serves databases hosted -without a platform blueprint — serverless PostgreSQL providers such as -Neon — where only the data moves: see the +The importer never guesses silently: `IMPORT-REPORT.md` records what was not +imported, every assumption and every step required before cutover. The +migration runs baseline, services, builds, health-gated deploys, routes, a +DNS-verification pause, TLS and a final evidence pass, and resumes exactly +where it stopped. Databases move with `cloudfall data copy`, which verifies +per-table row counts and writes a receipt. See the +[Render migration guide](docs/render-migration-guide.md) and the [Neon migration guide](docs/neon-migration-guide.md). -## Inspect servers and audit drift +## Documentation -Each `Server` references a reusable `ServerType` -describing its required Debian version, software RAID, filesystem capacity, -packages, systemd services, and allowlisted configuration evidence. +| Guide | What it covers | +| --- | --- | +| [Getting started](docs/getting-started.md) | Projects, the agent contract, inspection, drift audit, dashboard | +| [Render migration](docs/render-migration-guide.md) | Importer, name mapping, data and DNS cutover | +| [Neon migration](docs/neon-migration-guide.md) | Moving serverless PostgreSQL data only | +| [Operator](docs/operator-guide.md) | The always-on operator, proposals, approvals, autonomy policy | +| [Secrets](docs/secrets-guide.md) | sops/age secrets rendered into per-component env files | +| [Logging](docs/logging-service-guide.md) | Loki, Grafana and Alloy behind an mTLS gateway | +| [Storage](docs/new-server-storage-guide.md) | Destructive, new-server-only RAID provisioning ([design](docs/hybrid-storage-design.md)) | +| [Architecture](ARCHITECTURE.md) | Module boundaries and the long-term fleet vision | -Collect a read-only snapshot from every reachable server, then compare it -with the config. Workflow commands use [Task](https://taskfile.dev) -(`brew install go-task` / `apt install task`); every task is a thin wrapper -over a `uv run` command, so if you prefer not to install Task, copy the -underlying command from [`Taskfile.yml`](Taskfile.yml): +## Architecture -```console -task inspect -task audit -``` +Three modules with strict boundaries: -The direct equivalent of `task audit`, for example, is: +- [`config/`](config/README.md): declarative YAML resources and their JSON Schemas +- [`sdk/`](sdk/README.md): the `cloudfall` CLI and Python API used by agents and tooling +- [`engine/`](engine/README.md): internal execution machinery (Ansible-based) -```console -uv run cloudfall audit --project config/examples --observed tmp/observed -``` +The config contains no execution logic, the CLI validates config without +Ansible internals, the engine never silently rewrites the config, and +mutating operations go through the engine's command-line and playbook +contracts. See [ARCHITECTURE.md](ARCHITECTURE.md). -Snapshots are written to `tmp/observed/.json` with mode `0600` and -never include configuration-file contents. The audit emits one JSON report: -exit code `0` compliant, `1` drift, `2` invalid input, `3` compliance unknown. +## Status -## Operations dashboard +Cloudfall is pre-1.0 and honestly labeled. Implemented and proven live on +disposable Debian targets: -Cloudfall renders validated config, host observations, and audit results into -a local read-only dashboard with an evidence-derived task queue and a public-service -lifecycle (planned → ready → deployed → configured → healthy): +- Typed YAML config with JSON Schemas, deterministic Ansible inventory, + read-only inspection and config-versus-observed drift audit +- Hardened Debian baseline, nftables firewall, and a Loki/Grafana/Alloy + logging stack with declared alert rules over mTLS +- Loopback PostgreSQL, Redis and Nginx/TLS sites with sops/age secrets, + receipted backups and a timer-driven restore drill +- Health-gated deploys with symlink rollback and an evidence-derived dashboard +- Render importers, guided data migration and the resumable `cloudfall migrate` +- `cloudfall mcp serve` and the always-on operator with receipted proposals + and policy-bounded autonomy -```console -task dashboard -``` +Not yet validated live: bare-metal RAID/storage provisioning. Next up: the +fleet-density track (resource sharing, node drain, PostgreSQL point-in-time +recovery). See the [roadmap](ROADMAP.md). -The build writes `tmp/dashboard/index.html` and machine-readable -`tmp/dashboard/operations.json`. Missing receipts or observations remain -visible as `no` or `unknown`; Cloudfall does not infer deployment merely because -a playbook exists. +### Direction: your Ansible, not ours -For a live view, run the dashboard as a server on the management host (a -VPS or your local machine): +Most teams that would use Cloudfall already run Ansible. The next step reads +their inventory as the only config and exposes each of their playbooks to +the agent as a declared operation with a risk level, a verify step and an +audit entry. Ansible is the hand, the agent is the brain, Cloudfall is the +conscience. Designed, not yet built: +[docs/brownfield-design.md](docs/brownfield-design.md). -```console -task dashboard:serve -``` +## Community -`cloudfall dashboard serve` re-derives the projection from state and evidence -on a refresh interval (default 10 seconds) and serves a page that updates -itself in place; `--inspect-services` additionally probes DNS, TLS, origin, -and public routes on every refresh, so service health is live. Server -hardware evidence still comes from observation snapshots: schedule -`task inspect` (cron or a timer) on the management host to keep it fresh. -The server binds `127.0.0.1:8100` by default; set `DASHBOARD_HOST`, -`DASHBOARD_PORT`, and `DASHBOARD_REFRESH` to override. Once it listens it -prints one JSON line with its URL, then serves until stopped. The projection stays -strictly read-only either way. - -## Always-on operator - -The operator is the always-on half of Cloudfall: a process on the management -host that watches declared alerts and audited drift, writes every diagnosis -as a schema-validated proposal receipt, and executes only existing engine -entry points — after an explicit approval, or autonomously for operation -classes licensed by a declared `OperatorPolicy` and earned receipt history. -DNS cutover, data deletion, and database promotion always require explicit -confirmation. See the [operator guide](docs/operator-guide.md). - -## Logging, secrets, and storage guides - -- A guarded parallel logging slice deploys Loki, loopback-only Grafana, an - mTLS ingestion gateway, and Alloy without touching legacy agents: see the - [logging service guide](docs/logging-service-guide.md) -- Declared secret references render into per-component `0600` environment - files from a sops/age-encrypted secrets directory: see the - [secrets guide](docs/secrets-guide.md) -- New two-drive servers use a RAID1 system area plus independent storage - tails: review the [hybrid storage design](docs/hybrid-storage-design.md) - and the destructive, new-server-only - [storage provisioning guide](docs/new-server-storage-guide.md) - -## Contributing and security - -See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md). -Notable changes are recorded in the [changelog](CHANGELOG.md). - -Everything in this repository is free under the AGPL and sufficient on its -own. The hosted always-on operator and commercial licensing are how the -project sustains itself; neither holds back the open core. +- Questions and ideas: [Discussions](https://github.com/cloudfall-dev/cloudfall/discussions) +- Bugs and feature requests: [Issues](https://github.com/cloudfall-dev/cloudfall/issues/new/choose) +- Want help moving off a PaaS? [Request a pilot](https://github.com/cloudfall-dev/cloudfall/issues/new?template=pilot.yml) +- Contributing: [CONTRIBUTING.md](CONTRIBUTING.md) · Conduct: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) · Security: [SECURITY.md](SECURITY.md) · Changes: [CHANGELOG.md](CHANGELOG.md) ## License -Cloudfall is licensed under the [GNU AGPL-3.0-or-later](LICENSE). Commercial -licensing exceptions are available from the copyright holder. +Everything in this repository is free under the +[GNU AGPL-3.0-or-later](LICENSE) and sufficient on its own. The hosted +always-on operator and commercial licensing are how the project sustains +itself; neither holds back the open core. Commercial licensing exceptions +are available from the copyright holder. diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..97df480 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..32220f9 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,149 @@ +# Getting started + +The longer walkthrough behind the [README](../README.md) quickstart: starting a +project of your own, the contract an agent operates it under, drift audit, and +the operations dashboard. + +## Start your own project + +Your servers, applications, evidence, and any playbooks of your own belong in +a project: a directory of its own, kept private. Cloudfall's wheel bundles the +schema catalog and the Ansible engine, so the project needs no checkout of +this repository. `cloudfall init` is the first command, and it pins the +project to the released Cloudfall that ran it. Without a directory argument +it initializes the current directory, which must be empty: + +```console +uvx cloudfall init my-project +cd my-project +uv sync +``` + +The project holds one directory per resource kind (`servers/`, +`server-types/`, `ssh-public-keys/`, `applications/`, `components/`, +`services/`, `domains/`, `alert-rules/`, `operator-policies/`, +`logging-stacks/`), a `secrets/` directory with a `.sops.yaml` template for +its encrypted fragments, a README with the next steps (pass `--description` +to say what the project manages), an `AGENTS.md` stating the terms on which +an AI agent operates the project (see below), a `.gitignore` for the +runtime `tmp/` directory where evidence and receipts land, a fresh git +repository, and a `pyproject.toml`: + +```toml +[project] +name = "my-project" +version = "0" +requires-python = ">=3.14" +dependencies = ["cloudfall=="] + +[tool.uv] +package = false +``` + +Bump that version and run `uv sync` to move the project to a newer +Cloudfall. To run an unreleased Cloudfall in a project, point the dependency +at a checkout or a commit with uv's own +[`[tool.uv.sources]`](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources); +Cloudfall itself only ever pins a release. + +Only the kind directories are read as resources, so playbooks, roles, docs, +and tooling files may live anywhere else in the project. Commands find the +project on their own: the current directory when it is one, else +`--project`, else `CLOUDFALL_PROJECT`. Declare your SSH key and your first +server with `cloudfall add`; the server's type is created from the bundled +Debian 13 baseline when it does not exist yet, and every written file is +plain YAML you can edit (the reference set under `config/examples/` shows +every kind). Then validate, render, inspect, and audit from inside the +project: + +```console +uv run cloudfall add ssh-key ~/.ssh/id_ed25519.pub --owner roman +uv run cloudfall add server h1 --address 203.0.113.10 +uv run cloudfall config validate +uv run cloudfall-engine inventory render --output tmp/ansible-inventory.json +uv run cloudfall-engine playbook run inspect --inventory tmp/ansible-inventory.json --extra-vars cloudfall_inspect_output_directory=$PWD/tmp/observed +uv run cloudfall audit --observed tmp/observed +uv run cloudfall-engine playbook run playbooks/proxy.yml --roles roles +``` + +Bundled playbooks are addressed by name (`cloudfall-engine playbook list` +shows them); project playbooks by path, with `--roles` directories searched +before the bundled roles. To move a project to a newer Cloudfall commit, bump +`rev` and run `uv sync`. + +## The agent contract + +Humans and AI agents both edit the resources and both run the CLI, with a +human approving anything that changes a server. `cloudfall init` writes +that arrangement down as the project's `AGENTS.md`, the operating contract +an agent started in the directory (Claude Code, Codex, or `cloudfall mcp serve`) +reads first: the canonical `uv run cloudfall` invocation and project +resolution, every command sorted by effect (changes nothing on servers, +writes project files, changes servers) with the gate each server-changing +command demands, the JSON-on-stdout, JSON-error-on-stderr output contract +and exit codes, what the agent may read under `tmp/`, the rule that secret +values are never read or written by an agent, and what is committed. A +one-line `CLAUDE.md` points Claude Code at it. The classification is +generated from the same catalog the tests check against the CLI, so a new +command cannot ship unclassified. + +## Inspect servers and audit drift + +Each `Server` references a reusable `ServerType` +describing its required Debian version, software RAID, filesystem capacity, +packages, systemd services, and allowlisted configuration evidence. + +Collect a read-only snapshot from every reachable server, then compare it +with the config. Workflow commands use [Task](https://taskfile.dev) +(`brew install go-task` / `apt install task`); every task is a thin wrapper +over a `uv run` command, so if you prefer not to install Task, copy the +underlying command from [`Taskfile.yml`](../Taskfile.yml): + +```console +task inspect +task audit +``` + +The direct equivalent of `task audit`, for example, is: + +```console +uv run cloudfall audit --project config/examples --observed tmp/observed +``` + +Snapshots are written to `tmp/observed/.json` with mode `0600` and +never include configuration-file contents. The audit emits one JSON report: +exit code `0` compliant, `1` drift, `2` invalid input, `3` compliance unknown. + +## Operations dashboard + +Cloudfall renders validated config, host observations, and audit results into +a local read-only dashboard with an evidence-derived task queue and a public-service +lifecycle (planned → ready → deployed → configured → healthy): + +```console +task dashboard +``` + +The build writes `tmp/dashboard/index.html` and machine-readable +`tmp/dashboard/operations.json`. Missing receipts or observations remain +visible as `no` or `unknown`; Cloudfall does not infer deployment merely because +a playbook exists. + +For a live view, run the dashboard as a server on the management host (a +VPS or your local machine): + +```console +task dashboard:serve +``` + +`cloudfall dashboard serve` re-derives the projection from state and evidence +on a refresh interval (default 10 seconds) and serves a page that updates +itself in place; `--inspect-services` additionally probes DNS, TLS, origin, +and public routes on every refresh, so service health is live. Server +hardware evidence still comes from observation snapshots: schedule +`task inspect` (cron or a timer) on the management host to keep it fresh. +The server binds `127.0.0.1:8100` by default; set `DASHBOARD_HOST`, +`DASHBOARD_PORT`, and `DASHBOARD_REFRESH` to override. Once it listens it +prints one JSON line with its URL, then serves until stopped. The projection stays +strictly read-only either way. + diff --git a/pyproject.toml b/pyproject.toml index ed69b3c..e856358 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,12 @@ [project] name = "cloudfall" version = "0.5.1" -description = "AI-native control plane for dedicated Debian application fleets" +description = "The operator's record for self-hosted servers run by an AI agent: Ansible executes, Cloudfall gates, verifies and keeps the receipts" readme = "README.md" license = "AGPL-3.0-or-later" license-files = ["LICENSE"] requires-python = ">=3.14" +keywords = ["ai-agents", "ansible", "mcp", "self-hosted", "devops", "sre", "debian", "hetzner", "audit-log", "paas-alternative"] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console",