Please help me create a systemd script that'll restart on its own if my VPN disconnects

So here’s the script I started:

[Unit]
Description=Connecting VPN via PIA
Wants=piavpn.service
After=piavpn.service
StartLimitIntervalSec=0

[Service]
Type=forking
ExecStartPre=/bin/bash -c 'while [ "$(systemctl is-active piavpn.service)" != "active" ]; do sleep 1; done'
ExecStart=/usr/bin/bash -c '/opt/piavpn/bin/piactl connect; \
        while true; do \
        connection_state=$(/opt/piavpn/bin/piactl get connectionstate); \
        if [ "$connection_state" == "Connected" ]; then \
                echo "PIA - Connected"; break; \
        fi; \
        echo "Waiting for connection..."; /usr/bin/sleep 5; \
        done; \
        while true; do if [ "$(/opt/piavpn/bin/piactl get connectionstate)" == "Connected" ]; then echo "PIA - Still Connected"; sleep 15; else echo "PIA - Disconnected; broken"; exit 1; fi; done'
ExecStop=/usr/bin/bash -c '/opt/piavpn/bin/piactl disconnect; while true; do if [ "$(/opt/piavpn/bin/piactl get connectionstate)" == "Disconnected" ]; then echo "PIA - Disconnected"; exit 0; else echo "PIA - Still Connected"; fi; sleep 5; done'
Restart=always
#RemainAfterExit=yes

[Install]
WantedBy=default.target

While the basic version was able to start properly, I’m unable to keep it checking and restart if it disconnects.

$ systemctl --user status pia_activate.service
● pia_activate.service - Connecting VPN via PIA
     Loaded: loaded (/home/.tor/.config/systemd/user/pia_activate.service; enabled; vendor preset: enabled)
     Active: activating (start) since Fri 2023-09-22 06:32:20 UTC; 1min 1s ago
    Process: 31744 ExecStartPre=/bin/bash -c while [ "$(systemctl is-active piavpn.service)" != "active" ]; do sleep 1; done (code=exited, status=0/SUCCESS)
Cntrl PID: 31746 (bash)
      Tasks: 2 (limit: 9231)
     Memory: 544.0K
        CPU: 341ms
     CGroup: /user.slice/user-1002.slice/user@1002.service/app.slice/pia_activate.service
             ├─31746 /usr/bin/bash -c "/opt/piavpn/bin/piactl connect;  \twhile true; do  \tconnection_state=\$(/opt/piavpn/bin/piactl get connectionstate);  \tif [ \"\$connection_state\" == \"Connected\" ]; then  \t\techo \"PIA - Connected\"; break;  \tfi;  \techo \"Waiting for connection...\"; /usr/bin/sleep 5;  \tdone;  \twhile >
             └─31815 sleep 15

Sep 22 06:32:20 evie systemd[761]: Starting Connecting VPN via PIA...
Sep 22 06:32:20 evie bash[31746]: PIA - Connected
Sep 22 06:32:20 evie bash[31746]: PIA - Still Connected
Sep 22 06:32:36 evie bash[31746]: PIA - Still Connected
Sep 22 06:32:51 evie bash[31746]: PIA - Still Connected
Sep 22 06:33:06 evie bash[31746]: PIA - Still Connected
Sep 22 06:33:21 evie bash[31746]: PIA - Still Connected

This line:

while true; do if [ "$(/opt/piavpn/bin/piactl get connectionstate)" == "Connected" ]; then echo "PIA - Still Connected"; sleep 15; else echo "PIA - Disconnected; broken"; exit 1; fi; done'

I originally added on ExecStartPost, and it’d cause Active: activating (start-post) instead of activating (start).

I’m not as experienced creating custom systemd scripts, and any help would be greatly appreciated.

In addition, would it be possible to create a systemd script for another service that’ll stop the service if this one stops working and will restart if it’s successfully back up?

I do something similar with a bash script from a cron job. In my case I want to kill a docker image if the vpn has been stopped, and start that container if the vpn as been restarted:

#!/bin/bash

if [ "$(mullvad status)" = 'Disconnected' ]; then
    docker stop my_image
    else
      if [ "$(docker ps | grep -i my_image | awk '{print $10 }')" = 'my_image' ]; then
        echo "Container is running"
      else
        docker-compose -f /home/mark/my_image/docker-compose.yml up -d
    fi
fi

There is a bit of complexity in your systemd script that needs some clearing up. One in particular is that I don’t think that you should classify your script as “forking” – defining it as “simple” should be adequate.

I ran your script through chatgpt and this is the resulting script:

[Unit]
Description=Connecting VPN via PIA
Wants=piavpn.service
After=piavpn.service
StartLimitIntervalSec=0

[Service]
Type=simple
ExecStart=/bin/bash -c '/opt/piavpn/bin/piactl connect; \
        while true; do \
        check_connection_state() { /opt/piavpn/bin/piactl get connectionstate; } \
        if [ "$(check_connection_state)" == "Connected" ]; then \
                echo "PIA - Connected"; break; \
        fi; \
        echo "Waiting for connection..."; sleep 5; \
        done; \
        while true; do \
        if [ "$(check_connection_state)" == "Connected" ]; then \
                echo "PIA - Still Connected"; \
                sleep 15; \
        else \
                echo "PIA - Disconnected; broken"; \
                exit 1; \
        fi; \
        done'
ExecStop=/bin/bash -c '/opt/piavpn/bin/piactl disconnect; \
        while true; do \
        if [ "$(check_connection_state)" == "Disconnected" ]; then \
                echo "PIA - Disconnected"; \
                exit 0; \
        else \
                echo "PIA - Still Connected"; \
        fi; \
        sleep 5; \
        done'
Restart=always
#RemainAfterExit=yes

[Install]
WantedBy=default.target

1 Like

Thanks for this!! I made a few changes since the last few days where I had created 2 services for my PIA and the other for my torrent.

My torrent service was inspired by your Docker where it’ll deactivate when it detects that PIA is disconnected. My PIA service is running alongside pia_activate.timer, and similar setup for qbittorrent.

Is that ideal? It’s working so far, but I’d love your 2c on that