pikvm/ustreamer

init.d and systemctl scripts

djkraven opened this issue · 24 comments

Has anyone put together scripts to start, restart, and stop the service so ustreamer could start at boot or easily managed?

Systemctl service configuration
Start ustreamer at boot, restart, check status. This process is designed to be able to start multiple cameras with one service script using an arguments.

Preparation
Confirm ustreamer is in the path to be used and has necessary permissions:

$ ls -l /usr/bin/ustreamer
-rwxr-xr-x 1 root root 253268 Nov 16 21:18 /usr/bin/ustreamer

Create a system user for better security and not run as root:

$ sudo useradd -r ustreamer

Add the system user to the group video allowing the system user access to the device:

$ sudo usermod -a -G video ustreamer

... or manually edit /etc/group.

systemctl creation
Create new service file with @ in the name to use start arguments:

$ sudo vi /etc/systemd/system/ustreamer@.service

Here is an example. You will need to update the options as needed for your setup and use:

[Unit]
Description=uStreamer service
After=network.target
[Service]
Environment="SCRIPT_ARGS=%I"
User=ustreamer
ExecStart=/usr/bin/ustreamer --process-name-prefix ustreamer-%I --log-level 0 --device /dev/video%I --device-timeout=8  --quality 100 --resolution 1920x1080 --desired-fps=29 --host=0.0.0.0 --port=808%I --static /var/www/html/ustreamer-%I/
[Install]
WantedBy=multi-user.target

Enable the service:

$ sudo systemctl enable ustreamer@.service

Enable the service with argument for starting at boot:

$ sudo systemctl enable ustreamer@0.service

Start the service:

$ sudo systemctl start ustreamer@0.service

The number after the ustreamer@ is the webcam device ex: /dev/video0, /dev/video1, etc. This number can also be used in the other options such as --port, --static, etc.

Verify added service is working:

$ sudo systemctl status ustreamer@0.service
● ustreamer@0.service - uStreamer-0 service
   Loaded: loaded (/etc/systemd/system/ustreamer@.service; disabled; vendor preset: enabled)
   Active: active (running) since Tue 2020-11-17 09:07:49 EST; 9s ago
 Main PID: 9901 (main)
    Tasks: 7 (limit: 4915)
   CGroup: /system.slice/system-ustreamer.slice/ustreamer@0.service
           └─9901 ustreamer-0: /usr/bin/ustreamer --process-name-prefix ustreamer-0 --log-level 0 --device /dev/video0 --device-timeout=8 
--quality 100 --resolution 1920x1080 --desired-fps=29 --host=0.0.0.0 --port=8080 --static /var/wwww/html/ustreamer-%I/

Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39419.673    stream] -- Using TV standard: DEFAULT
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39419.690    stream] -- Using resolution: 1920x1080
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39419.690    stream] -- Using pixelformat: YUYV
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39419.708    stream] -- Using HW FPS: 29 -> 5 (coerced)
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39419.708    stream] -- Using IO method: MMAP
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39419.749    stream] -- Requested 5 device buffers, got 5
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39420.123    stream] -- Capturing started
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39420.123    stream] -- Using JPEG quality: 100%
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39420.123    stream] -- Creating pool with 4 workers ...
Nov 17 09:07:55 hostname ustreamer[9901]: -- INFO  [39420.124    stream] -- Capturing ...
lines 1-18/18 (END)

You might want to make sure that each usb port has a specific camera. This is easy to do with udev:

https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name

https://wiki.archlinux.org/index.php/Udev#Setting_static_device_names

For example: https://github.com/pikvm/kvmd/blob/master/configs/os/udev/v0-vga-rpi3.rules#L3

Looks like I answered your question :) I've added a link to this issue to README for those who will encounter this problem.

Here's a udev rules file I wrote for my C920. I put it in /etc/udev/rules.d/99-video-c920.rules, working on Raspbian buster.

# reference: https://man7.org/linux/man-pages/man7/udev.7.html

ACTION=="remove", GOTO="end"

SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="082d", ATTRS{serial}=="XXXXXXXX", ATTR{index}="0", GOTO="is_rear"
GOTO="end"

LABEL="is_rear"
RUN="/usr/bin/logger -t udev /dev/video-rear connected"
OWNER="root",GROUP="video",MODE="0660",SYMLINK="video-rear"
RUN+="/usr/bin/v4l2-ctl --device /dev/video-rear --set-ctrl=exposure_auto_priority=0 --set-ctrl=exposure_auto=1 --set-ctrl=white_balance_temperature_auto=0 --set-ctrl=power_line_frequency=0 --set-ctrl=focus_auto=0"
RUN+="/usr/bin/v4l2-ctl --device /dev/video-rear --set-ctrl=exposure_absolute=55 --set-ctrl=gain=0 --set-ctrl=white_balance_temperature=3100 --set-ctrl=focus_absolute=25 --set-ctrl=sharpness=224"

LABEL="end"

Hi! Thank you for the systemctl script, it works great! Do you know if I can create a systemd socket unit instead of a systemd service unit? I want it to be activated when requested and deactivated after the client disconnects. Is that possible? Thanks!

Sup. This is not supported now because it requires some code on the part of ustreamer.

Ok, anyway without it works great! Thanks!

@cthulux please create the new issue for this feature, I will add it. I can't promise how soon, but I'll try to find the time.

@cthulux Implemented socket activation. Please try it from master branch. Build with make WITH_SYSTEMD=1 and use startup command ustreamer --systemd.

@cthulux upd: added option --exit-on-no-clients=N. N is the number of seconds without clients after which ustreamer will finish execution.

Thank you @mdevaev ! This is great! I had an issue I solved by installing libsystemd-dev (just commenting it for anyone else that wants to try it). I'll try it and come back to let you know how it was. Thanks!

Hi @mdevaev, I've been trying to follow online tutorials in order to create the systemd socket file with the new options but I was not very sucessful, I don't know what is missing:

cat /lib/systemd/system/rpicam@.service
[Unit]
Description=RaspberryPi camera stream
After=network.target
Wants=network.target
Requires=rpicam.socket

[Service]
Type=simple
User=ustreamer
#StandardOutput=socket
PrivateUsers=yes
DeviceAllow=/dev/video0
PrivateTmp=yes
ProtectHome=yes
Restart=on-abnormal
RestartSec=20

ExecStart=/usr/local/bin/ustreamer --process-name-prefix ustreamer-%I --log-level 0 --drop-same-frames=30 --desired-fps 6 --device-timeout=8 --buffers=3 -r 1296x972 --device=/dev/video%I --quality 80 --slowdown --systemd --host 127.0.0.1 --port=8089

[Install]
WantedBy=multi-user.target

And the systemd socket file is:

cat /lib/systemd/system/rpicam.socket
[Unit]
Description=CUPS Scheduler
PartOf=rpicam@0.service

[Socket]
ListenStream=/run/ustreamer.sock
SocketUser=ustreamer

Accept=yes

[Install]
WantedBy=sockets.target

Thanks!

Remove --host 127.0.0.1 --port=8089

Also remove Accept=yes

Hi @mdevaev , I followed your suggestions and had to change the service template file to service file only because there was some issue with the template I couldn't manage. Finaly I was able to make the sytemd socket file run without issues but I couldn't connect to it.

I am using Haproxy in order to add TLS encryption and authentication. Previous to adding the systemd socket file, It listened in port 8090 and forwarded it to ustreamer localhost port 8089.

Now I adapted it so it listens on the tcp unix path but I am unable to see any video since it looks like the systemd service is not trigered or I don't know what.

This is the Haproxy which worked previously with "server rpicam 127.0.0.1:8089" and now for the socket systed file I changed to "server rpicam /run/ustreamer.sock":

userlist rpicamcredentials
   user USERNAME password HASHED_PASSWORD

frontend rpilibcam
    bind-process 2-3
    bind :8090 tfo ssl crt /etc/haproxy/certs/ process 2-3 alpn h2,http/1.1 curves X25519:P-256:secp384r1
    bind abns@haproxy-clt7 accept-proxy tfo ssl crt /etc/haproxy/certs/ process 2-3 alpn h2,http/1.1 curves X25519:P-256:secp384r1
    mode http
    option forwardfor
    http-request redirect scheme https unless { ssl_fc }
    http-request auth unless { http_auth(rpicamcredentials) }
    default_backend rpicam_8089

backend rpicam_8089
    mode http
    option forwardfor
    retry-on all-retryable-errors
    #server rpicam 127.0.0.1:8089
    server rpicam /run/ustreamer.sock

This is the systemd service file:

cat /lib/systemd/system/ustreamer.service
[Unit]
Description=Ustreamer RaspberryPi camera stream
After=network.target ustreamer.socket
Wants=network.target
Requires=ustreamer.socket

[Service]
#Environment="SCRIPT_ARGS=%I" 
Type=simple
User=ustreamer
#StandardOutput=socket
PrivateUsers=yes
DeviceAllow=/dev/video0
PrivateTmp=yes
ProtectHome=yes
Restart=on-abnormal
RestartSec=20

ExecStart=/usr/local/bin/ustreamer --process-name-prefix ustreamer-0 --log-level 0 --drop-same-frames=30 --desired-fps 6 --device-timeout=8 --buffers=3 -r 1296x972 --device=/dev/video0 --quality 80 --slowdown --systemd

[Install]
WantedBy=multi-user.target

And this is the systemd socket file:

cat /lib/systemd/system/ustreamer.socket
[Unit]
Description=Ustreamer socket unit
PartOf=ustreamer.service

[Socket]
ListenStream=/run/ustreamer.sock
SocketUser=ustreamer
SocketMode=755

[Install]
WantedBy=sockets.target

I added ustreamer user for both systemd unit files, also added haproxy user to the ustreamer group and just in case made the ustreamer socket permissions 755 so haproxy can read the socket (with option SocketMode=755).

ls -lrth /run/ | grep -i ustreamer
srwxr-xr-x  1 ustreamer ustreamer    0 Feb 14 11:57 ustreamer.sock

After I login with the credentials I defined in haproxy basic authentication to https://host:8090 I can see the error
503 Service Unavailable

Do you know if there is another way (instead of Haproxy) to test if the /run/ustreamer.sock.

If this is something dificult, I can use the previous configuration without socket file which worked really fine.

Thanks a lot!

Try curl --unix /run/ustreamer.sock http://127.0.0.1:8080/state.

I used the systemd debugging tool to check. Everything works:

$ systemd-socket-activate -l /tmp/foo.sock ./ustreamer --systemd
$ curl --unix /tmp/foo.sock http://127.0.0.1:8080/state

Thank you @mdevaev! It works! Now I'll have to figure out myself howto make it work with Haproxy. :)

Glad to hear it!

I'm having some trouble trying to parse the comments here and am not sure if I am having the same issues that others are having.

after enabling the script with systemctl I recieve this error:
Failed to enable unit: File multi-user.target: Identifier removed
I found people discussing changing the target to default.target which caused an error when enabling the script but by the command to the following I could enable, start and get the status with log files indicating it was working:
sudo systemctl enable ustreamer@user.service

Unfortunately when I try to connect to the specified port I can't connect as I can when I manually start ustreamer. Do you have any suggestions? Thank you!

I tried setting up the socket but failed, anyone care to give me a step-by-step example? Would be an nice addition to the otherwise well written guide.

Hi @ZyberSE, It is a bit difficult for me to answer this since I am not currently using it.

This are the instructions I was able to recover from what I did to get it working:

Step 1: Download latest version and compile with "WITH_SYSTEMD=1". You don't need to use git, since current releases may include the systemd compilation flag. At the time I did it, I had to use git due to it was a new feature @mdevaev implemented:

git fetch --depth=1 https://github.com/pikvm/ustreamer
make WITH_SYSTEMD=1
make install

If you need to update it later:

git fetch
git reset --hard HEAD
git merge '@{u}'
make WITH_SYSTEMD=1
make install

Create specific camera user:

sudo useradd -r ustreamer; sudo usermod -a -G video ustreamer

Check if you have the systemd services for ustreamer:

root@rpi4:/home/pi# systemctl list-unit-files | grep -i ustream
ustreamer.service                      disabled        enabled
ustreamer@.service                     disabled        enabled
ustreamer.socket                       masked          enabled
root@rpi4:/home/pi#

If you have those, then just start/enable the socket:

systemctl daemon-reload && sudo systemctl enable –now ustreamer.socket

