diff --git a/docker/Dockerfile b/docker/Dockerfile index 56bb05961..080102d60 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -29,41 +29,26 @@ ENV NVM_DIR=/root/.nvm # use bash instead of sh SHELL ["/bin/bash", "-c"] +# Switch to Slips installation dir on login. +WORKDIR ${SLIPS_DIR} + +COPY . $SLIPS_DIR -RUN apt update && apt install -y --no-install-recommends \ - wget \ - ca-certificates \ - git \ - curl \ - gnupg \ +RUN apt-get update -o Acquire::Retries=5 -o Acquire::https::No-Cache=True \ + && apt-get install -y --no-install-recommends --fix-broken --fix-missing \ + $(cat install/apt_dependencies.txt) \ lsb-release \ software-properties-common \ - build-essential \ - file \ - lsof \ - iptables \ - iproute2 \ - nfdump \ - tshark \ - whois \ - yara \ - net-tools \ vim \ less \ unzip \ - golang \ - python3-certifi \ - python3-dev \ - python3-tzlocal \ - python3-pip \ nano \ tree \ tmux \ - arp-scan \ && echo 'deb http://download.opensuse.org/repositories/security:/zeek/xUbuntu_22.04/ /' | tee /etc/apt/sources.list.d/security:zeek.list \ && curl -fsSL https://download.opensuse.org/repositories/security:zeek/xUbuntu_22.04/Release.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/security_zeek.gpg > /dev/null \ && apt update \ - && apt install -y --no-install-recommends --fix-missing \ + && apt-get install -y --no-install-recommends --fix-missing \ zeek-8.0 \ npm \ && ln -s /opt/zeek/bin/zeek /usr/local/bin/bro \ @@ -72,11 +57,15 @@ RUN apt update && apt install -y --no-install-recommends \ && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash - \ && export NVM_DIR="$HOME/.nvm" \ && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" \ - && nvm install 22 + && nvm install 22 \ + && apt purge -y redis-server redis # we'll be compiling it manually + + # why are we compiling redis instead od just using apt? # to support running slips on the rpi (arm64). the rpi uses jemmalloc by default, which expects a different page size # than the default on x86_64 +WORKDIR / RUN pip3 install --no-cache-dir --upgrade pip \ && curl -O https://download.redis.io/redis-stable.tar.gz \ && tar xzf redis-stable.tar.gz \ @@ -87,11 +76,9 @@ RUN pip3 install --no-cache-dir --upgrade pip \ ENV PATH="$PATH:/redis-stable/src" -# Switch to Slips installation dir on login. -WORKDIR ${SLIPS_DIR} -COPY . $SLIPS_DIR +WORKDIR ${SLIPS_DIR} # Retrieve Iris COPY --from=build /iris/iris ./modules/irisModule @@ -110,4 +97,5 @@ ENV PATH="$PATH:/StratosphereLinuxIPS/p2p4slips/" WORKDIR ${SLIPS_DIR} + CMD /bin/bash diff --git a/docs/images/immune/a8/failovers_script_output.jpg b/docs/images/immune/a8/failovers_script_output.jpg new file mode 100644 index 000000000..7e0addb65 Binary files /dev/null and b/docs/images/immune/a8/failovers_script_output.jpg differ diff --git a/docs/immune/failover_mechanisms.md b/docs/immune/failover_mechanisms.md new file mode 100644 index 000000000..f7d8ced63 --- /dev/null +++ b/docs/immune/failover_mechanisms.md @@ -0,0 +1,101 @@ +# Failover Mechanisms + +The project has a few failure points listed below that we explicitly want to control instead and try to recover from. + +Our goal if something breaks, is to try to recover automatically where possible, but if recovery is not possible or the failure is critical, the user must lose internet so they are forced to debug and restart Slips manually instead of staying connected without Slips protection. + + +All failure points are handled by the ```failover_handler.sh``` script located in ```StratosphereLinuxIPS/rpi_scripts/```. + +## Prerequisites + +- Raspberry Pi with docker installed +- StratosphereLinuxIPS cloned. (or just the rpi_scripts/ directory) +- Root access to the Raspberry Pi. +- A [running access point](https://stratospherelinuxips.readthedocs.io/en/develop/immune/installing_slips_in_the_rpi.html#protect-your-local-network-with-slips-on-the-rpi). + + +## How to use + +Run the following command from Slips main directory as root: + +```bash +sudo ./rpi_scripts/failover_handler.sh , +``` + +**Where** + +- `````` is the name of the wifi interface used by the access point (e.g. ```wlan0```, etc). + +- `````` is the name of the ethernet interface connected to the router (e.g. ```eth0```, etc). + +**You should see output similar to the image below:** + +![](../images/immune/a8/failovers_script_output.jpg) + + +**Output:** + +The script will +- Log Slips docker container status, used command, and any errors to ```slips_container.log``` for debugging purposes. This file should be checked in case you notice any issues with the AP or Slips. +- Start Slips and iptables watcher services through systemd so they start automatically on reboot and on failure. +- Start slips inside a docker container monitring your ethernet and wifi interfaces. +- Mount your local ```StratosphereLinuxIPS/output``` to ```/StratosphereLinuxIPS/output``` inside the started Slips container so any output generated by Slips will be available on the host machine. + + + +## How Failovers are Handled + +Slips in the Raspberry Pi has 3 main failure points that we want to handle: +1. The access point dies +2. Slips dies +3. The Raspberry Pi reboots + +### The access point dies + +If the AP dies, clients get disconnected. There's nothing to handle here. Slips keeps monitoring the ethernet interface and when the AP is back up, clients can reconnect and Slips continues protecting them. + + +### Slips dies + +Failovers when Slips dies consist of: + +* **Iptables firewall rules persistence**: The iptables firewall rules are saved periodically by the systemd unit that watches for iptables changes and saves them using ```netfilter-persistent``` whenever a change is detected. +* **Shutting down the AP process**: If Slips crashes we do not want the AP to keep running without Slips protection so we intentionally shut down the access point for the user to notice, debug and restart Slips manually instead of staying connected without Slips protection. +* **Restarting Slips automatically through systemd:** Slips restarts automatically through systemd on failure and on reboot. +* **Logging Slips container status to a file for debugging**: Slips container logs are places in ```slips_container.log``` for debugging purposes. + + + +### The Raspberry Pi reboots + +Failovers consist of: + +* **Iptables firewall rules persistence** +* **Automatic start of Slips service through systemd** + + +When the Pi reboots, we want Slips to start automatically, and we want the iptables rules added by Slips to persist. The automatic restart is handled by systemd through the generated ```slips.service``` file, and firewall persistence is handled using the custom iptables watcher through the generated ```iptables-watcher.service```. + + +Both units are generated and started and added to the user's ```/etc/systemd/system``` by the ```failover_handler.sh``` script. + +--- + +## File Descriptions + +All the files involved in failover mechanisms are placed in ```StratosphereLinuxIPS/rpi_scripts/``` and are described in the table below: + + +| File | What it does | +|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| failover_handler.sh | The central orchestrator that checks AP status, ensures root access, prepares directories, sets up iptables persistence, builds the Slips runner script, generates the systemd unit, and enforces all failover behavior. This is the piece that links every component together and decides how the system should react when something breaks. | +| iptables_autosave/check-iptables-hash.sh | Keeps track of the hash of the current iptables rules and triggers a save when change is detected. | +| iptables_autosave/iptables-watcher.service | The systemd service that runs check-iptables-hash.sh (because we can't run the script directly by the timer), this is triggered by the iptables-watcher.timer every 10s to check for iptables changes. | +| iptables_autosave/iptables-watcher.timer | A systemd timer that periodically runs iptables-watcher.service so iptables rule changes are captured and saved automatically. | +| slips_container.log | A runtime log collecting Docker container output, commands, and status. useful for investigating restarts, failures, or unexpected behavior. | +| slips-runner-template.sh | The script that launches Slips container and launches slips inside of it in a tmux. This runner keeps the container up as long as Slips is running. | +| slips.service.template | The systemd unit that starts slips on reboot and on failure, it runs the slips-runner-template.sh. | + + +--- diff --git a/docs/immune/reimplement_slips_features_incompatible_with_the_rpi.md b/docs/immune/reimplement_slips_features_incompatible_with_the_rpi.md index 7f3f5dbf5..a14de848f 100644 --- a/docs/immune/reimplement_slips_features_incompatible_with_the_rpi.md +++ b/docs/immune/reimplement_slips_features_incompatible_with_the_rpi.md @@ -24,8 +24,8 @@ Docker can run amd64 images on arm devices using emulation, but this method come So we decided to go for an ARM docker image specifically for ARM devices and the RPI. This is doable without maintaining 2 different Dockerfiles one for each architechture thanks to docker buildx multiplatform support. -**Commands for building the ARM image** - +**Commands for building one multi-architecture image (supporting AMD and ARM)** +``` docker buildx create --name slips\_builder docker buildx use slips\_builder @@ -33,6 +33,7 @@ docker buildx use slips\_builder export BUILDKIT\_CONTAINERD=1 docker buildx build --platform linux/amd64,linux/arm64 -t stratosphereips/slips:latest -f docker/Dockerfile --push . +``` Our goal is to maintain one Dockerfile that is able to run on both ARM and AMD architectures. diff --git a/install/apt_dependencies.txt b/install/apt_dependencies.txt index bcf5363bc..6620b9ea0 100644 --- a/install/apt_dependencies.txt +++ b/install/apt_dependencies.txt @@ -1,15 +1,21 @@ +curl +gnupg +ca-certificates python3 -redis-server python3-pip python3-certifi python3-dev +python3-watchdog +python3-tzlocal +wget +npm +iw build-essential file lsof net-tools iproute2 iptables -python3-tzlocal nfdump tshark git @@ -21,11 +27,5 @@ yara libnotify-bin wireless-tools arp-scan -python3-watchdog -curl -gnupg -ca-certificates +redis-server redis -wget -npm -iw diff --git a/managers/ap_manager.py b/managers/ap_manager.py index 4b7597796..e5a7d6f60 100644 --- a/managers/ap_manager.py +++ b/managers/ap_manager.py @@ -14,7 +14,7 @@ def store_ap_interfaces(self, input_information): """ stores the interfaces given with -ap to slips in the db """ - self.wifi_interface, self.eth_interface = input_information.split(",") + self.wifi_interface, self.eth_interface = input_information.split("_") interfaces = { "wifi_interface": self.wifi_interface, "ethernet_interface": self.eth_interface, diff --git a/rpi_scripts/failover_handler.sh b/rpi_scripts/failover_handler.sh new file mode 100755 index 000000000..6fbfc5264 --- /dev/null +++ b/rpi_scripts/failover_handler.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash + +set -euo pipefail + +RESET="\033[0m"; BOLD="\033[1m"; RED="\033[0;31m"; GREEN="\033[0;32m"; YELLOW="\033[0;33m"; BLUE="\033[0;34m" +echoc() { printf "%b\n" "$*"; } + +usage() { + cat <, + +-h Show help +, e.g. wlan0,eth0 + +Example: + $0 wlan0,eth0 + +This script will: + - Require root (re-exec with sudo if needed) + - Check for a running create_ap instance, and exit if not found + - Create ./output and ./config if missing + - Install iptables persistence & save iptables rules on any change. + - Run Slips inside Docker + tmux + - Log Slips & Docker status to slips_container.log + - Create a systemd unit for Slips for persistence +EOF +} + +ensure_root() { + if [ "$(id -u)" -ne 0 ]; then + echoc "${YELLOW}Root required, re-running with sudo...${RESET}" + # Flush output to terminal + sleep 0.1 + exec sudo bash "$0" "$@" + fi +} + +parse_interfaces() { + if [ "${1:-}" = "-h" ]; then usage; exit 0; fi + if [ $# -ne 1 ]; then usage; exit 1; fi + IFS=',' read -r WIFI_IF ETH_IF <<< "$1" || { echoc "${RED}Invalid format.${RESET}"; exit 1; } + [ -z "$WIFI_IF" ] || [ -z "$ETH_IF" ] && { echoc "${RED}Missing interface(s).${RESET}"; exit 1; } + echoc "${GREEN}Using WiFi interface: ${WIFI_IF}, Ethernet interface: ${ETH_IF}${RESET}" +} + +ensure_create_ap_is_running() { + echoc "${BLUE}Checking for running create_ap...${RESET}" + if ! pgrep -a create_ap | grep -E "\\b${WIFI_IF}\\b.*\\b${ETH_IF}\\b" >/dev/null 2>&1; then + echoc "${RED}create_ap is not running for ${WIFI_IF},${ETH_IF}.${RESET}" + echoc "${YELLOW}Run first:${RESET}" + echoc "${BOLD}sudo create_ap ${WIFI_IF} ${ETH_IF} rpi_wifi mysecurepassword -c 40${RESET}" + exit 1 + fi +} + + +create_directories() { + CWD="$(pwd -P)" + OUTPUT_DIR="$CWD/output" + echoc "${BLUE}\nChecking if ${OUTPUT_DIR} exists...${RESET} " + + if [ -d "$OUTPUT_DIR" ]; then + echoc "${GREEN}${OUTPUT_DIR} exists${RESET}" + else + mkdir -p "$OUTPUT_DIR" + echoc "${GREEN}Created ${OUTPUT_DIR} successfully.${RESET}" + fi + echoc "${GREEN}This script will mount ${OUTPUT_DIR} into the Docker container as Slips output directory in /StratosphereLinuxIPS/output.\n ${RESET}" +} + +setup_iptables_persistence() { + # Persistence here is a systemd unit that watches for iptables changes and saves them whenever a change is detected + echoc "${BLUE}Setting up iptables persistence...${RESET}" + + # Install required packages + apt-get update -y + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends iptables-persistent netfilter-persistent + + # Enable and start netfilter-persistent as fallback + systemctl enable netfilter-persistent || true + systemctl restart netfilter-persistent || true + netfilter-persistent save || iptables-save > /etc/iptables/rules.v4 || true + + # Deploy custom systemd units + UNIT_DIR="/etc/systemd/system" + SRC_DIR="$(pwd)/iptables_autosave" + + # copy each unit file in iptables_autosave to the systemd directory + for unit in iptables-watcher.service iptables-watcher.timer; do + if [[ -f "$SRC_DIR/$unit" ]]; then + cp -f "$SRC_DIR/$unit" "$UNIT_DIR/$unit" + chmod 644 "$UNIT_DIR/$unit" + echoc "${GREEN}Copied $unit to $UNIT_DIR${RESET}" + else + echoc "${RED}File $SRC_DIR/$unit not found, skipping.${RESET}" + fi + done + + + # Deploy the check-iptables-hash.sh script + cp -f "$SRC_DIR/check-iptables-hash.sh" "/usr/local/bin/check-iptables-hash.sh" + chmod +x /usr/local/bin/check-iptables-hash.sh + + + # Reload systemd, enable and start units + systemctl daemon-reload + systemctl enable iptables-watcher.service + systemctl enable iptables-watcher.timer + systemctl start iptables-watcher.timer + + echoc "${GREEN}Done setting up iptables persistence using iptables-watcher units.${RESET}" + echoc "${BOLD}You can check the status with: ${RESET} sudo systemctl status iptables-watcher.timer\n" +} + + +create_slips_runner_script() { + # Creates the slips-runner.sh script from template slips-runner-template.sh + RUNNER_PATH="/usr/local/bin/slips-runner.sh" + TEMPLATE="./slips-runner-template.sh" + LOG_FILE="${CWD}/slips_container.log" + + echoc "${BLUE}Creating runner script from template for slips systemd unit to use...${RESET}" + [ -f "$TEMPLATE" ] || { echoc "${RED}Template not found: $TEMPLATE${RESET}"; exit 1; } + echoc "PS: This Slips runner script doesn't start slips with the blocking modules enabled, modify the Slips command in ${TEMPLATE} + if you want to enable them and rerun this script for the changes to take effect." + export WIFI_IF ETH_IF CWD LOG_FILE + envsubst '$WIFI_IF $ETH_IF $CWD $LOG_FILE' < "$TEMPLATE" > "$RUNNER_PATH" + chmod +x "$RUNNER_PATH" + echoc "${GREEN}Runner created at $RUNNER_PATH.${RESET}" +} + +create_slips_systemd_unit() { + SERVICE_PATH="/etc/systemd/system/slips.service" + TEMPLATE="./slips.service.template" + + echoc "${BLUE}Creating slips systemd service from template ./slips.service.template ...${RESET}" + [ -f "$TEMPLATE" ] || { echoc "${RED}Template not found: $TEMPLATE${RESET}"; exit 1; } + + # Ensure all needed vars are exported for envsubst + export WIFI_IF ETH_IF CWD LOG_FILE + export OUTPUT_DIR="$CWD/output" + export CONFIG_DIR="$CWD/config" + export CONTAINER_NAME="slips" + export DOCKER_IMAGE="stratosphereips/slips:latest" + export RUNNER_PATH="/usr/local/bin/slips-runner.sh" + + envsubst < "$TEMPLATE" > "$SERVICE_PATH" + + systemctl daemon-reload + systemctl enable slips.service + systemctl restart slips.service + echoc "${GREEN}Slips systemd service installed and started.${RESET}" + echoc "${BOLD}You can check the status with: ${RESET} sudo systemctl status slips\n" +} + + +main() { + parse_interfaces "$@" + ensure_root "$@" + ensure_create_ap_is_running + + create_directories + setup_iptables_persistence + create_slips_runner_script + create_slips_systemd_unit + + echoc "${YELLOW}Slips is running inside tmux in Docker.${RESET}" + echoc "You can attach using: ${BOLD}docker exec -it slips${RESET}" + echoc "For container logs check: ${BOLD}${CWD}/slips_container.log${RESET}" +} + +main "$@" diff --git a/rpi_scripts/iptables_autosave/check-iptables-hash.sh b/rpi_scripts/iptables_autosave/check-iptables-hash.sh new file mode 100644 index 000000000..a43e51b67 --- /dev/null +++ b/rpi_scripts/iptables_autosave/check-iptables-hash.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +HASH_FILE="/var/run/iptables.hash" + +# Get the current ruleset and hash it +CURRENT_HASH=$(/usr/sbin/iptables-save | sha256sum) + +# If the hash file doesn't exist, create it and exit +if [ ! -f "$HASH_FILE" ]; then + echo "$CURRENT_HASH" > "$HASH_FILE" + exit 0 +fi + +# Read the old hash +OLD_HASH=$(cat "$HASH_FILE") + +# Compare hashes +if [ "$CURRENT_HASH" != "$OLD_HASH" ]; then + # 1. Update the hash file with the new hash + echo "$CURRENT_HASH" > "$HASH_FILE" + + + # 2. Trigger the action service to reload rules + echo "Saving updated iptables rules." + netfilter-persistent save || iptables-save > /etc/iptables/rules.v4 || true + +else + # No changes, do nothing + : +fi diff --git a/rpi_scripts/iptables_autosave/iptables-watcher.service b/rpi_scripts/iptables_autosave/iptables-watcher.service new file mode 100644 index 000000000..84d0a5ecb --- /dev/null +++ b/rpi_scripts/iptables_autosave/iptables-watcher.service @@ -0,0 +1,11 @@ +[Unit] +Description=Check for iptables rule changes +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/check-iptables-hash.sh + + +[Install] +WantedBy=multi-user.target diff --git a/rpi_scripts/iptables_autosave/iptables-watcher.timer b/rpi_scripts/iptables_autosave/iptables-watcher.timer new file mode 100644 index 000000000..1eb7e4319 --- /dev/null +++ b/rpi_scripts/iptables_autosave/iptables-watcher.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Run iptables-watcher.service every 10 seconds + +[Timer] +# run the service 5 seconds after boot +OnBootSec=5s +# then run the iptables-watcher every 10 seconds periodically +OnUnitActiveSec=10s +Unit=iptables-watcher.service + +[Install] +WantedBy=timers.target diff --git a/rpi_scripts/slips-runner-template.sh b/rpi_scripts/slips-runner-template.sh new file mode 100644 index 000000000..3eb674171 --- /dev/null +++ b/rpi_scripts/slips-runner-template.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# This template script is to be copied and edited with actual values for running Slips in AP mode +# it's used by the slips service unit to run slips inside a docker container on boot and on failure + + +set -euo pipefail + +WIFI_IF="${WIFI_IF}" +ETH_IF="${ETH_IF}" +CWD="${CWD}" +OUTPUT_DIR="$CWD/output" +#CONFIG_DIR="$CWD/config" +CONTAINER_NAME="${CONTAINER_NAME:-slips}" +DOCKER_IMAGE="${DOCKER_IMAGE:-stratosphereips/slips:latest}" +LOG_FILE="${LOG_FILE}" + +log() { printf "%b\n" "$(date -Iseconds) - $*" | tee -a "$LOG_FILE"; } + +remove_existing_container() { + # Removes existing slips container if found, because we'll be starting a new one with the same name + if docker ps -a --format '{{.Names}}' | grep -xq "$CONTAINER_NAME"; then + log "Container '$CONTAINER_NAME' already exists. Removing it." + docker rm -f "$CONTAINER_NAME" || true + fi +} + +kill_ap_process() { + PATTERN="create_ap.*\b$WIFI_IF\b.*\b$ETH_IF\b" + log "Searching for AP processes matching: $PATTERN" + + pids=$(ps -ef | grep -E "$PATTERN" | grep -v grep | awk '{print $2}' || true) + + if [ -n "$pids" ]; then + log "Found AP process(es): $pids" + kill -TERM $pids + if [ $? -eq 0 ]; then + log "Successfully sent TERM to AP process(es)." + else + log "Failed to kill AP process(es)." + fi + else + log "No AP processes found." + fi +} + + +main() { + # Removes existing slips containers, restarts slips docker, and monitors status + # Stops the AP process on slips container exit to cut the user's internet to notice that something went wrong with slips + # and his traffic is no longer being inspected. + log "=== Slips Runner Started ===" + + remove_existing_container + docker_cmd=( + docker run -d -it + -v "$OUTPUT_DIR":/StratosphereLinuxIPS/output/ + # -v "$CONFIG_DIR":/StratosphereLinuxIPS/config/ + --name "$CONTAINER_NAME" + --net=host + --cpu-shares 800 + --memory 8g + --memory-swap 8g + --shm-size 512m + --cap-add NET_ADMIN + "$DOCKER_IMAGE" + bash -c "tmux new -s slips './slips.py -ap $WIFI_IF,$ETH_IF'; tmux wait-for -S slips_done" + ) + + log "Starting Slips container using command: ${docker_cmd[*]}" + + # Execute + container_id=$("${docker_cmd[@]}") + + log "Container started: $container_id" + + + # This loop blocks forever until the docker container dies. + # qx matches exactly "slips" container + while docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null | grep -qx "true" ; do + sleep 10 + status=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "unknown") + log "Container status: $status" + done + + + ### Docker exited. Termination Logic #### :D + exit_code=$(docker inspect --format='{{.State.ExitCode}}' "$container_id" 2>/dev/null || echo 0) + log "Exited with code $exit_code" + + + # we want the user to notice that slips is no longer protecting their traffic, so we kill the AP process to cut internet access + kill_ap_process + + netfilter-persistent save || true + log "Runner finished." +} + +main "$@" diff --git a/rpi_scripts/slips.service.template b/rpi_scripts/slips.service.template new file mode 100644 index 000000000..f6bfbd830 --- /dev/null +++ b/rpi_scripts/slips.service.template @@ -0,0 +1,32 @@ +[Unit] +Description=Slips Docker Runner with tmux +After=network.target docker.service +Wants=docker.service + +[Service] +Type=simple +ExecStart=${RUNNER_PATH} +# this will be handled by the runner script, no need to handle it here too # TODO +# ExecStop=/bin/bash -c 'docker rm -f slips >/dev/null 2>&1 || true' + +# always restart the service if it exits, no matter why (clean exit, crash, non-zero exit, etc.). +Restart=always + +# wait 5 seconds before restarting the service after it stops. +RestartSec=5 + +#KillMode=process +# kills child processes of the ExecStart command on shutdown. +KillMode=control-group + +# wait up to 10mins for slips service to complete its ExecStart command (to start). why? because it may pull docker image, do apt update, etc. so this takes time. +TimeoutStartSec=600 + +# wait 5 mins for the script to shutdown +TimeoutStopSec=300 +User=root +StandardOutput=append:${CWD}/slips_container.log +StandardError=append:${CWD}/slips_container.log + +[Install] +WantedBy=multi-user.target