Skip to content

Documentation

Agents

The agent is the only part of Provibr that runs on your own hardware. It holds the credentials for the systems you connect, carries out the work the panel asks for, and reports back what it sees. The platform never dials in.

What an agent is

One agent covers one network: the place from which your hypervisors and panels are reachable. Everything Provibr does to your infrastructure passes through it.

  • The connection is outbound. The agent opens it to the platform and keeps it open; nothing has to be reachable from the internet, and no port has to be forwarded to it.
  • Credentials live on your side. They are pushed to the agent once and sealed in an encrypted vault on that host; the platform stores only the names of the fields you filled in.
  • It reports what it observes. Machines are polled on a short rhythm and a full comparison runs every few minutes, which is how the panel notices a machine that was stopped outside Provibr.
  • It only touches what you handed over. A resource with no record in Provibr is ignored, counted and forgotten again. The agent never adopts a machine on its own.

The host it needs

Nothing exotic, and nothing to install first. The agent is one statically linked binary: it carries its own C library, so there is no runtime, no interpreter and no distribution package that has to match. A small virtual machine is enough. The sizes we measured are further down this page.

What an agent host has to provide
RequirementWhat you needWhy
Processor64-bit x86 (x86_64, also called amd64)We publish one Linux build and this is the architecture it is built for. The installer checks uname -m before it downloads anything and stops on any other architecture, rather than installing something that cannot run. There is no ARM build yet.
Operating systemAny Linux distributionNothing links against your system libraries, so the distribution, its version and its package manager make no difference. The next section lists the ones this build was actually started on.
Service managersystemd, or your ownThe installer writes a systemd unit and enables it. On a host without systemd it still installs and enrols everything, and then prints the command to start the agent under whatever you use instead. Half an installation would be worse than an honest one.
Rights while installingroot, onceInstalling creates the provibr-agent service account, two directories and the unit. After that the agent runs as that unprivileged account, and the update script it is not allowed to write to is what keeps it that way.
Outbound connectionsTCP 50051 and 50052 to gw.provibr.netPort 50051 is used once, while enrolling. Port 50052 carries the session and stays open. Both are TLS. If your firewall filters outgoing traffic, those two rules are all it has to allow.
Inbound connectionsNoneThe agent opens the connection itself, so nothing has to be reachable from the internet and no port has to be forwarded to it. Installing machines over the network is the one exception, and even then it only listens on the local segment.
Reach into your own networkTo the systems you connectYour hypervisor, control panel or game panel is spoken to over its own API, from this host. What the agent cannot reach, Provibr cannot manage.
Memory512 MB is plentyWe measured 18 MB with a thousand machines spread over five systems, which is the heaviest case in the table below. The peak is somewhere else: unlocking the licence file while enrolling briefly takes about 70 MB, because that step is deliberately expensive to brute-force.
Disk1 GB is plentyThe binary is 17 MB and everything the agent keeps beside it stayed under 40 kB in our measurements. Installing machines over the network is the exception: it caches boot media, up to 20 GB by default.
ClockRoughly rightCertificates and licence files carry validity dates and both ends check them, so a host whose clock is days out cannot enrol. Any NTP client will do.
The agent runs as its own unprivileged account under a hardened systemd unit. It gets exactly two extra capabilities, and only so it can serve network installations; remove them and everything except that keeps working.

Which Linux

There is no package to install and no distribution to match, so what follows is not a list of supported systems. It is the list of systems this build was actually started on. Anything else with a 64-bit x86 Linux kernel should behave the same, and if it does not we would like to hear about it.

Linux distributions the agent was run on
SystemVersionNotes
Debian13 (Trixie)
Debian12 (Bookworm)The distribution the full test ran on: installed, enrolled, connected and polling.
Ubuntu24.04 LTSUbuntu 24.04 is where the kernel began restricting exactly the namespaces that automations need. The agent ships an AppArmor profile for it; everything else works untouched.
Ubuntu22.04 LTS
AlmaLinux9
AlmaLinux8A far older C library than the one this build was made on. It makes no difference, because the agent brings its own.
Fedora42
openSUSE Leap15
Amazon Linux2023
Alpine Linux3.21musl and no glibc at all, the sharpest test there is for a statically linked binary.
Two honest limits on that list. It shows that the binary starts and does its cryptographic work on each of these userlands; the complete session, with certificates and polling, was measured on one of them. And every check ran on machines sharing a single kernel, so what it proves is independence from the distribution, not from the kernel version.

