Skip to content

feat(cliproxy): back up manager analytics to S3 - #2233

Merged
shunkakinoki merged 3 commits into
mainfrom
codex/cpamp-s3-backup
Aug 4, 2026
Merged

feat(cliproxy): back up manager analytics to S3#2233
shunkakinoki merged 3 commits into
mainfrom
codex/cpamp-s3-backup

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • extend the existing cliproxyapi-backup.service to snapshot CPA Manager Plus analytics hourly
  • use SQLite online backup plus an integrity check, then archive usage.sqlite with its matching data.key
  • upload the recoverable archive through the existing S3-compatible object-store credentials
  • cover the combined auth/analytics backup path with ShellSpec

Verification

  • shellspec spec/cliproxyapi_backup_spec.sh (8 examples, 0 failures)
  • shellcheck home-manager/services/cliproxyapi/scripts/*.sh spec/cliproxyapi_backup_spec.sh
  • make shell-inline-check
  • make nix-format-check
  • evaluated homeConfigurations.kyber backup timer and service configuration
  • functional WAL-mode snapshot/archive/restore test with matching data.key

Baseline limitation

The full make nix-flake-check reaches the Kyber Home Manager evaluation successfully, then fails on the existing host-platform mismatch when the aarch64-darwin machine is asked to build x86_64-linux Docker derivations.


Summary by cubic

Adds wall-clock hourly backups of CPA Manager Plus analytics via cliproxyapi-backup, creating a consistent SQLite snapshot and uploading both latest and per-hour archives to S3. Also improves resilience and runs the CPA Manager Plus container as the service user with automatic data ownership migration.

  • New Features

    • Runs on auth changes and every wall-clock hour (launchd StartCalendarInterval + user systemd timer with OnCalendar=hourly, Persistent=true); analytics backup runs independently of auth files.
    • Takes an online SQLite snapshot (.backup + PRAGMA integrity_check), archives usage.sqlite + data.key, and uploads to s3://cliproxyapi/cpa-manager-plus/analytics-backup.tar.gz and analytics-backup-HH.tar.gz (24 hourly slots).
    • Adds sqlite, gnutar, and gzip; sets UMask=0077; ShellSpec covers snapshot and hourly uploads.
  • Bug Fixes

    • Skips cleanly when no auths; still backs up analytics when the database/key exist, cleans temp files, and reduces S3 noise.
    • CPA Manager Plus now runs as the service user and auto-chowns existing data to prevent permission issues.

Written for commit 0211b44. Summary will update on new commits.

Review in cubic

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@indent-zero

indent-zero Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Extends the existing cliproxyapi-backup service to also snapshot the CPA Manager Plus analytics SQLite database and upload it to S3, alongside the current auth-file sync. The backup runs on a wall-clock hourly schedule (in addition to file-watch triggers) on both macOS launchd and Linux systemd, uses SQLite's online .backup command with an integrity check to safely handle WAL-mode writes, and keeps 24 UTC-hour rollback slots plus a latest alias.

  • Added cliproxy_backup_manager_data / cliproxy_manager_backup_s3_uri helpers in scripts/common.sh that snapshot ~/.cpa-manager-plus/usage.sqlite, verify integrity, tar it with data.key at mode 600, and upload to both s3://<bucket>/cpa-manager-plus/analytics-backup-HH.tar.gz (24 UTC-hour slots) and .../analytics-backup.tar.gz (latest alias).
  • Reworked scripts/backup.sh so the empty-auth-dir case no longer short-circuits the script — auth sync becomes conditional and the analytics backup step always runs when CPA_MANAGER_PLUS_DATA_DIR exists; also switched to cp -fu for non-interactive copies.
  • Wired @sqlite3@ / @tar@ Nix placeholders through common.sh and default.nix for parity with @aws@, and added pkgs.sqlite/pkgs.gnutar/pkgs.gzip to the backup service PATH on both platforms.
  • Switched to wall-clock schedules — launchd StartCalendarInterval = [{ Minute = 0; }] and systemd OnCalendar = "hourly" — so the analytics cadence is independent of auth-file churn.
  • Hardened cpa-manager-plus/start.sh to run a one-shot chown -R migration container and start the main container as --user "${host_uid}:${host_gid}", so bind-mounted usage.sqlite/data.key are readable by the user-scoped backup service.
  • Added UMask = "0077" on the Linux backup service and chmod 600 on the archive.
  • Test suite updates: sqlite3 mock now honors SQLITE_INTEGRITY_RESULT, new spec exercises the failure-branch (fails closed and skips analytics uploads when the snapshot is corrupt), all five bash -c invocations now preserve the script's exit status via status=$?; …; exit "$status", and a new backup scheduling describe verifies the wall-clock configuration; cpa_manager_plus_spec.sh gained coverage for the chown migration and --user invocation.
  • README documents the new wall-clock cadence, the dual S3 object layout, and the 24-hour rollback retention.

Issues

All clear! No issues remaining. 🎉

5 issues already resolved
  • Latent: A manager-backup failure will fail the whole cliproxyapi-backup service, hiding that the auth sync earlier in the same run already succeeded — under set -euo pipefail in backup.sh, a non-zero return from cliproxy_backup_manager_data propagates out and systemd/launchd report the run failed. Consider running the manager step with its exit status decoupled from the auth step (e.g., log-and-continue, or split into a second unit). (fixed by commit bb457e4)
  • Latent: The cpa-manager-plus container is started without --user, so bind-mounted ~/.cpa-manager-plus/{usage.sqlite,data.key} are likely owned by root, but the new backup runs as the systemd/launchd user — sqlite3 .backup and the install/tar steps would fail with permission errors on the very first run on Kyber. Either pass --user "$(id -u):$(id -g)" in cpa-manager-plus/start.sh or open the DB read-only from a root-privileged snapshot step. (fixed by commit bb457e4)
  • Nit: The manager backup always writes to a single fixed key s3://<bucket>/cpa-manager-plus/analytics-backup.tar.gz, so every hourly run overwrites the previous archive with no history — if the live SQLite gets truncated or corrupted, the next successful backup wipes out the last-good snapshot. Consider either timestamped keys, a rotated retention scheme, or documenting that S3 bucket versioning is a hard requirement. (fixed by commit bb457e4)
  • Nit: aws is invoked via the @aws@ Nix placeholder but the new sqlite3 and tar calls use bare names and rely on the service Environment.PATH. It works today because default.nix adds pkgs.sqlite/pkgs.gnutar/pkgs.gzip, but any future PATH regression would silently break the snapshot instead of failing at Nix eval time — consider adding @sqlite3@/@tar@ placeholders for parity. (fixed by commit bb457e4)
  • Functional: The bash "$__BACKUP_SCRIPT" ...; cat "$MOCK_LOG" 2>/dev/null || true shell pipeline in the new manager-backup test at spec/cliproxyapi_backup_spec.sh:164 returns the exit status of the last stage, so a non-zero exit from backup.sh (which now runs set -euo pipefail through cliproxy_backup_manager_data) is silently masked and The status should be success will still pass — meaning the new test can't actually catch a broken snapshot/upload. The same anti-pattern also exists at lines 61, 73, 142, and 155; fixing all five (capture status=$? before cat, then exit "$status") makes both the new and existing coverage genuinely load-bearing. (fixed by commit bb457e4)

CI Checks

Waiting for CI checks...

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • CPA Manager Plus analytics data is now backed up hourly to S3.
    • Backups include a consistent, integrity-checked SQLite snapshot and matching encryption key.
    • Backup services now run automatically on Darwin and Linux systems.
  • Bug Fixes

    • Missing authentication files no longer prevent analytics backups from completing.
  • Tests

    • Added coverage for analytics archive creation, validation, and upload.

Walkthrough

The cliproxyapi-backup service now creates validated CPA Manager Plus SQLite snapshots, archives them with data.key, uploads them to S3, and runs hourly on Darwin and Linux. Missing authentication files no longer stop analytics backups.

Changes

CPA Manager Plus backup flow

Layer / File(s) Summary
Analytics snapshot and archive
home-manager/services/cliproxyapi/scripts/common.sh, spec/cliproxyapi_backup_spec.sh, home-manager/services/cliproxyapi/README.md
The backup function creates and validates an online SQLite snapshot, archives it with data.key, uploads it to S3, and documents the archive layout and WAL requirement. Tests mock SQLite operations and verify the upload flow.
Backup script integration
home-manager/services/cliproxyapi/scripts/backup.sh
The script defines the CPA Manager Plus data directory and invokes analytics backup when the directory is available. Missing authentication files now produce a warning and do not stop the script.
Hourly service scheduling
home-manager/services/cliproxyapi/default.nix, home-manager/services/cliproxyapi/README.md
Darwin and Linux configurations add gnutar, gzip, and sqlite. Both services run hourly, and Linux applies a restrictive UMask with a persistent systemd timer.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BackupService
  participant backup.sh
  participant cliproxy_backup_manager_data
  participant sqlite3
  participant S3
  BackupService->>backup.sh: Run hourly
  backup.sh->>cliproxy_backup_manager_data: Back up CPA Manager Plus data
  cliproxy_backup_manager_data->>sqlite3: Create and validate SQLite snapshot
  cliproxy_backup_manager_data->>S3: Upload archive with data.key
Loading

Possibly related PRs

Poem

A rabbit packs a SQLite file,
With data.key tied in style.
Each hour the archive hops away,
To S3 before the end of day.
Integrity checked, the burrow cheers!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main change: backing up CPA Manager analytics to S3.
Description check ✅ Passed The description accurately explains the analytics backup, validation, uploads, tests, and known baseline limitation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/cpamp-s3-backup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@home-manager/services/cliproxyapi/scripts/backup.sh`:
- Line 24: Update the cp invocation in the backup script to include the force
flag, changing the existing options to use non-interactive cp -f behavior while
preserving the current source glob, destination, error suppression, and
fallback.

In `@spec/cliproxyapi_backup_spec.sh`:
- Line 164: Update the backup invocation in the test command to capture the exit
status of bash running $__BACKUP_SCRIPT before executing cat on $MOCK_LOG. Read
the mock log afterward, then exit using the captured backup status so
log-reading success cannot mask backup failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a4b00f9-b73f-4319-8a9f-47df6817c29c

📥 Commits

Reviewing files that changed from the base of the PR and between 4b42a13 and c6e4983.

📒 Files selected for processing (5)
  • home-manager/services/cliproxyapi/README.md
  • home-manager/services/cliproxyapi/default.nix
  • home-manager/services/cliproxyapi/scripts/backup.sh
  • home-manager/services/cliproxyapi/scripts/common.sh
  • spec/cliproxyapi_backup_spec.sh

Comment thread home-manager/services/cliproxyapi/scripts/backup.sh Outdated
Comment thread spec/cliproxyapi_backup_spec.sh Outdated
Comment thread home-manager/services/cliproxyapi/scripts/backup.sh
Comment thread home-manager/services/cliproxyapi/scripts/common.sh Outdated
Comment thread spec/cliproxyapi_backup_spec.sh Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread home-manager/services/cliproxyapi/scripts/common.sh
Comment thread spec/cliproxyapi_backup_spec.sh Outdated
Comment thread spec/cliproxyapi_backup_spec.sh Outdated
Comment thread home-manager/services/cliproxyapi/default.nix Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="home-manager/services/cpa-manager-plus/start.sh">

<violation number="1" location="home-manager/services/cpa-manager-plus/start.sh:55">
P3: The fallback wrapper can make this capture the Docker group's GID instead of the service user's primary GID, causing the data tree and container process to run as `uid:docker_gid`. Using the real group ID keeps ownership and container identity consistent across both direct and `sg` launches.</violation>
</file>

<file name="home-manager/services/cliproxyapi/scripts/common.sh">

<violation number="1" location="home-manager/services/cliproxyapi/scripts/common.sh:141">
P2: The new hourly-rollback upload runs before the primary "latest" upload, and neither `aws s3 cp` is protected by `|| true`. Under `set -euo pipefail`, a transient S3 failure on the auxiliary hourly slot upload aborts the function before the main `analytics-backup.tar.gz` (latest) upload is attempted, so the primary backup is lost for that run even though the snapshot/archive are valid and the latest object is the more important one. Recommend uploading the latest archive first (primary before convenience slot), or at minimum tolerating a failure of the hourly upload so it can't prevent the latest upload.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

--endpoint-url="$OBJECTSTORE_ENDPOINT" \
--only-show-errors \
"$archive_path" \
"$(cliproxy_manager_backup_s3_uri "$hourly_object")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The new hourly-rollback upload runs before the primary "latest" upload, and neither aws s3 cp is protected by || true. Under set -euo pipefail, a transient S3 failure on the auxiliary hourly slot upload aborts the function before the main analytics-backup.tar.gz (latest) upload is attempted, so the primary backup is lost for that run even though the snapshot/archive are valid and the latest object is the more important one. Recommend uploading the latest archive first (primary before convenience slot), or at minimum tolerating a failure of the hourly upload so it can't prevent the latest upload.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/cliproxyapi/scripts/common.sh, line 141:

<comment>The new hourly-rollback upload runs before the primary "latest" upload, and neither `aws s3 cp` is protected by `|| true`. Under `set -euo pipefail`, a transient S3 failure on the auxiliary hourly slot upload aborts the function before the main `analytics-backup.tar.gz` (latest) upload is attempted, so the primary backup is lost for that run even though the snapshot/archive are valid and the latest object is the more important one. Recommend uploading the latest archive first (primary before convenience slot), or at minimum tolerating a failure of the hourly upload so it can't prevent the latest upload.</comment>

<file context>
@@ -116,18 +117,29 @@ cliproxy_backup_manager_data() (
+    --endpoint-url="$OBJECTSTORE_ENDPOINT" \
+    --only-show-errors \
+    "$archive_path" \
+    "$(cliproxy_manager_backup_s3_uri "$hourly_object")"
+
   AWS_ACCESS_KEY_ID="$OBJECTSTORE_ACCESS_KEY" \
</file context>

fi

host_uid="$(id -u)"
host_gid="$(id -g)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The fallback wrapper can make this capture the Docker group's GID instead of the service user's primary GID, causing the data tree and container process to run as uid:docker_gid. Using the real group ID keeps ownership and container identity consistent across both direct and sg launches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/cpa-manager-plus/start.sh, line 55:

<comment>The fallback wrapper can make this capture the Docker group's GID instead of the service user's primary GID, causing the data tree and container process to run as `uid:docker_gid`. Using the real group ID keeps ownership and container identity consistent across both direct and `sg` launches.</comment>

<file context>
@@ -51,10 +51,23 @@ if ! ensure_container_removed; then
 fi
 
+host_uid="$(id -u)"
+host_gid="$(id -g)"
+
+# Older launches ran as root inside the container. Migrate the bind-mounted data
</file context>
Suggested change
host_gid="$(id -g)"
host_gid="$(id -rg)"

@shunkakinoki
shunkakinoki merged commit 3485a4f into main Aug 4, 2026
30 of 36 checks passed
@shunkakinoki
shunkakinoki deleted the codex/cpamp-s3-backup branch August 4, 2026 03:46
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