Skip to content

feat: host-side billing foundation for Dokku (issue #37, PR1+PR2) - #105

Merged
axisrow merged 1 commit into
mainfrom
ao/clihost_af28bd122d-26/billing-foundation
Jul 9, 2026
Merged

feat: host-side billing foundation for Dokku (issue #37, PR1+PR2)#105
axisrow merged 1 commit into
mainfrom
ao/clihost_af28bd122d-26/billing-foundation

Conversation

@axisrow

@axisrow axisrow commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

Foundation for host-side usage billing of clihost apps on a Dokku host (issue #37) — PR1 + PR2 only (accounting + report). A host-only tool pair (like claude-auth-snapshot-host.sh), never copied into the image; runs as the dokku user, which is in the docker group and can call docker stats/ps -s/inspect without sudo.

  • bin/clihost-billing.sh — bash dispatcher (set -euo pipefail, die/usage/main+case). Subcommands:
    • collect — sample every clihost-* container once (cron-driven, flock-serialized, appends JSONL).
    • cron-line — print a ready-to-paste crontab line (operator installs it manually; never auto-installed).
    • report [--json] / raw — aggregated per-app table (avg CPU cores / avg mem / disk / container-hours / uptime% / COST).
    • help.
  • bin/clihost_billing_lib.py — stdlib-only parsers + aggregation + formatting (extracted so it's unit-tested without root/Docker). Container-hours are left-Riemann integrated over the actual dt per container, with gap detection (dt > 2.5 × interval → excluded) and dt ≤ 0 skip; running:false samples count toward uptime% denominator but not billable running hours; disk is a point-in-time size. apply_rates is a stage-2 hook — no rates.json yet → COST renders as .
  • Data sources: docker stats (CPU/Mem), docker ps -a -s writable-layer Size (rootfs + virtualimage), docker inspect (RestartCount/Status/ExitCode/OOMKilled). No sudo, no du, no jq/sqlite3.
  • Storage: append-only JSONL under ${CLIHOST_BILLING_DIR:=/home/dokku/.clihost-billing} (samples/YYYY-MM.jsonl, state/collect.lock, state/last-collect.json).

Environment variables (new)

  • CLIHOST_BILLING_DIR — storage root (default /home/dokku/.clihost-billing).
  • CLIHOST_BILLING_INTERVAL — collector cadence in seconds (default 300; also the gap-detection base).
  • CLIHOST_APP_PREFIX — container/app prefix to account (default clihost-).

These are host-only runtime knobs for the billing tool (not container env), so .env.example is intentionally untouched; documented in the new CLAUDE.md section.

Ports / volumes

None. Dockerfile is not touched — the tool is host-only and never enters the image.

Tests

  • tests/unit/test_clihost_billing_agg.py (new, 24 tests): parsers on real server strings, left-Riemann, gap exclusion, running:false exclusion, non-positive dt, multi-instance summing, torn JSONL line, rates hook, table formatting.
  • TestClihostBillingScript added to tests/unit/test_shell_scripts.py: static invariants (host-only, no-sudo/no-du, flock, cron-line-never-auto-installs, stdlib-only) + end-to-end report/raw/collectreport with fake docker/flock.
  • python -m pytest tests/unit/test_clihost_billing_agg.py tests/unit/test_shell_scripts.py187 passed; bash -n bin/clihost-billing.sh → OK.

Follow-ups (not in this PR)

diagnose/restart (#37.2), gc-mounts (#37.3), idle-sleep (#37.4), and stage 2 (tariffs/invoices).

🤖 Generated with Claude Code

Add a host-only usage-accounting tool for clihost apps on a Dokku host:
collect samples of every clihost-* container and render an aggregated
per-app report (avg CPU cores / mem / disk / container-hours / uptime%).

- bin/clihost-billing.sh — bash dispatcher (set -euo pipefail; die/usage/
  main+case) with collect/cron-line/report/raw/help. Data via docker
  stats/ps -s/inspect (no sudo, no du); JSONL append-only store under
  ${CLIHOST_BILLING_DIR:=/home/dokku/.clihost-billing}; flock-serialized
  collect; "collector likely not running" warning in report.
- bin/clihost_billing_lib.py — stdlib-only parsers (parse_cpu_perc,
  parse_mem_usage, parse_size_field, parse_ts, iter_samples), left-Riemann
  aggregation with gap-detect (dt>2.5x interval → skip) and dt<=0 skip,
  per-container integration summed per app, apply_rates hook (no rates.json
  → COST em dash), format_report_table.
- tests/unit/test_clihost_billing_agg.py (new, 24 tests) + TestClihostBilling
  Script in tests/unit/test_shell_scripts.py (static + end-to-end with fake
  docker/flock).
- CLAUDE.md "Host-side billing" section; .gitignore .clihost-billing/.
- Dockerfile untouched (host-only; not copied into the image).

diagnose/restart, gc-mounts, idle-sleep and stage 2 (tariffs/invoices) are
follow-up PRs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zW2CZeUSv85ub2KicKdBV
@axisrow
axisrow merged commit f80d4d1 into main Jul 9, 2026
3 checks passed
@axisrow
axisrow deleted the ao/clihost_af28bd122d-26/billing-foundation branch July 9, 2026 18:05
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