Properly Launching User-Specific Startup Scripts as Non-Root with Process Detachment in Linux


2 views

When implementing system-wide startup/shutdown scripts that execute user-specific scripts from their home directories (~/.startUp and ~/.shutDown), we face two technical challenges:

  • Privilege escalation to execute as target users
  • Process detachment to avoid lingering parent processes

The first approach using sudo creates unwanted root-owned parent processes:

sudo -b -u username /home/username/.startUp/watchdog.sh

This leaves permanent sudo processes in the process tree, which isn't ideal for system monitoring.

We discovered two working solutions that properly detach child processes:

sudo with Background Execution

sudo -u username bash -c "/home/username/.startUp/script.sh &"

su Alternative

su username -c "/home/username/.startUp/script.sh &"
Method Requires sudoers config Password prompt Environment
sudo Yes Configurable More controlled
su No Possible Closer to user login

Here's a production-ready example for /etc/init.d/user-startup:

#!/bin/bash
# chkconfig: 2345 99 01
# description: User-specific startup scripts

case "$1" in
    start)
        for user in /home/*; do
            if [ -d "$user/.startUp" ]; then
                for script in "$user/.startUp/"*; do
                    if [ -x "$script" ]; then
                        sudo -u ${user##*/} bash -c "$script &" >/dev/null 2>&1
                    fi
                done
            fi
        done
        ;;
    stop)
        for user in /home/*; do
            if [ -d "$user/.shutDown" ]; then
                for script in "$user/.shutDown/"*; do
                    if [ -x "$script" ]; then
                        sudo -u ${user##*/} bash -c "$script &" >/dev/null 2>&1
                    fi
                done
            fi
        done
        ;;
    *)
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac
  • Ensure ~/.startUp and ~/.shutDown directories have 700 permissions
  • Regularly audit user scripts for malicious content
  • Consider setting up resource limits via /etc/security/limits.conf
  • Implement logging for troubleshooting

For modern systems, consider using systemd's per-user manager:

# Enable lingering to allow user services to run without active login
loginctl enable-linger username

# User service example (~/.config/systemd/user/myservice.service)
[Unit]
Description=My User Service

[Service]
ExecStart=/home/username/.startUp/myscript.sh

[Install]
WantedBy=default.target

When launching user scripts via sudo -u from init.d scripts, many administrators encounter the persistent root-owned sudo processes that remain as parents to the user processes. Here's why this happens and how to properly detach them:

# Problematic approach (leaves sudo parent)
sudo -u username /home/username/watchdog.sh

For modern Linux systems, these are the most reliable methods to launch user scripts without leaving parent processes:

# Method 1: Using sudo with shell detachment
sudo -u username bash -c "nohup /home/username/script.sh &>/dev/null &"

# Method 2: Using su with full process detachment
su username -s /bin/bash -c "exec /home/username/daemon.sh >/dev/null 2>&1 &"

# Method 3: Systemd-friendly approach (for newer systems)
sudo -u username systemd-run --scope --user /home/username/service.sh

The key difference between these methods lies in how they handle process relationships:

$ pstree -p | grep -A5 "watchdog"
# Bad output:
init(1)───sudo(1234)───watchdog(1235)

# Good output:
init(1)───watchdog(1235)

For system-wide implementation, consider these architectural approaches:

#!/bin/bash
### /etc/init.d/user-scripts
case "$1" in
  start)
    for user in /home/*; do
      username=$(basename $user)
      [ -f "$user/.startUp/run" ] && \
        su "$username" -c "nohup \"$user/.startUp/run\" &>/dev/null &"
    done
    ;;
  stop)
    # Similar implementation for shutdown scripts
    ;;
esac

When implementing this solution:

  • Always validate script paths to prevent directory traversal
  • Consider resource limits with ulimit
  • Log the execution results to /var/log/user-scripts.log

For systems using systemd, consider implementing per-user systemd units:

# /etc/systemd/system/user@.service
[Unit]
Description=User startup scripts for %i

[Service]
Type=oneshot
ExecStart=/usr/local/bin/run-user-startup %i
User=%i
Group=%i