Running a Complex Python Script as a Systemd Service: Best Practices and Troubleshooting

Salam everyone,

I’m trying to run a Python script as a systemd service. I asked ChatGPT for help and got this snippet:

# The-p*r-blocker
systemd.services.pornblocker = {
  description = "Porn-Blocker";
  after = [ "network.target" ];
  wantedBy = [ "multi-user.target" ];

  serviceConfig = {
    ExecStart = "/home/mwalid/Security/prn-blocker/run.sh";
    User = "root";
    Group = "root";
    Restart = "always";

    WorkingDirectory = "/home/mwalid/Security/prn-blocker";
  };

  path = [ pkgs.bash pkgs.nix ];
};

As the snippet suggests, the process seems simple. There is a shell script (run.sh) responsible for running a nix-shell with the necessary packages for my script to run properly. However, the problem is that while the run.sh script runs without any issues in my normal user environment, it doesn’t work in the service environment.

How can I solve this problem and what are the best practices for this setup?

Additionally, as the service name suggests, this is a Porn blocker. All the files for the script are in a folder owned by root, and I want the service to work for all users currently running.

Here is the content of the run.sh script:

#!/usr/bin/env bash

# Ensure nix-shell is run and wait for it to finish setting up the environment
nix-shell /home/mwalid/Security/prn-blocker/shell.nix --run "python /home/mwalid/Security/prn-blocker/main.py"

And the content of the shell.nix file:

{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = [
    pkgs.python310
    pkgs.poetry
    pkgs.zlib
    pkgs.glib
    pkgs.gcc
    pkgs.grim # screenshot taking
    pkgs.libnotify # notify-send
    pkgs.stdenv.cc.cc
  ];
  LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib";
  shellHook = ''
    echo 'You are now in the >>> Haram Blocker for Linux development environment.'

    export VENV_DIR="/home/mwalid/Security/prn-blocker/.venv"

    echo 'Done setting the poetry venvs path.'

    poetry config virtualenvs.in-project true

    if [ ! -d "$VENV_DIR" ]; then
      poetry env use python3.10
      poetry install
    fi

    source "$VENV_DIR/bin/activate"

    # Set environment variables for grim to access the user's display
    export WAYLAND_DISPLAY=/run/user/1000/wayland-1
    export XDG_RUNTIME_DIR=/run/user/1000

    echo 'Environment variables for Wayland display set.'
  '';
}

Logs:

Jun 18 12:00:22 mohamedwalid-laptop systemd[1]: Started Porn-Blocker.
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]: error:
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:        … while calling the 'import' builtin
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:          at /home/mwalid/Security/prn-blocker/shell.nix:1:10:
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:             1| { pkgs ? import <nixpkgs> {} }:
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:              |          ^
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:             2|
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:        … while calling the 'findFile' builtin
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:          at /home/mwalid/Security/prn-blocker/shell.nix:1:17:
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:             1| { pkgs ? import <nixpkgs> {} }:
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:              |                 ^
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:             2|
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:        error: file 'nixpkgs' was not found in the Nix search path (add it using $NIX_PATH or -I)
Jun 18 12:00:22 mohamedwalid-laptop run.sh[69475]:        at «none»:0: (source not available)
Jun 18 12:00:22 mohamedwalid-laptop systemd[1]: pornblocker.service: Main process exited, code=exited, status=1/FAILURE
Jun 18 12:00:22 mohamedwalid-laptop systemd[1]: pornblocker.service: Failed with result 'exit-code'.

If there are any alternative methods to run my code without needing to refactor it or change its environment, please let me know.

Thanks in advance for any guidance and help you can provide!

:wave: mwalid207!

One practice I have found helpful is to use the nix-shell interpreter trick.
This allows you to write scripts using bash, python or your favorite dynamic interpreter.
I recommend the nix.dev tutorial on the matter for a better explanation and a demonstration of how to use this technique: Reproducible interpreted scripts — nix.dev documentation

I don’t have experience with NixOS generating systemd service definitions.
Though I do have some experience authoring systemd services by hand.
You might consider using systemctl cat command. It prints out the full systemd configuration and can be a helpful debugging tool.

For content filtering, I find unbound and GitHub - StevenBlack/hosts: 🔒 Consolidating and extending hosts files from several well-curated sources. Optionally pick extensions for porn, social media, and other categories. very helpful.

  • The problem with this blocking method is that it blocks one source of the bad content, but not all. So my method which is -for my use case the best method- to take screenshots from all the displays and screens then analyse the content using an ML model then depending on the result will be action, By that whatever the source it will be blocked.
  • Thanks to ALLAH, The script is working and doing what I expected and explained above, But the problem is now that I’m trying to secure it because Me the enemy :expressionless: , Please if you or any good man like you can help me Or even If you know a good man like you once more :slight_smile: that can help me even for money, please let me know.

:slight_smile: The problem was solved thanks to ALLAH, by exporting my normal user env by running this command in the place that your command runs well into -in my case- in the terminal envprint then using it in the service through EnvironmentFile = 'path-to-the-env-file' .