feat: host-side billing foundation for Dokku (issue #37, PR1+PR2) - #105
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thedokkuuser, which is in thedockergroup and can calldocker stats/ps -s/inspectwithout sudo.bin/clihost-billing.sh— bash dispatcher (set -euo pipefail,die/usage/main+case). Subcommands:collect— sample everyclihost-*container once (cron-driven,flock-serialized, appends JSONL).cron-line— print a ready-to-pastecrontabline (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 actualdtper container, with gap detection (dt > 2.5 × interval→ excluded) anddt ≤ 0skip;running:falsesamples count toward uptime% denominator but not billable running hours; disk is a point-in-time size.apply_ratesis a stage-2 hook — norates.jsonyet → COST renders as—.docker stats(CPU/Mem),docker ps -a -swritable-layerSize(rootfs+virtual→image),docker inspect(RestartCount/Status/ExitCode/OOMKilled). Nosudo, nodu, nojq/sqlite3.${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 (default300; also the gap-detection base).CLIHOST_APP_PREFIX— container/app prefix to account (defaultclihost-).These are host-only runtime knobs for the billing tool (not container env), so
.env.exampleis intentionally untouched; documented in the new CLAUDE.md section.Ports / volumes
None.
Dockerfileis 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:falseexclusion, non-positivedt, multi-instance summing, torn JSONL line, rates hook, table formatting.TestClihostBillingScriptadded totests/unit/test_shell_scripts.py: static invariants (host-only, no-sudo/no-du, flock, cron-line-never-auto-installs, stdlib-only) + end-to-endreport/raw/collect→reportwith fakedocker/flock.python -m pytest tests/unit/test_clihost_billing_agg.py tests/unit/test_shell_scripts.py→ 187 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