Else, create the systemd files. Here you are some systemd examples (they are overloaded with options that certainly are not needed and others may not be up to date, that's why I said examples):

root@rpi4:/home/pi# systemctl cat ustreamer@.service

# /lib/systemd/system/ustreamer@.service
[Unit]
Description=Ustreamer RaspberryPi camera stream
After=network.target 
Wants=network.target

[Service]
Environment="SCRIPT_ARGS=%I"
Type=simple
User=ustreamer
PrivateUsers=yes
DeviceAllow=/dev/video0
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
Restart=on-abnormal
RestartSec=5
NoNewPrivileges=yes

# SIN systemd socket #
ExecStart=/usr/local/bin/ustreamer --process-name-prefix ustreamer-%I --log-level 0 --drop-same-frames=30 --desired-fps 6 --device-timeout=8 --buffers=3 -r 1296x972 --d>

[Install]
WantedBy=multi-user.target

root@rpi4:/home/pi# systemctl cat ustreamer.service

# /lib/systemd/system/ustreamer.service
[Unit]
Description=Ustreamer RaspberryPi camera stream
After=network.target ustreamer.socket
Wants=network.target
Requires=ustreamer.socket
KillMode=control-group

[Service]
#Environment="SCRIPT_ARGS=%I"
NoNewPrivileges=yes
Type=simple
User=ustreamer
ProcSubset=pid
ProtectProc=noaccess
ProtectClock=yes
#StandardOutput=socket
PrivateUsers=yes
DeviceAllow=/dev/video0
InaccessiblePaths=/etc/
InaccessiblePaths=/boot/
InaccessiblePaths=/home/
PrivateTmp=yes
ProtectHome=tmpfs
ProtectSystem=strict
#ProtectHome=yes
Restart=on-abnormal
#RestrictNetworkInterfaces=lo
PrivateNetwork=yes
RestartSec=10
#NoExecPaths=/
UMask=0007
#ExecPaths=/usr/local/bin/ustreamer
ExecStart=/usr/local/bin/ustreamer --process-name-prefix ustreamer-0 --log-level 0 --drop-same-frames=30 --desired-fps 6 --device-timeout=8 --buffers=3 -r 1296x972 --device=/dev/video0 --quality 80 --slowdown --exit-on-no-clients=300 --systemd

[Install]
WantedBy=multi-user.target

This is were you create the socket file, please edit and replace "/var/lib/haproxy/ustreamer.sock" with your socket name
root@rpi4:/home/pi# cat /lib/systemd/system/ustreamer.socket

[Unit]
Description=Ustreamer socket unit
PartOf=ustreamer.service

[Socket]
#Please, your socket here!
ListenStream=/var/lib/haproxy/ustreamer.sock
SocketUser=ustreamer
SocketMode=006

[Install]
WantedBy=sockets.target

I tried setting up the socket but failed, anyone care to give me a step-by-step example? Would be an nice addition to the otherwise well written guide.

I'm having some trouble trying to parse the comments here and am not sure if I am having the same issues that others are having.

after enabling the script with systemctl I recieve this error: Failed to enable unit: File multi-user.target: Identifier removed I found people discussing changing the target to default.target which caused an error when enabling the script but by the command to the following I could enable, start and get the status with log files indicating it was working: sudo systemctl enable ustreamer@user.service

Unfortunately when I try to connect to the specified port I can't connect as I can when I manually start ustreamer. Do you have any suggestions? Thank you!

Hi! I just saw your question today, are you still facing this issue?

Hi. I Found this thread and decided to create my own autostart script. After some time i manage to make full working and enabling on start. Wanted to share it. I'm running OctoPrint on LUbuntu. Video is from USB camera

[Unit]
Description=uStreamer service
After=network-online.target
Wants=network-online.target // Those two are very important! If u wont add it script doesn't turn on system boot!
Restart=on-failure
[Service]
DeviceAllow=/dev/video0
User=[username]
ExecStart= [_Here i put path to ./ustreamer script. In my case it's "/home/me/ustreamer/src/ustreamer.bin"  with addons like --device=/dev/video0 etc._]
[Install]
WantedBy=default.target

All other things like creating etc. same as @mdevaev mentioned in first post (btw thx for it). If u're gonna use only one camera it better to create script without "@" allias.

I slapped together a quick nixos module for this with socket activation, since there wasn't one in nixpkgs, and figured I'd share it here!

{
  config,
  lib,
  pkgs,
  ...
}:
with lib;
let
  cfg = config.services.ustreamer;
in
{
  options = {
    services.ustreamer = {
      enable = mkEnableOption "ustreamer webcam streamer";

      user = mkOption {
        type = types.str;
        default = "ustreamer";
        description = "ustreamer user name.";
      };

      group = mkOption {
        type = types.str;
        default = "video";
        description = "ustreamer group name.";
      };

      listenStream = mkOption {
        type = types.str;
        default = "/run/ustreamer.sock";
        description = "ustreamer socket.";
      };

      extraArgs = mkOption {
        type = types.str;
        default = "";
        description = "ustreamer cli args";
      };
    };
  };

  config = mkIf cfg.enable {
    users.users = optionalAttrs (cfg.user == "ustreamer") {
      ustreamer = {
        group = cfg.group;
        isSystemUser = true;
      };
    };

    systemd.services.ustreamer = {
      description = "ustreamer webcam streamer";
      after = [
        "network.target"
        "ustreamer.socket"
      ];
      requires = [ "ustreamer.socket" ];
      serviceConfig = {
        User = cfg.user;
        Group = cfg.group;
        ExecStart = "${pkgs.ustreamer}/bin/ustreamer --systemd --exit-on-no-clients 300 ${cfg.extraArgs}";
      };
    };

    systemd.sockets.ustreamer = {
      description = "ustreamer webcam socket";
      partOf = [ "ustreamer.service" ];
      wantedBy = [ "sockets.target" ];
      listenStreams = [ cfg.listenStream ];
    };
  };
}

Then you can use it with nginx+mainsail like this!

services.ustreamer = {
  enable = true;
  extraArgs = "--resolution 1280x720";
};
services.nginx.upstreams.ustreamer = {
  servers = {
    "unix:${config.services.ustreamer.listenStream}" = { };
  };
};
services.mainsail = {
  enable = true;
  nginx = {
    # ...
    locations."/webcam/" = {
      proxyPass = "http://ustreamer/";
      extraConfig = ''
        postpone_output 0;
        proxy_buffering off;
        proxy_ignore_headers X-Accel-Buffering;
      '';
    };
  };
};

@jacob-swanson, your config looks good to me. You should submit it to nixpkgs and help everyone out.