Add real socket activation to the quadlets

Each heavy service now has a loopback .socket and a systemd-socket-proxyd
service (Requires=/After= the container, --exit-idle-time) on a shifted
container port. Container units keep StopWhenUnneeded=true, lose their
[Install] section and SecurityLabelType=spc, use Notify=healthy, and the
emulator uses AddDevice=/dev/kvm under default container_t confinement.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_012AuVk4nEvBXaTjs4G6wdW4
This commit is contained in:
Claude committed 2026-10-16 19:29:57 +00:00
1 parent 7caa02a7f0
commit 6700a16adc
11 files changed
+207 -52

No files matched your search

@@ -0,0 +1,16 @@
# quadlets/heretek-android-kvm-adb-proxy.service — ADB socket proxy for heretek-android-kvm
#
# Started by heretek-android-kvm-adb-proxy.socket. Pulls up the emulator unit
# (Requires=/After=; Notify=healthy there delays "started" until noVNC
# answers) and forwards connections to the container's loopback port. It exits
# after 15 idle minutes (--exit-idle-time, systemd-socket-proxyd's idle
# timeout); once both android proxies are gone, StopWhenUnneeded=true stops
# the emulator.
[Unit]
Description=Heretek Android KVM ADB socket proxy (exits when idle)
Requires=heretek-android-kvm.service heretek-android-kvm-adb-proxy.socket
After=heretek-android-kvm.service heretek-android-kvm-adb-proxy.socket
[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=15min 127.0.0.1:15555
@@ -0,0 +1,13 @@
# quadlets/heretek-android-kvm-adb-proxy.socket — ADB activation socket for heretek-android-kvm
#
# Listens on the loopback-only port compose also uses (127.0.0.1:5555) and
# activates heretek-android-kvm-adb-proxy.service on the first connection.
[Unit]
Description=Heretek Android KVM ADB activation socket (127.0.0.1:5555)
[Socket]
ListenStream=127.0.0.1:5555
[Install]
WantedBy=sockets.target
@@ -0,0 +1,16 @@
# quadlets/heretek-android-kvm-vnc-proxy.service — noVNC socket proxy for heretek-android-kvm
#
# Started by heretek-android-kvm-vnc-proxy.socket. Pulls up the emulator unit
# (Requires=/After=; Notify=healthy there delays "started" until noVNC
# answers) and forwards connections to the container's loopback port. It exits
# after 15 idle minutes (--exit-idle-time, systemd-socket-proxyd's idle
# timeout); once both android proxies are gone, StopWhenUnneeded=true stops
# the emulator.
[Unit]
Description=Heretek Android KVM noVNC socket proxy (exits when idle)
Requires=heretek-android-kvm.service heretek-android-kvm-vnc-proxy.socket
After=heretek-android-kvm.service heretek-android-kvm-vnc-proxy.socket
[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=15min 127.0.0.1:16080
@@ -0,0 +1,13 @@
# quadlets/heretek-android-kvm-vnc-proxy.socket — noVNC activation socket for heretek-android-kvm
#
# Listens on the loopback-only port compose also uses (127.0.0.1:6080) and
# activates heretek-android-kvm-vnc-proxy.service on the first connection.
[Unit]
Description=Heretek Android KVM noVNC activation socket (127.0.0.1:6080)
[Socket]
ListenStream=127.0.0.1:6080
[Install]
WantedBy=sockets.target
+33 -20
View File
@@ -1,39 +1,52 @@
# quadlets/heretek-android-kvm.container — socket-activated Android emulator (Wave 3)
# quadlets/heretek-android-kvm.container — on-demand Android emulator (rootless Podman quadlet)
#
# Heavy KVM emulator from compose/profile-android.yml, expressed as a rootless
# Podman systemd quadlet. StopWhenUnneeded=true keeps it at 0 MB idle RAM
# until socket activation pulls it up. Image and loopback ports mirror the
# compose profile exactly: budtmo/docker-android:emulator_14.0 on
# 127.0.0.1:6080 (VNC) and 127.0.0.1:5555 (ADB).
# Socket-activated through two loopback sockets, one per published port:
# heretek-android-kvm-vnc-proxy.socket 127.0.0.1:6080 (noVNC)
# heretek-android-kvm-adb-proxy.socket 127.0.0.1:5555 (ADB)
# The first connection to either starts its systemd-socket-proxyd service,
# which Requires= this unit, so the emulator is started on demand and proxied
# to its container-only ports (127.0.0.1:16080 / 127.0.0.1:15555). When both
# proxies have exited after --exit-idle-time, StopWhenUnneeded=true stops the
# emulator: 0 MB RAM while idle. No [Install] section on purpose — it must
# never start at login.
#
# Enable + start (user systemd):
# Image: budtmo/docker-android:emulator_14.0_v3.8.0-p1, the same immutable
# release tag as compose/profile-android.yml.
#
# KVM: AddDevice=/dev/kvm grants only the KVM device node. The container keeps
# the default container_t SELinux confinement (no SecurityLabelType /
# SecurityLabelDisable). If an enforcing host denies /dev/kvm access, allow
# device use for containers rather than disabling labelling:
# sudo setsebool -P container_use_devices=true
#
# Install (see README "Quadlets"):
# cp quadlets/heretek-android-kvm.container ~/.config/containers/systemd/
# cp quadlets/heretek-android-kvm-*-proxy.socket quadlets/heretek-android-kvm-*-proxy.service ~/.config/systemd/user/
# systemctl --user daemon-reload
# systemctl --user enable --now heretek-android-kvm.service
# KVM passthrough comes from the Device= mapping below (a device node grant,
# not a capability and not a volume mount — no SELinux label rule applies).
# systemctl --user enable --now heretek-android-kvm-vnc-proxy.socket heretek-android-kvm-adb-proxy.socket
[Unit]
Description=Heretek Android KVM emulator (socket-activated, 0 MB idle RAM)
Description=Heretek Android KVM emulator (socket-activated, stops when idle)
After=network-online.target
Wants=network-online.target
StopWhenUnneeded=true
[Container]
Image=budtmo/docker-android:emulator_14.0
Image=budtmo/docker-android:emulator_14.0_v3.8.0-p1
ContainerName=heretek-android-kvm
User=1000:1000
Device=/dev/kvm
PublishPort=127.0.0.1:6080:6080
PublishPort=127.0.0.1:5555:5555
AddDevice=/dev/kvm
PublishPort=127.0.0.1:16080:6080
PublishPort=127.0.0.1:15555:5555
Volume=heretek-android-data:/data:Z
Environment=EMULATOR_DEVICE=Samsung Galaxy S10
DropCapability=ALL
NoNewPrivileges=true
SecurityLabelType=spc
HealthCmd=bash -c 'exec 3<>/dev/tcp/127.0.0.1/6080'
HealthInterval=10s
HealthStartPeriod=180s
Notify=healthy
[Service]
Restart=on-failure
TimeoutStartSec=300
[Install]
WantedBy=default.target
TimeoutStartSec=600
@@ -0,0 +1,15 @@
# quadlets/heretek-blender-mcp-proxy.service — socket proxy for heretek-blender-mcp
#
# Started by heretek-blender-mcp-proxy.socket. Pulls up the container unit
# (Requires=/After=; Notify=healthy there delays "started" until the server
# answers) and forwards connections to the container's loopback port. It exits
# after 15 idle minutes (--exit-idle-time, systemd-socket-proxyd's idle
# timeout), which lets StopWhenUnneeded=true stop the container.
[Unit]
Description=Heretek Blender MCP socket proxy (exits when idle)
Requires=heretek-blender-mcp.service heretek-blender-mcp-proxy.socket
After=heretek-blender-mcp.service heretek-blender-mcp-proxy.socket
[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=15min 127.0.0.1:18781
+14
View File
@@ -0,0 +1,14 @@
# quadlets/heretek-blender-mcp-proxy.socket — activation socket for heretek-blender-mcp
#
# Listens on the loopback-only port compose also uses (127.0.0.1:8781) and
# activates heretek-blender-mcp-proxy.service on the first connection.
# This is the only Blender unit that is enabled (WantedBy=sockets.target).
[Unit]
Description=Heretek Blender MCP activation socket (127.0.0.1:8781)
[Socket]
ListenStream=127.0.0.1:8781
[Install]
WantedBy=sockets.target
+27 -15
View File
@@ -1,34 +1,46 @@
# quadlets/heretek-blender-mcp.container — socket-activated Blender MCP server (Wave 3)
# quadlets/heretek-blender-mcp.container — on-demand Blender MCP server (rootless Podman quadlet)
#
# Headless Blender FastMCP server from compose/profile-game.yml, expressed as
# a rootless Podman systemd quadlet. StopWhenUnneeded=true keeps it at 0 MB
# idle RAM until socket activation pulls it up. Image and loopback port mirror
# the compose profile exactly: ghcr.io/sandraschi/blender-mcp:v1.2.0 on
# 127.0.0.1:8781 (MIT-licensed FastMCP 3.2 stack, blender --background).
# Socket-activated: heretek-blender-mcp-proxy.socket owns 127.0.0.1:8781. The
# first connection starts heretek-blender-mcp-proxy.service (systemd-socket-proxyd),
# which Requires= this unit, so the container is started on demand and proxied
# to its container-only port 127.0.0.1:18781. When the proxy exits after
# --exit-idle-time with no connections, nothing needs this unit any more and
# StopWhenUnneeded=true stops the container: 0 MB RAM while idle. This unit has
# no [Install] section on purpose — it must never start at login.
#
# Enable + start (user systemd):
# Image: localhost/heretek/blender-mcp:0.11.1 (MIT FastMCP server + Blender
# 4.2.3 LTS), the same pinned local build as compose/profile-game.yml. Build it
# once before enabling the socket:
# podman build -t localhost/heretek/blender-mcp:0.11.1 images/blender-mcp
#
# Install (see README "Quadlets"):
# cp quadlets/heretek-blender-mcp.container ~/.config/containers/systemd/
# cp quadlets/heretek-blender-mcp-proxy.socket quadlets/heretek-blender-mcp-proxy.service ~/.config/systemd/user/
# systemctl --user daemon-reload
# systemctl --user enable --now heretek-blender-mcp.service
# systemctl --user enable --now heretek-blender-mcp-proxy.socket
#
# SELinux: default container_t confinement (no SecurityLabelType override).
[Unit]
Description=Heretek Blender MCP background server (socket-activated, 0 MB idle RAM)
Description=Heretek Blender MCP background server (socket-activated, stops when idle)
After=network-online.target
Wants=network-online.target
StopWhenUnneeded=true
[Container]
Image=ghcr.io/sandraschi/blender-mcp:v1.2.0
Image=localhost/heretek/blender-mcp:0.11.1
Pull=never
ContainerName=heretek-blender-mcp
User=1000:1000
PublishPort=127.0.0.1:8781:8781
PublishPort=127.0.0.1:18781:8781
Volume=heretek-blender-data:/data:Z
DropCapability=ALL
NoNewPrivileges=true
SecurityLabelType=spc
HealthCmd=bash -c 'exec 3<>/dev/tcp/127.0.0.1/8781'
HealthInterval=10s
HealthStartPeriod=60s
Notify=healthy
[Service]
Restart=on-failure
TimeoutStartSec=300
[Install]
WantedBy=default.target
+15
View File
@@ -0,0 +1,15 @@
# quadlets/heretek-ghidra-mcp-proxy.service — socket proxy for heretek-ghidra-mcp
#
# Started by heretek-ghidra-mcp-proxy.socket. Pulls up the container unit
# (Requires=/After=; Notify=healthy there delays "started" until the server
# answers) and forwards connections to the container's loopback port. It exits
# after 15 idle minutes (--exit-idle-time, systemd-socket-proxyd's idle
# timeout), which lets StopWhenUnneeded=true stop the container.
[Unit]
Description=Heretek Ghidra MCP socket proxy (exits when idle)
Requires=heretek-ghidra-mcp.service heretek-ghidra-mcp-proxy.socket
After=heretek-ghidra-mcp.service heretek-ghidra-mcp-proxy.socket
[Service]
ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=15min 127.0.0.1:18089
+14
View File
@@ -0,0 +1,14 @@
# quadlets/heretek-ghidra-mcp-proxy.socket — activation socket for heretek-ghidra-mcp
#
# Listens on the loopback-only port compose also uses (127.0.0.1:8089) and
# activates heretek-ghidra-mcp-proxy.service on the first connection.
# This is the only Ghidra unit that is enabled (WantedBy=sockets.target).
[Unit]
Description=Heretek Ghidra MCP activation socket (127.0.0.1:8089)
[Socket]
ListenStream=127.0.0.1:8089
[Install]
WantedBy=sockets.target
+31 -17
View File
@@ -1,38 +1,52 @@
# quadlets/heretek-ghidra-mcp.container — socket-activated Ghidra MCP server (Wave 3)
# quadlets/heretek-ghidra-mcp.container — on-demand Ghidra MCP server (rootless Podman quadlet)
#
# Headless Ghidra MCP server from compose/profile-appsec.yml, expressed as a
# rootless Podman systemd quadlet. StopWhenUnneeded=true keeps it at 0 MB idle
# RAM until socket activation pulls it up. Image and loopback port mirror the
# compose profile exactly: bethington/ghidra-mcp-headless:v6.0.0 on
# 127.0.0.1:8089.
# Socket-activated: heretek-ghidra-mcp-proxy.socket owns 127.0.0.1:8089. The
# first connection starts heretek-ghidra-mcp-proxy.service (systemd-socket-proxyd),
# which Requires= this unit, so the container is started on demand and proxied
# to its container-only port 127.0.0.1:18089. When the proxy exits after
# --exit-idle-time with no connections, nothing needs this unit any more and
# StopWhenUnneeded=true stops the container: 0 MB RAM while idle. This unit has
# no [Install] section on purpose — it must never start at login.
#
# Image: localhost/heretek/ghidra-mcp:6.0.0, the same pinned local build as
# compose/profile-appsec.yml. Build it once before enabling the socket:
# podman build -t localhost/heretek/ghidra-mcp:6.0.0 images/ghidra-mcp
#
# The mandatory bearer token is NOT stored here. Provide it via a user-owned
# environment file before starting (fail closed — the server refuses
# unauthenticated requests without it):
# environment file (the container refuses to start without it):
# printf 'GHIDRA_MCP_AUTH_TOKEN=<secret>\n' > ~/.config/heretek/ghidra-mcp.env
# chmod 600 ~/.config/heretek/ghidra-mcp.env
# systemctl --user enable --now heretek-ghidra-mcp.service
#
# Install (see README "Quadlets"):
# cp quadlets/heretek-ghidra-mcp.container ~/.config/containers/systemd/
# cp quadlets/heretek-ghidra-mcp-proxy.socket quadlets/heretek-ghidra-mcp-proxy.service ~/.config/systemd/user/
# systemctl --user daemon-reload
# systemctl --user enable --now heretek-ghidra-mcp-proxy.socket
#
# SELinux: default container_t confinement (no SecurityLabelType override).
[Unit]
Description=Heretek Ghidra MCP headless server (socket-activated, 0 MB idle RAM)
Description=Heretek Ghidra MCP headless server (socket-activated, stops when idle)
After=network-online.target
Wants=network-online.target
StopWhenUnneeded=true
[Container]
Image=bethington/ghidra-mcp-headless:v6.0.0
Image=localhost/heretek/ghidra-mcp:6.0.0
Pull=never
ContainerName=heretek-ghidra-mcp
User=1000:1000
EnvironmentFile=%h/.config/heretek/ghidra-mcp.env
PublishPort=127.0.0.1:8089:8089
Environment=GHIDRA_MCP_ALLOWED_HOSTS=localhost,127.0.0.1
PublishPort=127.0.0.1:18089:8089
Volume=heretek-ghidra-data:/data:Z
DropCapability=ALL
NoNewPrivileges=true
SecurityLabelType=spc
HealthCmd=bash -c 'exec 3<>/dev/tcp/127.0.0.1/8089'
HealthInterval=10s
HealthStartPeriod=120s
Notify=healthy
[Service]
Restart=on-failure
TimeoutStartSec=300
[Install]
WantedBy=default.target
TimeoutStartSec=600