@heronlabs/terminal-cli
v2.2.1
Published
Heron CLI — database backup & rollup (PostgreSQL/MySQL → S3)
Maintainers
Readme
🪶 terminal-cli — Heron CLI (hcli)
Database backup & rollup CLI for PostgreSQL and MySQL. Dumps a database to a gzipped SQL file, uploads it to S3 (or keeps it local), and restores it back on demand. Built with NestJS + nest-commander, AWS S3, and Luxon. Designed to run as a scheduled cron container on EasyPanel.
Table of Contents
- Why This Project
- Install
- Quick Start
- Commands
- Configuration
- Architecture
- Docker & Cron
- Testing
- Contributing
- License
Why This Project
Scheduled, type-safe database backups with a single binary:
- One tool, two engines — PostgreSQL (
pg_dump/psql) and MySQL (mysqldump/mysql) behind one CLI. - S3 or local — push backups to S3 by default, or keep them on disk with
--local(handy for seeding). - Cron-ready container — one image plus
easypanel/inline-Dockerfile templates run a backup on start and every 12 hours. - 100% tested — v8 coverage + Stryker mutation testing, both at 100% thresholds.
Install
hcli is a global CLI binary. Install it globally to put hcli on your PATH:
npm i -g @heronlabs/terminal-cli
# or: pnpm add -g @heronlabs/terminal-cliThen run any command:
hcli version
hcli psql-backupYou also need the database client tools for the engine you back up: pg_dump /
psql for PostgreSQL, mysqldump / mysql for MySQL.
Quick Start
Prerequisites
| Requirement | Version | Purpose |
|-------------|---------|---------|
| Node.js | >=22 | Runtime |
| pnpm | >=10.29.3 | Package manager |
| pg_dump / psql | — | PostgreSQL backup/restore |
| mysqldump / mysql | — | MySQL backup/restore |
From source (development)
git clone https://github.com/heronlabs/terminal-cli.git
cd terminal-cli
pnpm install
pnpm build # nest build → bin/After pnpm build, run the CLI via node bin/src/main.js <command> or link the
hcli bin (pnpm link --global).
Verify
pnpm build # nest build → bin/
pnpm lint:check # gts + eslint
pnpm test:unit # vitest run (100% coverage)
pnpm test:mutation # stryker (100% break)
pnpm dep:cruise # architecture checkCommands
| Command | Description |
|---|---|
| hcli psql-backup | Back up a PostgreSQL database (S3 by default) |
| hcli psql-rollup --filename <file> | Restore a PostgreSQL database from a backup |
| hcli mysql-backup | Back up a MySQL database (S3 by default) |
| hcli mysql-rollup --filename <file> | Restore a MySQL database from a backup |
| hcli easypanel-backup | Snapshot an EasyPanel host — config + Docker state — to a .tar.gz (S3 by default). Run as root on the host. |
| hcli easypanel-rollup --filename <file> | Restore an EasyPanel host from a snapshot. Run as root on the host. Overwrites host files. |
| hcli version | Print the current version |
Flags
| Flag | Applies to | Meaning |
|---|---|---|
| -f, --filename <name> | all | Backup filename. DB backups default to <database>-<timestamp>.sql.gz and easypanel-backup to easypanel-<timestamp>.tar.gz; rollup requires it. |
| --local | all | Read/write the backup on the local filesystem instead of S3. |
Examples:
# Back up to S3 with an auto-generated, timestamped filename
hcli psql-backup
# Back up to a local file (no S3)
hcli psql-backup --local --filename seed.sql.gz
# Restore from a local file
hcli psql-rollup --local --filename seed.sql.gz
# Restore from S3
hcli mysql-rollup --filename mydb-2026-03-05T12-00-00Z.sql.gz
# Snapshot an EasyPanel host to S3 (run as root on the host)
sudo hcli easypanel-backup
# Snapshot to a local file with a custom name
sudo hcli easypanel-backup --local --filename easypanel-snapshot.tar.gz
# Restore an EasyPanel host from S3 (OVERWRITES host files)
sudo hcli easypanel-rollup --filename easypanel-2026-03-05T12-00-00Z.tar.gzConfiguration
All configuration comes from environment variables (see .env.example).
| Variable | Required | Description |
|---|---|---|
| DB_URL | ✅ | Connection URL (postgres:///mysql://user:pass@host:port/dbname) or an AWS SSM Parameter Store ARN resolved via @heronlabs/env-ssm |
| AWS_S3_BUCKET_NAME | for S3 | Destination bucket for remote backups |
| AWS_REGION | for S3 | AWS region |
| AWS_ACCESS_KEY_ID | for S3 | AWS credentials (or use an instance role) |
| AWS_SECRET_ACCESS_KEY | for S3 | AWS credentials (or use an instance role) |
Locally, pnpm start -- <command> loads variables from a .env file via dotenv.
Architecture
Hexagonal NestJS layering:
src/
├── application/ # CLI surface (nest-commander)
│ └── cli/
│ ├── cli-module.ts
│ └── commands/ # backup/, rollup/, version/ + per-command option types
├── core/ # domain logic
│ ├── interfaces/ # BackupService / RollupService abstract base services
│ └── services/ # {mysql,psql}-{backup,rollup}-service
└── infrastructure/ # adapters
├── environment/ # EnvironmentService (ConfigService wrapper)
├── log/ # nestjs-pino logger module
└── storage/ # S3StorageService (AWS SDK v3)application/wires commands into modules — the composition root.core/holds the backup/rollup flow. The abstractBackupService/RollupServiceown the S3 upload/download + local-cleanup orchestration; engine subclasses implement thedump/restoreshell commands.infrastructure/adapters never import inward (enforced bypnpm dep:cruise).
Docker & Cron
One published image — heronlabs/terminal-cli (built from Dockerfile) — carries
hcli on PATH, both DB clients, and busybox crond. Scheduled backups are
deployed from the inline-Dockerfile templates under easypanel/:
each one is FROM heronlabs/terminal-cli:<tag>, adds a 12-hourly crontab, and
runs an immediate backup before starting crond in the foreground (dumping
printenv to /etc/environment so cron inherits the runtime variables EasyPanel
injects). See easypanel/README.md for deploy steps.
Local stack for manual testing — docker-compose.yml runs the psql + mysql DBs
(exposed on ports 5434/3307) and the psql-integration/mysql-integration
services that run the backup/rollup round-trip inside the prod-shaped
integration-{postgres,mysql}.dockerfile images:
docker compose up postgres mysql # local DBs only
docker compose run --build --rm psql-integration # PostgreSQL round-trip
docker compose run --build --rm mysql-integration # MySQL round-tripEasyPanel host backup
easypanel-backup / easypanel-rollup snapshot a whole EasyPanel
host — /etc/easypanel, /var/lib/docker/volumes, and /var/lib/docker/buildkit —
into a single .tar.gz. To keep the snapshot consistent the command stops the
Docker daemon while the archive is taken and restarts it afterwards (even if the
backup fails).
Because it runs systemctl stop docker and reads /var/lib/docker, it must run
natively on the host as root, not inside a container managed by that daemon.
If not run as root the command refuses to start (it will not stop Docker and
then fail), so schedule it as root.
⚠️
easypanel-rollupextracts the archive to/and overwrites host files (/etc/easypaneland/var/lib/docker). It does not prompt — run it deliberately, against a host you intend to roll back.
Scheduling (every 12h)
Install the CLI on the host (Ubuntu 22.04) and match the DB backup templates' cadence with a host crontab line:
npm i -g @heronlabs/terminal-cli# /etc/crontab — run as root, source the env first
0 */12 * * * root . /etc/easypanel-backup.env; hcli easypanel-backupPut the AWS_* variables (the easypanel-* commands need only AWS_*, no DB
vars) in /etc/easypanel-backup.env.
Testing
| Detail | Value |
|---|---|
| Framework | Vitest 4.x (vitest.config.ts, SWC transform for decorators) |
| Test location | tests/specs/ (mirrors src/) |
| Shared mocks | tests/__mocks__/create-testing-module.ts (moq.ts + vitest) |
| Coverage | v8, 100% lines/functions/branches/statements |
| Coverage excludes | **/main.ts, **/*.d.ts, **/*factory.ts, **/types/ |
| Mutation | Stryker 9.x (stryker.conf.json), 100% break threshold |
| Mutation scope | src/**/*.ts excluding main.ts, *.d.ts, *factory.ts, *-module.ts |
pnpm test:unit # vitest run with coverage
pnpm test:mutation # stryker mutation testingContributing
pnpm lint:check # check
pnpm lint:fix # auto-fix
pnpm build # verify compilation
pnpm test:unit # verify tests pass at 100% coverage
pnpm dep:cruise # verify architectureConventional Commits:
feat: add mongodb backup engine
fix: handle empty database name in psql dump
test: cover s3 download error path- All work branches from
main; PRs targetmain. Never commit directly tomain.
License
MIT — see LICENSE.
Built by HeronLabs
