首页 > 解决方案 > bash 脚本在终端中运行,但不使用 systemd 自动启动

问题描述

我正在尝试在启动时将 bash 脚本作为 systemd 服务运行,我在带有 Raspbian Buster Lite 的 Raspberry Pi 4 上执行此操作。

如果我手动运行它./hls.sh,我可以执行 bash 脚本,如果我这样做,我也可以运行该服务,sudo service tv start但是 tv.service 似乎无法hls.sh在启动时执行 bash 脚本。我确实也授予chmod 777了服务和 bash 文件的权限。

在这里的任何帮助将不胜感激,我已经尝试解决这个问题一个月了。

编辑:使用卡尔的建议,我修改了文件。但是,它仍然不起作用。我还注意到,当 Type=oneshot 时,您无法执行 Restart=always,如果我执行此操作,则会restorecon -r出现一个错误,提示找不到该命令。根据 Carl 的建议,我将所有内容都放入 /opt 我决定不使用临时文件建议,因为我需要每次都将 ffmpeg 输出转到同一个地方(我的理解是另一种方式会随机生成一个文件夹?)。

Edit2:问题与此 systemd 问题有关:https : //unix.stackexchange.com/questions/209832/debian-systemd-network-online-target-not-working 解决方法只是在 [Service] 下执行 RestartSec=5s

Bash 脚本 [之前]

#!/bin/bash
/usr/bin/ffmpeg -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2 -y -nostdin \
-hide_banner -loglevel fatal \
-i http://10.0.0.11:9981/stream/channelnumber/2 \
-vcodec copy -acodec copy -scodec copy -g 60 \
-fflags +genpts -user_agent HLS_delayer \
-metadata service_provider="TimeShift" \
-metadata service_name="TV 1" \
-f hls -hls_flags delete_segments \
-hls_time 60 \
-hls_list_size 480 \
-hls_wrap 481 \
-hls_segment_filename /home/pi/hls/1_%03d.ts /home/pi/hls/1_hls.m3u8

Bash 脚本 [之后]

#!/usr/bin/env bash
set -e -o pipefail # return exit code of command with non-zero exit code and stop executing
echo "The solution works!"
/usr/bin/ffmpeg -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2 -y -nostdin \
-hide_banner -loglevel fatal \
-i http://10.0.0.11:9981/stream/channelnumber/2 \
-vcodec copy -acodec copy -scodec copy -g 60 \
-fflags +genpts -user_agent HLS_delayer \
-metadata service_provider="TimeShift" \
-metadata service_name="JSTV 1" \
-f hls -hls_flags delete_segments \
-hls_time 60 \
-hls_list_size 480 \
-hls_wrap 481 \
-hls_segment_filename /opt/hls/tmp/1_%03d.ts /opt/hls/tmp/1_hls.m3u8

服务 [之前]

[Unit]
Description=Timeshift TV
After=tvheadend.service
PartOf=tvheadend.service
Restart=always

[Service]
ExecStartPre=/bin/mkdir -p /home/pi/hls/
ExecStart=/home/pi/hls.sh 103 &
ExecStop=/bin/rm -rf /home/pi/hls

[Install]
WantedBy=default.target

服务 [之后]

[Unit]
Description=Timeshift TV
After=tvheadend.service
PartOf=tvheadend.service

[Service]
WorkingDirectory=/opt/hls
User=nobody
Type=oneshot
ExecStartPre=+/bin/mkdir -p -m777 /opt/hls/tmp
ExecStart=/opt/hls/hls.sh
ExecStopPost=+/bin/rm -rf /opt/hls/tmp
#Restart=always

[Install]
WantedBy=multi-user.target

重启后,通过 systemctl status 检查状态 [之前]

● tv.service - Timeshift TV
   Loaded: loaded (/etc/systemd/system/tv.service; enabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Mon 2020-05-18 05:36:40 BST; 45s ago
  Process: 465 ExecStartPre=/bin/mkdir -p -m777 /home/pi/hls/ (code=exited, status=0/SUCCESS)
  Process: 472 ExecStart=/home/pi/hls.sh 103 & (code=exited, status=1/FAILURE)
 Main PID: 472 (code=exited, status=1/FAILURE)

May 18 05:36:39 tv3 systemd[1]: Starting Timeshift TV...
May 18 05:36:39 tv3 systemd[1]: Started Timeshift TV.
May 18 05:36:40 tv3 systemd[1]: tv.service: Main process exited, code=exited, status=1/FAILURE
May 18 05:36:40 tv3 systemd[1]: tv.service: Failed with result 'exit-code'.

重启后,通过 systemctl status 检查状态 [After]

● tv.service - Timeshift tv
   Loaded: loaded (/etc/systemd/system/tv.service; enabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Mon 2020-05-18 20:13:39 BST; 15min ago
  Process: 464 ExecStartPre=/bin/mkdir -p -m777 /opt/hls/tmp (code=exited, status=0/SUCCESS)
  Process: 471 ExecStart=/opt/hls/hls.sh (code=exited, status=1/FAILURE)
  Process: 484 ExecStopPost=/bin/rm -rf /opt/hls/tmp (code=exited, status=0/SUCCESS)
 Main PID: 471 (code=exited, status=1/FAILURE)

May 18 20:13:38 tv3 systemd[1]: Starting Timeshift tv...
May 18 20:13:38 tv3 hls.sh[471]: The solution works!
May 18 20:13:39 tv3 systemd[1]: tv.service: Main process exited, code=exited, status=1/FAILURE
May 18 20:13:39 tv3 systemd[1]: tv.service: Failed with result 'exit-code'.
May 18 20:13:39 tv3 systemd[1]: Failed to start Timeshift tv.

[之后]手动启动该过程仍然有效

● tv2.service - Timeshift tv
   Loaded: loaded (/etc/systemd/system/tv2.service; enabled; vendor preset: enabled)
   Active: activating (start) since Mon 2020-05-18 20:53:13 BST; 17s ago
  Process: 865 ExecStartPre=/bin/mkdir -p -m777 /opt/hls/tmp (code=exited, status=0/SUCCESS)
 Main PID: 866 (bash)
    Tasks: 2 (limit: 4915)
   Memory: 15.3M
   CGroup: /system.slice/tv2.service
           ├─866 bash /opt/hls/hls.sh
           └─867 /usr/bin/ffmpeg -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2 -y -nostdin -hide_banner -logle

May 18 20:53:13 tv3 systemd[1]: Starting Timeshift tv...
May 18 20:53:13 tv3 hls.sh[866]: The solution works!

pi@tv3:/etc/systemd/system $ cd /opt/hls
pi@tv3:/opt/hls $ ls
hls.sh  tmp
pi@tv3:/opt/hls $ cd tmp
pi@tv3:/opt/hls/tmp $ ls
1_000.ts

标签: linuxbashraspberry-piraspbiansystemd

解决方案


此服务文件存在一些问题。我将在下面解决它们,然后展示最终解决方案。

  1. 避免将系统级服务放在/home目录下。虽然它可能有效,但不建议这样做。这样做可能会出现许多超出范围的问题。相反,请考虑将脚本放在/opt/srv/usr/local.
    • 像这样大规模移动文件时,请确保restorecon -r在移动后在该目录上运行。这确保 SELinux 上下文被重置为正确的值,而不是将新上下文与旧上下文混合。当 SELinux 处于强制模式时,这可以帮助解决权限被拒绝错误。
  2. Restart=指令属于[Service]not [Unit],因此应该将其移到那里或完全删除。这可能是无害的,因为 systemd 有时是宽容的,但systemd.service是定义该指令的那个,而不是systemd.unit
  3. 最后改成ExecStart=没有&。只需保留它并将其作为普通脚本运行即可。如果配置正确,Systemd 将处理其余部分。
  4. 改为ExecStop=改为ExecStopPost=。这将允许 systemd 执行其正常的停止行为,即SIGTERM在被要求停止服务时向脚本发送一个。停止完成后(优雅地或强制地),ExecStopPost=将运行。由于您似乎在脚本运行后使用它进行清理,因此应该更改。如果保持原样,systemd 将删除目录,然后等待脚本退出,脚本永远不会这样做,因为它没有被告知,因此 systemd 将在向内核询问SIGKILL脚本之前等待超时时间。绝对不理想。
  5. 下面的一些额外指令[Service]将解决有关此服务应如何运行的一些假设。
    • WorkingDirectory=——所以它是定义的而不是假设的。如果保留为默认值,它将成为 的主目录User,如果也保留默认值,它将是/root. 相反,将其设置为脚本的位置或/tmpif PrivateTmp=true
    • User=— 这样脚本就不会root以默认用户身份运行。我建议nobody或您希望管理ffmpeg执行的任何用户。这可以防止在脚本运行时授予脚本 root 权限,而这是ffmpeg不需要的。
    • Type=——这可以说是最重要的,因为脚本是一次性运行的,成功后会退出;它不会在后台守护并继续像默认simple服务一样运行。这应该设置为,oneshot以便 systemd 知道;它将脚本退出(状态码为 0)视为服务的成功运行,并将继续。
    • PrivateTmp=— 在大多数情况下,应启用此功能。只有在极少数情况下才应禁用它。默认行为被禁用。启用后,脚本尝试存储的任何内容/tmp实际上都将透明地存储在名为 的命名空间目录中/tmp/systemd-private-xxxx-servicename.service-xxxxx/,从而防止与/tmp. 如果您使用它,我会将您更改ffmpeg为写入/tmp而不是管理脚本旁边的目录,从而使您能够ExecStartPre=完全删除ExecStopPost=
  6. 确保ExecStartPre=andExecStopPost=有一个+前置,这样它们就可以运行,root而不是定义User=的可能不是 root 的;这可能是必要的,也可能不是必要的,具体取决于您对目录的期望。有关详细信息,请参阅systemd.service 表 1。特殊的可执行文件前缀
  7. 更改WantedBy=default.targetmulti-user.target; 更有可能应该为该目标运行此服务,而不是默认启动任何目标。
  8. 您可能不应该编写脚本chmod 777,我会选择适度的chmod 755。它应该只需要读取和执行位 ( +rx)。

因此,无需进一步告别,这是最终的解决方案:

目录结构: ls -lZ /opt/sfo-61862759

total 8
-rwxr-xr-x. 1 carl users unconfined_u:object_r:usr_t:s0  58 May 18 01:44 sfo-61862759.bash
-rw-r--r--. 1 carl users unconfined_u:object_r:usr_t:s0 316 May 18 01:53 sfo-61862759.service

外壳脚本: /opt/sfo-61862759/sfo-61862759.bash

#!/usr/bin/env bash
set -e -o pipefail # return exit code of command with non-zero exit code and stop executing
echo "The solution works!"
# put ffmpeg stuff here and remove the echo if you choose

服务文件: /etc/systemd/system/sfo-61862759.service

[Unit]
Description=Stackoverflow Question #61862759 Solution

[Service]
WorkingDirectory=/tmp
User=nobody
Type=oneshot
#ExecStartPre=+/bin/mkdir -p /opt/sfo-61862759/temp
ExecStart=/opt/sfo-61862759/sfo-61862759.bash
#ExecStopPost=+/bin/rm -rf /opt/sfo-61862759/temp
PrivateTmp=true

[Install]
WantedBy=multi-user.target

系统状态输出:

01:53 carl@utility.localdomain:/opt/sfo-61862759$ sudo systemctl start sfo-61862759.service
01:54 carl@utility.localdomain:/opt/sfo-61862759$ sudo systemctl status sfo-61862759.service -n 4
● sfo-61862759.service - Stackoverflow Question #61862759 Solution
     Loaded: loaded (/opt/sfo-61862759/sfo-61862759.service; linked; vendor preset: disabled)
     Active: inactive (dead)

May 18 01:54:02 utility.localdomain systemd[1]: Starting Stackoverflow Question #61862759 Solution...
May 18 01:54:03 utility.localdomain sfo-61862759.bash[459746]: The solution works!
May 18 01:54:03 utility.localdomain systemd[1]: sfo-61862759.service: Succeeded.
May 18 01:54:03 utility.localdomain systemd[1]: Finished Stackoverflow Question #61862759 Solution.

推荐阅读