Skip to content

TheMeinerLP/proxmox-license-proxy

Repository files navigation

proxmox-license-proxy

CI Go Reference License: MIT

A single Go binary that emulates the Proxmox subscription endpoint for labs and homelabs, and doubles as the client tool to point a Proxmox host at it.

For private / internal test environments only. This tool does not contact Proxmox' real servers and changes nothing on a host unless you explicitly run the client steps there. Removing the subscription nag on a production host bypasses a commercial subscription - production deployments need a real Proxmox subscription.

How it works

Proxmox verifies online keys by POSTing to shop.proxmox.com/.../verify.php. The response is only "signed" with md5(SHARED_KEY_DATA + check_token), where the constant is public (open-source proxmox-subscription). The proxy reproduces a valid response locally. Point shop.proxmox.com at the proxy, trust its certificate, and Proxmox writes status = active.

Hosts that contact the proxy are auto-registered as pending and only become active once an admin approves them (or via auto_approve).

Scope: this only fakes the local subscription status (the nag screen and status = active). It does not unlock the Proxmox enterprise apt repository - that is gated server-side by Proxmox (subscription key + IP checks at enterprise.proxmox.com), which a local proxy cannot bypass. Use the free no-subscription repo for package updates.

Install

One-line install (recommended)

Auto-detects your CPU architecture and package format (.deb/.rpm/.apk) and installs the matching package (including shell completion and the systemd service) from the latest release:

curl -fsSL https://raw.githubusercontent.com/TheMeinerLP/proxmox-license-proxy/main/install.sh | sh

Want just the CLI, with no service and no config (e.g. to run serve by hand on a single host)? Use CLI-only mode - it installs the binary to /usr/local/bin plus shell completions:

curl -fsSL https://raw.githubusercontent.com/TheMeinerLP/proxmox-license-proxy/main/install.sh | PMOX_CLI_ONLY=1 sh

Pin a version with PMOX_VERSION (set it on the sh, not the curl, when piping): curl -fsSL .../install.sh | PMOX_VERSION=2.0.0 sh. Or review the script first: curl -fsSL .../install.sh -o install.sh && less install.sh && sh install.sh.


All artifacts are also attached to every GitHub Release (linux/darwin, amd64/arm64) for manual install - replace <ver> with the release version.

Debian / Ubuntu / Proxmox (.deb)

The package installs a hardened systemd service, a dedicated pmox user and a default config at /etc/pmox/config.yaml:

ARCH=amd64   # or arm64
curl -fsSLO https://github.com/TheMeinerLP/proxmox-license-proxy/releases/latest/download/proxmox-license-proxy_<ver>_linux_${ARCH}.deb
sudo apt install ./proxmox-license-proxy_<ver>_linux_${ARCH}.deb

The service is enabled but not started on install - review the config first, then start it:

sudoedit /etc/pmox/config.yaml
sudo systemctl start proxmox-license-proxy
systemctl status proxmox-license-proxy

RHEL / Fedora (.rpm), Alpine (.apk)

sudo dnf install ./proxmox-license-proxy_<ver>_linux_amd64.rpm
sudo apk add --allow-untrusted proxmox-license-proxy_<ver>_linux_amd64.apk

Docker (multi-arch, GitHub Container Registry)

docker run -d --name pmox -p 443:8443 -v pmox:/data \
  ghcr.io/themeinerlp/proxmox-license-proxy:latest
# or use the bundled compose file (builds locally):
docker compose up -d

Plain binary (any Linux/macOS)

The tar.gz bundles the binary plus a systemd unit and sample config under contrib/:

curl -fsSL https://github.com/TheMeinerLP/proxmox-license-proxy/releases/latest/download/proxmox-license-proxy_<ver>_linux_amd64.tar.gz | tar xz
sudo install proxmox-license-proxy /usr/local/bin/

From source (Go 1.26+)

git clone https://github.com/TheMeinerLP/proxmox-license-proxy && cd proxmox-license-proxy
CGO_ENABLED=0 go build -ldflags "-s -w" -o proxmox-license-proxy .

Set it up (who runs what, where)

There are two roles. They can be two machines or the same machine.

  • Proxy host - runs the subscription server (serve). Answers the subscription check.
  • Proxmox host - the PVE/PBS/PMG you want to show "subscribed". It must trust the proxy's certificate and resolve shop.proxmox.com to the proxy.

1. Proxy host - run the server

curl -fsSL https://raw.githubusercontent.com/TheMeinerLP/proxmox-license-proxy/main/install.sh | sh
sudoedit /etc/pmox/config.yaml          # optional; enable auto_approve here if you like
sudo systemctl start proxmox-license-proxy

It serves HTTPS on :443 with a persistent self-signed cert (stable across restarts/upgrades) and advertises itself on the LAN via mDNS.

2. Proxmox host - point it at the proxy

# installs the binary, discovers the proxy via mDNS, trusts its cert, and points
# shop.proxmox.com at it. Prints the cert fingerprint so you can verify it.
proxmox-license-proxy client install

Same machine as the proxy? Add --no-binary (the package already installed the binary - this avoids a /usr/local/bin copy shadowing it):

proxmox-license-proxy client install --no-binary

3. Generate a key and approve the host

# on the PROXY host: mint a lab key (interactive; or --product/--level/--sockets)
proxmox-license-proxy subscription generate

# then tell Proxmox to use it (PBS shown; PVE/PMG have their own command)
proxmox-backup-manager subscription set pbsc-1ab1234567

# first contact registers the host as PENDING; approve it on the PROXY host:
proxmox-license-proxy server pending
proxmox-license-proxy server approve <serverid>
# ...or skip this by setting auto_approve in the config (see below).

proxmox-backup-manager subscription update   # on the Proxmox host

client install offers the mDNS-discovered servers and lets you pick which IP (or localhost for a single host). List them anytime with client discover; disable advertising with serve --mdns=false. Docker users: docker compose up -d then run the subscription/server commands via docker compose exec proxy ....

Configuration

Config precedence: flags > PMOX_* env > config file > defaults.

Key (config.yaml) Env Default
listen PMOX_LISTEN :443
log PMOX_LOG info
registry_file PMOX_REGISTRY_FILE /etc/pmox/registry.json
tls.mode PMOX_TLS_MODE auto (auto/files/http)
hosts.file / hosts.ip PMOX_HOSTS_FILE / PMOX_HOSTS_IP /etc/hosts / -
auto_approve.enabled / .private / .networks PMOX_AUTO_APPROVE_ENABLED false / false / -

On a terminal, proxmox-license-proxy config init runs a guided wizard (TLS mode, registry path, auto-approval and more) and writes the config for you; it is the same wizard as setup server. Prefer a static, fully-commented scaffold to edit by hand? Use config init --defaults (or see config.example.yaml).

Auto-approval

By default a host that first contacts the server is registered as PENDING and must be approved with server approve. To skip that for trusted machines, enable auto-approval by source IP - new (and still-pending) hosts from a trusted network are approved on the spot, for any product (PVE/PBS/PMG). A BLOCKED/REJECTED host is never re-approved automatically.

auto_approve:
  enabled: true
  private: true                 # trust RFC1918 / ULA / loopback / link-local
  networks: ["100.64.0.0/10"]   # optional extra CIDRs

The match is on the TCP source address, so put the proxy on the same L2/L3 as the hosts (no NAT between them) for it to be meaningful.

Commands

serve, status, doctor, subscription {add,generate,list,show,rm,set-due,export,import}

  • server {list,pending,approve,reject,block,rm}, account {list,approve,block}
  • client {install,enroll,uninstall,discover}, cert {generate,install}
  • hosts {enable,disable,status}, config {init,show,path}, version, completion

serve prints a startup summary (reachable URLs, TLS mode, CA fingerprint and next steps) before it starts logging. Stuck? doctor runs read-only checks - a shadowing binary on PATH, an unreadable registry, an expired/missing TLS cert, a missing /etc/hosts entry or untrusted CA - and prints a hint for each. Both doctor and version --check ask GitHub whether a newer release is out (a best-effort, short-timeout call that never blocks the command if offline).

subscription generate mints a lab-only key: it is format-valid (so the emulation works) but deliberately marked - the key carries a visible 1ab ("lab") signature (e.g. pbsc-1ab879865b) and its product name is tagged (LAB, proxmox-license-proxy - NOT FOR PRODUCTION), which Proxmox shows in its subscription panel. The command prints a warning banner and requires confirmation. Run interactively it asks for the product and level (and, for PVE, the CPU socket count, since PVE keys encode [1248]); or pass --product/--level/--sockets for scripts, e.g. subscription generate --product pve --level s --sockets 4 --yes.

Every subscription must carry the 1ab signature - subscription add (and the REST API / import) reject any key without it. This guarantees the proxy can only ever manage clearly-marked lab keys, never something mistakable for a real subscription. The easiest path is subscription generate.

Guided by default. Run a management command without arguments on a terminal and it walks you through it interactively instead of erroring - no need to copy ids or keys around:

  • subscription / client (no subcommand) - open a guided menu that lists the actions, runs the one you pick and loops until you choose quit
  • server approve / reject / block - multi-select hosts from a list
  • server rm - pick the host to remove
  • subscription show / rm / set-due - pick the key (and, for set-due, type the new date) from a prompt
  • subscription add - prompts for the key (tip: subscription generate mints one)
  • config init / setup server - a full wizard (TLS, registry, auto-approve, ...)
  • cert generate / cert install - prompt for hostnames / the cert source, and show the fingerprint before touching the trust store
  • hosts enable - confirms before editing /etc/hosts (and asks for the IP if unset)

The flags still work for scripts: approve/reject/block accept multiple server ids; approve/reject also take --all (all pending hosts) and --note. Read commands (status, subscription list/show, server list/pending) support -o/--output table|json|yaml.

