feat(git-gate): include hostname in deploy key title #393

Merged
didericis merged 2 commits from deploy-key-hostname-in-title into main 2026-07-16 23:34:09 -04:00
Collaborator

Closes #388 (part 1 of 3).

Summary

  • Deploy key titles now include the machine hostname so keys from different hosts no longer collide on the forge when a prior bottle was abandoned without teardown.
  • Format change: bot-bottle:<hostname>:<slug>:<repo-name> (was bot-bottle:<slug>:<repo-name>).
  • No new dependencies — socket is stdlib.
Closes #388 (part 1 of 3). ## Summary - Deploy key titles now include the machine hostname so keys from different hosts no longer collide on the forge when a prior bottle was abandoned without teardown. - Format change: `bot-bottle:<hostname>:<slug>:<repo-name>` (was `bot-bottle:<slug>:<repo-name>`). - No new dependencies — `socket` is stdlib.
didericis requested review from didericis-codex 2026-07-16 22:42:27 -04:00
didericis-codex approved these changes 2026-07-16 22:47:02 -04:00
didericis-codex left a comment
Collaborator

No actionable findings. Reviewed the provisioning and revocation paths; deploy-key cleanup remains ID-based and is unaffected by the title change. Validation: all 1,723 unit tests passed, including 26 focused deploy-key tests.

No actionable findings. Reviewed the provisioning and revocation paths; deploy-key cleanup remains ID-based and is unaffected by the title change. Validation: all 1,723 unit tests passed, including 26 focused deploy-key tests.
didericis reviewed 2026-07-16 22:49:49 -04:00
@@ -47,3 +48,3 @@
if owner_repo.endswith(".git"):
owner_repo = owner_repo[:-4]
title = f"bot-bottle:{slug}:{entry.Name}"
title = f"bot-bottle:{socket.gethostname()}:{slug}:{entry.Name}"
Owner

bigger change, but if there's still a slug util method that's used to generate these slugs, I think we should add hostname to that instead. Ideally we want a consistent slug reused throughout the app.

bigger change, but if there's still a `slug` util method that's used to generate these slugs, I think we should add hostname to that instead. Ideally we want a consistent slug reused throughout the app.
didericis-claude reviewed 2026-07-16 22:52:34 -04:00
@@ -47,3 +48,3 @@
if owner_repo.endswith(".git"):
owner_repo = owner_repo[:-4]
title = f"bot-bottle:{slug}:{entry.Name}"
title = f"bot-bottle:{socket.gethostname()}:{slug}:{entry.Name}"
Author
Collaborator

Yes — mint_slug in bot_bottle/backend/resolve_common.py is that utility. Its output flows into Docker compose project names, container instance names (bot-bottle-{slug}), network names, and state-dir paths in addition to this deploy-key title.

Adding socket.gethostname() inside mint_slug would make ALL of those identifiers host-qualified — a consistent approach but a broader blast radius than just the key title. Two things to verify before doing that:

  1. Docker compose project names and container names have a 63-char limit — a long hostname on top of a label or agent name could push over it.
  2. Existing bottles resumed after the change would get a different slug (their state dirs live under the old slug), so resume would break for in-flight bottles.

Happy to make the change in mint_slug if that is the intended scope. If you prefer to keep the blast radius narrow, an alternative is a thin host_slug(slug) helper in resolve_common.py that only the deploy-key path calls — still centralises how we qualify slugs with hostname without touching Docker names or state dirs.

Which direction do you want?

Yes — mint_slug in bot_bottle/backend/resolve_common.py is that utility. Its output flows into Docker compose project names, container instance names (bot-bottle-{slug}), network names, and state-dir paths in addition to this deploy-key title. Adding socket.gethostname() inside mint_slug would make ALL of those identifiers host-qualified — a consistent approach but a broader blast radius than just the key title. Two things to verify before doing that: 1. Docker compose project names and container names have a 63-char limit — a long hostname on top of a label or agent name could push over it. 2. Existing bottles resumed after the change would get a different slug (their state dirs live under the old slug), so resume would break for in-flight bottles. Happy to make the change in mint_slug if that is the intended scope. If you prefer to keep the blast radius narrow, an alternative is a thin host_slug(slug) helper in resolve_common.py that only the deploy-key path calls — still centralises how we qualify slugs with hostname without touching Docker names or state dirs. Which direction do you want?
Owner

Second objection doesn't matter (nothing published yet), but first objection is valid. Think a host_slug method is useful. Explicitly state it assumes a slug returned from mint_slug. Also, the entry.Name is a bit of a smell... that's the random characters needed for uniqueness, correct? If so, maybe should change the method to globalize_slug, and have it prepend the hostname/append the random slug. Respond to this before doing anything.

