# Restic Backup

[Restic](https://restic.net/) ist ein einfaches aber sehr mächtiges Backup-Programm. Gepaart mit Salt können Sie Backups vollautomatisch durchführen und über den Salt-Master zentral steuern. Ein einfacher SSH-Server dient als zentraler Speicher für die Backups.

### Backup-Server vorbereiten

Restic kann auf einer [Vielzahl von Zielen](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html) die Backups ablegen. SFTP bzw. SSH gehört dazu, was mit jedem Linux-System einfach zu realisieren ist.

Grundsätzlich kann jeder Benutzer-Account verwendet werden, ein eigener Benutzer `restic`ist empfohlen,  um das System „ordentlich“  zu halten. Restic authentifiziert sich ausschließlich per SSH-Key. Legen Sie einen neuen Benutzer an und generieren Sie SSH Schlüssel.

```bash
mkdir /srv/salt/restic
cd /srv/salt/restic
ssh-keygen -N "" -t ed25519 -C "restic@salt-master.local" -f ./id_ed25519
```

### Backup-Software ausrollen

{% code title="/srv/salt/restic/base.sls" %}

```yaml
#
# Create a restic user
#
restic-group:
  group.present:
    - name: restic

restic:
  user.present:
    - fullname: Restic Backup
    - shell: /bin/bash
    - home: /var/lib/restic
    - createhome: true
    - system: true
    - groups:
      - restic
    - require:
      - group: restic-group

bzip2:
  pkg.installed: []
#
# Download restic from github (the debian packages are quite outdated)
#
{% set version = '0.16.2' %}
download-restic:
  file.managed:
    - name: /var/tmp/restic_{{ version }}_linux_amd64.bz2
    - source: https://github.com/restic/restic/releases/download/v{{ version }}/restic_{{ version }}_linux_amd64.bz2
      - user: restic
    - group: restic
    - mode: 0755
    - require:
      - bzip2

#
# Install restic by unpacking the compressed binary
#
install-restic:
  cmd.run:
    - name: |
        set -e
        which bunzip2
        bunzip2 -k /var/tmp/restic_{{ version }}_linux_amd64.bz2 -c > restic
        chmod 0700 restic
        ./restic version
        rm -f /var/tmp/restic_{{ version }}_linux_amd64.bz2
    - cwd: /var/lib/restic
    - runas: restic
    - unless:
      - /var/lib/restic/restic version|grep -q {{ version }}
    - require:
      - file: download-restic
      - pkg: bzip2

/var/lib/restic/restic:
  file.managed:
    - mode: 0700
    - user: restic
    - require:
      - install-restic

/var/lib/restic/.ssh/:
  file.directory:
    - user: restic
    - group: restic
    - mode: 0700
```

{% endcode %}

😯 **Probleme:**

1. Wenn Sie Downloads aus dem Internet ungeprüft in Ihr System übernehmen, kann Schadcode ins System gelangen.
2. Der State `download-restic` speichert dauerhaft das komprimierte Archiv oder lädt es erneut herunter, wenn es auf dem temporären Verzeichnis gelöscht wurde.&#x20;

**🧩 Aufgaben:** Optimieren Sie den State. Nutzen Sie eine der folgenden Optionen:

* Erweitern Sie den State mit einer Prüfsummen-Prüfung. Der Download darf nur dann verwendet werden, wenn die Prüfsumme stimmt. Suchen Sie dazu in der [Dokumentation](https://docs.saltproject.io/en/latest/ref/states/all/salt.states.file.html#salt.states.file.managed) nach dem Stichwort `source_hash`.
* Fügen Sie eine [unless](https://docs.saltproject.io/en/latest/ref/states/requisites.html#unless) Bedingung ein, damit der Download nicht mehr ausgeführt wird, sobald `restic` erfolgreich installiert wurde.
* Laden Sie das komprimierte Archiv zuerst auf den Salt-Master herunter, entpacken Sie es und verteilen Sie es von dort.

### Backup-Server ausrollen

Restic kann die Sicherungskopien auf einer Vielzahl von Fileservern abspeichern. In einem Intranet ist der in SSH integrierte SFTP-Server die einfachste Lösung. Das bedeutet, Sie benötigen keine spezielle Server-Software. Der Server muss lediglich den passenden SSH-Private-Key nutzen, der zum Public-Key der Clients passt.&#x20;

{% code title="/srv/salt/restic/server.sls" lineNumbers="true" %}

```yaml
include:
  - .base

openssh-server:
  pkg.installed: []

sshd:
  service.running:
    - enable: true
    
authorized_keys:
  file.managed:
    - name: /var/lib/restic/.ssh/authorized_keys
    - source: salt://restic/id_ed25519.pub
    - user: restic
    - group: restic
    - mode: 0600
```

{% endcode %}

### Backup-Clients ausrollen

{% code title="/srv/salt/restic/client.sls" lineNumbers="true" %}

```yaml
include:
  - .base

#
# Set package names and dependencies according to the disctribution
# 
{% set restic_deps = salt['grains.filter_by']({
    'Debian': {'libcap':'libcap2-bin','cron':'cron','crond':'cron'},
    'RedHat': {'libcap':'libcap','cron':'cronie','crond':'crond'},
    'Suse': {'libcap':'libcap-progs','cron':'cron','crond':'cron'},
}) %}

#
# Deploy the private key need for the backup transfer over SFTP
#
priv-key:
  file.managed:
    - name: /var/lib/restic/.ssh/id_ed25519
    - source: salt://restic/id_ed25519
    - user: restic
    - group: restic
    - mode: 0600

# Install the setcap utility
libcap2-bin:
  pkg.installed:
    - name: {{ restic_deps.libcap }}

# Allow restic do read alls files without root-rights
set-cap:
  cmd.run: 
    - name: /usr/sbin/setcap cap_dac_read_search=+ep /var/lib/restic/restic
    - unless: /usr/sbin/getcap /var/lib/restic/restic|grep -q cap_dac_read_search+ep
    - require:
      - pkg: libcap2-bin

#
# Create a SSH Client config
#
ssh-client-conf:
  file.managed:
    - name: /var/lib/restic/.ssh/config
    - contents: |
        HashKnownHosts no
        Host restic-server
            StrictHostKeyChecking No
            Hostname {{ pillar['restic_server'] }}
            User restic
            Port 22

#
# Create a password used for the backup encryption
#
password-create:
  cmd.run:
    - name: openssl rand -hex 20 > .restic-password && chmod 0600 .restic-password
    - cwd: /var/lib/restic
    - runas: restic
    - creates:
      - /var/lib/restic/.restic-password

#
# Backup the genrated password to the salt master
# to restore if the machine gets completely lost
#
password-backup:
  module.run:
    - name: cp.push
    - path: /var/lib/restic/.restic-password

#
# Initialize the remote repository where the backups will be stored
#
repo-init:
  cmd.run:
    - name: ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} init
    - runas: restic
    - cwd: /var/lib/restic
    - unless: ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} stats
    - require:
      - cmd: password-create
      - module: password-backup

#
# Optionally store the password on the backup server 
# for direct access to unencrypted backups
#
sftp-backup-to-restic-server:
  cmd.run:
    - name: |
        sftp restic-server <<EOF
        cd {{ grains['id'] }}
        pwd
        PUT .restic-password
        ls -a
        EOF
    - runas: restic
    - cwd: /var/lib/restic
    - require:
      - cmd: repo-init
    
/var/lib/restic/data:
  file.directory:
    - user: restic
    - group: restic
    - mode: 0700

#
# Create a script that will execute the backup
#
/var/lib/restic/run.sh:
  file.managed:
    - user: restic
    - group: restic
    - mode: 0700
    - contents: |
        #!/bin/bash
        set -e
        cd /var/lib/restic
        # Do the backup
        ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} backup --files-from ./files_to_backup
        # Remove old backups
        ./restic -p .restic-password -r sftp:restic-server:./{{ grains['id'] }} forget --keep-last 7 --prune

#
# Create the list of files or folders to backup
#
/var/lib/restic/files_to_backup:
  file.managed:
    - user: restic
    - groupd: restic
    - mode: 0600
    - contents: |
        /root
        /etc
        /var/lib/restic/data

#
# Create the cronjob
#
install-cron:
  pkg.installed:
    - name: {{ restic_deps.cron }}

run-cron:
  service.running:
    - name: {{ restic_deps.crond }}
    - enable: true

restic-cron:
  cron.present:
    - name: |
       /var/lib/restic/run.sh > /var/lib/restic/last.log 2>&1
    - user: restic
    - minute: '*/5'
    - identifier: RESTIC
    - require:
      - file: /var/lib/restic/files_to_backup
      - file: /var/lib/restic/run.sh
      - pkg: install-cron
```

{% endcode %}

😯 **Probleme:**

1. Die Liste der Ordner, welche gesichert werden, ist „hart kodiert“. Was machen Sie, wenn Systeme unterschiedliche Ordner sichern sollen?

**🧩 Aufgabe:**

* Speichern Sie die Backup-Ordner in einem Pillar und erweitern Sie den State, sodass `/var/lib/restic/files_to_backup` aus einem Pillar gelesen wird.

### Backups prüfen

Prüfen Sie, wann das letzte Backup erstellt wurde, z. B. mit einem State:

{% code title="/srv/salt/restic/report.sls" %}

```yaml
restic-report:
  cmd.run:
    - name: |
        ./restic -p .restic-password -r sftp:restic-server:./{{grains.id}} snapshots
    - cwd: /var/lib/restic
    - runas: restic
```

{% endcode %}

#### Zurücksichern

Oder prüfen Sie die Backups direkt auf dem Backup-Server, indem Sie die verschlüsselte Backupdatei auf dem Backup-Server mounten.

```
# Change to the restic user account
root@server:~# su - restic

# Create a folder to mount a backup
restic@server:~$ mkdir -p mnt/<MINION_ID>  
# Mount a backup                   
restic@server:~$ ./restic -r <MINION_ID> mount mnt/<MINION_ID>/ -p <MINION_ID>/.restic-password
```

Das zum Mounten benötigte Password entnehmen Sie der Datei `/var/cache/salt/master/minions/<CLIENT>/files/var/lib/restic/.restic-password` auf dem Salt-Master.

Weitere Information zum Zurücksichern in der 📖 [Restic Dokumentation](https://restic.readthedocs.io/en/latest/050_restore.html).

😯 **Problem:**

Falls Sie das Backup nicht mounten können, ist das `fuse` Paket nicht installiert. Erweitern Sie den `restic.server` State und installieren Sie darüber das benötigte Paket.

#### Backups überwachen

Optional können Sie mit dem kostenlosen Dienst [Betteruptime.com](https://betteruptime.com/) ein Monitoring der Backups einrichten.

{% code title="/srv/salt/restic/monitoring.sls" %}

```yaml
#
# Create a monitoring at betteruptime.com
#
{% set token='xxx' %}
register-monitoring:
  cmd.run:
    - runas: restic
    - cwd: /var/lib/restic
    - name: |
        curl -sf --request POST \
        -o monitoring-conf.json \
        --url https://betteruptime.com/api/v2/heartbeats \
        --header 'Authorization: Bearer {{ token }}' \
        --header 'Content-Type: application/json' \
        --data '{
            "name": "{{ grains['id'] }} Restic Backup",
            "period": 86400,
            "grace": 3600
        }'
    - creates: /var/lib/restic/monitoring-conf.json
```

{% endcode %}

Mit diesem State wird ein Monitoring Item auf dem Dashboard von Betteruptime registriert. Dieses Item erwartet alle 24 Stunden eine positive Rückmeldung, andernfalls wird ein Alarm ausgelöst. Erweitern Sie das Skript, welches das Backup durchführt, mit folgender Zeile:

```
curl -fs $(jq -r .data.attributes.url < monitoring-conf.json)
```

{% file src="<https://3666581685-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LFRZJPZX11pwB1staCp%2Fuploads%2FV612xTw2l3qOUmpgKzI3%2Fsrv-salt-restic.tar.gz?alt=media&token=c965dd3e-6f95-4ff5-85e1-f1e6a97ef428>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://thorstenkramm.gitbook.io/saltstack/beispiele/restic-backup.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