Destructive commands (subscription rm, server rm, hosts disable) prompt for confirmation; pass -y/--yes to skip it. The registry keeps a .bak of the previous state on every write.

Automatic enrollment (ACME-style)

Instead of minting a key by hand, a Proxmox host can enrol itself, like certbot for the subscription nag. client enroll:

  1. detects the installed products (PVE/PBS/PMG - a host may run several),
  2. trusts the proxy CA (pinned by fingerprint) and creates an account key,
  3. registers an ACME account and orders a subscription per product (auto-picking the PVE socket tier from the host's CPU sockets, so a 2-/4-socket host is not handed a 1-socket key pvesubscription rejects),
  4. installs each issued key with the product's own … subscription set tool.
# on the Proxmox host - first time, point it at the proxy
proxmox-license-proxy client enroll --server https://192.168.68.100

You only pass --server the first time. enroll remembers the proxy via the /etc/hosts redirect it writes, so later runs need no arguments:

proxmox-license-proxy client enroll        # reuses the saved proxy

If the host was never set up at all, a bare enroll on a terminal kicks off client install (discovers the proxy, trusts its cert, writes the redirect) and then continues - so one command takes a fresh host from nothing to subscribed.

Issuance is gated on the account, not a guessed host id. A new account is PENDING until an admin approves it (or it registered from an auto_approve network); enroll prints the account id and exits, and you re-run it after:

# on the PROXY host
proxmox-license-proxy account list
proxmox-license-proxy account approve <thumbprint>

The server can invalidate a subscription at any time (server/admin API or the account itself); the host drops to unsubscribed on its next check.

Automatic renew (systemd timer)

The packages ship a certbot-style renew timer (disabled by default, since the same package runs on the proxy host too). On a Proxmox host, point it at the proxy and enable it:

# on the Proxmox host
sudo cp /etc/pmox/enroll.env.example /etc/pmox/enroll.env
sudo $EDITOR /etc/pmox/enroll.env          # set PMOX_ENROLL_SERVER=https://<proxy>
sudo systemctl enable --now proxmox-license-proxy-enroll.timer

The timer runs client enroll daily. Because enroll is idempotent (the server de-duplicates by account+product and re-setting an unchanged key is a no-op), a run safely picks up a just-approved account, re-applies a re-issued key or heals a cleared one — without touching anything when all is current. Every client enroll flag has a PMOX_ENROLL_* env equivalent for enroll.env; the unit stays inert until /etc/pmox/enroll.env exists (ConditionPathExists).

Note the ACME-issued subscriptions do not hard-expire: Proxmox re-checks against the proxy on its own schedule and the proxy keeps answering active until you revoke. The renew timer is about approval pickup and self-healing, not a looming expiry.

REST API

Legacy (unversioned): POST /modules/servers/licensing/verify.php, GET /ca.crt, GET /healthz, GET /readyz, GET /status, /api/subscriptions, /api/servers.

Versioned, ACME-style /api/v1 (account keys + JWS, RFC 8555-inspired): GET /directory, GET /new-nonce, POST /new-account, POST /new-order (issues keys), POST /subscriptions, POST /revoke; plus a bearer-token /api/v1/admin/* for account/host approval and subscription revocation. See docs/api-v1.md. The product is at v2.0.0; the API is at its first version (v1) - the two version numbers are independent.

Upgrading from v1 to v2

v2 consolidates config, the registry and the auto cert under /etc/pmox (the registry was /var/lib/pmox/registry.json). Migration is automatic:

  • Packages (.deb/.rpm/.apk): the postinstall moves the old registry, backup and persisted cert across on upgrade.
  • Binary / manual installs: on its next start, serve copies a pre-2.0 registry from /var/lib/pmox to the configured location (it never overwrites existing data). doctor warns if an un-migrated registry is found.
  • Docker: unaffected - the image pins PMOX_REGISTRY_FILE=/data/registry.json.

The registry format is backward-compatible (the new fields are additive), so existing subscriptions and approved hosts keep working. To migrate by hand: mv /var/lib/pmox/registry.json* /etc/pmox/.

Development

CI (GitHub Actions) is the source of truth - it runs tests (-race), golangci-lint and a build on every push/PR. Locally:

go test -race ./...
golangci-lint run        # same config as CI
go build ./...

Commits follow Conventional Commits. Release Please maintains a release PR; merging it tags the release, after which GoReleaser publishes the binaries + .deb/.rpm/.apk packages and a multi-arch image to ghcr.io.

See CONTRIBUTING.md for details.

Security & responsible use

This is for home labs and internal test clusters - not a way to dodge paying for production. If you run Proxmox in production, buy a subscription. See SECURITY.md for the full policy and how to report vulnerabilities.

License

MIT

About

Self-hosted Proxmox subscription endpoint emulator for labs and homelabs - server + client CLI with mDNS discovery. Lab use only.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages