Skip to content

No CLI command applies one Domain: its vhost and certificate come only from migrate, which converges every server #87

Open

Description

@shipmill-romamo

Problem

Found while deploying Abecra's staging on 2026-10-19 to a server lent from another fleet. That server's baseline, firewall and services belong to its owner's project, so the Abecra project may run per-component cloudfall deploy on it, never migrate or baseline convergence.

The Domain was declared (abecra.cloudfall.dev, proxy and origin on the lent server), but there was no safe way to apply it:

  • cloudfall migrate is the only CLI path to domains.yml (sdk/src/cloudfall/migrate.py: steps domains-http, dns-verify, domains-tls), and the same plan runs baseline and services on every declared server first
  • the converge_domains MCP tool runs domains.yml without the baseline, but it is MCP only, renders every declared domain on every server in cloudfall_servers, and takes no domain or server argument
  • task domains:deploy exists in the repository Taskfile, not in the installed CLI

So the nginx vhost and the Let's Encrypt certificate were written by hand, outside the receipts, and the next domains.yml run from anywhere will overwrite that file with no record of what it replaced.

Proposal

A domains command group matching deploy's shape:

cloudfall domains apply <domain> [--issue-certificate] [--yes]
cloudfall domains observe <domain>
  • apply runs cloudfall_nginx_site for one Domain, limited to its proxy.server (and origin.server when it differs); without --yes it shows the plan, like deploy
  • --issue-certificate runs the HTTP-then-TLS sequence migrate uses (domains-http, then issuance), refusing when DNS for primaryName and aliases does not resolve to the proxy server
  • It touches no baseline, firewall or service, so it is safe on a server whose baseline another project owns
  • A DeploymentReceipt per run, as today, with the hash of the vhost it replaced if one was there
  • The same scoping (domain argument) added to the converge_domains MCP tool

Alternatives

  • migrate --only domains-http,domains-tls: reuses the plan, but a step filter on a resumable plan is easy to get wrong and still reads as "migrate"
  • A server-level managedBaseline: false flag that makes migrate skip baseline on lent servers: useful too, but a per-domain command is needed regardless (adding a domain to a running fleet should not mean rerunning a migration)

Acceptance

  • In a project with two servers, cloudfall domains apply <d> --yes changes only the Domain's proxy server, and its Ansible run reports no baseline, firewall or service tasks
  • --issue-certificate on a fresh domain ends with a valid Let's Encrypt certificate, certbot.timer enabled, and a receipt; a second run reports no changes
  • Without --yes it exits 0 and changes nothing
  • An unknown domain id fails with CONFIG_INVALID before any SSH connection

Activity

  1. shipmill-romamo commented on Oct 20, 2026

    @shipmill-romamo
    ContributorAuthor

    Triage: feature.

    Confirmed on main (348ed73): cloudfall migrate (sdk/src/cloudfall/migrate.py) is the only CLI path to domains.yml, and its plan converges baseline and services first; converge_domains is MCP only and takes no domain or server. A scoped cloudfall domains apply / observe is a new command group with its own plan, receipts and certificate sequence, so it gets a spec with acceptance criteria first, covering the converge_domains scoping too.

    Writing it alongside #88's spec, since both extend the per-component deploy shape (plan without --yes, a receipt per run).

    The spec PR is being written now (docs/specs/004-domains-apply.md); I'll put its link and the hold line here. Merging it is the approval.

  2. shipmill-romamo commented on Oct 20, 2026

    @shipmill-romamo
    ContributorAuthor

    The spec is in #100 (docs/specs/004-domains-apply.md). Where it departs from the issue, following the code: an unknown domain exits PRECONDITION like deploy's unknown component, not CONFIG_INVALID; apply touches only the proxy server, since no playbook acts on origin.server; DNS is checked once before a single role run, and skipped for proxied edges as migrate does. The new optional receipt field means an older cloudfall rejects the newer receipts, which is worth a look in review.

    On hold: the build waits on #100, the spec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions