Lightweight signal forwarder for daemonizing programs.
Inspired from supervisor project's pidproxy script
- Signal rewriting
- Monitoring and signal processes using pidfd_open(2) & pidfd_send_signal(2)
- Small (When compiled statically with musl libc, and packed with upx, binary is about 36 kilobytes on x86_64) - Ideal for containers.
USAGE: pidproxy [options] <path to pid file> <argv>
pidproxy supports following optional arguments:
-g
- Whether to send signal to a process group or not.-r from:to
- Rewrite a signal. Can be specified multiple times to rewrite multiple signals. Example:-r 15:1
-U uid/username
- Run target program as user. Can be either uid or username. Example:-U game
-G gid/group
- Override the group which program should run as. Can be either gid or group name. Example:-G game
-E path-to-program
- An external program to run after monitored process exits.
Since pidproxy does not do process reaping like normal PID 1 would do, then I strongly recommend to launch pidproxy using an init process.
Here are few suggestions:
- Use
--init
flag with Docker - Use dumb-init
- Use S6 overlay
- Cook your own using go-reaper for example
Following environment variables will be exposed to the external program:
PIDPROXY_PID_FILE
- PID file initially supplied to the pidproxy.PIDPROXY_EXIT_CODE
- pidproxy's own exit code.PIDPROXY_PID
- pidproxy's PID.
Run make
to get statically compiled binary. You'll get best results using musl libc (Using musl-gcc
wrapper or zig cc is sufficient)
To use musl-gcc
, do CC=musl-gcc make
To use zig cc
, do CC="zig cc -target x86_64-linux-musl"
(see available targets using zig targets | jq '.libc'
)
Makefile also provides pidproxy.upx
target, which produces packed executable using upx (packed executable is usually ~50% smaller than original).
Note that building against glibc
isn't generally necessary, unless your target users/groups come only from NSS (e.g systemd DynamicUser) and not via
standard /etc/passwd
and/or /etc/group
, or you just do not want to set up musl.
Building functional static pidproxy binary with glibc
might be outright impossible with systems running older systemd (< 246).
See issue comment @ htop-dev/htop for solutions - TL;DR update systemd to version >= 246 (easiest and most reliable).
GDB backtrace
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7c87bf1 in _nss_systemd_is_blocked () from /usr/lib/libnss_systemd.so.2
(gdb) bt
#0 0x00007ffff7c87bf1 in _nss_systemd_is_blocked () from /usr/lib/libnss_systemd.so.2
#1 0x00007ffff7c8e577 in _nss_systemd_getgrgid_r () from /usr/lib/libnss_systemd.so.2
#2 0x00000000004410a9 in getgrgid_r ()
#3 0x0000000000401a00 in resolve_gid (gid=<optimized out>, name=<optimized out>) at user.c:153
If upgrading or rebuilding systemd is not possible, then you are probably better off building non-static pidproxy.
- Monitoring processes using pidfd_open(2) is not available in Docker containers before Docker engine version 20.10, because older default seccomp profile does not allow pidfd syscalls. pidproxy will fall back to polling the PID, but that has possible PID race on busier containers, and makes pidproxy exit bit later than monitored process.
- Using
-g
flag to send a signal to the process group makes pidproxy use kill(2) instead of pidfd_send_signal(2), which has possible PID race (on busier containers).
GNU General Public License version 3