Skip to content

A first deploy over a hand-made unit of the same name leaves the service stopped when the health gate fails #151

Closed

Description

@shipmill-romamo

What happens

cloudfall_deploy installs /etc/systemd/system/<service.name>.service from component.service.j2 before it activates the release, and does not keep the file it replaces. When the first release fails its health gate, the rescue path finds no previous release (current did not exist) and runs "Stop the failed first release". The service that ran before Cloudfall is gone: its unit file was overwritten and the unit is stopped.

Checked on main (27d4f88, 0.7.0): engine/ansible/roles/cloudfall_deploy/tasks/main.yml, tasks "Install the managed service unit", "Record the rollback candidate" and the rescue block.

Why it matters

Adopting a running service is the first step of any brownfield move. In our case as-rest runs on h5 as a hand-made asrest.service (user asrest, checkout in /home/asrest). Its first Cloudfall deploy has to be safe to fail: a failed gate must leave the old service serving, as it was.

Asked

  • Before installing the unit, if a unit file of that name exists and is not Cloudfall-managed (no "Managed by Cloudfall" header), keep a copy of it
  • On a failed first release, put the kept unit back, daemon-reload and restart it, instead of stopping the service; the failure message says the pre-Cloudfall unit was restored
  • deploy --dry-run (or the plan) says that a hand-made unit will be replaced and kept

Done when

A test deploys over an existing non-managed unit with a release that fails its health check, and afterwards the original unit file is back and active.

Activity

  1. shipmill-romamo commented on Oct 28, 2026

    @shipmill-romamo
    ContributorAuthor

    Triage: implement, for the next release candidate.

    Confirmed on main (27d4f88): "Install the managed service unit" templates /etc/systemd/system/<service.name>.service over whatever is there, and on a failed first release the rescue block only runs "Stop the failed first release", so a hand-made unit of the same name is lost and its service left stopped.

    The plan:

    • Before "Install the managed service unit", read the existing unit file; when it exists and its first line isn't the template's # Managed by Cloudfall header, copy it to /etc/systemd/system/<service.name>.service.pre-cloudfall (root, 0644, never overwritten once there) and record whether the unit was enabled and active
    • In the rescue block, when there is no previous release and a kept unit exists: put it back, daemon-reload, restore its enabled state, and start it when it was active before; the failure message says the pre-Cloudfall unit was restored. Without a kept unit, the first-release path stops the service as today
    • In check mode (cloudfall-engine playbook run --check), the role reports that a hand-made unit would be replaced and kept, so a dry run shows it
    • A regression test deploys over a non-managed unit with a release that fails its health gate and finds the original unit file back and active, plus one for a unit that already carries the header (no copy taken)

    Design. New stored state on the server: the kept copy, next to the unit under a name systemd doesn't load (it ends in .pre-cloudfall, not .service) and root-owned, so the application user can't write a unit that root later restores; the shared directory under the install root was ruled out for that reason. Nothing changes for a component whose unit Cloudfall already manages. The kept copy stays after a successful first release, as the record of what was replaced. Drop-ins under <service.name>.service.d/ are left as they are.

    cloudfall deploy has no dry-run flag today; adding one is out of scope here, the check-mode report covers the plan. I'll link the PR here.

  2. shipmill-romamo commented on Oct 28, 2026

    @shipmill-romamo
    ContributorAuthor

    The fix is in #154: before installing its unit, the deploy keeps a same-name unit that lacks the Cloudfall header at /etc/systemd/system/<name>.service.pre-cloudfall and records whether it was enabled and active; when the first release fails its gate, it puts that unit back, reloads systemd, restores its enabled state, and starts it when it was running. A check-mode run reports the unit it would keep.

    Trade-offs, named in the PR: a deploy now refuses to start when the hand-made unit differs from a .pre-cloudfall copy already on disk, so the copy always matches what was replaced; the enabled state is restored as enabled or disabled, so a rarer state such as static comes back disabled. The tests replay the role's decisions without a live systemd run; a live run on a disposable host is still unverified. This issue closes when #154 merges.

  3. shipmill-romamo commented on Oct 28, 2026

    @shipmill-romamo
    ContributorAuthor

    Released in v0.8.0rc8 (pre-release). uv tool install --prerelease allow cloudfall==0.8.0rc8
    A plain install gets it with the next stable release.
    If you filed this: react 👍 on this comment once it works for you, or reply if it doesn't.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions