Table of Contents
- Understanding Systemd Basics
- Creating a Bash Script for Systemd Integration
- Writing the Systemd Service File
- Installing and Enabling the Service
- Managing the Systemd Service
- Troubleshooting Common Issues
- Advanced Tips and Best Practices
- Conclusion
- References
1. Understanding Systemd Basics
Before diving into integration, let’s clarify key systemd concepts to avoid confusion:
What is systemd?
systemd is a system and service manager for Linux. It initializes the system at boot, manages running processes, and handles service dependencies. Unlike older init systems (e.g., SysVinit), systemd uses units to describe resources it manages.
Key systemd Units
Units are configuration files that define system resources. The most common unit type for services is the .service file, which describes how to start, stop, and manage a service.
Structure of a .service File
A .service file has three main sections:
[Unit]: Metadata and dependencies (e.g., description, when to start the service).[Service]: Execution details (e.g., how to run the service, user/group, restart policy).[Install]: Installation configuration (e.g., which target to enable the service under).
Service Types
systemd supports multiple service types, which dictate how it interacts with the service process:
Type=simple(default): The service starts immediately, andsystemdconsiders it active once the main process begins. Ideal for long-running scripts (e.g., loops).Type=oneshot: The service runs once and exits.systemdwaits for it to finish before proceeding. Useful for one-time tasks (e.g., boot-time setup).Type=forking: The service forks a child process and exits.systemdtracks the child process.Type=notify: The service sends a signal tosystemdwhen it’s ready (requires explicit support in the script).
2. Creating a Bash Script for Systemd Integration
Not all Bash scripts work seamlessly with systemd out of the box. To ensure compatibility, follow these best practices when writing your script:
Best Practices for Bash Scripts
- Use absolute paths:
systemdruns services in a minimal environment, so avoid relative paths (e.g., use/usr/local/bin/script.shinstead of./script.sh). - Set error handling: Use
set -euo pipefailto exit on errors, undefined variables, or failed pipeline commands. - Log output: Write logs to a file or
stdout/stderr(systemd captures these viajournald). - Make it executable: Set the executable bit with
chmod +x script.sh.
Example Bash Script
Let’s create a sample script that logs timestamps every 30 seconds (a long-running service). Save it as /usr/local/bin/monitor.sh:
#!/bin/bash
set -euo pipefail
# Log file path (use absolute path)
LOG_FILE="/var/log/monitor.log"
# Ensure log directory exists and is writable
mkdir -p "$(dirname "$LOG_FILE")"
chmod 755 "$(dirname "$LOG_FILE")"
# Main loop: log timestamp every 30 seconds
while true; do
TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S')"
echo "[$TIMESTAMP] Service is running..." >> "$LOG_FILE"
sleep 30
done
Test the Script Manually
Before integrating with systemd, run the script manually to verify it works:
chmod +x /usr/local/bin/monitor.sh
/usr/local/bin/monitor.sh
Check the log file to confirm output:
tail -f /var/log/monitor.log
Stop the script with Ctrl+C when done.
3. Writing the Systemd Service File
Next, create a .service file to define how systemd manages the script. Service files are typically stored in:
/etc/systemd/system/(user-defined services, persistent across reboots)./usr/lib/systemd/system/(distro-provided services, avoid modifying here).
Example Service File
Create /etc/systemd/system/monitor.service with the following content:
[Unit]
Description=Custom Monitoring Service
After=network.target # Start after the network is up (optional)
Documentation=https://example.com/monitor-docs # Optional
[Service]
Type=simple # Long-running script with no forking
User=root # User to run the service (use non-root if possible)
Group=root # Group for the service
WorkingDirectory=/tmp # Optional: Working directory for the script
ExecStart=/usr/local/bin/monitor.sh # Path to your script
Restart=always # Restart if the script exits (e.g., crashes)
RestartSec=5 # Wait 5 seconds before restarting
StandardOutput=append:/var/log/monitor.stdout.log # Capture stdout (optional)
StandardError=append:/var/log/monitor.stderr.log # Capture stderr (optional)
[Install]
WantedBy=multi-user.target # Start when the system reaches multi-user mode (boot)
Key Service File Parameters Explained
-
[Unit]Section:Description: A human-readable name for the service.After=network.target: Ensures the service starts after the network is available (adjust based on your script’s dependencies).
-
[Service]Section:Type=simple: The script runs in the foreground (no forking), sosystemdtracks the main process.User/Group: The user/group under which the service runs. Use a non-root user (e.g.,monitor-user) for security, if possible.ExecStart: The absolute path to your Bash script.Restart=always: Restarts the service if it exits unexpectedly (useon-failurefor conditional restarts).StandardOutput/StandardError: Redirect output to log files (optional;journaldcaptures these by default).
-
[Install]Section:WantedBy=multi-user.target: Ensures the service starts when the system boots to multi-user mode (typical for server services).
4. Installing and Enabling the Service
Once the script and service file are created, install and enable the service to integrate it with systemd:
Step 1: Reload systemd Daemon
systemd needs to detect the new service file. Run:
sudo systemctl daemon-reload
Step 2: Enable the Service (Start on Boot)
To ensure the service starts automatically at boot, enable it:
sudo systemctl enable monitor.service
Output:
Created symlink /etc/systemd/system/multi-user.target.wants/monitor.service → /etc/systemd/system/monitor.service.
Step 3: Start the Service Immediately
Start the service without rebooting:
sudo systemctl start monitor.service
Step 4: Verify the Service Status
Check if the service is running:
sudo systemctl status monitor.service
Expected output:
● monitor.service - Custom Monitoring Service
Loaded: loaded (/etc/systemd/system/monitor.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2024-05-20 12:34:56 UTC; 2s ago
Main PID: 12345 (monitor.sh)
Tasks: 2 (limit: 4915)
Memory: 1.2M
CGroup: /system.slice/monitor.service
├─12345 /bin/bash /usr/local/bin/monitor.sh
└─12346 sleep 30
5. Managing the Systemd Service
Use systemctl commands to manage the service throughout its lifecycle:
Common systemctl Commands
| Command | Description |
|---|---|
sudo systemctl start monitor.service | Start the service immediately. |
sudo systemctl stop monitor.service | Stop the service. |
sudo systemctl restart monitor.service | Stop and restart the service. |
sudo systemctl reload monitor.service | Reload configuration (if supported by the service). |
sudo systemctl status monitor.service | Check runtime status, logs, and PID. |
sudo systemctl disable monitor.service | Disable auto-start on boot. |
sudo systemctl enable monitor.service | Re-enable auto-start on boot. |
Viewing Logs with journalctl
systemd logs service output to journald, a centralized logging system. To view logs for your service:
# View all logs for the service (newest last)
sudo journalctl -u monitor.service
# View logs with timestamps and follow new entries
sudo journalctl -u monitor.service -o short-iso -f
# View logs from the last hour
sudo journalctl -u monitor.service --since "1 hour ago"
6. Troubleshooting Common Issues
Even with careful setup, issues may arise. Here are solutions to common problems:
Issue 1: Service Fails to Start
Symptom: systemctl status monitor.service shows active (failed).
Causes:
- Script not executable: Fix with
chmod +x /usr/local/bin/monitor.sh. - Incorrect path in
ExecStart: Verify the script path withls -l /usr/local/bin/monitor.sh. - Permission denied: Ensure the service user has read/execute access to the script and write access to the log file.
Issue 2: Logs Not Appearing
Symptom: No output in journalctl or custom log files.
Causes:
- Script uses relative paths: Replace with absolute paths (e.g.,
/var/log/monitor.loginstead ofmonitor.log). - Log file permissions: Ensure the service user (e.g.,
root) can write to the log directory:sudo chown -R root:root /var/log/monitor.log sudo chmod 644 /var/log/monitor.log
Issue 3: Service Restarts Unexpectedly
Symptom: Service restarts even when not intended.
Causes:
Restart=alwaysin the service file: UseRestart=on-failureto restart only on errors, orRestart=noto disable restarts.
Issue 4: Environment Variables Missing
Symptom: Script works manually but fails in systemd (e.g., command not found).
Cause: systemd runs services in a minimal environment (no PATH from your shell).
Fix: Use absolute paths for commands (e.g., /usr/bin/date instead of date) or define Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin in the [Service] section.
7. Advanced Tips and Best Practices
To optimize your integration, consider these advanced techniques:
Use an Environment File
Store configuration variables in an environment file (e.g., /etc/monitor.env) and reference it in the service file:
[Service]
EnvironmentFile=/etc/monitor.env # Loads variables like LOG_FILE=/var/log/monitor.log
ExecStart=/usr/local/bin/monitor.sh
Example monitor.env:
LOG_FILE="/var/log/monitor.log"
INTERVAL=30 # Seconds between logs
Sandbox the Service
Enhance security by restricting the service’s privileges with systemd sandboxing options:
[Service]
PrivateTmp=yes # Isolate /tmp from the system
NoNewPrivileges=yes # Prevent privilege escalation
ProtectSystem=full # Read-only access to /usr and /boot
ProtectHome=yes # Hide /home, /root, and /run/user
Use Type=oneshot for One-Time Tasks
For scripts that run once (e.g., boot-time cleanup), use Type=oneshot and RemainAfterExit=yes to mark the service as active after completion:
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
RemainAfterExit=yes # Service stays "active" after exiting
Schedule with Timers (Instead of Cron)
For periodic tasks (e.g., daily backups), pair a oneshot service with a timer unit. Example monitor.timer file:
[Unit]
Description=Run monitor service daily at 2 AM
[Timer]
OnCalendar=*-*-* 02:00:00 # Daily at 2 AM
Persistent=true # Run missed tasks on boot
[Install]
WantedBy=timers.target
Enable and start the timer:
sudo systemctl enable --now monitor.timer
8. Conclusion
Integrating Bash scripts with systemd services unlocks powerful automation capabilities, ensuring your scripts run reliably at boot, restart on failure, and integrate with system logging. By following the steps in this guide—writing a robust script, creating a .service file, and managing the service with systemctl—you can seamlessly integrate custom workflows into your Linux system.