Skip to content

Instantly share code, notes, and snippets.

@lbussy
Last active December 12, 2024 13:51
Show Gist options
  • Save lbussy/a409f9a210d9b5e4ac170c0e53bdc7cc to your computer and use it in GitHub Desktop.
Save lbussy/a409f9a210d9b5e4ac170c0e53bdc7cc to your computer and use it in GitHub Desktop.
Systemd .service File Generator

Systemd .service File Generator

This script automates the creation or updating of systemd service unit files. It validates user inputs, generates or modifies the service files, validates them, and enables them to start with the system.


Header

  • Purpose: Generate or update systemd service files.
  • Author: Lee Bussy
  • Version: 1.0.0
  • Usage:
    • -n/--service-name: Specify the service name (required).
    • -e/--exec-start: Specify the command to run (required).
    • Additional options allow customization of the service unit (see below).

Functions

  1. check_root

    • Ensures the script is run with root privileges.
    • Exits with error code 1 if the user is not root.
  2. usage

    • Displays usage instructions, including all options:
      • -n, --service-name: Name of the service (required).
      • -e, --exec-start: Command to execute when the service starts (required).
      • -d, --description: Service description (optional).
      • -u, --user: User to run the service (optional).
      • -g, --group: Group to run the service (optional).
      • -w, --working-directory: Service working directory (optional).
      • -a, --after: Dependencies for service startup (optional).
      • -U, --update: Update existing service instead of creating a new one.
  3. parse_arguments

    • Parses both short and long options using getopt.
    • Validates that required arguments (-n, -e) are present.
    • Sets values for optional arguments (e.g., user, group).
  4. validate_inputs

    • Ensures the service name only contains valid characters ([a-zA-Z0-9_-]).
    • Checks if the ExecStart command exists and is executable.
  5. create_or_update_service_file

    • Generates a new service file or updates an existing one.
    • Writes service file configuration to a temporary file:
      • [Unit] section: Description, After (optional dependencies).
      • [Service] section: ExecStart, User, Group, WorkingDirectory, Restart=always.
      • [Install] section: WantedBy=multi-user.target.
    • Moves the temporary file to /etc/systemd/system/<service_name>.service.
  6. validate_service_file

    • Uses systemctl to validate the syntax and content of the generated service file.
  7. enable_service

    • Reloads the systemd configuration.
    • Enables the service to start at boot.
  8. main

    • Orchestrates script execution:
      • Ensures root privileges.
      • Parses arguments.
      • Validates inputs.
      • Creates or updates the service file.
      • Validates the file.
      • Enables the service.

Flow

  1. Root Privileges:

    • The script checks for root permissions since it modifies system-level service files.
  2. Argument Parsing:

    • Handles all options and sets default values for optional parameters.
  3. Validation:

    • Ensures correctness of inputs, service name, and executable paths.
  4. Service File Management:

    • Either creates a new service file or updates an existing one.
  5. Validation:

    • Uses systemctl to ensure the service file is valid and syntactically correct.
  6. Enable Service:

    • Reloads systemd and enables the service to start at boot.

Usage Example

# Create a new service
sudo ./script.sh -n myservice -e "/usr/bin/python3 /path/to/script.py" -d "My Python Service" -u myuser -g mygroup -w /home/myuser -a "network.target"

# Update an existing service
sudo ./script.sh -n myservice -e "/usr/bin/python3 /new/path/script.py" -U

Key Features

  1. Dynamic Option Parsing:

    • Supports both short and long options via getopt.
  2. Service File Update:

    • Allows modification of existing service files with the -U flag.
  3. Error Handling:

    • Validates all inputs and commands.
    • Ensures service files are correctly formatted and operational.