Second objection doesn't matter (nothing published yet), but first objection is valid. Think a `host_slug` method is useful. Explicitly state it assumes a slug returned from `mint_slug`. Also, the `entry.Name` is a bit of a smell... that's the random characters needed for uniqueness, correct? If so, maybe should change the method to `globalize_slug`, and have it prepend the hostname/append the random slug. Respond to this before doing anything.
didericis-claude reviewed 2026-07-16 23:01:03 -04:00
@@ -47,3 +48,3 @@
if owner_repo.endswith(".git"):
owner_repo = owner_repo[:-4]
title = f"bot-bottle:{slug}:{entry.Name}"
title = f"bot-bottle:{socket.gethostname()}:{slug}:{entry.Name}"
Author
Collaborator

Clarifying entry.Name: it is the user-defined key from git-gate.repos. in the manifest (e.g. "upstream" or "myrepo"), not random characters. The 5-char random suffix that prevents collisions lives inside the slug itself (from bottle_identity()). entry.Name is needed in the title because a single bottle can gate multiple repos — it disambiguates which deploy key belongs to which entry.

Given that, globalize_slug makes sense as:

def globalize_slug(slug: str) -> str:
    """Return a globally-unique slug qualified with the hostname.
    Assumes slug is a value returned from mint_slug."""
    return f"{socket.gethostname()}-{slug}"

And the title would become:

title = f"bot-bottle:{globalize_slug(slug)}:{entry.Name}"

This keeps entry.Name as a suffix (for per-repo disambiguation within a bottle) and centralises the hostname-qualification logic in globalize_slug. Does that match your intent?

Clarifying entry.Name: it is the user-defined key from git-gate.repos.<Name> in the manifest (e.g. "upstream" or "myrepo"), not random characters. The 5-char random suffix that prevents collisions lives inside the slug itself (from bottle_identity()). entry.Name is needed in the title because a single bottle can gate multiple repos — it disambiguates which deploy key belongs to which entry. Given that, globalize_slug makes sense as: def globalize_slug(slug: str) -> str: """Return a globally-unique slug qualified with the hostname. Assumes slug is a value returned from mint_slug.""" return f"{socket.gethostname()}-{slug}" And the title would become: title = f"bot-bottle:{globalize_slug(slug)}:{entry.Name}" This keeps entry.Name as a suffix (for per-repo disambiguation within a bottle) and centralises the hostname-qualification logic in globalize_slug. Does that match your intent?
Owner

@didericis-claude cool, sounds good. Go for it.

@didericis-claude cool, sounds good. Go for it.
didericis-claude reviewed 2026-07-16 23:05:47 -04:00
@@ -47,3 +48,3 @@
if owner_repo.endswith(".git"):
owner_repo = owner_repo[:-4]
title = f"bot-bottle:{slug}:{entry.Name}"
title = f"bot-bottle:{globalize_slug(slug)}:{entry.Name}"
Author
Collaborator

Done — c44a010. globalize_slug lives in bottle_state.py next to bottle_identity (resolve_common.py caused a circular import: git_gate_provision → resolve_common → git_gate → git_gate_provision). The title format is now bot-bottle:{host}-{slug}:{entry.Name}, using - as the separator between host and slug within the globalized portion, with : still separating the three top-level fields.

Done — c44a010. globalize_slug lives in bottle_state.py next to bottle_identity (resolve_common.py caused a circular import: git_gate_provision → resolve_common → git_gate → git_gate_provision). The title format is now bot-bottle:{host}-{slug}:{entry.Name}, using - as the separator between host and slug within the globalized portion, with : still separating the three top-level fields.
didericis added 2 commits 2026-07-16 23:07:17 -04:00
Closes #388 (part 1 of 3). Deploy key titles now carry the machine
hostname so keys provisioned on different hosts don't collide with
each other on the forge when a prior bottle was never torn down.

Title format: bot-bottle:<hostname>:<slug>:<repo-name>
refactor(git-gate): centralize hostname qualification in globalize_slug
test / unit (pull_request) Successful in 1m19s
test / integration (pull_request) Successful in 24s
test / coverage (pull_request) Successful in 1m24s
lint / lint (push) Successful in 2m37s
test / unit (push) Successful in 1m27s
test / integration (push) Successful in 35s
test / coverage (push) Successful in 1m36s
Update Quality Badges / update-badges (push) Successful in 1m29s
bbb8913382
Adds globalize_slug(slug) to bottle_state alongside bottle_identity.
git_gate_provision now calls globalize_slug(slug) instead of inlining
socket.gethostname(), so the hostname-qualification logic has a single,
named home. Assumes slug is a mint_slug output.

Title format changes from bot-bottle:{host}:{slug}:{name}
to bot-bottle:{host}-{slug}:{name} to match the globalize_slug contract.
didericis force-pushed deploy-key-hostname-in-title from c44a010464 to bbb8913382 2026-07-16 23:07:17 -04:00 Compare
didericis approved these changes 2026-07-16 23:12:21 -04:00
didericis merged commit bbb8913382 into main 2026-07-16 23:34:09 -04:00
didericis deleted branch deploy-key-hostname-in-title 2026-07-16 23:34:11 -04:00
Sign in to join this conversation.