Skip to content

Commit 5ee394f

Browse files
authored
Transition devcontainers to systemd as init system (#162)
* Transition devcontainers to systemd as init system systemd 257 has excellent container support — auto-detecting container environments via /.dockerenv, gracefully skipping host-only mounts, falling back to unsandboxed generators when namespace sandboxing fails, and disabling unnecessary services via ConditionVirtualization=!container. Running systemd as PID 1 in privileged Docker containers just works now, so let's transition the devcontainers to better match HAOS and Supervised installations where Supervisor normally runs. Key changes: - Run systemd as PID 1 via /sbin/init with overrideCommand: false - Remove the fake systemctl and policy-rc.d shipped by the Microsoft base images, which silently blocked all service management at build time - Make sure all required services which are not enabled by default are enabled (haos-agent, journal-gatewayd) via systemctl enable in the Dockerfile so they start automatically on boot - Mask unnecessary units (networkd, getty) to avoid interference - Remove all manual daemon start/stop functions from the common scripts (start_docker, stop_docker, init_dbus, init_udev, init_os_agent, start_systemd_journald) — systemd manages the full service lifecycle - Clean up supervisor_run scripts to only handle Supervisor-specific logic (build, run, cleanup) since services are already running - Remove machine-id regeneration from supervisor_bootstrap, systemd handles this at boot - Add /var/lib/containerd volume mount alongside /var/lib/docker, since modern Docker uses the containerd snapshotter which stores state in its own top-level directory rather than under /var/lib/docker - Move systemd-journal-remote and systemd-resolved installs into the Dockerfiles directly - Set up persistent journal storage via systemd-tmpfiles at build time - Use dbus-broker as D-Bus broker (used by HAOS) * Fail if curl isn't able to fetch version information * Bump container image version in devcontainer.json template
1 parent 32e08e8 commit 5ee394f

File tree

11 files changed

+68
-180
lines changed

11 files changed

+68
-180
lines changed

apps/Dockerfile

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
1010
RUN \
1111
apt-get update \
1212
&& apt-get install -y --no-install-recommends \
13-
dbus \
13+
dbus-broker \
1414
network-manager \
1515
libpulse0 \
16+
systemd-journal-remote \
17+
systemd-resolved \
1618
xz-utils
1719

1820
COPY ./common/rootfs /
@@ -30,3 +32,27 @@ RUN \
3032
&& usermod -aG docker vscode
3133

3234
COPY ./apps/rootfs /
35+
36+
# Configure systemd as init system
37+
# Remove the fake systemctl and policy-rc.d that the base image ships
38+
# to prevent service management during build. We need the real systemctl
39+
# since this container runs systemd as PID 1.
40+
RUN rm -f /usr/local/bin/systemctl /usr/sbin/policy-rc.d
41+
42+
# Mask unnecessary systemd units to make sure they don't interfere
43+
RUN systemctl mask \
44+
systemd-networkd.service \
45+
systemd-networkd-wait-online.service \
46+
getty@.service \
47+
serial-getty@.service
48+
49+
# Create persistent journal directory so journald stores logs on disk
50+
RUN systemd-tmpfiles --create --prefix /var/log/journal
51+
52+
# Enable services which are otherwise disabled by default
53+
RUN systemctl enable \
54+
haos-agent \
55+
systemd-journal-gatewayd
56+
57+
STOPSIGNAL SIGRTMIN+3
58+
CMD ["/sbin/init"]

apps/devcontainer.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"name": "Example devcontainer for apps repositories",
3-
"image": "ghcr.io/home-assistant/devcontainer:2-apps",
3+
"image": "ghcr.io/home-assistant/devcontainer:4-apps",
4+
"overrideCommand": false,
5+
"remoteUser": "vscode",
46
"appPort": ["7123:8123", "7357:4357"],
57
"postStartCommand": "bash devcontainer_bootstrap",
68
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
@@ -28,6 +30,8 @@
2830
},
2931
"mounts": [
3032
"type=volume,target=/var/lib/docker",
31-
"type=volume,target=/mnt/supervisor"
33+
"type=volume,target=/var/lib/containerd",
34+
"type=volume,target=/mnt/supervisor",
35+
"type=tmpfs,target=/tmp"
3236
]
3337
}

apps/rootfs/usr/bin/supervisor_run

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ source /etc/supervisor_scripts/common
66

77
echo "Run Supervisor"
88

9-
start_systemd_journald
10-
start_docker
11-
trap "stop_docker" ERR
12-
139
function run_supervisor() {
1410
validate_devcontainer "apps"
1511

@@ -35,21 +31,12 @@ function run_supervisor() {
3531
if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == "running" ]; then
3632
echo "Restarting Supervisor"
3733
docker rm -f hassio_supervisor
38-
init_dbus
39-
init_udev
40-
init_os_agent
4134
cleanup_lastboot
4235
run_supervisor
43-
stop_docker
44-
4536
else
4637
echo "Starting Supervisor"
4738
docker system prune -f
4839
cleanup_lastboot
4940
cleanup_docker
50-
init_dbus
51-
init_udev
52-
init_os_agent
5341
run_supervisor
54-
stop_docker
5542
fi

apps/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
4

common/install/docker

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ apt-get update
66
apt-get install -y --no-install-recommends \
77
ca-certificates \
88
curl \
9-
gnupg \
10-
systemd-journal-remote
9+
gnupg
1110

1211
install -m 0755 -d /etc/apt/keyrings
1312
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg

common/install/versions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"cosign": "2.4.0",
3-
"os-agent": "1.6.0",
2+
"cosign": "2.5.3",
3+
"os-agent": "1.8.1",
44
"nvm": "0.40.1"
55
}

common/rootfs_supervisor/etc/supervisor_scripts/common

Lines changed: 1 addition & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -2,109 +2,15 @@
22

33
set -e
44

