Skip to content

Network backups (restic)

The darkone.service.restic module backs up fleet hosts with restic 🡕: each host (client) pushes its data to a repository, either local or remote via a zone REST server.

Two roles, one module:

  • REST server: a host that declares the restic service in config.yaml. It stores repositories for all hosts and listens on the internal network.
  • Client: any host with enable = true that declares targets (backup destinations).
Diagram

Repository layout, by category:

<root>/<host>/system # "system" category (/ minus exclusions)
<root>/<host>/srv/nfs # "nfs" category (/srv/nfs/<...>)
<root>/<host>/srv/medias # "media" category (/srv/medias/<...>)
  1. In etc/config.yaml, add the restic service to the server host:

    services:
    restic:
  2. Attach the backup disk and mount it on /mnt/backup in the host configuration (usr/machines/<host>/), with the nofail option:

    fileSystems."/mnt/backup" = {
    device = "/dev/disk/by-uuid/<uuid>";
    fsType = "ext4";
    options = [ "nofail" ];
    };
    # The REST server only starts once the disk is mounted.
    systemd.services.restic-rest-server = {
    requires = [ "mnt-backup.mount" ];
    after = [ "mnt-backup.mount" ];
    };
  3. The repository is stored under serverDataDir (default /mnt/backup/restic). The firewall opens the REST port on the internal interface automatically.

Declare one or more targets in the host configuration:

darkone.service.restic = {
enable = true;
targets = [
{
# Remote repository via the zone's REST server.
root = "rest:http://restic.home.example.net:8888";
zone = "home";
categories = [ "system" "nfs" ];
}
];
};

target fields:

FieldRole
nameIdentifier (default main), suffix for jobs/units.
rootLocal path or rest:// URL.
zoneZone that selects the restic-password-<zone> passphrase.
categoriesData to back up: system, nfs, medias.

An idempotent recipe creates missing secrets (never overwrites):

Fenêtre de terminal
just passwd-restic

It generates, in usr/secrets/secrets.yaml:

  • one restic-password-<zone> passphrase per zone;
  • one restic/<host>/rest-password credential per fleet host.

The module creates one systemd service per target × category pair, named restic-backups-<category>-<name> (the <name> comes from the target’s name field, main by default). Examples: restic-backups-system-main, restic-backups-nfs-main.

  1. List the backup services present on the host:

    Fenêtre de terminal
    systemctl list-units 'restic-backups-*'
  2. Trigger an immediate backup without waiting for the timer:

    Fenêtre de terminal
    sudo systemctl start restic-backups-system-main.service
  3. Follow its progress in real time:

    Fenêtre de terminal
    journalctl -fu restic-backups-system-main.service

The REST server runs with private repositories (privateRepos = true): each host only accesses the repository whose path starts with its own hostname. The module automatically aligns the REST account (<hôte>) with the repository prefix (<racine>/<hôte>/...), so isolation is permanent → no configuration needed. An attempt to access another host’s repository returns a 403 error.

On the target host, secrets are already in place. List then restore:

Fenêtre de terminal
# Adjust the repository zone (and category: system, srv/nfs, srv/medias).
ZONE=home
# REST access variables (generated by sops) and repository passphrase.
set -a; . /run/secrets/rendered/restic-rest-env; set +a
export RESTIC_PASSWORD_FILE=/run/secrets/restic-password-$ZONE
export RESTIC_REPOSITORY=rest:http://restic.$ZONE.example.net:8888/$(hostname)/system
restic snapshots
restic restore latest --target /restore
  1. On a server, one account per fleet host in the htpasswd file:

    Fenêtre de terminal
    sudo cut -d: -f1 /run/restic-rest/htpasswd
  2. On a client, run a manual backup (see above) and check that it completes without errors.

  3. Isolation: from a client, accessing another host’s repository must be denied (401/403 error).