How much machine

The agent itself is small and stays small. What grows is the inventory it watches: every twenty seconds it reads the full list of machines from each connected system, compares it with the round before and forwards only what changed. Every five minutes it forwards the whole picture instead, which is the safety net for anything a lost message would otherwise have cost, and that is also the round in which it asks your nodes how much capacity they have.

Measured on one agent; each row is a five-minute window
SituationMemoryProcessor
Connected, nothing attached yet8 MB0.02% of one core
One system, 10 machines12 MB0.03%
One system, 250 machines14 MB0.06%
One system, 1,000 machines15 MB0.14%
Five systems, 200 machines each18 MB0.19%
The steps between the rows say more than the rows themselves. Attaching the first system costs about 4 MB and every system after it about 0.75 MB, which is exactly where the last two rows differ. Machines get cheaper the more there are: going from 10 to 1,000 added 3 MB in all, some 3 kB each, and the step from 250 to 1,000 was cheaper still. Measured in August 2026 against simulated systems of exactly those sizes, so that the numbers say something about the agent rather than about somebody's cluster.

Traffic is the other number worth having. Those thousand machines cost about 250 kB every twenty seconds towards your own systems, roughly a gigabyte a day on your own network, while ten machines cost 2.5 kB a round. Of that thousand-machine figure about 17 kB a round travels on to us, some 70 MB a day, because only what changed is forwarded.

Does the kind of system matter? Barely. Whether the agent is talking to a hypervisor, a hosting control panel or a game panel, the work is the same: one call per system per round, one comparison, one message. Two differences are real but small: a hypervisor is also asked for the addresses inside its guests, at most once every five minutes per running machine, and a panel that measures nothing sends no usage figures. What differs enormously is the machine on the other side, and that one is sized by what it runs, not by the agent.

The sizes below are therefore not minimums we measured; the agent fits in far less than this. They are what we would order, because an operating system, its logs and your own tooling want more room than the agent does.

What we would give an agent host
What it has to handlevCPUMemoryDisk
Up to a few hundred machines11 GB10 GB
Up to a few thousand machines22 GB10 GB
Also installing machines over the network22 GB40 GB

What the extras ask for

Everything above is what an agent needs in order to watch and control your systems. Four of its abilities are worth a word on top of that, and each of them degrades honestly: leave a requirement unmet and the agent keeps doing the rest, and tells you which ability it is not offering.

Installing machines over the network
This one needs the agent to sit on the same network segment as the machine being installed, because it answers that machine's boot request directly. Its unit already carries the two capabilities it needs for the boot ports; they are granted narrowly and only used while an installation is running. What it does add is disk: boot media are cached on the host, up to 20 GB by default, oldest thrown out first.
Running automations
Automations are your own TypeScript, so they run inside a sandbox built from kernel namespaces, and the runtime for that is not part of the agent: one pinned version of Bun has to sit in the agent's root-owned directory, where the account the agent runs as cannot replace it. Ubuntu 24.04 and later restrict exactly the namespace this needs; the agent ships an AppArmor profile that gives that one permission back for that one binary. If either piece is missing the agent says so at startup, does not offer the ability and carries on. Each run gets at least 4 GB of address space. That is virtual, not memory that has to exist. What a running automation really costs on your host is something we have not measured.
Restarting the host from the panel
Restarting is an authorisation question rather than a privilege one: the agent asks logind, logind asks polkit, and the installer leaves behind a rule that permits exactly two actions for exactly this account. Hosts installed before that rule existed need the installer run once more, because updating the agent from the panel replaces the binary and nothing in /etc. Until then the panel refuses and says why.
Console and diagnostics
Nothing to install. Ping and traceroute use an ICMP socket that needs no privileges as long as the host allows it for the service account's group, which it did on every host we measured; where it does not, the agent measures with TCP instead and tells you which method it used. The console is relayed over the connection that is already open.

Installing with the one-liner

On the agent's settings tab the panel builds a single command with an install link in it. Run it as root on the host. The link works once: it stops working as soon as the agent has enrolled, and after 72 hours at the latest. You can make a new one at any time.

shell
curl -fsSL https://app.provibr.com/api/install/<token> | sh
Treat the link as a credential. Anyone who has it can enrol this one agent, in the same way an authentication key would let them. It is never stored in the panel, so make a new one rather than looking the old one up.
Commands and terminal output are in English everywhere, in the panel and in these pages. One text, one truth: what you read here is literally what your host will print back.

