Why I Built a Package Manager That Teaches While It Runs

I don’t like magic.

When something breaks at 2 AM, I don’t want to stare at a tool that “just works” and wonder what it’s doing. I want to know exactly what’s happening so I can fix it.

That’s why I built apkg - a wrapper around Gentoo’s package management that does the tedious work automatically, but shows you every command it runs. The goal isn’t to hide Gentoo’s complexity. It’s to manage it while teaching you.


The Problem: Gentoo Is Exhausting

Here’s what installing Discord looks like on vanilla Gentoo:

# Step 1: Find the package
$ eix discord
[I] net-im/discord
     Available versions:  ~0.0.40(~amd64)
     
# Step 2: Wait, it's masked
$ echo "net-im/discord ~amd64" >> /etc/portage/package.accept_keywords/discord

# Step 3: If using a binhost, check if it exists there first
$ ssh binhost "ls /var/cache/binpkgs/net-im/discord*"
# ...it doesn't exist

# Step 4: Build on binhost
$ ssh binhost "emerge net-im/discord"
# ...wait 10 minutes...

# Step 5: Sync the binary
$ rsync binhost:/var/cache/binpkgs/net-im/ /var/cache/binpkgs/net-im/

# Step 6: Actually install
$ emerge --usepkgonly net-im/discord

# Step 7: Handle config files
$ etc-update

# Step 8: Does this have a service?
$ qlist net-im/discord | grep init.d
# (nothing, this one doesn't)

That’s 8 commands to install a chat app. And if you forget to unmask on the binhost too? Silent compilation failure. If you skip the rsync? Emerge downloads the source instead. If you misconfigure USE flags? Broken binary.

After the hundredth package, I was done. I wanted:

$ apkg install discord

And to have it just work. But I also wanted to see exactly what was happening.


The Solution: Intelligent Automation That Explains Itself

Here’s what apkg install discord actually does:

┌─────────────────────────────────────────────────────────────┐
│                    apkg install discord                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. RESOLVE PACKAGE                                         │
│     • Search binhost eix: net-im/discord-0.0.40             │
│     ✓ Found: net-im/discord                                 │
│                                                             │
│  2. CHECK MASKING                                           │
│     • Status: ~amd64 (testing)                              │
│     • Prompted: "Accept testing version? [Y/n]"             │
│     ✓ Added to /etc/portage/package.accept_keywords/discord │
│     ✓ Synced to binhost                                     │
│                                                             │
│  3. CHECK BINARY AVAILABILITY                               │
│     • Binhost check: Not found                              │
│     • Building in tmux session...                           │
│     ✓ Build complete (8m 32s)                               │
│     ✓ Synced to local cache                                 │
│                                                             │
│  4. INSTALL                                                 │
│     • Running: emerge --usepkgonly net-im/discord           │
│     ✓ Installed                                             │
│                                                             │
│  5. POST-INSTALL                                            │
│     • Service check: None detected                          │
│     • Desktop integration: ✓                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

And at the end:

╔══════════════════════════════════════════════════════════════╗
║                    What Just Happened                         ║
╠══════════════════════════════════════════════════════════════╣
║ 1. eix -Ae discord                                            ║
║    → Search for package in database                           ║
║                                                               ║
║ 2. echo "net-im/discord ~amd64" >> .../package.accept_keywords║
║    → Accept testing version                                   ║
║                                                               ║
║ 3. rsync binhost:/var/cache/binpkgs/... /var/cache/binpkgs/  ║
║    → Download pre-compiled binary                             ║
║                                                               ║
║ 4. emerge --usepkgonly net-im/discord                        ║
║    → Install from binary package only                         ║
╚══════════════════════════════════════════════════════════════╝

This is the philosophy: Do the work. Explain the work. Make yourself unnecessary.

If I ever need to install Gentoo on a machine without apkg, I know exactly what to do. The tool teaches you its own replacement.


The Architecture

apkg isn’t a package manager. It’s a workflow coordinator for Gentoo’s existing tools.

┌─────────────────────────────────────────────────────────────┐
│                    Your Desktop (Driver)                     │
│                                                             │
│  apkg ─────────────────────────────────────────────────────┐│
│    │                                                       ││
│    ├── eix (local)     - Search packages                   ││
│    ├── emerge          - Install packages                  ││
│    ├── equery          - Query installed packages          ││
│    ├── rc-service      - Manage services                   ││
│    │                                                       ││
│    └── SSH ───────────────── To Binhost ──────────────────┘│
│              │                                              │
└──────────────┼──────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    Binhost (Compiler)                        │
│                                                             │
│  Portage tree                                               │
│  Binary package cache (/var/cache/binpkgs/)                 │
│  tmux sessions for long builds                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Key Insight: The Binhost/Driver Split

My desktop (“the driver”) never compiles. It only installs binaries.

A dedicated machine (“the binhost”) does all the compilation. It’s headless, it has good cooling, and it can churn through builds while I sleep.

The problem: keeping these two systems in sync is nightmarishly complex. If I unmask a package on my desktop but forget to unmask it on the binhost, compilation fails. If USE flags differ, I get broken packages.

apkg solves this by treating both machines as one system. When you unmask a package, it unmasks on both. When you install, it checks the binhost first. When you update, both systems stay synchronized.


Command Reference

Here’s everything apkg does:

Package Operations

CommandWhat It Does
apkg install <pkg>Full workflow: resolve → unmask → build/fetch → install
apkg uninstall <pkg>Remove with service cleanup
apkg search <term>Search all packages (local + overlays)
apkg info <pkg>Detailed package information
apkg status <pkg>Where is it installed? Binhost? Local? Both?
apkg list [local|binhost]List installed packages

System Operations

CommandWhat It Does
apkg updateFull system update from binhost
apkg syncDownload all new binaries from binhost
apkg sync-reposSync Portage tree on both systems
apkg cleanSystem maintenance (old kernels, temp files)

Build Operations

CommandWhat It Does
apkg buildsShow active build sessions on binhost
apkg monitor <session>Watch a tmux build session
apkg log <project>View build log
apkg github <url>Build from GitHub (prefers overlays)

System Administration (Welcome)

CommandWhat It Does
apkg welcomeTUI system control panel
apkg welcome --guiGraphical system control panel

The Welcome Panel

apkg welcome is a control panel I built after getting tired of typing the same diagnostic commands:

╔═══════════════════════════════════════════════════════════════════════════╗
║                        ARGO OS CONTROL PANEL                               ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ SYSTEM                                                                     ║
║   Hostname: Canopus-Outpost                                               ║
║   Kernel:   6.12.9-gentoo                                                  ║
║   Uptime:   4 days, 12:34:56                                               ║
║   Profile:  default/linux/amd64/23.0/desktop/plasma                        ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ HEALTH                                                                     ║
║   Disk:     / [████████░░░░░░░░░░░░] 42% (186G free)                       ║
║   Memory:   [██████████░░░░░░░░░░] 51% (16G/32G)                           ║
║   Services: 47 running, 0 failed                                           ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ HARDWARE                                                                   ║
║   GPU:      NVIDIA RTX 4070 Ti @ 42°C                                      ║
║   Logitech: G Pro Wireless [████████████████████] 95%                      ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ BUILD SWARM                                                                ║
║   Gateway:     ✓ 10.42.0.199                                                ║
║   Orchestrator: ✓ 10.42.0.201 (primary)                                     ║
║   Drones:      4 online (62 cores)                                         ║
║   Queue:       0 packages                                                  ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ [u] Update  [h] Health  [c] Check  [d] Doctor  [x] Clean  [r] Refresh      ║
║ [g] GUI     [q] Quit                                                       ║
╚═══════════════════════════════════════════════════════════════════════════╝

Press g for a full graphical interface with service management, snapshot control, and package browsing.


Smart Behaviors

Automatic Unmasking Synchronization

When you install a masked package:

$ apkg install discord
Package net-im/discord is masked (~amd64).
Accept testing version? [Y/n]: y

 Added: /etc/portage/package.accept_keywords/discord (local)
 Added: /etc/portage/package.accept_keywords/discord (binhost)

Both systems know about the unmask. No more “but it compiled yesterday” confusion.

Intelligent GitHub Builds

When you run apkg github https://github.com/user/project:

  1. Check Portage tree - Maybe the package already exists?
  2. Check overlays - It might be in GURU (like Arch’s AUR)
  3. Offer to enable GURU - One command to add the overlay
  4. Rust projects: cargo-ebuild - Generate a proper ebuild automatically
  5. Last resort: direct build - Compiles but warns about unmanaged install
$ apkg github https://github.com/sharkdp/bat

Searching for existing ebuild...
 Found in GURU overlay: sys-apps/bat-0.24.0

GURU is not currently enabled.
Enable GURU overlay? [Y/n]: y

 Enabled GURU overlay
 Building sys-apps/bat...

Service Detection

After installing a package, apkg checks if it provides services:

$ apkg install syncthing

 Installed app-misc/syncthing

Service detected: /etc/init.d/syncthing
Enable and start? [Y/n]: y

 Added to default runlevel
 Started syncthing

It detects both OpenRC and systemd services. No more forgetting to enable something after install.


Configuration

/etc/apkg/config.sh

# Binhost connection
BINHOST_ADDRESS="10.42.0.194"
BINHOST_USER="root"
BINHOST_PKGDIR="/var/cache/binpkgs"
LOCAL_BINPKGDIR="/var/cache/binpkgs"

# Features
SHOW_LEARNING_SUMMARY="yes"      # Show "What Just Happened" box
AUTO_SERVICE_SETUP="prompt"      # "yes", "no", or "prompt"

Driver machine /etc/portage/make.conf

# Use binhost for packages
PORTAGE_BINHOST="ssh://[email protected]/var/cache/binpkgs"
FEATURES="getbinpkg"
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --usepkg --getbinpkg"

Troubleshooting

”Binary package not found” but it exists on binhost

  1. Check SSH connectivity:

    ssh [email protected] echo ok
  2. Verify package path format (GPKG uses subdirectories):

    # Correct: /var/cache/binpkgs/net-im/discord-0.0.40.gpkg.tar
    # Wrong:   /var/cache/binpkgs/discord-0.0.40.gpkg.tar
  3. Clear cache and retry:

    sudo rm -rf /var/cache/binpkgs/*
    apkg sync

Build never completes on binhost

  1. Check active sessions:

    apkg builds
  2. Attach to watch:

    apkg monitor build-discord
  3. Common causes: disk full, dependency conflict, network timeout.


Why Not Just Use emerge?

You can! apkg just automates the tedious parts:

Taskemergeapkg
Install packageemerge packageapkg install package
Handle maskingEdit file, re-runAutomatic
Binary fetchIf configuredExplicit with status
Service setupManualAutomatic detection
Cross-machine syncManualAutomatic
Learning what happenedRead logs”What Just Happened” box

If you’re comfortable with raw Portage, emerge works great. apkg is for when you want the convenience without losing visibility.


The Philosophy: Teach Yourself Out of a Job

The best tools make themselves unnecessary.

apkg shows you every command it runs. Every file it edits. Every SSH call it makes. After using it for a month, you’ll know how to do everything manually.

That’s the point.

I don’t want to be dependent on my own wrapper. I want to understand Gentoo deeply enough that I could operate without it. apkg is training wheels that show you how the bike works.

When it inevitably breaks (and all software breaks), I won’t be stuck. Neither will you.


apkg is part of the Argo OS project, a custom Gentoo distribution built around binary package distribution and bulletproof rollback.

See also: