Skip to content

deploy: move live updates to git-free /opt release snapshots#56

Merged
benvinegar merged 2 commits into
mainfrom
bentlegen/git-free-opt-releases
Feb 18, 2026
Merged

deploy: move live updates to git-free /opt release snapshots#56
benvinegar merged 2 commits into
mainfrom
bentlegen/git-free-opt-releases

Conversation

@benvinegar

Copy link
Copy Markdown
Member

Summary

This PR decouples production operations from mutable developer checkouts by moving live updates to git-free release snapshots under /opt/baudbot.

Instead of baudbot update doing git pull in-place, updates now:

  1. clone into /tmp/baudbot-update.*
  2. run preflight checks in that temp checkout
  3. publish immutable snapshot to /opt/baudbot/releases/<sha> (no .git)
  4. deploy from that snapshot
  5. atomically switch /opt/baudbot/current only after successful deploy/restart/health checks

It also adds first-class rollback (baudbot rollback previous|<sha>) and updates install/setup/docs/CI checks for the new model.


Why

Current behavior couples live update operations to a mutable repo path and local working state. This PR gives us:

  • clean separation of dev checkouts vs live artifacts
  • deterministic immutable releases
  • safer failure handling (no current mutation on failed preflight/deploy)
  • rollback without network access

Key changes

New scripts

  • bin/update-release.sh

    • temp checkout update pipeline
    • git-free release publish + metadata (baudbot-release.json)
    • atomic current/previous symlink management
    • deploy/restart/health integration
  • bin/rollback-release.sh

    • rollback to previous or explicit SHA/prefix
    • deploy/restart/health checks
    • atomic swap of current/previous

CLI

  • bin/baudbot
    • update now delegates to update-release.sh
    • new rollback command
    • help text updated

Deploy metadata compatibility

  • bin/deploy.sh
    • stamps version from git checkout or baudbot-release.json when deploying from git-free snapshots
    • preserves config-copy behavior for root→agent deploy path

Install/setup/uninstall wiring

  • setup.sh and install.sh

    • bootstrap initial release through update-release.sh (not direct mutable checkout deployment)
  • bin/uninstall.sh

    • removes /opt/baudbot release tree

CI checks and tests

  • New tests:

    • bin/update-release.test.sh
    • bin/rollback-release.test.sh
  • bin/test.sh

    • includes new update/rollback shell suites
  • Droplet setup scripts now verify:

    • /usr/local/bin/baudbot resolves into /opt/baudbot/releases/.../bin/baudbot
    • /opt/baudbot/current exists
    • /opt/baudbot/releases/* are git-free

Docs

  • Updated README.md, CONFIGURATION.md, AGENTS.md for:
    • git-free /opt release architecture
    • new update/rollback operator flow
    • updated test suite count and examples

Validation performed

Local

  • bash bin/test.sh10/10 passed
  • bash bin/update-release.test.sh3/3 passed
  • bash bin/rollback-release.test.sh3/3 passed

DigitalOcean acceptance (real droplets)

Validated on both:

  • Ubuntu 24.04 x64
  • Arch image 217410218

For each distro:

  • fresh install via install.sh
  • /opt release bootstrap successful
  • CLI target and symlink checks passed
  • full test suite passed (10/10)
  • explicit baudbot update --skip-preflight --skip-restart advanced to new release SHA
  • explicit baudbot rollback previous --skip-restart returned to prior SHA
  • verified no .git under /opt/baudbot/releases
  • baudbot start/stop smoke checks passed (activeinactive)

All CI test droplets used for verification were destroyed after validation.


Issues found during validation (and fixed)

  1. Root cloning from local admin checkout hit git safe.directory protections.

    • Added safe-directory handling for local path repos during update.
  2. Config deploy path in update/rollback did not consistently preserve BAUDBOT_CONFIG_USER.

    • Propagated config user through update/rollback deploy path.

Both fixes were re-tested locally and on droplets.


Operator impact

  • Recommended live operations now:
    • sudo baudbot update
    • sudo baudbot rollback previous
  • Existing source-only deploy path (baudbot deploy) remains available.

@greptile-apps

greptile-apps Bot commented Feb 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR decouples live production updates from mutable git checkouts by introducing git-free release snapshots under /opt/baudbot. Instead of git pull in-place, baudbot update now clones into a temp dir, runs preflight checks, publishes an immutable snapshot (no .git), deploys from it, and atomically switches the current symlink only after success. A new baudbot rollback command enables network-free rollback to the previous or any specified release.

  • New bin/update-release.sh: Full update pipeline — temp clone, preflight, git-free publish, deploy, restart, health check, atomic symlink swap. Includes cleanup traps and environment overrides for testability.
  • New bin/rollback-release.sh: Rollback via previous symlink or SHA/prefix match. Deploy + restart + health before symlink swap ensures safety.
  • bin/deploy.sh changes: Now reads version metadata from baudbot-release.json when deploying from git-free snapshots (fallback from git). Config copy reworked to use a staging temp file — note the temp file is briefly 644 (world-readable) while containing secrets.
  • bin/baudbot CLI: update delegates to update-release.sh, new rollback subcommand added.
  • install.sh / setup.sh: Bootstrap now routes through update-release.sh to establish the /opt layout from first install.
  • bin/uninstall.sh: Cleans up /opt/baudbot release tree.
  • New tests: update-release.test.sh (3 tests) and rollback-release.test.sh (3 tests) cover success, preflight failure, deploy failure, and rollback scenarios.
  • CI checks: Both Ubuntu and Arch setup scripts verify the /opt release layout, CLI symlink resolution, and git-free constraint.
  • Docs: README, AGENTS.md, CONFIGURATION.md updated consistently for the new architecture.

Confidence Score: 4/5

  • This PR is safe to merge — the architecture is sound with atomic symlink swaps and proper failure handling, with only minor style improvements suggested.
  • The release snapshot model is well-designed with correct failure semantics (symlinks only updated on success), cleanup traps, and comprehensive test coverage. The two flagged issues are non-blocking: a briefly world-readable secrets temp file and a bare cd side effect. Both are style improvements, not functional bugs. The PR has been validated on real droplets across two distros.
  • bin/deploy.sh has a briefly world-readable secrets temp file and a cd side effect worth addressing.

Important Files Changed

Filename Overview
bin/update-release.sh New script implementing temp-checkout → git-free release snapshot → deploy pipeline. Well-structured with atomic symlink swaps, cleanup traps, and clear failure semantics. Thorough env var overrides for testing.
bin/rollback-release.sh New rollback script with SHA-prefix matching, previous symlink resolution, and deploy/restart/health integration. Clean failure handling — symlinks only updated after successful deploy.
bin/deploy.sh Two changes: (1) env staging via temp file introduces a brief world-readable window for secrets, (2) git-free release metadata fallback with a bare cd side effect. Both functional but have minor style/security improvements available.
bin/baudbot CLI dispatcher updated cleanly: update now delegates to update-release.sh, new rollback subcommand added. Uses exec for clean handoff. Help text updated.
bin/update-release.test.sh Solid integration tests covering: successful git-free publish, preflight failure keeps current, deploy failure keeps current. Uses env overrides for non-root test execution.
bin/rollback-release.test.sh Integration tests for rollback: previous switches current, missing release fails cleanly, deploy failure preserves current. Good failure-mode coverage.
bin/test.sh Adds two new shell test suites (update-release, rollback-release) to the test runner. Straightforward addition.
bin/uninstall.sh Adds cleanup of /opt/baudbot release tree during uninstall. Correctly placed in the uninstall sequence.
install.sh Bootstrap deploy now routes through update-release.sh instead of direct deploy.sh, establishing the /opt release layout from first install. Passes --skip-preflight --skip-restart correctly.
setup.sh Setup now publishes initial git-free /opt release and verifies CLI path instead of creating a direct symlink. CLI verification section correctly handles the case where the bootstrap didn't create the link.
bin/ci/setup-ubuntu.sh CI verification script adds checks for /opt release layout: releases dir exists, current symlink exists, CLI resolves into /opt/baudbot/releases, and releases are git-free.
bin/ci/setup-arch.sh Same CI verification additions as setup-ubuntu.sh for the Arch distro path. Identical checks for /opt release layout and git-free constraint.
AGENTS.md Documentation updated to reflect new release-based architecture, /opt layout, update/rollback workflows, and test suite count (10 suites).
CONFIGURATION.md Adds release updater/rollback env var reference table and updates config reload instructions to use CLI commands.
README.md README updated with /opt release tree in architecture diagram, new update/rollback operations, and updated security layer description for immutable snapshots.

Flowchart

flowchart TD
    A["baudbot update"] --> B["Clone into /tmp/baudbot-update.*"]
    B --> C{"Preflight checks?"}
    C -->|Pass| D["Publish git-free snapshot to /opt/baudbot/releases/&lt;sha&gt;"]
    C -->|Fail| X1["❌ Abort — current unchanged"]
    D --> E["Deploy from snapshot (bin/deploy.sh)"]
    E -->|Fail| X2["❌ Abort — current unchanged, release preserved"]
    E -->|Pass| F["Restart + health check"]
    F -->|Fail| X3["❌ Abort — current unchanged"]
    F -->|Pass| G["Atomic symlink swap: previous → old current, current → new release"]
    G --> H["Update CLI link /usr/local/bin/baudbot → /opt/baudbot/current/bin/baudbot"]
    H --> I["✅ Update complete"]

    J["baudbot rollback previous"] --> K["Resolve previous symlink"]
    K --> L["Deploy from target snapshot"]
    L -->|Fail| X4["❌ Abort — current unchanged"]
    L -->|Pass| M["Restart + health check"]
    M -->|Pass| N["Atomic symlink swap: previous ↔ current"]
    N --> O["✅ Rollback complete"]
Loading

Last reviewed commit: fcf2c5a

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread bin/deploy.sh Outdated
Comment on lines +292 to +294
env_tmp=$(mktemp)
cp "$ADMIN_CONFIG" "$env_tmp"
chmod 644 "$env_tmp"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Secrets briefly world-readable in /tmp

The temp file holding the admin .env (API keys, tokens) is widened to 644 (world-readable) before as_agent copies it. While the window is small, any process on the system can read the secrets during this interval. Since this is a security-hardened environment, consider narrowing access to just the agent's group:

Suggested change
env_tmp=$(mktemp)
cp "$ADMIN_CONFIG" "$env_tmp"
chmod 644 "$env_tmp"
env_tmp=$(mktemp)
cp "$ADMIN_CONFIG" "$env_tmp"
chgrp "$AGENT_USER" "$env_tmp"
chmod 640 "$env_tmp"

This preserves the intent (agent can read the staged file) without exposing secrets to all users.

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/deploy.sh
Line: 292:294

Comment:
**Secrets briefly world-readable in /tmp**

The temp file holding the admin `.env` (API keys, tokens) is widened to `644` (world-readable) before `as_agent` copies it. While the window is small, any process on the system can read the secrets during this interval. Since this is a security-hardened environment, consider narrowing access to just the agent's group:

```suggestion
    env_tmp=$(mktemp)
    cp "$ADMIN_CONFIG" "$env_tmp"
    chgrp "$AGENT_USER" "$env_tmp"
    chmod 640 "$env_tmp"
```

This preserves the intent (agent can read the staged file) without exposing secrets to all users.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 7c496bd. I removed the temp-file staging flow entirely and now stream the admin config directly into the agent-owned target (as_agent ... cat > ~/.config/.env), then chmod 600. This avoids any world-readable /tmp window for secrets.

Comment thread bin/deploy.sh Outdated
GIT_SHA_SHORT=""
GIT_BRANCH=""

if cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cd changes CWD as a side effect

The bare cd "$BAUDBOT_SRC" here mutates the working directory of the main script process. While subsequent commands use absolute paths so this doesn't cause a functional bug, it's fragile — any future relative-path usage would break silently. Consider wrapping the git check in a subshell to avoid the side effect:

Suggested change
if cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1; then
if (cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1); then

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/deploy.sh
Line: 326:326

Comment:
**`cd` changes CWD as a side effect**

The bare `cd "$BAUDBOT_SRC"` here mutates the working directory of the main script process. While subsequent commands use absolute paths so this doesn't cause a functional bug, it's fragile — any future relative-path usage would break silently. Consider wrapping the git check in a subshell to avoid the side effect:

```suggestion
  if (cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1); then
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 7c496bd. Updated the git-probe condition to use a subshell: (cd "$BAUDBOT_SRC" && git rev-parse ...) so script CWD is not mutated as a side effect.

@benvinegar

Copy link
Copy Markdown
Member Author

Addressed both review items in commit 7c496bd:

  • bin/deploy.sh: removed temp-file staging of the admin .env; now streaming directly into the agent-owned target (~/.config/.env) to eliminate any world-readable /tmp window for secrets.
  • bin/deploy.sh: wrapped the git probe in a subshell ((cd ... && git rev-parse ...)) to avoid mutating script CWD as a side effect.

Validation rerun:

  • bash bin/test.sh shell
  • bash bin/test.sh

Both pass (10/10 overall).

@benvinegar benvinegar merged commit 339734d into main Feb 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant