Skip to content

Instantly share code, notes, and snippets.

@Guiorgy
Created August 5, 2025 14:58
Show Gist options
  • Save Guiorgy/5e852b4936dc6379342206593715f9a1 to your computer and use it in GitHub Desktop.
Save Guiorgy/5e852b4936dc6379342206593715f9a1 to your computer and use it in GitHub Desktop.
A Docker entry point script that manages multiple daemons/services
#!/bin/bash
# Bash 4+ requred
# Copyright 2025 Guiorgy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# - The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Make sure we are running as root
if [[ "$(id -u)" -ne 0 ]]; then
echo 1>&2 'This script must be run as root'
exit 1
fi
# The daemons to manage
declare -A daemons
# Key: Process name
# Value: Executable
daemons['service1']='/usr/bin/service1'
daemons['service2']='/usr/bin/service2'
# CLI arguments for the daemon executables
declare -A arguments
# Key: Process name
# Value: Arguments
# Services with no args can be skipped
arguments['service1']='-c /config.json'
# Whether the daemon executables fork internally and exit
declare -A forking
# Key: Process name
# Value: 0 for normal; 1 for forking
# Skipped entries will be set to 0 by default
arguments['service1']=1
# The user:group to run the daemons with
declare -A user_groups
# Key: Process name
# Value: User:Group
# Skipped entries will be set to 0:0 by default
arguments['service1']='1000:1000'
declare -A stop_signals
# Key: Process name
# Value: Stop signal
# Skipped entries will be set to TERM by default
stop_signals['service1']='INT'
# Set skipped arguments entries to ''
for process_name in "${!daemons[@]}"; do
if [[ ! -v arguments[process_name] ]]; then
arguments["$process_name"]=''
fi
done
# Set skipped forking entries to 0
for process_name in "${!daemons[@]}"; do
if [[ ! -v forking[process_name] ]]; then
forking["$process_name"]=0
fi
done
# Set skipped arguments entries to ''
for process_name in "${!daemons[@]}"; do
if [[ ! -v user_groups[process_name] ]]; then
user_groups["$process_name"]='0:0'
fi
done
# Set skipped stop_signals entries to TERM
for process_name in "${!daemons[@]}"; do
if [[ ! -v stop_signals[process_name] ]]; then
stop_signals["$process_name"]='TERM'
fi
done
# Guard agains repeated attempts to stop daemons
STOPPING_DAEMONS=0
# Stops all daemons
stop_daemons() {
if [ $STOPPING_DAEMONS -ne 0 ]; then
return 1
fi
STOPPING_DAEMONS=1
local to_skip="$1"
if [[ "$to_skip" == '--exit' ]]; then
to_skip=''
fi
echo "Stopping daemons..."
local exit_code=0
local result=0
declare -A pids
# Send the stop signal to the daemons
for process_name in "${!daemons[@]}"; do
if [[ "$process_name" == "$to_Skip" ]]; then
continue
fi
start-stop-daemon --stop --quiet --name "$process_name" --signal stop_signals["$process_name"] &>/proc/self/fd/1 &
pids["$process_name"]=($!)
done
# Wait for the daemons to exit and check the exit codes
for process_name in "${!pids[@]}"; do
pid=${pids["$process_name"]}
wait $pid
result=$?
if [[ $result -eq 0 ]]; then
echo 1>&2 "Daemon '$process_name' stopped"
else
echo 1>&2 "Error: Daemon '$process_name' stopped ($result)"
exit_code=$result
fi
done
echo "Daemons stopped."
STOPPING_DAEMONS=0
if [[ "$1" == '--exit' ]]; then
exit $exit_code
else
return $exit_code
fi
}
# Stop all daemons in TERM and INT signals
trap 'stop_daemons --exit' SIGTERM SIGINT
# Sleep for the total duration in small intervals to allow the trap to respond to signals
half_sleep() {
local total_sleep_time=$1
local sleep_interval=${2:-1}
local elapsed_time=0
while [[ $elapsed_time -lt $total_sleep_time ]]; do
sleep $sleep_interval
elapsed_time=$((elapsed_time + sleep_interval))
done
}
# Get the status of a daemon
get_daemon_status() {
process_name="$1"
start-stop-daemon \
--status --quiet \
--name "$process_name"
return $?
}
# Start a daemon
start_daemon() {
process_name="$1"
local other_args=''
if [[ ${forking["$process_name"]} -eq 0 ]]; then
other_args="$other_args --background --output /proc/self/fd/1"
fi
if [[ ${user_groups["$process_name"]} != '0:0' ]]; then
other_args="$other_args --chuid ${user_groups["$process_name"]}"
fi
start-stop-daemon \
--start --quiet \
$other_args \
--name "$process_name" \
--exec "${daemons["$process_name"]}" \
-- ${arguments["$process_name"]}
local result=$?
if [[ $result -eq 0 ]]; then
echo "Daemon '$process_name' started"
else
echo 1>&2 "Error: Daemon '$process_name' failed to start ($result)"
fi
return $result
}
# Stop a daemon
stop_daemon() {
process_name="$1"
stop_signal="${stop_signals["$process_name"]}"
start-stop-daemon \
--stop --quiet \
--name "$process_name" \
--signal "$stop_signal"/10/KILL/10
local result=$?
if [[ $result -eq 0 ]]; then
echo "Daemon '$process_name' stopped"
else
echo 1>&2 "Error: Daemon '$process_name' stopped ($result)"
fi
return $result
}
# Start daemons
for process_name in "${!daemons[@]}"; do
start_daemon "$process_name"
done
# Wait a bit for the daemons to start
half_sleep 10
# Monitor for the daemon statuses and restarting them if necessary
while true; do
INTERVAL=60
# Check daemon statuses
for process_name in "${!daemons[@]}"; do
# Skip if stop initiated
if [[ $STOPPING_DAEMONS -ne 0 ]]; then
break
fi
if [[ ! get_daemon_status "$process_name" ]]; then
echo 1>&2 "Error: Daemon '$process_name' stopped unexpectedly ($?)"
# Restart the stopped daemon
echo "Restarting '$process_name' daemon"
start_daemon "$process_name"
INTERVAL=10
fi
done
half_sleep $INTERVAL
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment