{"id":51262421,"url":"https://github.com/7c/aptbase","last_synced_at":"2026-06-29T13:01:11.064Z","repository":{"id":366238402,"uuid":"1275534855","full_name":"7c/aptbase","owner":"7c","description":"aptly cli tool for fast deployment","archived":false,"fork":false,"pushed_at":"2026-06-20T23:07:37.000Z","size":110,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T23:14:28.647Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/7c.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-20T20:24:15.000Z","updated_at":"2026-06-20T23:07:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/7c/aptbase","commit_stats":null,"previous_names":["7c/aptbase"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/7c/aptbase","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/7c%2Faptbase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/7c%2Faptbase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/7c%2Faptbase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/7c%2Faptbase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/7c","download_url":"https://codeload.github.com/7c/aptbase/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/7c%2Faptbase/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34927687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-29T13:01:09.995Z","updated_at":"2026-06-29T13:01:11.057Z","avatar_url":"https://github.com/7c.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aptbase\n\nA human-friendly command-line client for the [aptly](https://www.aptly.info/)\nREST API. `aptbase` drives one or more **remote** aptly servers over HTTP so you\ncan manage Debian/Ubuntu package repositories from your terminal — upload and\npublish packages, inspect what is live, and run day-to-day operations — with\ncolored output, aligned tables, live task progress, and a config file so you are\nnot retyping URLs and credentials.\n\nIt is a friendly adapter, not a reimplementation: every command maps onto aptly's\nown API. The flagship command, `aptbase deploy`, takes a `.deb` from your\ndisk to live-and-verified in a single step.\n\n---\n\n## Installation\n\n### With `go install` (recommended)\n\nRequires Go 1.25+.\n\n```bash\ngo install github.com/7c/aptbase@latest\n```\n\nThis builds and installs the `aptbase` binary into your Go bin directory. Make\nsure it is on your `PATH`:\n\n```bash\nexport PATH=\"$PATH:$(go env GOPATH)/bin\"   # add to ~/.zshrc or ~/.bashrc\n```\n\nVerify:\n\n```bash\naptbase --version\n```\n\nTo install a specific release:\n\n```bash\ngo install github.com/7c/aptbase@v1.0.0\n```\n\n### From source\n\n```bash\ngit clone https://github.com/7c/aptbase.git\ncd aptbase\nmake build        # produces ./bin/aptbase\n# or:\nmake install      # installs into $GOBIN via `go install` with version metadata\n```\n\n`make build` injects the version, git commit, and build date via `-ldflags`.\n`go install` builds without the Makefile but still reports the module version and\nVCS commit embedded by Go.\n\n---\n\n## Quick start\n\n```bash\n# 1. Point aptbase at your aptly API and check connectivity\naptbase --api http://localhost:8080 ping\n\n# 2. Scaffold a config file so you stop retyping flags\naptbase config new --path ~/aptbase.ini\n$EDITOR ~/aptbase.ini\n\n# 3. See what aptbase resolved (and where each value came from)\naptbase config list\n\n# 4. Release a package end to end\naptbase deploy app-stable ./app_1.2.3_amd64.deb -d noble\n```\n\n---\n\n## Configuration\n\n`aptbase` reads settings from layered sources. **Later layers override earlier\nones (last value wins):**\n\n```\nbuilt-in defaults\n  → /etc/aptbase.ini\n  → ~/aptbase.ini\n  → APTBASE_* environment variables\n  → command-line flags\n```\n\nWhen `--config \u003cpath\u003e` (or `APTBASE_CONFIG`) names an explicit file, only that\nfile is read instead of the two well-known locations.\n\nInspect the fully resolved configuration at any time, including the source of\neach value and its built-in default:\n\n```bash\naptbase config list\n```\n\n```\nSETTING        VALUE                          SOURCE                   DEFAULT\n─────────────  ─────────────────────────────  ───────────────────────  ───────────────\napi            http://localhost:8080          ~/aptbase.ini            (none)\ndistributions  noble jammy focal              ~/aptbase.ini            (none)\nprefix         .                              default                  .\ntimeout        1m0s                           default                  1m0s\n...\n```\n\n### The config file\n\nGenerate an annotated starter file:\n\n```bash\naptbase config new                                   # writes /etc/aptbase.ini (needs sudo)\naptbase config new --path ./aptbase.ini              # write anywhere\naptbase config new --path ~/aptbase.ini --force\n```\n\nExample `config.ini`:\n\n```ini\n[default]\n# One or more aptly API base URLs. Commands fan out to all of them unless\n# narrowed with --api or --server. Whitespace- or comma-separated.\napi           = http://prod:8080 http://replica:8080\n\n# Basic-auth username and password (both optional — see Authentication).\nuser          = deploy\n# password    = s3cr3t        ; if set here, chmod 600 this file\n\n# Default distributions used when -d/--distribution is omitted.\ndistributions = noble jammy focal\n\n# Default local repos used when the \u003crepo\u003e argument is omitted.\nrepos         = app-stable\n\n# Default publish prefix, TLS, timeout, output.\nprefix        = .\ninsecure      = false\ntimeout       = 60s\njson          = false\nno-color      = false\n\n# A named server, selected with: aptbase --server staging ...\n[server:staging]\napi           = http://staging:8080\ndistributions = noble\n```\n\n### Multiple servers (fan-out)\n\nList several URLs under `api` (or pass `--api` repeatedly) and commands run\nagainst **all** of them, with a per-server status line and an aggregate exit\ncode:\n\n```bash\naptbase --api http://prod:8080 --api http://replica:8080 deploy app-stable ./app.deb -d noble\n```\n\nUse `[server:NAME]` sections plus `--server NAME` to switch between named\nenvironments.\n\n### Environment variables\n\nEvery setting has an `APTBASE_*` counterpart:\n\n| Variable | Setting |\n|----------|---------|\n| `APTBASE_API` | `api` (space/comma list) |\n| `APTBASE_USER` | `user` |\n| `APTBASE_PASSWORD` | `password` |\n| `APTBASE_DISTRIBUTIONS` | `distributions` |\n| `APTBASE_REPOS` | `repos` |\n| `APTBASE_PREFIX` | `prefix` |\n| `APTBASE_INSECURE` | `insecure` |\n| `APTBASE_TIMEOUT` | `timeout` |\n| `APTBASE_JSON` | `json` |\n| `APTBASE_NO_COLOR` | `no-color` |\n| `APTBASE_YES` | `yes` |\n| `APTBASE_CONFIG` | explicit config file path |\n\n### Global flags\n\n| Flag | Description |\n|------|-------------|\n| `--api` (repeatable) | aptly API base URL; fans out to all |\n| `--server` | named `[server:NAME]` config section |\n| `--user` | HTTP Basic auth username |\n| `--password` | HTTP Basic auth password (prompted on 401 if omitted) |\n| `--config` | explicit config file path |\n| `-d, --distributions` (repeatable) | target distribution |\n| `--prefix` | publish prefix (default `.`) |\n| `--json` | machine-readable JSON output |\n| `--no-color` | disable colored output |\n| `--insecure` | skip TLS certificate verification |\n| `--timeout` | per-request timeout (default `1m0s`) |\n| `--yes` | assume yes; skip destructive-action confirmations |\n\n---\n\n## Authentication\n\nHTTP Basic auth is **optional and server-triggered**:\n\n- If your aptly API needs no auth (trusted network / localhost), do nothing.\n- If it sits behind Basic auth and you have **not** supplied a password,\n  aptbase prompts for it (hidden input) the first time the server returns `401`.\n- To skip the prompt — for automation/CI — supply the password ahead of time via\n  any of: `password` in `config.ini`, `APTBASE_PASSWORD`, or `--password`.\n\n```bash\n# Interactive: prompts for the password only if the server challenges\naptbase --api https://apt.example.com --user deploy ping\n\n# Non-interactive: credentials supplied up front\naptbase --api https://apt.example.com --user deploy --password \"$APT_PW\" ping\n```\n\n\u003e If you store the password in a config file, restrict it: `chmod 600`. On a\n\u003e shared machine, prefer `APTBASE_PASSWORD` or the interactive prompt.\n\n---\n\n## Commands\n\nRun `aptbase \u003ccommand\u003e --help` for full, example-rich help on any command.\n\n### Connectivity\n\n```bash\naptbase ping             # reach every configured server, print its aptly version\naptbase version          # local build info + remote aptly versions\naptbase --version        # just the version string\naptbase discover         # detailed overview of each server (see below)\n```\n\n### Discover\n\n`discover` probes each configured server and prints a rich overview: aptly\nversion and auth status, a summary count of repositories, mirrors, snapshots,\npublications and tasks, and detailed tables for each. By default it counts\npackages per local repository and previews the top few from each (newest\nversion first); `--no-counts` skips the package query entirely, and `--top N`\ncontrols how many to preview (`0` disables it, a negative value shows all).\n\n```bash\naptbase --api http://aptbase:8080 discover\naptbase --api http://aptbase:8080 discover --top 10\naptbase --api http://aptbase:8080 discover --no-counts\naptbase --api http://aptbase:8080 discover --json | jq '.[].repos'\n```\n\n```\nhttp://aptbase:8080\naptly 1.5.0  •  auth: none\n\nSummary\nrepositories  2\nmirrors       0\nsnapshots     0\npublications  6\ntasks         0\n\nLocal repositories\nNAME    DIST   COMPONENT  PACKAGES  COMMENT\n──────  ─────  ─────────  ────────  ───────\napp     noble  main       8\n\nPackages (top 5 per repo)\napp\nNAME   VERSION   ARCH\n─────  ────────  ─────\nnginx  1.20.1-1  amd64\n...\n```\n\n### Config\n\n```bash\naptbase config new [--path FILE] [--force]   # scaffold an annotated config.ini\naptbase config print                         # resolved config as config.ini to stdout (pipeable)\naptbase config list [--json]                 # resolved settings, sources, defaults\n\n# Capture an ad-hoc invocation as a system config file:\naptbase --api http://aptbase:8080 -d noble config print | sudo tee /etc/aptbase.ini\n```\n\n### Repositories\n\n```bash\naptbase repo list                            # all local repos (per server)\naptbase repo show app-stable                 # repo details\naptbase repo create app-stable --distribution noble --component main\naptbase repo edit app-stable --comment \"stable channel\"\naptbase repo drop app-edge [--force] [--yes] # delete (asks to confirm unless --yes)\naptbase repo packages app-stable -q 'nginx (\u003e= 1.20)'\n```\n\n### Add packages\n\nUpload one or more `.deb` files and add them to a repo. Runs as an async aptly\ntask whose progress is streamed live; the temporary upload directory is cleaned\nup afterward.\n\n```bash\naptbase repo add app-stable ./app_1.2.3_amd64.deb\naptbase repo add ./a_1_amd64.deb ./b_1_amd64.deb     # repo(s) from config defaults\n```\n\n### Deploy (flagship)\n\n`deploy` is the one-command release workflow. For each target server it:\n\n1. uploads the `.deb` file(s),\n2. adds them to the target repo(s),\n3. refreshes every publication that **sources** the repo so the package goes\n   live — the publication prefix and distribution are discovered from the\n   server (no need to know the prefix), and\n4. verifies the package is present in the repo.\n\nRepos come from the positional argument, `--repo` (repeatable), or the\nconfigured `repos`. By default every distribution the repo is published to is\nrefreshed; narrow that with `-d`/`--distribution`. It fans out across every\nconfigured server.\n\n```bash\n# Repo published under a non-root prefix — prefix is auto-detected\naptbase deploy ./app_1.2.3_amd64.deb --repo 99835\n\n# Single repo, narrow to two distributions\naptbase deploy app-stable ./app_1.2.3_amd64.deb -d noble -d jammy\n\n# Use config defaults for repo / distributions / servers\naptbase deploy ./app_1.2.3_amd64.deb\n\n# Signed publish\naptbase deploy app-stable ./app.deb -d noble --gpg-key DEADBEEF --batch\n\n# Unsigned (lab) publish, no confirmation prompts\naptbase deploy app-stable ./app.deb -d noble --skip-signing --yes\n```\n\nDeploy and publish accept signing flags: `--gpg-key`, `--keyring`,\n`--passphrase`, `--batch`, `--skip-signing`, and `--force-overwrite`. Deploy also\ntakes `--no-verify` to skip the post-publish check.\n\n### Publish\n\n```bash\naptbase publish list                         # all publications (per server)\naptbase publish update noble                 # re-publish a distribution\naptbase publish update                       # re-publish configured distributions\naptbase publish update noble jammy --gpg-key DEADBEEF --batch\n```\n\n### Packages\n\n```bash\naptbase package list                         # all packages, NAME/VERSION/ARCH table\naptbase package list --latest                # newest version of each package only\naptbase package list --details --repo app    # full records: version, arch, size, maintainer\naptbase package list --arch amd64 --limit 20 # filter by arch, cap output\naptbase package list --keys                  # raw aptly package keys\naptbase package search 'nginx (\u003e= 1.20)'     # same options, query required\naptbase package show 'Pamd64 nginx 1.20.1-1 abc123'\n```\n\nBoth `list` and `search` query each target repo (`--repo`, else the configured\n`repos`); aptly has no global package index. `list` lists everything (an\noptional query narrows it); `search` requires a query. Queries use aptly's\n[query syntax](https://www.aptly.info/doc/feature/query/) (names match exactly;\nuse `%` for wildcards, e.g. `Name (% nginx*)`).\n\nShared options:\n\n| Flag | Effect |\n|------|--------|\n| `--repo` (repeatable) | repos to query (default: configured `repos`) |\n| `--latest` | only the newest version of each package (client-side) |\n| `--details` | full records as a wider table (version, arch, size, maintainer) |\n| `--arch` (repeatable) | filter by architecture |\n| `--limit N` | cap results per repo |\n| `--sort name\\|version` | sort order (default `name`) |\n| `--keys` | print raw aptly keys instead of the parsed table |\n\nOutput defaults to a parsed `NAME / VERSION / ARCH` table grouped by repo;\n`--json` emits structured results. `--latest` and version sorting use Debian\nversion comparison, so `0.0.9` precedes `0.0.10`.\n\n### Tasks\n\naptbase uses aptly's asynchronous task API for long operations and streams output\nlive. You can also inspect tasks directly:\n\n```bash\naptbase task list                            # tasks on a server\naptbase task show 7                          # a task's state\naptbase task wait 7                          # block until done, streaming output\naptbase task output 7                        # print accumulated output\n```\n\nTask IDs are server-local; with multiple servers configured, task subcommands act\non the first one (narrow with `--api`/`--server`).\n\n---\n\n## Output\n\n- Human-readable colored tables by default.\n- `--json` on any command emits machine-readable JSON for scripting.\n- Color auto-disables when output is piped or `NO_COLOR` is set; force it off with\n  `--no-color`.\n\n```bash\naptbase --json publish list | jq '.[].Distribution'\n```\n\n---\n\n## Debugging \u0026 bug reports\n\n`--debug` is available on **every** command and prints debug-level diagnostics to\n**stderr** (so it never pollutes `--json` on stdout): config resolution (files\nread, resolved values + sources), every HTTP request/response with status and\ntiming, request/error bodies, and async task polling.\n\n```bash\naptbase --debug discover                       # trace to the terminal\naptbase --debug deploy ./app.deb --repo myrepo 2\u003e debug.log   # capture for a report\nAPTBASE_DEBUG=1 aptbase ping                    # or via env / 'debug = true' in config\n```\n\nSecrets are redacted: the Basic-auth password is never printed (only `auth=y\nuser=…`), `--password` is masked in the startup args line, and `password` /\n`Passphrase` / `GpgKeyArmor` are masked in any logged request body. When filing\na bug, re-run with `--debug` and attach the stderr output.\n\n---\n\n## Exit codes\n\n`aptbase` exits non-zero when an operation fails. With multiple servers, it\nattempts all of them and exits non-zero if **any** failed, after printing a\nper-server result.\n\n---\n\n## Development\n\n```bash\nmake build      # compile to ./bin/aptbase (with version metadata)\nmake test       # run unit tests\nmake vet        # go vet\nmake fmt        # gofmt\nmake version    # print the current version\nmake bump       # bump version.txt (PART=patch|minor|major, default patch)\nmake help       # list all targets\n```\n\n### Versioning \u0026 releasing\n\n`version.txt` at the repo root is the **single source of truth** for the build\nversion (a [semver](https://semver.org/) string). `make build` reads it and\ninjects the version, git commit, and build date into the binary via `-ldflags`,\nso `aptbase version` is self-describing on any host. A plain `go build` (without\nthe Makefile) shows the placeholder `dev`, which is a useful signal that the\nbinary was not built through the release path.\n\nRelease flow:\n\n```bash\nmake bump PART=minor          # e.g. 0.1.0 -\u003e 0.2.0 (or PART=patch|major)\ngit commit -am \"release v$(cat version.txt)\"\ngit tag \"v$(cat version.txt)\" \u0026\u0026 git push --tags\n```\n\nTagging the commit to match `version.txt` keeps `go install\ngithub.com/7c/aptbase@latest` reporting the same version: a `go install` build\ncannot read `version.txt`, so it falls back to the version and commit Go embeds\nfrom the module's VCS tag.\n\nThe codebase is organized by concern:\n\n```\nversion.txt         single source of truth for the build version (semver)\ncmd/                cobra commands (thin, human-facing)\ninternal/config/    layered config resolver\ninternal/client/    typed aptly API client (+ async tasks, 401 auth)\ninternal/target/    multi-server fan-out\ninternal/ui/        colored output and tables\ninternal/render/    human vs JSON rendering\ntools/              dev tools (e.g. increaseversion.go); not part of the binary\n```\n\n---\n\n## License\n\n[MIT](LICENSE) © 7c\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F7c%2Faptbase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F7c%2Faptbase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F7c%2Faptbase/lists"}