What the command does, in order:

  • Checks that it is running as root on a 64-bit x86 host and picks curl or wget, whichever is present.
  • Downloads the binary and its checksum and verifies them. If the host has no tool to compute a checksum at all, it stops rather than skipping the check.
  • Creates the service account and the two directories, installs the binary, and puts the update script and its public key in place, both owned by root, so the account the agent runs as cannot replace them.
  • Asks for the activation code on the terminal, fetches the licence file with that code as a request header, and installs it.
  • Enrols as the service account, then installs the systemd unit and starts the service.

Check the result on the host:

shell
systemctl status provibr-agent
journalctl -u provibr-agent -f
Running the command again is safe, with a new link: the old one stopped working when the agent enrolled. If the host is already enrolled, only the binary and the unit are refreshed; the identity, the certificate and the credential vault are left untouched. On a host without systemd everything is still installed and enrolled, and the command prints how to run the agent yourself.

Installing by hand

Everything the one-liner does is also a handful of commands, and the panel prints them for you with the current values filled in. This is the shape of it.

  1. Prepare the host: the service account, the directories and the binary.

    shell
    # System user without a shell and without a home directory
    id -u provibr-agent >/dev/null 2>&1 || \
      sudo useradd --system --no-create-home --shell /usr/sbin/nologin provibr-agent
    
    # Directories for configuration (license, key) and state (identity, vault)
    sudo install -d -o provibr-agent -g provibr-agent -m 0750 /etc/provibr-agent
    sudo install -d -o provibr-agent -g provibr-agent -m 0700 /var/lib/provibr-agent
    
    # The binary from the download
    sudo install -o root -g root -m 0755 ./provibr-agent-linux-x86_64 /usr/local/bin/provibr-agent
    /usr/local/bin/provibr-agent version
  2. Put the licence file in place. You download it from the agent page; it is encrypted with the activation code, and it can be downloaded once per token.

    shell
    sudo install -o root -g provibr-agent -m 0640 \
      ./provibr-<agent>.plic /etc/provibr-agent/license.plic
  3. Enrol as the service account, with the activation code.

    shell
    sudo -u provibr-agent /usr/local/bin/provibr-agent enroll \
      --license /etc/provibr-agent/license.plic --code XXXXX-XXXXX
  4. Install the unit and start the service.

    shell
    sudo install -o root -g root -m 0644 provibr-agent.service /etc/systemd/system/provibr-agent.service
    sudo systemctl daemon-reload
    sudo systemctl enable --now provibr-agent
The download of the binary also offers its checksum and its signature. Verifying the signature is the step a plain download does not give you, and it is the same check the update step performs on every new release.

Docker and Kubernetes

The agent also runs as a container. The image holds the same signed release binary as the Linux download and nothing else: no shell and no package manager, and it runs as a user without root rights. It connects outbound only, so it needs no published ports and no service.

Enrollment happens on the first start. Give the container the installation link from the panel and the activation code; it fetches its license file, enrolls, and keeps its identity on the volume. On every later start that identity is used and the two values are no longer read.

The installation link and the code sit in the container's environment, and so in docker inspect, your shell history or the Kubernetes secret. That is the same exposure as the one-liner, and it lasts until the agent has enrolled: from then on the link no longer works. Remove it from the secret afterwards anyway; the agent does not read it again.
Everything the agent keeps lives on the volume at /var/lib/provibr-agent: its certificate, the encrypted vault with your credentials and the key to that vault. Without a volume every new container is a new, unenrolled agent. With one, the volume is the whole secret: treat a copy of it like a copy of the agent.

Docker

The panel shows this command with the link and code of your agent filled in. The named volume takes its owner from the image, so the agent can write to it without a chown.

shell
docker run -d --name provibr-agent --restart unless-stopped \
  --hostname provibr-agent \
  --read-only --cap-drop ALL --security-opt no-new-privileges \
  -v provibr-agent-data:/var/lib/provibr-agent \
  -e PROVIBR_INSTALL_URL='https://app.provibr.com/api/install/<token>' \
  -e PROVIBR_ACTIVATION_CODE='XXXXX-XXXXX' \
  ghcr.io/provibr/provibr-agent:latest

Or the same as a Docker Compose file:

docker-compose.yml
# Provibr agent — Docker Compose.
#
# The agent connects outbound to the platform; it needs no published ports.
# On the first start it enrolls itself with the installation link and the
# activation code below, and stores its identity on the volume. After that the
# two values are no longer read; the link expires 72 hours after it was made.
#
# Update: change the image tag, then `docker compose up -d`. The volume keeps
# identity and credentials.
services:
  provibr-agent:
    image: ghcr.io/provibr/provibr-agent:latest
    container_name: provibr-agent
    hostname: provibr-agent
    restart: unless-stopped
    read_only: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    environment:
      PROVIBR_INSTALL_URL: "https://app.provibr.com/api/install/<token>"
      PROVIBR_ACTIVATION_CODE: "XXXXX-XXXXX"
    volumes:
      - provibr-agent-data:/var/lib/provibr-agent

volumes:
  provibr-agent-data:
Prefer a bind mount over a named volume? Create the directory first and give it to the agent's user: mkdir -p /srv/provibr-agent && chown 65532:65532 /srv/provibr-agent. A bind mount starts out owned by root, and the agent, which does not run as root, cannot write to it.

Kubernetes

Plain manifests, no Helm: a namespace, a secret with the link and the code, a volume claim and a deployment. The panel fills in the secret for your agent.

provibr-agent.yaml
# Provibr agent — Kubernetes (plain manifests, no Helm).
#
# kubectl apply -f provibr-agent.yaml
#
# The agent connects outbound to the platform; there is no Service and no port.
# On the first start it enrolls itself with the installation link and the
# activation code from the Secret, and stores its identity on the volume. After
# that the Secret is no longer read; the link expires 72 hours after it was made.
#
# Exactly one replica, and Recreate: one agent is one identity (one
# certificate). Two pods would be two agents fighting over it.
apiVersion: v1
kind: Namespace
metadata:
  name: provibr-agent
---
apiVersion: v1
kind: Secret
metadata:
  name: provibr-agent-enrollment
  namespace: provibr-agent
type: Opaque
stringData:
  PROVIBR_INSTALL_URL: "https://app.provibr.com/api/install/<token>"
  PROVIBR_ACTIVATION_CODE: "XXXXX-XXXXX"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: provibr-agent-data
  namespace: provibr-agent
spec:
  # One pod, and only one, may mount this volume. On clusters older than
  # Kubernetes 1.29 (or storage without it), use ReadWriteOnce instead; the
  # agent also refuses to start a second time on the same data directory.
  accessModes:
    - ReadWriteOncePod
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: provibr-agent
  namespace: provibr-agent
  labels:
    app.kubernetes.io/name: provibr-agent
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app.kubernetes.io/name: provibr-agent
  template:
    metadata:
      labels:
        app.kubernetes.io/name: provibr-agent
    spec:
      hostname: provibr-agent
      automountServiceAccountToken: false
      enableServiceLinks: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        fsGroup: 65532
        # Only when the volume root does not match yet; otherwise the kubelet
        # widens the modes on the private keys at every mount.
        fsGroupChangePolicy: OnRootMismatch
        seccompProfile:
          type: RuntimeDefault
        # The ping check uses an unprivileged ICMP socket. Some container
        # runtimes keep those closed, and then the check reports a source
        # error. This safe sysctl opens them for the agent's group only.
        sysctls:
          - name: net.ipv4.ping_group_range
            value: "65532 65532"
      containers:
        - name: agent
          image: ghcr.io/provibr/provibr-agent:latest
          args: ["run"]
          envFrom:
            # optional: the agent reads it on its first start only, so you can
            # delete the Secret once it is enrolled.
            - secretRef:
                name: provibr-agent-enrollment
                optional: true
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          resources:
            requests:
              cpu: 50m
              memory: 32Mi
            limits:
              memory: 256Mi
          volumeMounts:
            - name: data
              mountPath: /var/lib/provibr-agent
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: provibr-agent-data
Keep replicas at 1 and the strategy at Recreate. One agent is one identity: two pods on the same volume share one certificate and push each other off the platform, and two pods with their own volume both enroll, after which only the last one is valid. A rolling update would briefly do exactly that.
The ping check uses an unprivileged ICMP socket, not the NET_RAW capability. Some container runtimes keep those sockets closed; measured with them closed, a diagnostic ping fell back to TCP and the monitoring check reported a source error. The manifest opens them for the agent's group with the safe sysctl net.ipv4.ping_group_range. Adding NET_RAW does not help: the agent does not run as root, so it gets no effective capabilities.

For Kubernetes the license file is the better route: put the license file and the code in the secret instead of the installation link, mount the file in the pod and set PROVIBR_LICENSE_FILE to its path. The token inside the file works exactly once, so the secret holds nothing that can enroll the agent a second time.

