What you're describing is a very common Unix architecture:
user
|
v
myctl <-- normal user command
|
v
IPC / socket / pipe
|
v
myd <-- privileged daemon (root)
Examples:
systemctl ---> systemd
nmcli ---> NetworkManager
docker ---> dockerd
virsh ---> libvirtd
bluetoothctl ---> bluetoothd
The user-facing command is usually not a wrapper executable anymore. It's more commonly called a:
- client/server architecture
- daemon + control utility
- frontend/backend architecture
- command/daemon architecture
For example, Docker:
docker (client)
|
| Unix socket
v
dockerd (root daemon)
When you run:
docker psthe docker binary doesn't do the privileged work itself.
Instead it:
- Connects to a Unix socket.
- Sends a request.
dockerdperforms the privileged operation.- Returns the result.
For your project, it could look like:
ffpaneld <-- root daemon
ffpanelctl <-- user client
or
ffpaneld <-- service
ffpanel <-- client
The daemon:
- monitors files
- manages mounts
- touches protected directories
- runs as root
The client:
- shows status
- starts jobs
- requests actions
- displays logs
Communication methods:
Unix domain socket (most common)
Named pipe (FIFO)
D-Bus
Shared memory
TCP localhost socket
For a modern Linux/macOS CLI application, I'd recommend:
ffpaneld <-- root daemon
ffpanel <-- user client
using a Unix domain socket:
/tmp/ffpanel.sock
or better:
/run/ffpanel.sock
on Linux.
The architectural names you'll hear are:
- Daemon + Client
- Client–Server
- Privileged Service + Control Utility
- Command/Daemon Pattern
If the privileged binary stays running in the background and the user binary merely sends commands to it, "daemon + client" is the term most Unix developers would immediately recognize.
For a personal project, I'd rank them like this:
| Method | Easy | Safe | Scalable | Recommendation |
|---|---|---|---|---|
| FIFO | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | Good for learning |
| Unix Domain Socket | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Best choice |
| TCP localhost | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | Usually unnecessary |
| Shared Memory | ⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | Complex |
| D-Bus | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Heavyweight |
For a real project:
ffpanel (client)
|
v
/run/ffpanel.sock
|
v
ffpaneld (daemon)
Use a Unix Domain Socket.
Why?
-
Local machine only.
-
Fast.
-
Simple C APIs (
socket(),bind(),connect()). -
Easy request/response.
-
File permissions work naturally:
srw-rw---- root ffpanel /run/ffpanel.sock
-
Supports multiple clients.
-
Used by many production daemons.
Examples:
docker <-> /var/run/docker.sock
podman <-> podman.sock
cups <-> cups.sock
Great for learning:
client --> daemon
But eventually you'll want:
client --> daemon --> reply
Then FIFOs become awkward:
/tmp/in.fifo
/tmp/out.fifo
or one FIFO per client.
Only use when performance is critical:
video
audio
large telemetry
game engines
Not for command/control messages.
If you're writing a desktop Linux integration:
NetworkManager
Bluetooth
GNOME services
Otherwise it's overkill.
127.0.0.1:8080
Works everywhere, but you're exposing a network service even if it's only intended for local IPC.
Then you start thinking about:
- ports
- firewalls
- authentication
- accidental exposure
which Unix sockets avoid.
If I were building ffpaneld today in C, I'd do:
ffpanel -> CLI/TUI client
ffpaneld -> root daemon
/run/ffpanel.sock
JSON messages
That's probably the sweet spot between easy, safe, and maintainable.