$ sudo inxi --filter --width 90 -SGIMCPDp --ms
System:
Kernel: 6.4.10-arch1-1 arch: x86_64 bits: 64 Console: pty pts/4 Distro: Arch Linux
Machine:
Type: Desktop Mobo: ASRock model: 970M Pro3 serial: <filter> UEFI: American Megatrends
v: P1.60 date: 06/17/2016
Memory:
System RAM: total: 8 GiB available: 7.73 GiB used: 4.46 GiB (57.7%)
Report: arrays: 1 slots: 4 modules: 2 type: DDR3
CPU:
Info: 6-core model: AMD Phenom II X6 1090T bits: 64 type: MCP cache: L2: 3 MiB
Speed (MHz): avg: 800 min/max: 800/3200 cores: 1: 800 2: 800 3: 800 4: 800 5: 800
6: 800
Graphics:
Device-1: NVIDIA GP107 [GeForce GTX 1050 Ti] driver: nvidia v: 535.98
Display: server: X.org v: 1.21.1.8 driver: N/A resolution: <missing: xdpyinfo/xrandr>
API: OpenGL Message: Unable to show GL data. Required tool glxinfo missing.
Drives:
Local Storage: total: 4.31 TiB used: 187.3 GiB (4.2%)
ID-1: /dev/sda vendor: Kingston model: SA400S37240G size: 223.57 GiB
ID-2: /dev/sdb vendor: Western Digital model: WD40EFAX-68JH4N1 size: 3.64 TiB
ID-3: /dev/sdc vendor: Seagate model: ST3500312CS size: 465.76 GiB
Partition:
ID-1: / size: 223.28 GiB used: 69.95 GiB (31.3%) fs: btrfs dev: /dev/sda2
[...]
ID-3: /boot size: 299.4 MiB used: 56.5 MiB (18.9%) fs: vfat dev: /dev/sda1
[...]
ID-24: /srv size: 223.28 GiB used: 69.95 GiB (31.3%) fs: btrfs dev: /dev/sda2
ID-25: /swap size: 223.28 GiB used: 69.95 GiB (31.3%) fs: btrfs dev: /dev/sda2
[...]
ID-27: /var/cache size: 223.28 GiB used: 69.95 GiB (31.3%) fs: btrfs dev: /dev/sda2
[...]
ID-37: /var/log size: 223.28 GiB used: 69.95 GiB (31.3%) fs: btrfs dev: /dev/sda2
ID-38: /var/tmp size: 223.28 GiB used: 69.95 GiB (31.3%) fs: btrfs dev: /dev/sda2
Info:
Processes: 531 Uptime: 12h 29m Shell: Sudo inxi: 3.3.28
/dev/sda
is a 120GB Kingston ssd, hold the root of the system./dev/sda1
->/boot
: hold the boot-loader (fat32
partition of 300MB)/dev/sda2
: btrfs partition
/dev/sdb
is a 4TB WD Red, for data storage./dev/sdb1
: btrfs partition
/dev/sdc
is a 500GB old hdd, not trustworthy and only hold torrents. Is formatted in EXT4 and is not part of the btrfs layout./dev/sdc1
: EXT4 partition
The 120GB Kingston hold the system, so /
, /home
are on this drive.
The 4TB WD Red hold some docker volumes and other data.
The layout of the main drive is:
@
->/
@home
->/home
@cache
->/var/cache
@log
->/var/log
@tmp
->/var/tmp
@srv
->/srv
Following Snapper#Suggested filesystem layout and Snapper#Preventing slowdowns.
All the mounts are done with fstab
, for example:
UUID=<...> / btrfs rw,relatime,ssd,space_cache=v2,subvolid=256,subvol=/@,compress=zstd 0 0
The options are generated using the genfstab
script (extra/arch-install-scripts)
@data
->/data/wd-red
: generic data folder.docker-volumes
: folder that contains some docker volumes mapped to btrfs sub-volumes. (Note, this is not a sub-volume but a normal folder.)
Docker is configured to use the BTRFS storage driver, as described here. This way the images of the containers are stored in sub-volumes and not included in the snapshots.
First, create the config for the sub-volumes you are interested in backing-up:
sudo snapper -c root create-config /
sudo snapper -c home create-config /home
As described in Snapper#Suggested filesystem layout I moved the .../.snapshots
out of the sub-volume I'm taking a snapshot of.
To do so I created a folder in the btrfs root (snapshots/
) and inside the sub-volumes that will hold the snapshots:
sudo btrfs subvolume list /
[...]
ID 413 gen 3398 top level 5 path snapshots/@root-snap
ID 414 gen 3397 top level 5 path snapshots/@home-snap
[...]
Than I mapped the sub-volumes to the .../.snapshots
using fstab
:
UUID=<...> /.snapshots btrfs rw,relatime,ssd,space_cache=v2,subvolid=413,subvol=/snapshots/@root-snap,compress=zstd 0 0
UUID=<...> /home/.snapshots btrfs rw,relatime,ssd,space_cache=v2,subvolid=414,subvol=/snapshots/@home-snap,compress=zstd 0 0
The configs are located in /etc/snapper/configs
. I enabled NUMBER_CLEANUP
, TIMELINE_CREATE
and TIMELINE_CLEANUP
. And changed the limits to keep less than 10 snapshots in total.
To upgrade my system I use topgrade. I could have added snapper to a pacman hook, but since topgrade also does a lot of other stuff I preferred to add a pre and post step. The full topgrade config can be found here
[pre_commands]
"snap before upgrade" = """echo \"taking a snap...\" && \
sudo snapper -c root create -t single -d \"snap before upgrade\" -c number && \
echo \"snap done...\""""
[post_commands]
"snapper cleanup" = """echo \"cleaning up the snaps...\" && \
sudo snapper -c root cleanup number && \
sudo snapper -c root list"""
The ssd is kind of old and will die before the WD Red. To prepare for this scenario I used snap-sync
(link) to send the snapshots from the ssd to the hdd.
To set this up I created a sub-volume in the WD Red, @system-backup
, and mounted it with fstab
to /system-backup
. To configure snap-sync
you have to run in manually the first time:
sudo snap-sync --UUID <UUID of WD Red> --subvolid <subvol id of @system-backup> -c "root home" -q -n
To avoid the creation of another 2 sub-volumes, when asked for the path leave it empty.
The directory structure will be:
/system-backup/
├── home/
│ ├── 8/
│ └── 9/
└── root/
├── 9/
└── 10/
As advised in the man of snap-sync
to run it periodically you need to create a systemd unit and timer. To set the timer see link.
$ sudo vim /etc/systemd/system/snap-sync.service
[Unit]
Description=Run snap-sync backup
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
ExecStart=/usr/bin/systemd-inhibit snap-sync --UUID <UUID of WD Red> --subvolid <subvol id of @system-backup> -c "root home" -q -n
$ sudo vim /etc/systemd/system/snap-sync.timer
[Unit]
Description=Run snap-sync every 3 days
[Timer]
OnCalendar=*-*-01,04,07,10,13,16,19,22,25,28,31 00:00:00
AccuracySec=12h
Persistent=true
[Install]
WantedBy=timers.target
Then you need to enable the timer:
sc-enable-now snap-sync.timer
To debug the unit you can run it with sc-start
, it will do it's stuff and exit.
snap-sync
is a wrapper around btrfs send receive, for this reason it could take some time to move the data between the 2 drives. With systemd-inhibit
I can prevent the shutdown of the server while the process is running. The poweroff / reboot command must include --check-inhibitors=yes
, so for example:
systemctl poweroff --check-inhibitors=yes
By default snap-sync
have no clean-up algorithm. A user created a script to implement the clean-up, link.
To use it, download, add execution permissions and move it to /usr/local/bin/snap-sync-cleanup.sh
. Than you need systemd service and timer:
$ sudo vim /etc/systemd/system/snap-sync-cleanup.service
[Unit]
Description=Run snap-sync-cleanup
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
ExecStart=/usr/local/bin/snap-sync-cleanup.sh
$ sudo vim /etc/systemd/system/snap-sync-cleanup.timer
[Unit]
Description=Run snap-sync-cleanup every 3 days
[Timer]
OnCalendar=*-*-01,04,07,10,13,16,19,22,25,28,31 00:00:00
AccuracySec=12h
Persistent=true
[Install]
WantedBy=timers.target
$ sc-enable-now snap-sync-cleanup.timer
To create a docker volume mapped to a btrfs sub-volume you need to:
-
create the sub-vol:
btrfs subvolume create @name
-
create the docker volume:
docker volume create --driver local --opt type=btrfs \ --opt device=/dev/sdb1 \ --opt o=subvol=<path>/@name \ name
-
than you can use it in the docker-compose with:
volumes: name: external: true
To back up some of the docker volumes to an external cloud storage I wrote my own backup script. To set it up you just need to configure snapper for the docker volume, as described on the arch wiki (with .snapshots mapped to a subvolume outsise the one you are taking a backup of).
As a reverse proxy for every service that need to be exposed to the web I use traefik. Is configured to work on the network proxy
, created with:
docker network create proxy
And referenced in the docker-compose files as:
networks:
proxy:
external: true
To redirect the traffic to the correct port you need to add:
labels:
- "traefik.http.routers.<service name>.service=<service name>-svc"
- "traefik.http.services.<service name>-svc.loadbalancer.server.port=<port of the service>"
To tell traefik in which network it should search the service, you need to add the following label:
service:
networks:
media:
proxy:
labels:
[...]
- "traefik.docker.network=proxy"
To get notification from the servers I did set up apprise and a telegram bot.
In the apprise configuration interface you can set up some topics:
general=tgram://<bot token>/<chat id>
admin=tgram://<bot token>/<chat id>
Then you can use the topic with:
curl -X POST -d '{"tag":"admin", "body":"test message"}' \
-H "Content-Type: application/json" \
http://localhost:8005/notify/apprise
To connect it with other containers I created an external network, then added both apprise and the other container to that network.
Apprise use the port 8000, but in my case it war already used by portainer. To overcome this I set the port mapping of apprise to - 8005:8000
. This means that a notification from the system must be sent to http://localhost:8005/notify/apprise
. While a notification from within the docker network must be sent to http://apprise:8000/notify/apprise
.
To enable notifications through apprise you need the Webhook plugin. It can be installed directly from the plugin section of Jellyfin.
Then, if for example you want a notification when an item is added, you need to create a generic destination
:
-
set the correct webhook url:
http://apprise:8000/notify/apprise
. You can confirm that the url is correct withdocker exec -it jellyfin bash
than using curl from within the container. -
check
Item Added
-
use a template like:
{ "tag": "general", {{#if_equals ItemType 'Season'}} "body": "<b>{{ItemType}} ora disponibile</b>\n\n<b>{{{SeriesName}}} ({{Year}})</b>\n<b><em>{{{Name}}}</em></b>\n{{Overview}}\n\n<b>Durata: {{RunTime}}</b>\n\nStato: Disponibile", {{else}} {{#if_equals ItemType 'Episode'}} "body": "<b>episodio ora disponibile</b>\n\n<b>{{{SeriesName}}} ({{Year}})</b>\n<b>S{{SeasonNumber00}}E{{EpisodeNumber00}}</b> - <b><em>{{{Name}}}</em></b>\n{{Overview}}\n\n<b>Durata: {{RunTime}}</b>\n\nStato: disponibile", {{else}} "body": "<b>{{ItemType}} ora disponibile</b>\n\n<b>{{{Name}}} ({{Year}})</b>\n{{Overview}}\n\n<b>Durata: {{RunTime}}</b>\n\nStato: disponibile", {{/if_equals}} {{/if_equals}} "format": "html" }
I took the template from here and modified it following this.
With the
tag
key you can specify to which topic the notification will be sent. -
Add a request header:
- key:
Content-Type
- value:
application/json
- key:
Follow this and this. The secret is to not put the menuLinks
at the end, but in the middle under themes
.