5-
VERSION_INFO=$(curl -s https://version.home-assistant.io/dev.json)
5+
VERSION_INFO=$(curl -sf https://version.home-assistant.io/dev.json)
66
HA_ARCH=$(get_arch ha)
77
QEMU_ARCH=$(get_arch qemu)
8-
DOCKER_TIMEOUT=30
98
SUPERVISOR_SHARE="/mnt/supervisor"
109

1110
export SUPERVISOR_VERSION="$(echo ${VERSION_INFO} | jq -e -r '.supervisor')"
1211
export SUPERVISOR_IMAGE="$(sed "s/{arch}/${HA_ARCH}/g" <<< "$(echo ${VERSION_INFO} | jq -e -r '.images.supervisor')")"
1312

1413

15-
function start_systemd_journald() {
16-
if ! [ -e /var/log/journal ]; then
17-
echo "Creating systemd-journald tmpfiles."
18-
sudo systemd-tmpfiles --create --prefix /var/log/journal
19-
fi
20-
21-
if ! pgrep -f systemd-journald; then
22-
echo "Starting systemd-journald."
23-
sudo /usr/lib/systemd/systemd-journald &
24-
fi
25-
26-
if ! pgrep -f systemd-journal-gatewayd; then
27-
echo "Starting systemd-journal-gatewayd."
28-
sudo /usr/lib/systemd/systemd-journal-gatewayd --system 2> /dev/null &
29-
fi
30-
}
31-
32-
33-
function start_docker() {
34-
local start_time
35-
local current_time
36-
local elapsed_time
37-
38-
if grep -q 'microsoft-standard\|standard-WSL' /proc/version; then
39-
# The docker daemon does not start when running WSL2 without adjusting iptables
40-
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy || echo "Fails adjust iptables"
41-
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || echo "Fails adjust ip6tables"
42-
fi
43-
44-
echo "Starting docker."
45-
if stat -f -c %T /var/lib/docker | grep -q overlayfs; then
46-
echo "Using \"vfs\" storage driver. Bind mount /var/lib/docker to use the faster overlay2 driver."
47-
storage_driver="vfs"
48-
else
49-
storage_driver="overlay2"
50-
fi
51-
sudo dockerd --storage-driver="${storage_driver}" > /dev/null 2>&1 &
52-
53-
echo "Waiting for Docker to initialize..."
54-
socket_path="/var/run/docker.sock"
55-
56-
start_time=$(date +%s)
57-
while [[ ! -S "$socket_path" || ! -r "$socket_path" ]]; do
58-
current_time=$(date +%s)
59-
elapsed_time=$((current_time - start_time))
60-
61-
if [[ $elapsed_time -ge $DOCKER_TIMEOUT ]]; then
62-
echo "Timeout: Docker did not start within $DOCKER_TIMEOUT seconds."
63-
exit 1
64-
fi
65-
66-
sleep 1
67-
done
68-
69-
# It seems that dockerd messes with the terminal somehow, carriage returns (\r)
70-
# seem not to function properly. Resetting the terminal fixes this.
71-
stty sane
72-
echo "Docker was initialized"
73-
}
74-
75-
function stop_docker() {
76-
local start_time
77-
local current_time
78-
local elapsed_time
79-
80-
# Check if docker pid is there
81-
if [ ! -f /var/run/docker.pid ]; then
82-
echo "No pid found for docker"
83-
return 0
84-
fi
85-
86-
echo "Stopping Docker daemon..."
87-
docker_pid=$(cat /var/run/docker.pid)
88-
echo "Sending SIGTERM to docker daemon $docker_pid"
89-
if sudo kill -0 "$docker_pid" 2> /dev/null; then
90-
start_time="$(date +%s)"
91-
92-
# Now wait for it to exit
93-
sudo kill "$docker_pid"
94-
while sudo kill -0 "$docker_pid" 2> /dev/null; do
95-
current_time=$(date +%s)
96-
elapsed_time=$((current_time - start_time))
97-
if [[ $elapsed_time -ge $DOCKER_TIMEOUT ]]; then
98-
echo "Timeout while waiting for Docker daemon to exit"
99-
exit 1
100-
fi
101-
sleep 1
102-
done
103-
else
104-
echo "Unable to find Docker daemon process"
105-
fi
106-
}
107-
10814
function cleanup_lastboot() {
10915
if [[ -f /mnt/supervisor/config.json ]]; then
11016
echo "Cleaning up last boot"
@@ -122,52 +28,6 @@ function cleanup_docker() {
12228
fi
12329
}
12430

125-
function init_dbus() {
126-
if pgrep dbus-daemon; then
127-
echo "Dbus is running"
128-
return 0
129-
fi
130-
131-
echo "Startup dbus"
132-
sudo mkdir -p /var/lib/dbus
133-
sudo cp -f /etc/machine-id /var/lib/dbus/machine-id
134-
135-
# cleanups
136-
sudo mkdir -p /run/dbus
137-
sudo rm -f /run/dbus/pid
138-
139-
# run
140-
sudo dbus-daemon --system --print-address
141-
}
142-
143-
144-
function init_udev() {
145-
if pgrep systemd-udevd; then
146-
echo "udev is running"
147-
return 0
148-
fi
149-
150-
echo "Startup udev"
151-
152-
# cleanups
153-
sudo mkdir -p /run/udev
154-
155-
# run
156-
sudo /lib/systemd/systemd-udevd --daemon
157-
sleep 3
158-
sudo udevadm trigger && sudo udevadm settle
159-
}
160-
161-
162-
function init_os_agent() {
163-
if pgrep os-agent; then
164-
echo "os-agent is running"
165-
return 0
166-
fi
167-
168-
sudo os-agent &
169-
}
170-
17131

17232
validate_devcontainer() {
17333
local devcontainer_type="$1"

common/rootfs_supervisor/usr/bin/supervisor_bootstrap

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
set -e
44

5-
sudo rm /etc/machine-id
6-
sudo dbus-uuidgen --ensure=/etc/machine-id
7-
85
if grep -q 'microsoft-standard\|standard-WSL' /proc/version; then
96
# The docker daemon does not start when running WSL2 without adjusting iptables
107
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy || echo "Fails adjust iptables"

supervisor/Dockerfile

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ RUN \
1919
RUN \
2020
apt-get update \
2121
&& apt-get install -y --no-install-recommends \
22-
dbus-daemon \
22+
dbus-broker \
2323
network-manager \
2424
libpulse0 \
25+
systemd-journal-remote \
26+
systemd-resolved \
2527
xz-utils
2628

2729
COPY ./common/rootfs /
@@ -49,3 +51,29 @@ RUN uv venv $VIRTUAL_ENV
4951
# Setting PATH here isn't enough, VSCode rewites it after initial scripts finish
5052
# Must also be set in remoteEnv in devcontainer.json
5153
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
54+
55+
# Configure systemd as init system
56+
USER root
57+
58+
# Remove the fake systemctl and policy-rc.d that the base image ships
59+
# to prevent service management during build. We need the real systemctl
60+
# since this container runs systemd as PID 1.
61+
RUN rm -f /usr/local/bin/systemctl /usr/sbin/policy-rc.d
62+
63+
# Mask unnecessary systemd units to make sure they don't interfere
64+
RUN systemctl mask \
65+
systemd-networkd.service \
66+
systemd-networkd-wait-online.service \
67+
getty@.service \
68+
serial-getty@.service
69+
70+
# Create persistent journal directory so journald stores logs on disk
71+
RUN systemd-tmpfiles --create --prefix /var/log/journal
72+
73+
# Enable services which are otherwise disabled by default
74+
RUN systemctl enable \
75+
haos-agent \
76+
systemd-journal-gatewayd
77+
78+
STOPSIGNAL SIGRTMIN+3
79+
CMD ["/sbin/init"]

supervisor/rootfs/usr/bin/supervisor_run

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ function get_build_from() {
2323

2424
echo "Run Supervisor"
2525

26-
start_systemd_journald
27-
start_docker
28-
trap "stop_docker" ERR
29-
3026
function build_supervisor() {
3127
local base_image
3228
base_image="$(get_build_from)"
@@ -71,22 +67,13 @@ function run_supervisor() {
7167
if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == "running" ]; then
7268
echo "Restarting Supervisor"
7369
docker rm -f hassio_supervisor
74-
init_dbus
75-
init_udev
76-
init_os_agent
7770
cleanup_lastboot
7871
run_supervisor
79-
stop_docker
80-
8172
else
8273
echo "Starting Supervisor"
8374
docker system prune -f
8475
build_supervisor
8576
cleanup_lastboot
8677
cleanup_docker
87-
init_dbus
88-
init_udev
89-
init_os_agent
9078
run_supervisor
91-
stop_docker
9279
fi

0 commit comments

Comments
 (0)