chore(turris-os): add forgejo container example

This commit is contained in:
Michele Cereda
2026-02-23 12:46:24 +01:00
parent dd27d1ceca
commit f93c3043ec

View File

@@ -18,7 +18,7 @@ Linux distribution based on top of OpenWrt. Check the [website] for more informa
1. [The SFP+ caged module](#the-sfp-caged-module)
1. [Use the SFP module as a LAN port](#use-the-sfp-module-as-a-lan-port)
1. [Further readings](#further-readings)
1. [Sources](#sources)
1. [Sources](#sources)
## TL;DR
@@ -121,8 +121,8 @@ day, but not to be dazzled by the diodes in the night.
Create the cron file in the `/etc/cron.d` directory:
```txt
# File /etc/cron.d/rainbow_night.
```sh
$ cat '/etc/cron.d/rainbow_night'
# Set the light intensity to the second lowest degree every day at 11 PM and set
# it back to maximum every day at 7 AM.
MAILTO="" # avoid automatic logging of the output
@@ -134,6 +134,7 @@ MAILTO="" # avoid automatic logging of the output
Turris OS can answer DNS queries for local devices.
> [!note]
> Requires the _Network Settings_ > _DNS_ > _Enable DHCP clients in DNS_ option to be enabled.
## Static DHCP leases and hostnames
@@ -148,7 +149,8 @@ the name the host presents itself with).<br/>
Not setting a hostname in an entry will make Turris OS resolve the IP address with the name the host presents itself
with.
CLI procedure:
<details>
<summary>CLI procedure</summary>
```sh
uci add dhcp host
@@ -160,36 +162,48 @@ reload_config
luci-reload
```
</details>
## Containers
Some packages are not available in `opkg`'s repository, but containers can replace them.<br/>
This is particularly useful to run services off the system which are not officially supported (like [Pi-hole]).
At the time of writing [LXC] is the only container runtime supported in Turris OS, and this guide will assume one is
At the time of writing, [LXC] is the only container runtime supported in Turris OS, and this guide will assume one is
using it.<br/>
This requires the `lxc` package to be installed.
> It is highly suggested to use an [expansion disk](turris%20omnia.md#hardware-upgrades) to store any container, but
> specially any one I/O heavy.
> [!tip]
> Use an [expansion disk](turris%20omnia.md#hardware-upgrades) to store containers' data.<br/>
> Especially suggested for any I/O heavy application, to avoid wearing down the device's internal storage.
The procedure to have a working container is as follows:
The _standard_ procedure to have a new working container is as follows:
1. [Create a new container](#create-new-containers).
1. Optionally, [assign it a static IP address](#assign-containers-a-static-ip-address).<br/>
This is particularly suggested in case of services.
1. \[optionally] [assign it a static IP address](#assign-containers-a-static-ip-address).<br/>
Particularly suggested in case of services.
1. [Start the container](#start-containers).
1. [Execute a shell](#execute-a-shell-into-containers) to enter it and set it all up.<br/>
See the configuration examples below.
1. [Execute a shell](#execute-a-shell-into-containers) in the container and set it all up.<br/>
See the examples below.
1. Check all is working as expected.
1. If you changed the container's hostname from inside if, restart it for good measure.
1. Set the container to [start at boot](#start-containers-at-boot) if required.
1. \[optionally] Set the container to [start at boot](#start-containers-at-boot).
Details for all actions are explained in the next sections.<br/>
Unless otherwise specified:
- All shell commands need to be executed from Turris OS.
- All WebUI actions need to be taken from LuCI.<br/>
At the time of writing reForis does not have a way to manage containers.
As of 2026-02-23, reForis does not allow to manage containers.
Gather information about running containers (in shell):
```sh
lxc-info --name 'gitea'
# Only the first IP address.
lxc-info --name 'forgejo' --ips --no-humanize | head -n '1'
```
### Create new containers
@@ -281,289 +295,346 @@ config container
### Examples
<details>
<summary>baikal</summary>
<summary>Baikal</summary>
> This procedure assumes one is using an LXC container based on the Debian Bullseye image.
>
> ```sh
> lxc-create -n 'baikal' -t 'download' -- -d 'Debian' -r 'Bookworm' -a 'armv7l'
> ```
> [!tip]
> Refer [baikal] and <https://sabre.io/baikal/install/>.
Refer [baikal] and <https://sabre.io/baikal/install/>.
1. Create and start the container:
```sh
# Set the correct hostname.
hostnamectl set-hostname 'baikal'
sed -i 's/LXC_NAME/baikal/' '/etc/hosts'
```sh
lxc-create -n 'baikal' -t 'download' -- -d 'Debian' -r 'Bookworm' -a 'armv7l'
```
# Install baikal.
# Also install `unattended-upgrades` to ease updates management.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes --no-install-recommends \
'apache2' 'ca-certificates' 'curl' 'php' 'php-sqlite3' 'php-sabre-dav' 'unattended-upgrades' 'unzip'
a2dismod 'mpm_event'
a2enmod 'rewrite' 'php*' 'ssl'
systemctl restart 'apache2'
openssl req -nodes \
-newkey 'rsa:4096' -keyout '/etc/ssl/private/baikal.key' -out '/etc/ssl/private/baikal.crt' -x509 -days '365' \
-subj '/C=NL/ST=North Holland/L=Amsterdam/O=Example Org/OU=Infra/CN=baikal.lan'
curl -fsL -o '/var/www/baikal.zip' 'https://github.com/sabre-io/Baikal/releases/download/0.10.1/baikal-0.10.1.zip'
unzip -ud '/var/www/' '/var/www/baikal.zip' && rm '/var/www/baikal.zip'
chown -R 'www-data:www-data' '/var/www/baikal/Specific' '/var/www/baikal/config'
cat <<EOF > '/etc/apache2/sites-enabled/010-baikal.conf'
<VirtualHost *:443>
1. Install and configure Baikal in the container:
DocumentRoot /var/www/baikal/html
ServerName baikal.lan
```sh
# Set the correct hostname.
hostnamectl set-hostname 'baikal'
sed -i 's/LXC_NAME/baikal/' '/etc/hosts'
RewriteEngine on
# Generally already set by global Apache configuration
# RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule /.well-known/carddav /dav.php [R=308,L]
RewriteRule /.well-known/caldav /dav.php [R=308,L]
# Install baikal.
# Also install `unattended-upgrades` to ease updates management.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes --no-install-recommends \
'apache2' 'ca-certificates' 'curl' 'php' 'php-sqlite3' 'php-sabre-dav' 'unattended-upgrades' 'unzip'
a2dismod 'mpm_event'
a2enmod 'rewrite' 'php*' 'ssl'
systemctl restart 'apache2'
openssl req -nodes \
-newkey 'rsa:4096' -keyout '/etc/ssl/private/baikal.key' -out '/etc/ssl/private/baikal.crt' -x509 -days '365' \
-subj '/C=NL/ST=North Holland/L=Amsterdam/O=Example Org/OU=Infra/CN=baikal.lan'
curl -fsL -o '/var/www/baikal.zip' 'https://github.com/sabre-io/Baikal/releases/download/0.10.1/baikal-0.10.1.zip'
unzip -ud '/var/www/' '/var/www/baikal.zip' && rm '/var/www/baikal.zip'
chown -R 'www-data:www-data' '/var/www/baikal/Specific' '/var/www/baikal/config'
cat <<EOF > '/etc/apache2/sites-enabled/010-baikal.conf'
<VirtualHost *:443>
<Directory "/var/www/baikal/html">
Options None
# If you install cloning git repository, you may need the following
# Options +FollowSymlinks
AllowOverride None
# Configuration for apache-2.4:
Require all granted
# Configuration for apache-2.2:
# Order allow,deny
# Allow from all
</Directory>
DocumentRoot /var/www/baikal/html
ServerName baikal.lan
<IfModule mod_expires.c>
ExpiresActive Off
</IfModule>
RewriteEngine on
# Generally already set by global Apache configuration
# RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule /.well-known/carddav /dav.php [R=308,L]
RewriteRule /.well-known/caldav /dav.php [R=308,L]
SSLEngine on
SSLCertificateFile /etc/ssl/private/baikal.crt
SSLCertificateKeyFile /etc/ssl/private/baikal.key
<Directory "/var/www/baikal/html">
Options None
# If you install cloning git repository, you may need the following
# Options +FollowSymlinks
AllowOverride None
# Configuration for apache-2.4:
Require all granted
# Configuration for apache-2.2:
# Order allow,deny
# Allow from all
</Directory>
</VirtualHost>
EOF
```
<IfModule mod_expires.c>
ExpiresActive Off
</IfModule>
Testing (after installing and creating a user):
SSLEngine on
SSLCertificateFile /etc/ssl/private/baikal.crt
SSLCertificateKeyFile /etc/ssl/private/baikal.key
```sh
curl -svvvko - --digest --user 'mark@baikal.lan:123p' https://baikal.lan/dav.php/calendars/mark@baikal.lan/default/
```
</VirtualHost>
EOF
```
1. Connect to <https://baikal> to start the first-time installation wizard.
1. Test the service:
```sh
curl -svvvko - --digest --user 'mark@baikal.lan:somePassword123' \
--url 'https://baikal.lan/dav.php/calendars/mark@baikal.lan/default/'
```
1. \[optionally] Configure Turris OS to [start the container at boot][start containers at boot].
</details>
<details>
<summary>basic, stripped git server</summary>
<summary>Basic, stripped git server</summary>
> This procedure assumes one is using an LXC container based on the Debian Bullseye image.
>
> ```sh
> lxc-create -n 'git' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
> ```
1. Create and start the container.
```sh
# Set the correct hostname.
hostnamectl set-hostname 'git'
sed -i 's/LXC_NAME/git/' '/etc/hosts'
```sh
lxc-create -n 'git' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
```
# Install Git and the SSH server.
# Also install `unattended-upgrades` to ease updates management.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'git' 'openssh-server' 'unattended-upgrades'
1. Install and configure the git server in the container:
# (Optionally) configure the SSH server.
vim '/etc/ssh/sshd_config'
systemctl restart 'ssh.service'
```sh
# Set the correct hostname.
hostnamectl set-hostname 'git'
sed -i 's/LXC_NAME/git/' '/etc/hosts'
# Create the git user.
adduser 'git'
# Install Git and the SSH server.
# Also install `unattended-upgrades` to ease updates management.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'git' 'openssh-server' 'unattended-upgrades'
# Set up passwordless authentication.
mkdir '/home/git/.ssh' && sudo chmod '700' '/home/git/.ssh'
touch '/home/git/.ssh/authorized_keys' && sudo chmod '600' '/home/git/.ssh/authorized_keys'
echo 'ssh-…' >> '/home/git/.ssh/authorized_keys'
# (Optionally) configure the SSH server.
vim '/etc/ssh/sshd_config'
systemctl restart 'ssh.service'
# (Optionally) create the repositories' root directory.
mkdir '/home/git/repositories'
# Create the git user.
adduser 'git'
# Make sure the 'git' user has the correct permissions on the folders.
chown -R 'git' '/home/git'
# Set up passwordless authentication.
mkdir '/home/git/.ssh' && sudo chmod '700' '/home/git/.ssh'
touch '/home/git/.ssh/authorized_keys' && sudo chmod '600' '/home/git/.ssh/authorized_keys'
echo 'ssh-…' >> '/home/git/.ssh/authorized_keys'
# (Optionally) lock down the git user.
# This will *prevent* clients to set their SSH key using `ssh-copy-id`.
chsh 'git' -s "$(which 'git-shell')"
# (Optionally) create the repositories' root directory.
mkdir '/home/git/repositories'
# All done!
exit
```
# Make sure the 'git' user has the correct permissions on the folders.
chown -R 'git' '/home/git'
# (Optionally) lock down the git user.
# This will *prevent* clients to set their SSH key using `ssh-copy-id`.
chsh 'git' -s "$(which 'git-shell')"
# All done!
exit
```
1. \[optionally] Configure Turris OS to [start the container at boot][start containers at boot].
</details>
<details>
<summary>cfengine hub</summary>
<summary>CFEngine hub</summary>
> CFEngine does not seem to support 32bits ARM processors anymore (but it does support arm64).<br/>
> Still, I am using a 32bit processor so this is not doable for me.
> [!warning]
> CFEngine does **not** seem to support 32bits ARM processors anymore (but it does support arm64).<br/>
> Still, I am using a 32bit processor so this is **not** doable for me.
<details style="padding-left: 1em;">
<summary>Old installation test</summary>
> This procedure assumes one is using an LXC container based on the Debian Bullseye image.
>
> ```sh
> lxc-create -n 'baikal' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
> ```
1. Create and start the container.
```sh
# Set the correct hostname.
hostnamectl set-hostname 'cfengine'
```sh
lxc-create -n 'cfengine' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
```
# Install CFEngine and the SSH server.
# Also install `unattended-upgrades` to ease updates management.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'cfengine3' 'openssh-server' 'unattended-upgrades'
1. Install and configure CFEngine in the container:
# Set up passwordless authentication.
mkdir "${HOME}/.ssh" && chmod '700' "${HOME}/.ssh"
echo 'ssh-…' >> "${HOME}/.ssh/authorized_keys" && chmod '600' "${HOME}/.ssh/authorized_keys"
```
```sh
# Set the correct hostname.
hostnamectl set-hostname 'cfengine'
# Install CFEngine and the SSH server.
# Also install `unattended-upgrades` to ease updates management.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'cfengine3' 'openssh-server' 'unattended-upgrades'
# Set up passwordless authentication.
mkdir "${HOME}/.ssh" && chmod '700' "${HOME}/.ssh"
echo 'ssh-…' >> "${HOME}/.ssh/authorized_keys" && chmod '600' "${HOME}/.ssh/authorized_keys"
```
</details>
</details>
<details>
<summary>gitea</summary>
<summary>Forgejo</summary>
> This procedure assumes one is using an LXC container based on LinuxContainers' Alpine 3.20 image:
>
> ```sh
> lxc-create --name 'gitea' --template 'download' -- \
> --server 'images.linuxcontainers.org' --dist 'alpine' --release '3.20' --arch 'armhf'
> ```
1. Create and start the container.
```sh
# Set the correct hostname.
# Should be already set correctly.
echo 'gitea' > '/etc/hostname'
hostname -F '/etc/hostname'
```sh
lxc-create --name 'forgejo' --template 'download' -- \
--server 'images.linuxcontainers.org' --dist 'alpine' --release '3.23' --arch 'armhf'
lxc-start --name 'forgejo'
```
# Install Gitea.
apk add 'gitea' 'gitea-openrc'
1. Install and configure Forgejo in the container:
# Start Gitea.
rc-update add 'gitea'
rc-service 'gitea' start
```sh
# Set the correct hostname.
# Should be already set correctly.
echo 'forgejo' > '/etc/hostname'
hostname -F '/etc/hostname'
# Connect to 'gitea:3000' to start the first-time installation wizard.
```
# Install Forgejo.
apk add 'forgejo' 'forgejo-openrc'
# Start Forgejo.
rc-update add 'forgejo'
rc-service 'forgejo' start
```
1. Connect to <http://forgejo:3000> to start the first-time installation wizard.
1. \[optionally] Configure Turris OS to [start the container at boot][start containers at boot].
</details>
<details>
<summary>monitoring</summary>
<summary>Gitea</summary>
> This procedure assumes you are using an LXC container based on the Debian Bullseye image.
>
> ```sh
> lxc-create -n 'baikal' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
> ```
1. Create and start the container.
```sh
# Set the correct hostname.
hostnamectl set-hostname 'monitoring'
```sh
lxc-create --name 'gitea' --template 'download' -- \
--server 'images.linuxcontainers.org' --dist 'alpine' --release '3.20' --arch 'armhf'
```
# Install the requirements
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'unattended-upgrades' 'wget'
1. Install and configure Gitea in the container:
# Stop installing recommended and suggested packages.
cat > /etc/apt/apt.conf.d/99norecommend << EOF
APT::Install-Recommends "0";
APT::Install-Suggests "0";
EOF
```sh
# Set the correct hostname.
# Should be already set correctly.
echo 'gitea' > '/etc/hostname'
hostname -F '/etc/hostname'
# Add Grafana's repository with its key.
wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | tee -a /etc/apt/sources.list.d/grafana.list
# Install Gitea.
apk add 'gitea' 'gitea-openrc'
# Install Prometheus and Grafana.
apt update
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'grafana-enterprise' 'prometheus'
# Start Gitea.
rc-update add 'gitea'
rc-service 'gitea' start
```
# Configure Prometheus and Grafana.
# See the '/docker/monitoring' example.
# Enable the services.
systemctl enable 'grafana-server.service'
systemctl enable 'prometheus.service'
# All done!
exit
```
1. Connect to <http://gitea:3000> to start the first-time installation wizard.
1. \[optionally] Configure Turris OS to [start the container at boot][start containers at boot].
</details>
<details>
<summary>pi-hole</summary>
<summary>Monitoring</summary>
> This procedure assumes you are using an LXC container based on the Debian Bullseye image.
>
> ```sh
> lxc-create -n 'baikal' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
> ```
> [!warning]
> The stack used in this container could _**easily**_ overload the device.<br/>
> Keep CPU and memory usage under control.
See [Installing pi-hole on Turris Omnia], [Install Pi-hole] and [Pi-Hole on Turris Omnia] for details.
1. Create and start the container.
Install and configure Pi-hole in the container:
```sh
lxc-create -n 'monitoring' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
```
```sh
# Set the correct hostname.
hostnamectl set-hostname 'pi-hole'
1. Install and configure the services in the container:
# Install pi-hole.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'ca-certificates' 'curl' 'unattended-upgrades'
curl -sSL 'https://install.pi-hole.net' | bash
```sh
# Set the correct hostname.
hostnamectl set-hostname 'monitoring'
# Follow the guided procedure.
# Install the requirements
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'unattended-upgrades' 'wget'
# Change the Web interface password, if needed.
/etc/.pihole/pihole -a -p
# Stop installing recommended and suggested packages.
cat > /etc/apt/apt.conf.d/99norecommend << EOF
APT::Install-Recommends "0";
APT::Install-Suggests "0";
EOF
# Update pi-hole as a whole, if needed.
/etc/.pihole/pihole updatePihole
/etc/.pihole/pihole -up
# Add Grafana's repository with its key.
wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | tee -a /etc/apt/sources.list.d/grafana.list
# Set the router as the primary DNS server.
sed -E -i'.bak' 's|^#?\s*DNS\s*=\s*.*$|DNS=192.168.1.1|' '/etc/systemd/resolved.conf'
# Install Prometheus and Grafana.
apt update
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'grafana-enterprise' 'prometheus'
# Set Cloudflare as the fallback DNS server.
# Optional.
sed -E -i'.bak' 's|^#?\s*FallbackDNS\s*=\s*.*$|FallbackDNS=1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001 # Cloudflare|' '/etc/systemd/resolved.conf'
# Configure Prometheus and Grafana.
# See the '/docker/monitoring' example.
# Set the interface to ignore DNS lists given by the DHCP server.
cp '/etc/systemd/network/eth0.network' '/etc/systemd/network/eth0.network.bak'
cat >> '/etc/systemd/network/eth0.network' <<EOF
[DHCP]
UseDNS=false
EOF
```
# Enable the services.
systemctl enable 'grafana-server.service'
systemctl enable 'prometheus.service'
Finish setting up the container as explained above.<br/>
Then, in Turris OS:
# All done!
exit
```
```sh
# Distribute pi-hole as the primary DNS.
# Keep the router as secondary.
uci set dhcp.lan.dhcp_option='6,192.168.111.2,192.168.111.1'
</details>
# The DNS server address in the IPv6 RA should be the container's ULA address
# since the global routable IPv6 address tend to change daily.
uci add_list dhcp.lan.dns="$(lxc-info --name pi-hole | grep -E 'IP.* f[cd]' | sed 's/IP: *//')"
<details>
<summary>Pi-hole</summary>
# Apply the new configuration.
uci commit 'dhcp' && reload_config && luci-reload
/etc/init.d/odhcpd restart
/etc/init.d/dnsmasq restart
```
> [!tip]
> See [Installing pi-hole on Turris Omnia], [Install Pi-hole] and [Pi-Hole on Turris Omnia] for details.
1. Create and start the container:
```sh
lxc-create -n 'pi-hole' -t 'download' -- -d 'Debian' -r 'Bullseye' -a 'armv7l'
```
1. Install and configure Pi-hole in the container:
```sh
# Set the correct hostname.
hostnamectl set-hostname 'pi-hole'
# Install pi-hole.
DEBIAN_FRONTEND='noninteractive' apt-get install --assume-yes 'ca-certificates' 'curl' 'unattended-upgrades'
curl -sSL 'https://install.pi-hole.net' | bash
# Follow the guided procedure.
# Change the Web interface password, if needed.
/etc/.pihole/pihole -a -p
# Update pi-hole as a whole, if needed.
/etc/.pihole/pihole updatePihole
/etc/.pihole/pihole -up
# Set the router as the primary DNS server.
sed -E -i'.bak' 's|^#?\s*DNS\s*=\s*.*$|DNS=192.168.1.1|' '/etc/systemd/resolved.conf'
# Set Cloudflare as the fallback DNS server.
# Optional.
sed -E -i'.bak' 's|^#?\s*FallbackDNS\s*=\s*.*$|FallbackDNS=1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001 # Cloudflare|' '/etc/systemd/resolved.conf'
# Set the interface to ignore DNS lists given by the DHCP server.
cp '/etc/systemd/network/eth0.network' '/etc/systemd/network/eth0.network.bak'
cat >> '/etc/systemd/network/eth0.network' <<EOF
[DHCP]
UseDNS=false
EOF
```
1. Connect to <http://pi-hole> to start the first-time installation wizard.
1. Configure Turris OS to [start the container at boot][start containers at boot].
1. Configure Turris OS to use Pi-hole as DNS server:
```sh
# Distribute pi-hole as the primary DNS.
# Keep the router as secondary.
uci set dhcp.lan.dhcp_option='6,192.168.111.2,192.168.111.1'
# The DNS server address in the IPv6 RA should be the container's ULA address
# since the global routable IPv6 address tend to change daily.
uci add_list dhcp.lan.dns="$(lxc-info --name pi-hole | grep -E 'IP.* f[cd]' | sed 's/IP: *//')"
# Apply the new configuration.
uci commit 'dhcp' && reload_config && luci-reload
/etc/init.d/odhcpd restart
/etc/init.d/dnsmasq restart
```
</details>
@@ -577,6 +648,7 @@ See:
List of [supported SFP modules].
> [!important]
> The physical WAN port and the SFP module cage are wired to a single controller; when a SFP module is inserted, the
> physical WAN **port** **will be disabled**, and the virtual WAN interface will automatically be switched to the SFP
> module.
@@ -657,9 +729,7 @@ luci-reload
- [UCI]
- [LXC]
## Sources
All the references in the [further readings] section, plus the following:
### Sources
- [Install Pi-hole]
- [Pi-Hole on Turris Omnia]
@@ -672,7 +742,7 @@ All the references in the [further readings] section, plus the following:
-->
<!-- In-article sections -->
[further readings]: #further-readings
[Start containers at boot]: #start-containers-at-boot
<!-- Knowledge base -->
[baikal]: baikal.md