Updating a container

By default a container does not update itself: the image is the release. The panel shows which image is current; pull it and recreate the container, or set the new tag on the deployment. The volume keeps identity and credentials. Automatic updates skip these agents; the option below changes that.

shell
docker pull ghcr.io/provibr/provibr-agent:latest
docker stop provibr-agent && docker rm provibr-agent
# then run the same docker run command again with the new image
shell
kubectl -n provibr-agent set image deployment/provibr-agent agent=ghcr.io/provibr/provibr-agent:latest

Option: update itself from the volume

Set PROVIBR_SELF_UPDATE=volume, or tick the option in the panel. The container then takes part in the update button and in the nightly updates: a new version is downloaded, its signature is checked against the platform key built into the image, and it is stored on the volume. The container stops with exit code 0, Docker or the kubelet starts it again, and the binary in the image starts the new version from the volume after checking its signature once more.

shell
docker run -d --name provibr-agent --restart unless-stopped \
  --hostname provibr-agent \
  --read-only --cap-drop ALL --security-opt no-new-privileges \
  -v provibr-agent-data:/var/lib/provibr-agent \
  -e PROVIBR_INSTALL_URL='https://app.provibr.com/api/install/<token>' \
  -e PROVIBR_ACTIVATION_CODE='XXXXX-XXXXX' \
  -e PROVIBR_SELF_UPDATE=volume \
  ghcr.io/provibr/provibr-agent:latest

A new version has 60 seconds to reach the platform. If it does not, it is marked as bad on the volume and never started again, and the container comes back with the previous version. When you roll out a newer image, the image wins: what was stored on the volume for the old image is no longer used.

What you trade: the image tag no longer says which version runs (the panel does), image scanners look at the binary in the image and not at the one on the volume, and a GitOps tool sees a pod that runs something other than its manifest says. We do not recommend it for clusters that a GitOps tool manages.

Option: the tools image for automations

The standard image has no automation runtime. The tools image, with the tag suffix -tools, holds the same signed agent on Debian with the pinned automation runtime added, so automations can run in the container. The Docker examples give it a small /tmp and a pids limit (--pids-limit); on Kubernetes /tmp is an in-memory emptyDir, and a pids limit is set on the kubelet (podPidsLimit), not in the manifest. The root filesystem stays read-only.

shell
docker run -d --name provibr-agent --restart unless-stopped \
  --hostname provibr-agent \
  --read-only --cap-drop ALL --security-opt no-new-privileges \
  --tmpfs /tmp:rw,noexec,nosuid,size=16m --pids-limit 256 \
  -v provibr-agent-data:/var/lib/provibr-agent \
  -e PROVIBR_INSTALL_URL='https://app.provibr.com/api/install/<token>' \
  -e PROVIBR_ACTIVATION_CODE='XXXXX-XXXXX' \
  ghcr.io/provibr/provibr-agent:latest-tools
On a Linux host an automation runs in a sandbox with its own namespaces: no network, no files of the host, its own process tree. A container cannot create those namespaces, so in the tools image the agent confines the runtime in two other ways. A seccomp filter, which works on any kernel, blocks every network socket (TCP and UDP), signals to other processes, starting new processes and reading the memory of another process. Landlock (Linux 5.13 or later) limits files: a script can read only the runtime and the system libraries and cannot write anything. The agent tests both when it starts; if the kernel or the seccomp profile of the container runtime does not allow them, automations stay off in the container. What remains is weaker than on a host: a script can still see which processes run in the container. Choosing the tools image is choosing that.

What does not work in a container

Some features need the host itself, and a locked-down container does not have it:

  • Network installs (PXE): they need the host network and ports below 1024.
  • Rebooting the host from the panel: the container has no access to the host's init system.
  • Automations in the standard image: it has no automation runtime, and the sandbox they get on a host needs user namespaces, which a locked-down container does not grant. The tools image runs them inside the container instead.
  • Host metrics: CPU, memory and uptime describe the machine the container runs on, not the container's own limits.

The activation code and the licence file

When you create an agent the panel shows an activation code once. It is never written down, not even as a hash. The code is what encrypts the licence file, so the file on your host is encrypted with something that is not on that host.

Lost the code, or the file? Generate a new token on the agent page. That invalidates the previous one, including a licence file that was already on its way, and gives you a fresh code and a fresh download.

The licence file itself carries no secret about your infrastructure. It tells the agent which platform it belongs to, which certificate authority to trust, and the one-time token it enrols with.

Managing agents in the panel

The agent list shows everything at once: status, version, whether it is connected right now, when it was last seen and when its certificate expires.

  • Name and description are yours to change and are only labels. A name already inside a delivered licence file stays as it was, which is cosmetic and no reason to re-enrol.
  • Connected or not is read from a live signal with a short expiry, not from the last time a row was written. If we cannot tell, the panel says so instead of claiming your agent is down.
  • Version and operating system are what the agent reported at its last connection. An agent that never connected shows a dash rather than a guess.
  • Select several agents in the list to update them in one go. The button counts only the agents that can actually be updated right now, so you see before clicking that twelve selected means three updated.
  • Progress is read from the command that is running, not from something your browser remembered, so a reload picks it back up.

The three states

Pending
Created in the panel, not yet enrolled. It has a token and an activation code waiting for it, and no certificate yet.
Enrolled
It has its own certificate and may connect. This is the only state in which it can be given work, and the only state in which it cannot be deleted.
Revoked
Withdrawn by you. Its session is closed, outstanding tokens are void, and it may not come back.

Revoking an agent

Revoking is the switch you reach for when a host is compromised, decommissioned or simply no longer yours. It takes effect without you touching the host.

  • A running session is closed within the minute. The agent is told why rather than simply dropped.
  • The agent then erases its own identity, its certificate and its credential vault. The credentials for your hypervisor are gone from that host.
  • Outstanding enrolment tokens are void, so a licence file that was already downloaded cannot be used to come back.
There is no undo. An agent you revoked by accident has to be created again, installed again and given its credentials again.

Deleting an agent

Deleting removes the agent from the panel. It is a separate step from revoking, and it is deliberately fussy about when it is allowed.

  • An enrolled agent cannot be deleted. Revoke it first, because otherwise you would be removing the record of something that is still connected.
  • An agent with integrations or servers attached cannot be deleted either, and the refusal names both counts. Those rows hang off the agent, so one click would take your inventory with it.
  • If the agent never ran a command and never had a server, the record really disappears. Otherwise it is hidden everywhere but its command history is kept, because an audit trail you can erase by deleting its subject is not an audit trail.

Removing it from the host

Revoking clears the agent's secrets but leaves the files. To clean the host, the panel offers a second one-liner, and the same four steps by hand.

This script does not revoke anything. Revoke in the panel first and then clean the host, in that order. The other way round leaves a host that still holds a valid identity.
  1. Stop the service, disable it and remove the unit.

    shell
    # Stop the service and forget the unit
    sudo systemctl disable --now provibr-agent
    sudo rm -f /etc/systemd/system/provibr-agent.service
    sudo systemctl daemon-reload
    sudo systemctl reset-failed provibr-agent 2>/dev/null || true
  2. Remove the binary, the update script and the update key.

    shell
    # Binary, update script and the trust anchor of the update step
    sudo rm -f /usr/local/bin/provibr-agent
    sudo rm -f /usr/local/lib/provibr-agent/apply-update.sh /usr/local/lib/provibr-agent/license-signing.pub
    
    # The polkit rule that allowed host.reboot (and its 0.105 fallback)
    sudo rm -f /etc/polkit-1/rules.d/49-provibr-agent-reboot.rules /etc/polkit-1/localauthority/50-local.d/49-provibr-agent-reboot.pkla
    
    # Only if nothing else lives in there
    sudo rmdir /usr/local/lib/provibr-agent 2>/dev/null || true
  3. Remove the data. This is the step that matters: the licence, the vault key and the encrypted credentials live here.

    shell
    # License and vault key
    sudo rm -rf /etc/provibr-agent
    
    # Identity, encrypted credential vault, outbox and staged updates
    sudo rm -rf /var/lib/provibr-agent
  4. Remove the service account.

    shell
    sudo userdel provibr-agent
    sudo groupdel provibr-agent 2>/dev/null || true

Afterwards, both of these should print nothing at all:

shell
ls -d /etc/provibr-agent /var/lib/provibr-agent /usr/local/lib/provibr-agent /usr/local/bin/provibr-agent /etc/systemd/system/provibr-agent.service /etc/polkit-1/rules.d/49-provibr-agent-reboot.rules /etc/polkit-1/localauthority/50-local.d/49-provibr-agent-reboot.pkla 2>/dev/null
id provibr-agent 2>/dev/null
# Both commands should print nothing at all