Index
By using the “Human Made” badge, I affirm that I am not using data-generated images (“genAI”) or data-generated text (“genAI”) in any asset of my work. My workflow consists of only using my skill crafted from experience and practice.
This site serves as a convenient, central location for standalone code snippets, commands, and tutorials. Some of these used to be on my blog, but I figure that they are best placed here so that potential readers don’t have to look through my blog archives, as my blog mostly contains infodumps about tech and other topics.
Feel free to use any of the content here without obligation. All articles are CC0 and no credits or attributions are required.
Some of these are links to my guides on FOSS Wiki.
Get the latest release from GitHub
Mass delete repos from Forgejo, Gitea, or Codeberg
Profile Sync Daemon config for Waterfox
It’s basically the same as Firefox except for the path to the profiles.
Create the file /usr/share/psd/browsers/waterfox and add the following contents.
if [[ -d "$XDG_CONFIG_HOME/waterfox" ]]; then
index=0
PSNAME="$browser"
while read -r profileItem; do
if [[ $(echo "$profileItem" | cut -c1) = "/" ]]; then
# path is not relative
DIRArr[$index]="$profileItem"
else
# we need to append the default path to give a
# fully qualified path
DIRArr[$index]="$XDG_CONFIG_HOME/waterfox/$profileItem"
fi
(( index=index+1 ))
done < <(grep '^[Pp]ath=' "$XDG_CONFIG_HOME/waterfox/profiles.ini" | sed 's/[Pp]ath=//')
fi
check_suffix=1
if [[ -d "$HOME"/.waterfox ]]; then
index=0
PSNAME="$browser"
while read -r profileItem; do
if [[ $(echo "$profileItem" | cut -c1) = "/" ]]; then
# path is not relative
DIRArr[$index]="$profileItem"
else
# we need to append the default path to give a
# fully qualified path
DIRArr[$index]="$HOME/.waterfox/$profileItem"
fi
(( index=index+1 ))
done < <(grep '^[Pp]ath=' "$HOME"/.waterfox/profiles.ini | sed 's/[Pp]ath=//')
fi
check_suffix=1
Add Waterfox to ~/.config/psd/psd.conf.
BROWSERS=(waterfox)
Restart the psd.service.
systemctl --user restart psd.service
Waterfox swim real fast now 😀. In theory, at least.
Profile Sync Daemon config for Waterfox Flatpak
Create the file /usr/share/psd/browsers/waterfox-flatpak and add the following contents:
if ! flatpak override --user --show net.waterfox.waterfox | grep -q "/run/user/$UID/psd"; then
flatpak override --user net.waterfox.waterfox --filesystem=/run/user/$UID/psd
fi
if [[ -d "$HOME"/.var/app/net.waterfox.waterfox/.waterfox ]]; then
index=0
PSNAME="$browser"
while read -r profileItem; do
if [[ $(echo "$profileItem" | cut -c1) = "/" ]]; then
# path is not relative
DIRArr[$index]="$profileItem"
else
# we need to append the default path to give a
# fully qualified path
DIRArr[$index]="$HOME/.var/app/net.waterfox.waterfox/.waterfox/$profileItem"
fi
(( index=index+1 ))
done < <(grep '[Pp]'ath= "$HOME"/.var/app/net.waterfox.waterfox/.waterfox/profiles.ini | sed 's/[Pp]ath=//')
fi
check_suffix=1
Add Waterfox Flatpak to ~/.config/psd/psd.conf:
BROWSERS=(waterfox-flatpak)
Restart psd.service.
systemctl --user restart psd.service
Profile Sync Daemon config for Vivaldi Flatpak
Create the file /usr/share/psd/browsers/vivaldi-flatpak and add the following contents:
if ! flatpak override --user --show com.vivaldi.Vivaldi | grep -q "/run/user/$UID/psd"; then
flatpak override --user com.vivaldi.Vivaldi --filesystem=/run/user/$UID/psd
fi
DIRArr[0]="$HOME/.var/app/com.vivaldi.Vivaldi/config/$browser"
PSNAME="$browser"-bin
Add Vivaldi Flatpak to ~/.config/psd/psd.conf:
BROWSERS=(vivaldi-flatpak)
Restart psd.service.
systemctl --user restart psd.service
Profile Sync Daemon for Zen Browser Flatpak
profile-sync-daemon is a li’l daemon for managing browser profiles in a temporary RAM filesystem and periodically syncing them back to the physical disk. This improves the responsiveness of the browser and reduces wear on physical drives.
Zen Browser is not officially supported by the profile-sync-daemon, but there is community support for the non-Flatpak version of Zen Browser. This can be activated by copying /usr/share/psd/contrib/zen to /usr/share/psd/browsers/. However, this won’t work for the Flatpak version of Zen Browser unless you do the following:
- Modify the
/usr/share/psd/contrib/zenfile to use the path to the Flatpak’s browser profile. - Give the Flatpak permission to access the temporary RAM filesystem.
To give the Zen Browser Flatpak permission to access the temporary RAM filesystem where PSD stores the profiles, run the following command:
flatpak override --user app.zen_browser.zen --filesystem=/run/user/$UID/psd
Now put the following contents into a file at /usr/share/psd/browsers/zen-flatpak:
if [[ -d "$HOME"/.var/app/app.zen_browser.zen/cache/zen ]]; then
index=0
PSNAME="$browser"
while read -r profileItem; do
if [[ $(echo "$profileItem" | cut -c1) = "/" ]]; then
# path is not relative
DIRArr[$index]="$profileItem"
else
# we need to append the default path to give a
# fully qualified path
DIRArr[$index]="$HOME/.var/app/app.zen_browser.zen/cache/zen/$profileItem"
fi
(( index=index+1 ))
done < <(grep '^[Pp]'ath= "$HOME"/.var/app/app.zen_browser.zen/.zen/profiles.ini | sed 's/^[Pp]ath=//')
fi
check_suffix=1
Edit ~/.config/psd/psd.conf:
BROWSERS=(zen-flatpak)
Restart the psd.service:
systemctl --user restart psd.service
That’s it.
Home networking and preventing DNS leaks
I think I’ve figured out how to prevent my Fedora Linux desktop from leaking DNS to Xfinity. My Linux desktop is part of a Tailscale network that uses Mullvad’s ad-blocking and malware blocking public DNS server, but I still have DNS leaks because my main network interface is using the Xfinity DNS servers from its DHCP connection via the Xfinity modem. Below are the steps I took to prevent DNS leaks to Xfinity.
systemd-networkd
NetworkManager is the default on Fedora 40. I disabled NetworkManager and enabled systemd-networkd with the following configuration:
[Match]
Name=eno1
[Network]
DHCP=yes
DNS=100.100.100.100
DNSSEC=allow-downgrade
[DHCPv4]
UseDNS=no
In the [DHCPv4] section, UseDNS=no ensures that you’re not using the DNS servers provided by the DHCP connection. In my case, my DHCP connection via my Xfinity modem was setting the DNS to the Xfinity DNS servers. So that is no longer the case now. For good measure, I added my tailnet’s DNS as a static DNS server in the [Network] section.
resolvectl status now shows that my primary network interface eno1 uses only the DNS from my Tailscale network, which is 100.100.100.100.

Disable IPv6
Another possible source of DNS leaks is IPv6. On Fedora 40, the way to disable IPv6 is by adding a kernel argument to the GRUB bootloader configuration. This can be done with the following command:
sudo grubby --args`ipv6.disable`1 --update-kernel=ALL
Reboot the system for the change to take effect.
Ensure Firefox or LibreWolf are not using DNS over HTTPS
In most cases Firefox/LibreWolf are configured to use the system DNS resolver by default, but if you have it configured to use one of the “protection” settings for DNS over HTTPS, this could be a source of DNS leaks.
Now you can check for DNS leaks with Mullvad’s connection checker.
Network-wide bullshit-blocking setup with Blocky and Tailscale
I will use an Orange Pi 5 Plus, but any device, including single board computers, should work, as long as they can run the latest stable Debian or Armbian release.
Orange Pi 5 Plus
- Unbound for recursive DNS resolver on 127.0.0.1:5335
- Blocky for DNS proxy, ad-blocking, and malware-blocking on 0.0.0.0:53. Uses Unbound on 127.0.0.1:5335 as upstream resolver.
- Tailscale with
–accept-dns=false unbound-resolveconf.serviceshould be disabled, and/etc/resolv.confshould not be managed by any other service.
I just put the following contents into /etc/resolv.conf for the Orange Pi 5 Plus’s local DNS resolution:
nameserver 9.9.9.9
nameserver 149.112.112.112
I have Blocky configured to use the strict strategy for the upstreams setting, so after a timeout of the topmost upstream server it will fallback to the next one, which is Quad9.
An idea I have is to setup a cheap VPS on Vultr or something and run a public DNS resolver on it, but Quad9 is fine for now.
I have the Orange Pi 5 Plus’s Tailnet IP address configured to be my Tailnet’s global nameserver. This can be done through the Tailscale admin console under the DNS tab. So every device on my Tailnet that uses MagicDNS will be using Blocky and Unbound.
Blocky configuration
upstreams:
strategy: strict
groups:
default:
- 127.0.0.1:5335
- 9.9.9.9
- 149.112.112.112
blocking:
denylists:
ads:
- <https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts>
- <https://adaway.org/hosts.txt>
- <https://v.firebog.net/hosts/AdguardDNS.txt>
suspicious:
- <https://v.firebog.net/hosts/static/w3kbl.txt>
tracking:
- <https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt>
- <https://v.firebog.net/hosts/Easyprivacy.txt>
- <https://v.firebog.net/hosts/Prigent-Ads.txt>
malicious:
- <http://phishing.mailscanner.info/phishing.bad.sites.conf>
- <https://v.firebog.net/hosts/Prigent-Crypto.txt>
- <https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts>
clientGroupsBlock:
default:
- ads
- suspicious
- tracking
- malicious
ports:
dns: 53
http: 4000
prometheus:
enable: yes
caching:
minTime: 60s
maxItemsCount: 10000
prefetching: yes
prefetchMaxItemsCount: 2000
queryLog:
type: csv-client
target: /home/jas/dns-query-logs
logRetentionDays: 5
clientLookup:
upstream: 10.0.0.1
singleNameOrder:
- 1
If you’re using a firewall, make sure ingress traffic to UDP port 53 is allowed on the Tailscale interface.
Tailscale DNS
Go to the Tailscale admin web UI. In the DNS tab, under Global nameservers, add the Orange Pi 5+’s tailnet IP address. Remove any other nameservers from this section. Make sure Override DNS servers is switched on. All the devices on your tailnet that have MagicDNS enabled will now be using Blocky and Unbound to block and resolve DNS queries.
Not using Tailscale?
This setup can be achieved without using Tailscale by setting the local nameserver on each of your devices to the Orange Pi 5+’s LAN IP address. The downside to this is that it is only available to devices connected to your LAN.
Setup a thick VNET jail on FreeBSD
Setup the VNET bridge
Create the bridge.
ifconfig bridge create
Attach the bridge to the main network interface. igc0 in this case. For some reason, the resulting bridge device is named bridge0.
ifconfig bridge0 addm igc0
To make this persistent across reboots, add the following to /etc/rc.conf.
defaultrouter="10.0.0.1"
cloned_interfaces="bridge0"
ifconfig_igc0bridge="inet 10.0.0.8/24 addm igc0 up"
Create the classic (thick) jail
Create the ZFS datasets for the jails. We’ll use basejail as a template for subsequent jails.
zfs create -o mountpoint=/jails naspool/jails
zfs create naspool/jails/basejail
Use the bsdinstall utility to bootstrap the base system to the basejail.
export DISTRIBUTIONS="base.txz"
export BSDINSTALL_DISTSITE=https://download.freebsd.org/ftp/releases/amd64/14.2-RELEASE/
bsdinstall jail /jails/basejail
Run freebsd-update to update the base jail.
freebsd-update -b /jails/basejail fetch install
freebsd-update -b /jails/basejail IDS
We now snapshot the basejail and create a clone of this snapshot for the torrenting jail that we will use for Anna’s Archive.
zfs snapshot naspool/jails/basejail@`freebsd-version`
zfs clone naspool/jails/basejail@`freebsd-version` naspool/jails/torrenting
We now use the following configuration for /etc/jail.conf.
torrenting {
exec.consolelog = "/var/log/jail_console_${name}.log";
allow.raw_sockets;
exec.clean;
mount.devfs;
devfs_ruleset = 11;
path = "/jails/${name}";
host.hostname = "${name}";
vnet;
vnet.interface = "${epair}b";
$id = "127";
$ip = "10.0.0.${id}/24";
$gateway = "10.0.0.1";
$bridge = "bridge0";
$epair = "epair${id}";
exec.prestart = "/sbin/ifconfig ${epair} create up";
exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
exec.start += "/sbin/ifconfig ${epair}b ${ip} up";
exec.start += "/sbin/route add default ${gateway}";
exec.start += "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a";
exec.poststop += "/sbin/ifconfig ${epair}a destroy";
}
Now we create the devfs ruleset to enable access to devices under /dev inside the jail. Add the following to /etc/devfs.rules.
[devfsrules_jail_vnet=11]
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login
add include $devfsrules_jail
add path 'tun*' unhide
add path 'bpf*' unhide
Enable the jail utility in /etc/rc.conf.
sysrc jail_enable="YES"
sysrc jail_parallel_start="YES"
Start the jail service for torrenting jail.
service jail start torrenting
Setting up Wireguard inside the jail
Since we have the /dev/tun* devfs rule, we now need to install Wireguard inside the jail.
jexec -u root torrenting
pkg install wireguard-tools wireguard-go
Download a Wireguard configuration for ProtonVPN, and save it to /usr/local/etc/wireguard/wg0.conf.
Enable Wireguard to run when the jail boots up.
sysrc wireguard_enable="YES"
sysrc wireguard_interfaces="wg0"
Start the Wireguard daemon and make sure you are connected to it properly.
service wireguard start
curl ipinfo.io
The curl command should display the IP address of the Wireguard server defined in /usr/local/etc/wireguard/wg0.conf.
Setting up qBittorrent inside the jail
Install the qbittorrent-nox package.
pkg install -y qbittorrent-nox
Before running the daemon from /usr/local/etc/rc.d/qbittorrent, we must run the qbittorrent command from the shell so that we can see the default password generated for the web UI. For some reason it is not shown in any logs, and the qbittorrent nox manpage wrongly says the password is “adminadmin”.
pkg install -y sudo
sudo -u qbittorrent qbittorrent-nox --profile=/var/db/qbittorrent/conf --save-path=/var/db/qbittorrent/Downloads --confirm-legal-notice
Copy the password displayed after running the command. Login to the qBittorrent web UI at http://10.0.0.127:8080 with login admin and the password you copied. In the web UI, open the options menu and go over to the Web UI tab. Change the login password to your own. Save the options to close the menu.
Now press CTRL-c to stop the qbittorrent-nox process. Make the following changes to the torrenting jail’s /etc/rc.conf.
sysrc qbittorrent_enable="YES"
sysrc qbittorrent_flags="--confirm-legal-notice"
Enable the qBittorrent daemon.
service qbittorrent start
Go back to the web UI at http://10.0.0.127:8080. Go to the options menu and go over to the Advanced tab, which is the very last tab. Change the network interface to wg0.
Finding the forwarded port that the ProtonVPN server is using
Install the libnatpmp package.
Make sure that port forwarding is allowed on the server you’re connected to, which it should be if you enabled it while creating the Wireguard configuration on the ProtonVPN website. Run the natpmpc command against the ProtonVPN Wireguard gateway.
natpmpc -g 10.2.0.1
If the output looks like the following, you’re good.
initnatpmp() returned 0 (SUCCESS)
using gateway : 10.2.0.1
sendpublicaddressrequest returned 2 (SUCCESS)
readnatpmpresponseorretry returned 0 (OK)
Public IP address : 62.112.9.165
epoch = 58081
closenatpmp() returned 0 (SUCCESS)
Now create the UDP and TCP port mappings, then loop natpmpc so that it doesn’t expire.
while true ; do date ; natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo -e "ERROR with natpmpc command \a" ; break ; } ; sleep 45 ; done
The port allocated for this server is shown on the line that says “Mapped public port XXXXX protocol UDP to local port 0 lifetime 60”. Port forwarding is now activated. Copy this port number and, in the qBittorrent web UI options menu, go to the Connections tab and enter it into the “Port used for incoming connections” box. Make sure to uncheck the “Use UPnP/NAT-PMP port forwarding from my router” box.
If the loop terminates, you’ll need to re-run this loop script each time you start a new port forwarding session or the port will only stay open for 60 seconds.
P2P NAT port forwarding script with supervisord
Install supervisord.
sudo pkg install -y py311-supervisor
Enable the supervisord service.
sudo sysrc supervisord_enable="YES"
Edit /usr/local/etc/supervisord.conf, and add the following to the bottom of the file.
[program:natpmpcd]
command=/usr/local/bin/natpmpcd
autostart=true
Add the following contents to a file at /usr/local/bin/natpmpcd.
#!/bin/sh
port=$(/usr/local/bin/natpmpc -a 1 0 udp 60 -g 10.2.0.1 | grep "Mapped public port" | awk '{print $4}')
echo "$port" | tee /usr/local/etc/natvpn_port.txt
curl -v --cookie /tmp/cookie.txt -d "json={\"listen_port\": \"$port\"}" http://localhost:8080/api/v2/app/setPreferences
while true; do
date
if ! /usr/local/bin/natpmpc -a 1 0 udp 60 -g 10.2.0.1 && /usr/local/bin/natpmpc -a 1 0 tcp 60 -g 10.2.0.1; then
echo "error Failure natpmpc $(date)"
break
fi
sleep 45
done
Ensure the script is executable with chmod +x /usr/local/bin/natpmpcd.
supervisord will start the above shell script automatically. Ensure supervisord service is started.
sudo service supervisord start
Every time it runs, the script will print out the forwarded port number at /usr/local/etc/natvpn_port.txt, and update the listen_port preference in qBittorrent to the new forwarded port.
cat /usr/local/etc/natvpn_port.txt
48565
Install Chimera Linux
Requirements
- UEFI
- LVM on LUKS with unencrypted
/boot
Disk partitioning
Use cfdisk to create the following partition layout.
| Partition Type | Size |
|---|---|
| EFI | +600M |
| boot | +900M |
| Linux | Remaining space |
Format the unencrypted partitions:
mkfs.vfat /dev/nvme0n1p1
mkfs.ext4 /dev/nvme0n1p2
Create LUKS on the remaining partition:
cryptsetup luksFormat /dev/nvme0n1p3
cryptsetup luksOpen /dev/nvme0n1p3 crypt
Create a LVM2 volume group for /dev/nvme0n1p3, which is located at /dev/mapper/crypt:
vgcreate chimera /dev/mapper/crypt
Create logical volumes in the volume group:
lvcreate --name swap -L 8G chimera
lvcreate --name root -l 100%FREE chimera
Create the filesystems for the logical volumes:
mkfs.ext4 /dev/chimera/root
mkswap /dev/chimera/swap
Create mount points for the chroot and mount the filesystems:
mkdir /media/root
mount /dev/chimera/root /media/root
mkdir /media/root/boot
mount /dev/nvme0n1p2 /media/root/boot
mkdir /media/root/boot/efi
mount /dev/nvme0n1p1 /media/root/boot/efi
Bootstrap
Chimera-bootstrap and chroot
chimera-bootstrap /media/root
chimera-chroot /media/root
Update the system:
apk update
apk upgrade --available
Install kernel, cryptsetup, and lvm2 packages:
apk add linux-stable cryptsetup-scripts lvm2
Fstab
genfstab / >> /etc/fstab
Crypttab
echo "crypt /dev/disk/by-uuid/$(blkid -s UUID -o value /dev/nvme0n1p3) none luks" > /etc/crypttab
Initramfs refresh
update-initramfs -c -k all
GRUB
apk add grub-x86_64-efi
grub-install --efi-directory=/boot/efi --target=x86_64-efi
Post-installation
passwd root
apk add zsh bash
useradd -c "Jeffrey Serio" -m -s /usr/bin/zsh -U jas
passwd jas
Add the following lines to /etc/doas.conf:
# Give jas access
permit nopass jas
Set hostname, timezone, and hwclock:
echo "falinesti" > /etc/hostname
ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime
echo localtime > /etc/hwclock
Xorg and Xfce4
apk add xserver-xorg xfce4
Reboot the machine.
Post-reboot
Login as jas. Run startxfce4. Connect to the internet via NetworkManager.
Ensure wireplumber and pipewire-pulse are enabled:
dinitctl enable wireplumber
dinitctl start wireplumber
dinitctl enable pipewire-pulse
dinitctl start pipewire-pulse
Install CPU microcode:
doas apk add ucode-intel
doas update-initramfs -c -k all
Install other packages
doas apk add chrony
doas dinitctl enable chrony
doas apk add ...
Install Debian with LUKS2 on Btrfs with GRUB
Create an RPM repository
Install Cgit with Caddy
Dependencies
- xcaddy package from releases page.
Install caddy-cgi:
xcaddy build --with github.com/aksdb/caddy-cgi/v2
Install remaining dependencies:
sudo apt install gitolite3 cgit python-is-python3 python3-pygments python3-markdown docutils-common groff
Configuration
Make a git user.
sudo adduser --system --shell /bin/bash --group --disabled-password --home /home/git git
Configure gitolite for the git user in ~/.gitolite.rc.
UMASK => 0027,
GIT_CONFIG_KEYS => 'gitweb.description gitweb.owner gitweb.homepage gitweb.category',
Add caddy user to the git group.
sudo usermod -aG git caddy
Configure cgit in /etc/cgitrc:
#
# cgit config
# see cgitrc(5) for details
css=/cgit/cgit.css
logo=/cgit/cgit.png
favicon=/cgit/favicon.ico
enable-index-links=1
enable-commit-graph=1
enable-log-filecount=1
enable-log-linecount=1
enable-git-config=1
branch-sort=age
repository-sort=name
clone-url=https://git.hyperreal.coffee/$CGIT_REPO_URL git://git.hyperreal.coffee/$CGIT_REPO_URL ssh://git@git.hyperreal.coffee:$CGIT_REPO_URL
root-title=hyperreal.coffee Git repositories
root-desc=Source code and configs for my projects
##
## List of common mimetypes
##
mimetype.gif=image/gif
mimetype.html=text/html
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.pdf=application/pdf
mimetype.png=image/png
mimetype.svg=image/svg+xml
# Enable syntax highlighting
source-filter=/usr/lib/cgit/filters/syntax-highlighting.py
# Format markdown, rst, manpages, text files, html files, and org files.
about-filter=/usr/lib/cgit/filters/about-formatting.sh
##
### Search for these files in the root of the default branch of repositories
### for coming up with the about page:
##
readme=:README.md
readme=:README.org
robots=noindex, nofollow
section=personal-config
repo.url=doom-emacs-config
repo.path=/home/git/repositories/doom-emacs-config.git
repo.desc=My Doom Emacs config
Setting up Restic with rest-server
Context
I recently decided to start using my own home server to store my dotfiles. The main reasons are simplicity, privacy, and security. I previously stored them in a repository on my GitHub account and installed them with Ansible, but I have increasingly found it cumbersome when trying to keep them updated and in sync. On GitHub, the changes (and mistakes!) I make to my dotfiles are publicly viewable; sometimes I’ll make changes several times a day, sometimes scrapping a change entirely when I later realize it was not such a good idea or breaks something in my activity flow. I also would love the convenience of keeping SSH keys and GPG keychains in sync and updated, and storing them on a public server is obviously not an option, nor even in a private repository hosted on GitHub or GitLab.
Cue Restic
My home server is basically just my old 2013 MacBook Pro running Fedora Server edition. It has a 250GB SSD, which is more than enough for what I need. I also have a 1TB external SSD which I will use to emulate redundancy. I installed and configure the rest-server software to act as a backend for my Restic backups.
Setting up the rest server
First build the rest-server binary and move it to a directory in PATH. This step requires Go 1.11 or higher. Optionally, you can download the latest compiled rest-server binary from its releases page.
* GitHub :: restic/rest-server/releases
git clone https://github.com/restic/rest-server
cd rest-server/
CGO_ENABLED=0 go build -o rest-server ./cmd/rest-server
sudo cp -v rest-server /usr/local/bin/
I also configured the systemd unit file so that rest-server runs on startup with the appropriate flags. I need only configure the options User, Group, ExecStart, and ReadWritePaths in the [Service] section:
cd ~/rest-server/examples/systemd/
ls .
rest-server.service:
[Service]
Type=simple
User=restic-data
Group=restic-data
ExecStart=/usr/local/bin/rest-server --path /opt/restic-backups --no-auth
Restart=always
RestartSec=5
# Optional security enhancements
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/restic-backups
Since this is a local home server, I pass the --no-auth flag to the rest-server ExecStart command.
I now create the restic-data user and group.
- Ensure a default home directory is not created under /home by passing the
-Mflag. - Set a custom home directory for the user at /opt/restic-backups with the
-dflag. - Ensure the shell is assigned to
/sbin/nologin. - The restic-data user is not meant to be used for logging in, so we pass the
--systemflag.
sudo useradd -c "Restic Data" -M -d /opt/restic-backups -s /sbin/nologin --system restic-data
- Ensure the backups path exists and has appropriate permissions.
- Copy the systemd unit file to a location where systemd will look for it.
- Enable and start the rest-server systemd service.
sudo mkdir /opt/restic-backups
sudo chown -R restic-data:restic-data /opt/restic-backups
sudo cp -v rest-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now rest-server.service
Since I’m using a firewall, I ensure the port the rest-server listens on is allowed locally:
sudo firewall-cmd --zone`FedoraServer --permanent --add-port`8000/tcp
sudo firewall-cmd --reload
Now on the host, which in this case is my laptop, I have the Restic client installed from my distribution’s package repository.
- Initialize a Restic storage repository on the server from the host, and supply it with a password. This password will be used every time I attempt to access the storage repository.
- Backup my dotfiles
restic -r rest:http://local-server:8000/dotfiles init
restic -r rest:http://local-server:8000/dotfiles backup ~/dotfiles
One of the best features of Restic is that it makes restory backups really simple. It also provides snapshot functionality, so I can restore different versions of specific files from other snapshots.
restic -r rest:http://local-server:8000/dotfiles snapshots
enter password for repository:
repository 9a280eb7 opened successfully, password is correct
ID Time Host Tags Paths
------------------------------------------------------------------------------
11738fec 2021-04-12 09:13:17 toolbox /var/home/jeff/dotfiles
dfc99aa3 2021-04-12 10:31:39 toolbox /var/home/jeff/dotfiles
f951eedf 2021-04-12 11:25:21 toolbox /var/home/jeff/dotfiles
62371897 2021-04-12 18:43:53 toolbox /var/home/jeff/dotfiles
------------------------------------------------------------------------------
4 snapshots
Since Restic saves the backup’s absolute path, restoring it to / will ensure it is restored to its original location on the local filesystem. To restore a snapshot:
restic -r rest:http://local-server:8000/dotfiles restore dfc99aa3 --target /
To list files in a snapshot:
restic -r rest:http://local-server:8000/dotfiles ls dfc99aa3
Yay, very nice!
Resources
Setting up a Bluesky PDS with Podman on CentOS Stream 10
Self-hosted container registry with web UI
Source: <https://github.com/Joxit/docker-registry-ui>
Docker/Podman compose
services:
registry-ui:
image: joxit/docker-registry-ui:main
restart: always
ports:
- "127.0.0.1:4433:80"
environment:
- SINGLE_REGISTRY=true
- REGISTRY_TITLE=hyperreal's Container Registry
- DELETE_IMAGES=true
- SHOW_CONTENT_DIGEST=true
- NGINX_PROXY_PASS_URL=http://registry-server:5000
- SHOW_CATALOG_NB_TAGS=true
- CATALOG_MIN_BRANCHES=1
- CATALOG_MAX_BRANCHES=1
- TAGLIST_PAGE_SIZE=100
- REGISTRY_SECURED=false
- CATALOG_ELEMENTS_LIMIT=1000
container_name: registry-ui
registry-server:
image: registry:2.8.2
restart: always
environment:
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Origin: '[http://aux-remote.carp-wyvern.ts.net]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: '[HEAD,GET,OPTIONS,DELETE]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Credentials: '[true]'
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Accept,Cache-Control]'
REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: '[Docker-Content-Digest]'
REGISTRY_STORAGE_DELETE_ENABLED: 'true'
volumes:
- ./registry/data:/var/lib/registry
container_name: registry-server
Authorization and Authentication
For a public registry with authentication, the following headers are required:
environment:
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Authorization,Accept,Cache-Control]'
For a private registry without authentication, the following headers are required:
environment:
REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: '[Accept,Cache-Control]'
Caddy reverse proxy
Public registry
registry.hyperreal.coffee {
reverse_proxy localhost:4433
}
Private registry via Tailnet
aux-remote.carp-wyvern.ts.net {
reverse_proxy localhost:4433
}
Ensure the following is added to /etc/default/tailscaled:
TS_PERMIT_CERT_UID=caddy
The above will ensure Caddy receives SSL certs from the Tailscale daemon.
By using the “Human Made” badge, I affirm that I am not using data-generated images (“genAI”) or data-generated text (“genAI”) in any asset of my work. My workflow consists of only using my skill crafted from experience and practice.