Docs
Reference

Bare VPS

For a host that can prove nothing else, where a copied key works once and locks both out.

For a host that can prove nothing else, where a copied key works once and locks both out.

What proves the identity

Nothing signs for this host. Hetzner, DigitalOcean, Linode, Vultr and OVH serve unsigned metadata over plain HTTP and expose no secure element, so there is no issuer, no subject and no audience here.

The host generates a key instead. penv keeps the public half, and every login advances a counter both sides hold. A copied key still works once, and using it leaves the real host stale and locks both out. That is detection rather than prevention.

Enroll the host

Run penv machine enroll on the host with the one-time secret the console gives you. A host that proves nothing walks it end to end.

What the dialog prints today

The script below belongs to an older setup that the current binary no longer uses, and so does the block under it. Enroll with penv machine enroll instead. A host that proves nothing has the working path.

# Run as root on the host. Nothing is kept unless enrollment succeeds.
( set -e
  PENV_ENROLL_SECRET='THE_ONE_TIME_SECRET'
  [ "$(id -u)" = 0 ] || { echo 'run as root'; exit 1; }
  for c in openssl curl sed; do command -v $c >/dev/null || { echo "need $c"; exit 1; }; done
  [ ! -e /etc/penv/host.key ] || { echo '/etc/penv/host.key exists. Refusing to overwrite.'; exit 1; }

  umask 077
  mkdir -p /etc/penv /var/lib/penv
  trap 'rm -f /etc/penv/host.key' EXIT
  openssl genpkey -algorithm ed25519 -out /etc/penv/host.key
  PUB=$(openssl pkey -in /etc/penv/host.key -pubout -outform DER | openssl base64 -A)

  R=$(curl -sS "https://penv.cloud/api/v1/auth/keypair/enroll" -H 'content-type: application/json' \
    -d "{\"secret\":\"$PENV_ENROLL_SECRET\",\"publicKey\":\"$PUB\"}")

  # The generation the server returned. A guessed one locks this host out for good.
  G=$(printf %s "$R" | sed -n 's/.*"generation":[ ]*\([0-9][0-9]*\).*/\1/p')
  [ -n "$G" ] || { echo "enrollment failed: $R"; exit 1; }

  printf %s "$G" > /var/lib/penv/generation
  trap - EXIT
  printf %s "$R" | sed -n 's/.*"credentialId":[ ]*"\([^"]*\)".*/\1/p'   # -> keypairAuth.credentialId
)

It then prints the block the retired provider read those paths from:

keypairAuth: {
  credentialId: "…printed by the command above…",
  keyPath: "/etc/penv/host.key",
  statePath: "/var/lib/penv/generation",
}