#!/bin/bash
# Script to generate or update a systemd service file
# This script automates the creation or modification of a systemd service unit file
# Author: Lee Bussy
# Version: 1.0.0
# Usage: Run with appropriate arguments to create or update and enable a service
set -e
set -o pipefail
# Check if the script is run with root privileges
# @function check_root
# @description Ensures the script is executed with root privileges.
# @exitcode 1 If the user does not have root privileges.
check_root() {
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root." >&2
exit 1
fi
}
# Function to display usage
# @function usage
# @description Displays the usage instructions for the script.
# @exitcode None
usage() {
echo "Usage: $0 -n <service_name> -e <exec_start> [options]"
echo " -n, --service-name <service_name>: Name of the service (required)"
echo " -e, --exec-start <exec_start>: Command to execute when the service starts (required)"
echo " -d, --description <description>: Description of the service (optional)"
echo " -u, --user <user>: User to run the service as (optional)"
echo " -g, --group <group>: Group to run the service as (optional)"
echo " -w, --working-directory <working_directory>: Working directory for the service (optional)"
echo " -a, --after <after>: Services to start after (optional, comma-separated)"
echo " -U, --update: Update an existing service file instead of creating a new one (optional)"
exit 1
}
# Parse arguments
# @function parse_arguments
# @description Parses the command-line arguments passed to the script.
# @exitcode 1 If required arguments are missing or invalid.
# @param $@ Command-line arguments.
parse_arguments() {
# Define short (-n, -e) and long (--service-name, --exec-start) options
OPTIONS=n:e:d:u:g:w:a:U
LONGOPTIONS=service-name:,exec-start:,description:,user:,group:,working-directory:,after:,update
# Parse the options using getopt
if ! PARSED=$(getopt --options="$OPTIONS" --longoptions="$LONGOPTIONS" --name "$0" -- "$@" ); then
usage
fi
# Evaluate parsed arguments
eval set -- "$PARSED"
while true; do
case "$1" in
-n|--service-name)
SERVICE_NAME="$2"
shift 2
;;
-e|--exec-start)
EXEC_START="$2"
shift 2
;;
-d|--description)
DESCRIPTION="$2"
shift 2
;;
-u|--user)
USER="$2"
shift 2
;;
-g|--group)
GROUP="$2"
shift 2
;;
-w|--working-directory)
WORKING_DIRECTORY="$2"
shift 2
;;
-a|--after)
AFTER="$2"
shift 2
;;
-U|--update)
UPDATE=true
shift
;;
--)
shift
break
;;
*)
usage
;;
esac
done
# Validate required arguments
if [[ -z "$SERVICE_NAME" || -z "$EXEC_START" ]]; then
echo "Error: Service name and exec start command are required." >&2
usage
fi
}
# Validate inputs
# @function validate_inputs
# @description Validates the service name and exec start command.
# @exitcode 1 If inputs are invalid.
validate_inputs() {
if [[ ! "$SERVICE_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Service name contains invalid characters." >&2
exit 1
fi
if [[ ! -x "$(command -v "${EXEC_START%% *}" 2>/dev/null)" ]]; then
echo "Error: ExecStart command not found or not executable." >&2
exit 1
fi
}
# Create or update the service file
# @function create_or_update_service_file
# @description Creates or updates the systemd service file with the provided inputs.
# @exitcode 1 If the service file creation or update fails.
create_or_update_service_file() {
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
if [[ "$UPDATE" == true && -f "$SERVICE_FILE" ]]; then
echo "Updating existing service file at $SERVICE_FILE"
else
echo "Creating new service file at $SERVICE_FILE"
fi
TEMP_SERVICE_FILE=$(mktemp)
{
echo "[Unit]"
echo "Description=$DESCRIPTION"
[[ -n "$AFTER" ]] && echo "After=$AFTER"
echo "[Service]"
echo "Type=simple"
echo "ExecStart=$EXEC_START"
[[ -n "$USER" ]] && echo "User=$USER"
[[ -n "$GROUP" ]] && echo "Group=$GROUP"
[[ -n "$WORKING_DIRECTORY" ]] && echo "WorkingDirectory=$WORKING_DIRECTORY"
echo "Restart=always"
echo "[Install]"
echo "WantedBy=multi-user.target"
} > "$TEMP_SERVICE_FILE" 2>/dev/null
if [[ ! -s "$TEMP_SERVICE_FILE" ]]; then
echo "Error: Failed to create or update service file." >&2
exit 1
fi
mv "$TEMP_SERVICE_FILE" "$SERVICE_FILE" 2>/dev/null
echo "Service file saved at $SERVICE_FILE"
}
# Validate the service file using meson test
# @function validate_service_file
# @description Validates the generated systemd service file using systemctl.
# @exitcode 1 If validation fails.
validate_service_file() {
if ! systemctl --quiet show --property=Id "$SERVICE_NAME" 2>/dev/null; then
echo "Error: Service file validation failed." >&2
exit 1
fi
echo "Service file validated successfully."
}
# Enable the service
# @function enable_service
# @description Reloads systemd and enables the service.
# @exitcode 1 If enabling the service fails.
enable_service() {
if ! systemctl daemon-reload 2>/dev/null; then
echo "Error: Failed to reload systemd." >&2
exit 1
fi
if ! systemctl enable "$SERVICE_NAME" 2>/dev/null; then
echo "Error: Failed to enable service." >&2
exit 1
fi
echo "Service $SERVICE_NAME created or updated and enabled successfully."
}
# Main function
# @function main
# @description Orchestrates the script execution flow.
# @param $@ Command-line arguments.
main() {
check_root
parse_arguments "$@"
validate_inputs
create_or_update_service_file
validate_service_file
enable_service
}
# Execute main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment