Skip to content

packtly/packtly-infra

Repository files navigation

packtly-infra

Infrastructure automation for deploying packtly — a self-hosted Debian package repository based on aptly and nginx, running as a rootless Podman container managed by systemd Quadlets.


Overview

packtly-infra/
├── ansible/                  # Ansible playbook and roles
│   ├── packtly_setup.yml     # Main playbook
│   └── roles/
│       ├── container_runtime/  # pip/podman setup
│       ├── packtly_secrets/    # GPG, SSH, API secret generation
│       ├── packtly_service/    # Container deploy, volume, Quadlet service
│       └── sshconfig/          # Local ~/.ssh/config.d entry
├── inventories/
│   ├── hosts.yml               # Target hosts (generated, gitignored)
│   └── group_vars/
│       ├── dev/local.yml       # Dev config (gitignored)
│       └── prod/local.yml      # Prod config (gitignored)
├── packtly-infra/
│   ├── Containerfile           # Container image definition
│   └── podman-compose.yml      # Local dev compose setup
├── scripts/
│   ├── packtly_init.py         # Interactive inventory setup wizard
│   ├── deploy                  # Ansible deploy wrapper
│   └── setup-ansible-inventory # Legacy bash inventory setup
└── justfile                    # Local dev workflow recipes

Prerequisites

  • Python 3.7+
  • pipx
  • Podman
  • Ansible + containers.podman collection

Install everything:

./scripts/install-requirements
./scripts/install-ansible

Deployment

1. Initialize inventory

Run the interactive wizard to create inventories/hosts.yml and encrypted group_vars:

./scripts/packtly_init.py

It will prompt for:

  • Environment name (dev / prod)
  • Target host (hostname/IP, or localhost for local deploy)
  • Bootstrap SSH user
  • Container method (build or pull) and registry settings
  • Service user, ports, GPG identity, API credentials
  • Vault password (used to encrypt secrets via ansible-vault)

2. Deploy

./scripts/deploy dev    # deploy to dev inventory group
./scripts/deploy prod   # deploy to prod inventory group

This runs the Ansible playbook against the specified --limit group.


Ansible Playbook

ansible/packtly_setup.yml contains two plays:

Play User Purpose
Bootstrap packtly host bootstrap_user (become root) Container runtime, secrets, service user
Deploy packtly service service_user Container image, volume, Quadlet unit, SSH config

Roles

Role Description
container_runtime Configures pip (optional proxy)
packtly_secrets Builds setup container, generates GPG key, SSH key, API credentials, stores them locally under ansible/generated-secrets/<host>/
packtly_service Creates service user, deploys container image (build or pull), provisions Podman volume with public keys and htpasswd, registers Quadlet systemd unit
sshconfig Writes ~/.ssh/config.d/packtly-<host> for SSH access to the running container

Inventory Structure

inventories/hosts.yml is generated by the wizard and gitignored. Example:

all:
  children:
    prod:
      hosts:
        mars:
          ansible_connection: ssh
          ansible_host: mars
          bootstrap_user: user
    dev:
      hosts:
        localhost:
          ansible_connection: local
          ansible_python_interpreter: /usr/bin/python3

inventories/group_vars/<env>/local.yml holds non-secret config:

service_user: packtly
container_name: packtly
container_method: build        # or pull
container_image_local: packtly-infra:dev
datadir: packtly_data
web_port: "8080"
ssh_port: "2222"
gpg_user: yourname
gpg_email: user@example.com
api_user: admin

inventories/group_vars/<env>/vault.yml holds ansible-vault encrypted secrets:

ansible_become_password: ...
gpg_pass: ...
api_pass: ...
vault_container_registry_password: ...

Local Development (justfile)

just                   # list available recipes
just all               # full local setup: build, secrets, volume, start, create repos
just setup-image       # build setup container image
just secrets-create    # generate GPG, SSH, API credentials into .dev-secrets/
just volume-create     # create Podman volume and Podman secrets
just volume-create recreate=true  # recreate (destroys existing data)
just create-repos      # import GPG key and create aptly repos
just service-logs      # follow container logs
just clean             # stop service, remove volume and images
just clean-all         # clean + delete .dev-secrets/

Service Architecture

The container runs as a rootless Podman service under the packtly user:

  • Quadlet unit: ~/.config/containers/systemd/packtly.container
  • Managed by: systemctl --user (scope: service_user)
  • Ports: HTTP 8080 → aptly REST API + nginx repo, SSH 2222 → dropbear
  • Volume: packtly_data — persists aptly DB, repos, GPG keyrings

Check service status by switching to the service user via systemd-run:

sudo systemd-run --system --scope su - packtly -c "systemctl --user status packtly.service"
sudo systemd-run --system --scope su - packtly -c "journalctl --user -u packtly.service -n 100 -f --no-pager"

On a remote host:

ssh packtly@<host> "systemctl --user status packtly.service"
ssh packtly@<host> "journalctl --user -u packtly.service -n 100 -f --no-pager"

SSH into the running container:

ssh packtly-<hostname>   # configured by sshconfig role

APT Client Configuration

Create /etc/apt/sources.list.d/packtly.sources:

Types: deb
URIs: http://<host>:8080
Suites: trixie-apollo
Components: main
Signed-By: /etc/apt/trusted.gpg.d/packtly.gpg

Import the signing key:

curl http://<host>:8080/public/repo_signing.key \
  | gpg --dearmor \
  | sudo tee /etc/apt/trusted.gpg.d/packtly.gpg > /dev/null

API Access

The aptly REST API is available at http://<host>:8080/api (basic auth):

curl -u admin:<password> http://<host>:8080/api/version

Managing Repository Packages

SSH into the running container to use the aptly CLI directly:

ssh packtly-<hostname>

Inspect packages in a repo

aptly repo show -with-packages trixie-apollo-contrib

Remove packages

Remove a specific package by exact name and architecture:

aptly repo remove trixie-apollo-contrib debhello_1.0.0_amd64

Wildcard removal (all architectures of a version):

aptly repo remove trixie-apollo-contrib debhello_1.0.0_*

Remove the source package:

aptly repo remove trixie-apollo-contrib debhello_1.0.0_source

Remove a debug symbol package:

aptly repo remove trixie-apollo-contrib debhello-dbgsym_1.0.0_amd64

After removing packages, republish the affected snapshot/repo so clients see the updated state.


Secrets & Security

  • All secrets are generated inside the temporary setup container — nothing is generated on the control node.
  • GPG private key and passphrase are stored as Podman secrets (packtly_gpg_private_key, packtly_gpg_passphrase) — never written to the container filesystem.
  • Local generated secrets are stored under ansible/generated-secrets/<host>/ and are gitignored.
  • Vault variables are encrypted with ansible-vault and must be decrypted at deploy time with --ask-vault-pass.

About

Infrastructure automation for Aptly debian package server

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors