Резервное копирование bj на SMB: настройки (хранилище, расписание, хранение), копия сейчас, список и проверка копий; восстановление из копии на шаге 1 мастера; таймер bj-backup, smbclient в установщике
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
+1
-1
@@ -6,5 +6,5 @@ import "embed"
|
||||
|
||||
// Files — linux/sbin/* (в /usr/local/sbin) и systemd/*.service (в /etc/systemd/system).
|
||||
//
|
||||
//go:embed linux/sbin/* systemd/bj-server.service systemd/bj-crypto.service systemd/bj-keymedia.service systemd/bj-npr.service systemd/bj-quikexport.service systemd/bj-lk.service systemd/bj-welcome.service systemd/bj-welcome.timer
|
||||
//go:embed linux/sbin/* systemd/bj-server.service systemd/bj-crypto.service systemd/bj-keymedia.service systemd/bj-npr.service systemd/bj-quikexport.service systemd/bj-lk.service systemd/bj-welcome.service systemd/bj-welcome.timer systemd/bj-backup.service systemd/bj-backup.timer
|
||||
var Files embed.FS
|
||||
|
||||
@@ -19,7 +19,7 @@ OUT=${2:?выходной каталог}
|
||||
[ "$(id -u)" = 0 ] || { echo "нужен root"; exit 1; }
|
||||
for c in mksquashfs xorriso; do command -v $c >/dev/null || { echo "нужен $c"; exit 1; }; done
|
||||
# пакеты — как в bj-installer (stepPackages)
|
||||
BJ_PKGS="ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3"
|
||||
BJ_PKGS="ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3 smbclient"
|
||||
LAYER=ubuntu-server-minimal.ubuntu-server.bj.squashfs
|
||||
|
||||
mkdir -p "$OUT"
|
||||
|
||||
@@ -59,7 +59,7 @@ d-i partman-md/device_remove_md boolean true
|
||||
|
||||
# Пакеты: без графики; SSH и всё, что нужно bj, ставится сразу
|
||||
tasksel tasksel/first multiselect standard, ssh-server
|
||||
d-i pkgsel/include string ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3 sudo
|
||||
d-i pkgsel/include string ca-certificates curl postgresql postgresql-client default-jre-headless pcscd python3 smbclient sudo
|
||||
d-i pkgsel/upgrade select safe-upgrade
|
||||
popularity-contest popularity-contest/participate boolean false
|
||||
|
||||
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# bj-backup — резервная копия сервера bj на сетевое хранилище (SMB): базы PostgreSQL bj и igate,
|
||||
# данные bj (/var/lib/bj/.bj, /etc/bj) и шлюза НРД (/var/lib/igate без журналов).
|
||||
# Куда, как часто и сколько хранить — /etc/bj/backup.conf (пишет bj-backup-admin из настроек bj),
|
||||
# логин хранилища — /etc/bj/backup-smb.cred (только root). Итог — /var/lib/bj/backup-status.json.
|
||||
set -uo pipefail
|
||||
CONF=/etc/bj/backup.conf
|
||||
CRED=/etc/bj/backup-smb.cred
|
||||
STATUS=/var/lib/bj/backup-status.json
|
||||
[ -r "$CONF" ] && . "$CONF"
|
||||
SHARE=${SHARE:-}
|
||||
DIR=${DIR:-BJ_Backup/bj}
|
||||
KEEP_DAYS=${KEEP_DAYS:-14}
|
||||
TS=$(date +%Y-%m-%d_%H%M)
|
||||
W=$(mktemp -d /var/tmp/bj-backup.XXXXXX)
|
||||
trap 'rm -rf "$W"' EXIT
|
||||
cd /
|
||||
status() { python3 - "$STATUS" "$1" "$DIR/$TS" "$2" <<'PY'
|
||||
import json,sys,datetime
|
||||
p,ok,d,msg=sys.argv[1:]
|
||||
json.dump({"at":datetime.datetime.now().astimezone().isoformat(timespec="seconds"),"ok":ok=="true","dir":d,"msg":msg},open(p,"w"),ensure_ascii=False)
|
||||
PY
|
||||
chown bj:bj "$STATUS" 2>/dev/null || true; }
|
||||
fail() { status false "$1"; echo "ОШИБКА: $1" >&2; exit 1; }
|
||||
smb() { smbclient "$SHARE" -A "$CRED" -m SMB3 -c "$1"; }
|
||||
# tar: «файл изменился во время чтения» (код 1) для работающих служб — не ошибка
|
||||
tarok() { tar "$@"; local rc=$?; [ $rc -le 1 ]; }
|
||||
|
||||
[ -n "$SHARE" ] || fail "не задано хранилище (Настройка → Резервное копирование)"
|
||||
[ -r "$CRED" ] || fail "нет логина хранилища (Настройка → Резервное копирование)"
|
||||
|
||||
runuser -u postgres -- pg_dump -Fc bj > "$W/bj.pgdump" || fail "дамп базы bj"
|
||||
if runuser -u postgres -- psql -Atc "select 1 from pg_database where datname='igate'" 2>/dev/null | grep -q 1; then
|
||||
runuser -u postgres -- pg_dump -Fc igate > "$W/igate.pgdump" || fail "дамп базы igate"
|
||||
fi
|
||||
tarok -C / -czf "$W/bj-data.tar.gz" --exclude=etc/bj/backup-smb.cred var/lib/bj/.bj etc/bj || fail "архив данных bj"
|
||||
if [ -d /var/lib/igate ]; then
|
||||
tarok -C / -czf "$W/igate-data.tar.gz" --warning=no-file-changed --exclude='var/lib/igate/.cache' \
|
||||
--exclude='var/lib/igate/NSD Integration Gateway/logs*' --exclude='var/lib/igate/logs-cli' var/lib/igate || fail "архив шлюза НРД"
|
||||
fi
|
||||
cat > "$W/backup-info.txt" <<INFO
|
||||
Bridge&Join — резервная копия сервера $(hostname) от $(date '+%d.%m.%Y %H:%M:%S')
|
||||
Версия bj: $(/opt/bj/bj-server -version 2>/dev/null | head -1)
|
||||
Восстановление: мастер настройки → «Восстановить из резервной копии» или bj-backup-admin restore.
|
||||
INFO
|
||||
(cd "$W" && sha256sum * > SHA256SUMS)
|
||||
SIZE=$(du -sh "$W" | cut -f1)
|
||||
|
||||
top=${DIR%%/*}
|
||||
smb "mkdir \"$top\"; mkdir \"$DIR\"" >/dev/null 2>&1 || true
|
||||
smb "mkdir \"$DIR/$TS\"; cd \"$DIR/$TS\"; lcd $W; prompt; mput *" >/dev/null || fail "запись на $SHARE/$DIR"
|
||||
for f in $(cd "$W" && ls); do
|
||||
smb "cd \"$DIR/$TS\"; ls \"$f\"" >/dev/null 2>&1 || fail "на хранилище нет $f"
|
||||
done
|
||||
|
||||
CUT=$(date -d "-$KEEP_DAYS days" +%Y-%m-%d_%H%M)
|
||||
smb "cd \"$DIR\"; ls" 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4}$' | while read -r d; do
|
||||
if [[ "$d" < "$CUT" ]]; then
|
||||
smb "cd \"$DIR/$d\"; prompt; mask *; mdel *; cd ..; rmdir \"$d\"" >/dev/null 2>&1 && echo "удалена старая копия $d"
|
||||
fi
|
||||
done
|
||||
status true "размер $SIZE"
|
||||
echo "копия $DIR/$TS готова ($SIZE)"
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
# bj-backup-admin — управление резервным копированием bj (вызывает root-демон bj-keymedia по запросу из
|
||||
# настроек bj). Параметры — JSON в BJ_EXTRA: share, dir, user, password, every_hours, keep_days, enabled, name.
|
||||
# config — сохранить хранилище, расписание и хранение; включить/выключить таймер
|
||||
# test — проверить доступ на запись к хранилищу
|
||||
# run — сделать копию сейчас (в фоне)
|
||||
# list — список копий на хранилище
|
||||
# verify — проверить копию name: скачать и сверить контрольные суммы (ничего не восстанавливает)
|
||||
# restore — восстановить копию name (в фоне: останавливает bj и шлюз НРД)
|
||||
set -uo pipefail
|
||||
CONF=/etc/bj/backup.conf
|
||||
CRED=/etc/bj/backup-smb.cred
|
||||
RSTATUS=/var/lib/bj/restore-status.json
|
||||
cmd=${1:-}
|
||||
j() { python3 -c "import json,os,sys;print(json.loads(os.environ.get('BJ_EXTRA') or '{}').get(sys.argv[1],''))" "$1"; }
|
||||
q() { python3 -c "import json,sys;print(json.dumps(sys.argv[1],ensure_ascii=False))" "$1"; }
|
||||
ok() { echo "{\"ok\":true,\"output\":$(q "$1")}"; }
|
||||
err() { echo "{\"ok\":false,\"error\":$(q "$1")}"; exit 0; }
|
||||
[ -r "$CONF" ] && . "$CONF"
|
||||
# хранилище из запроса (восстановление на новом сервере, проверка до сохранения) или из настроек
|
||||
SHARE_IN=$(j share); DIR_IN=$(j dir); USER_IN=$(j user); PASS_IN=$(j password)
|
||||
SHARE=${SHARE_IN:-${SHARE:-}}; DIR=${DIR_IN:-${DIR:-BJ_Backup/bj}}
|
||||
case "$SHARE" in //*) ;; \\\\*) SHARE=$(printf '%s' "$SHARE" | tr '\\' '/') ;; ?*) SHARE="//$SHARE" ;; esac
|
||||
TMPCRED=""
|
||||
credfile() {
|
||||
if [ -n "$USER_IN" ] && [ -n "$PASS_IN" ]; then
|
||||
TMPCRED=$(mktemp /run/bj-backup-cred.XXXXXX); chmod 600 "$TMPCRED"
|
||||
printf 'username=%s\npassword=%s\n' "$USER_IN" "$PASS_IN" > "$TMPCRED"; echo "$TMPCRED"
|
||||
else echo "$CRED"; fi
|
||||
}
|
||||
trap '[ -n "$TMPCRED" ] && rm -f "$TMPCRED"' EXIT
|
||||
# smbclient ставит установщик; на серверах, установленных раньше, доставляем при первом обращении
|
||||
case "$cmd" in config|test|list|verify|restore)
|
||||
command -v smbclient >/dev/null 2>&1 || DEBIAN_FRONTEND=noninteractive apt-get install -y -qq smbclient >/dev/null 2>&1
|
||||
command -v smbclient >/dev/null 2>&1 || err "на сервере нет smbclient и его не удалось установить (apt-get install smbclient)" ;;
|
||||
esac
|
||||
smbx() { smbclient "$SHARE" -A "$1" -m SMB3 -c "$2" 2>&1; }
|
||||
# расписание таймера: каждые N часов (24 — раз в сутки в 02:00)
|
||||
schedule() {
|
||||
install -d -m 755 /etc/systemd/system/bj-backup.timer.d
|
||||
if [ "$1" = 24 ]; then cal='*-*-* 02:00:00'; else cal="*-*-* 00/$1:00:00"; fi
|
||||
printf '[Timer]\nOnCalendar=\nOnCalendar=%s\n' "$cal" > /etc/systemd/system/bj-backup.timer.d/schedule.conf
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
case "$cmd" in
|
||||
config)
|
||||
every=$(j every_hours); keep=$(j keep_days); en=$(j enabled)
|
||||
case "$every" in 1|2|3|4|6|8|12|24) ;; *) every=6 ;; esac
|
||||
[[ "$keep" =~ ^[0-9]+$ ]] && [ "$keep" -ge 1 ] || keep=14
|
||||
[ -n "$SHARE" ] || err "не указано хранилище"
|
||||
install -d -m 755 /etc/bj
|
||||
umask 077
|
||||
case "$en" in True|true) enabled=1 ;; *) enabled=0 ;; esac
|
||||
printf 'SHARE=%q\nDIR=%q\nKEEP_DAYS=%s\nEVERY_HOURS=%s\nENABLED=%s\n' "$SHARE" "$DIR" "$keep" "$every" "$enabled" > "$CONF"
|
||||
if [ -n "$USER_IN" ] && [ -n "$PASS_IN" ]; then
|
||||
printf 'username=%s\npassword=%s\n' "$USER_IN" "$PASS_IN" > "$CRED"; chmod 600 "$CRED"
|
||||
elif [ -n "$USER_IN" ] && [ -r "$CRED" ]; then # сменили только логин — пароль прежний
|
||||
sed -i "s/^username=.*/username=$USER_IN/" "$CRED"
|
||||
fi
|
||||
schedule "$every"
|
||||
if [ "$enabled" = 1 ]; then
|
||||
systemctl enable --now bj-backup.timer >/dev/null 2>&1 || err "не удалось включить таймер bj-backup.timer"
|
||||
ok "сохранено: $SHARE/$DIR, каждые $every ч, хранение $keep дн., расписание включено"
|
||||
else
|
||||
systemctl disable --now bj-backup.timer >/dev/null 2>&1
|
||||
ok "сохранено, расписание выключено"
|
||||
fi ;;
|
||||
test)
|
||||
[ -n "$SHARE" ] || err "не указано хранилище"
|
||||
c=$(credfile); [ -r "$c" ] || err "не указан логин и пароль хранилища"
|
||||
f=/tmp/bj-backup-test.$$; echo test > "$f"; top=${DIR%%/*}
|
||||
smbx "$c" "mkdir \"$top\"; mkdir \"$DIR\"" >/dev/null
|
||||
out=$(smbx "$c" "cd \"$DIR\"; put $f bj-write-test.txt; del bj-write-test.txt"); rc=$?; rm -f "$f"
|
||||
if [ $rc -ne 0 ] || printf '%s' "$out" | grep -q "NT_STATUS"; then err "нет доступа на запись в $SHARE/$DIR: $(printf '%s' "$out" | grep -o 'NT_STATUS[A-Z_]*' | head -1)"; fi
|
||||
ok "доступ на запись к $SHARE/$DIR есть" ;;
|
||||
run)
|
||||
systemctl start --no-block bj-backup.service || err "не удалось запустить bj-backup.service"
|
||||
ok "копирование запущено" ;;
|
||||
list)
|
||||
[ -n "$SHARE" ] || err "не указано хранилище"
|
||||
c=$(credfile); [ -r "$c" ] || err "не указан логин и пароль хранилища"
|
||||
out=$(smbx "$c" "cd \"$DIR\"; ls")
|
||||
printf '%s' "$out" | grep -q "NT_STATUS" && err "хранилище: $(printf '%s' "$out" | grep -o 'NT_STATUS[A-Z_]*' | head -1)"
|
||||
names=$(printf '%s\n' "$out" | awk '{print $1}' | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4}$' | sort -r | head -200)
|
||||
echo "{\"ok\":true,\"list\":$(python3 -c 'import json,sys;print(json.dumps([l for l in sys.stdin.read().split() if l]))' <<< "$names")}" ;;
|
||||
verify)
|
||||
name=$(j name)
|
||||
[[ "$name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4}$ ]] || err "не выбрана копия"
|
||||
[ -n "$SHARE" ] || err "не указано хранилище"
|
||||
c=$(credfile); [ -r "$c" ] || err "не указан логин и пароль хранилища"
|
||||
W=$(mktemp -d /var/tmp/bj-verify.XXXXXX); trap 'rm -rf "$W"; [ -n "$TMPCRED" ] && rm -f "$TMPCRED"' EXIT
|
||||
out=$(smbclient "$SHARE" -A "$c" -m SMB3 -c "cd \"$DIR/$name\"; lcd $W; prompt; mget *" 2>&1)
|
||||
printf '%s' "$out" | grep -q "NT_STATUS" && err "не удалось скачать копию $name: $(printf '%s' "$out" | grep -o 'NT_STATUS[A-Z_]*' | head -1)"
|
||||
[ -s "$W/SHA256SUMS" ] || err "в копии $name нет SHA256SUMS — копия неполная"
|
||||
(cd "$W" && sha256sum -c SHA256SUMS >/dev/null 2>&1) || err "контрольные суммы копии $name не сходятся — копия повреждена"
|
||||
[ -s "$W/bj.pgdump" ] && [ -s "$W/bj-data.tar.gz" ] || err "в копии $name нет базы или данных bj"
|
||||
pg_restore -l < "$W/bj.pgdump" >/dev/null 2>&1 || err "база bj в копии $name не читается"
|
||||
tar -tzf "$W/bj-data.tar.gz" >/dev/null 2>&1 || err "архив данных bj в копии $name не читается"
|
||||
ok "копия $name цела: $(cd "$W" && ls | grep -vc SHA256SUMS) файлов, $(du -sh "$W" | cut -f1), контрольные суммы сходятся, база и архивы читаются" ;;
|
||||
restore)
|
||||
name=$(j name)
|
||||
[[ "$name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4}$ ]] || err "не выбрана копия"
|
||||
[ -n "$SHARE" ] || err "не указано хранилище"
|
||||
c=$(credfile); [ -r "$c" ] || err "не указан логин и пароль хранилища"
|
||||
rc=/run/bj-restore.cred; cp "$c" "$rc"; chmod 600 "$rc"
|
||||
printf 'SHARE=%q\nDIR=%q\nNAME=%q\n' "$SHARE" "$DIR" "$name" > /run/bj-restore.conf; chmod 600 /run/bj-restore.conf
|
||||
echo '{"stage":"start","msg":"восстановление запущено"}' > "$RSTATUS"; chown bj:bj "$RSTATUS" 2>/dev/null
|
||||
systemd-run --unit="bj-restore-$(date +%s)" --collect /usr/local/sbin/bj-backup-admin restore-now >/dev/null 2>&1 || err "не удалось запустить восстановление"
|
||||
ok "восстановление копии $name запущено: bj перезапустится" ;;
|
||||
restore-now)
|
||||
. /run/bj-restore.conf
|
||||
st() { python3 - "$RSTATUS" "$1" "$2" <<'PY'
|
||||
import json,sys,datetime
|
||||
json.dump({"at":datetime.datetime.now().astimezone().isoformat(timespec="seconds"),"stage":sys.argv[2],"msg":sys.argv[3]},open(sys.argv[1],"w"),ensure_ascii=False)
|
||||
PY
|
||||
chown bj:bj "$RSTATUS" 2>/dev/null; }
|
||||
die() { st error "$1"; rm -f /run/bj-restore.cred /run/bj-restore.conf; systemctl start igate bj-server >/dev/null 2>&1
|
||||
for u in bj-npr bj-lk; do systemctl is-enabled "$u" >/dev/null 2>&1 && systemctl start "$u" >/dev/null 2>&1; done; exit 1; }
|
||||
W=$(mktemp -d /var/tmp/bj-restore.XXXXXX)
|
||||
st download "скачивание копии $NAME"
|
||||
smbclient "$SHARE" -A /run/bj-restore.cred -m SMB3 -c "cd \"$DIR/$NAME\"; lcd $W; prompt; mget *" >/dev/null 2>&1 || die "не удалось скачать копию $NAME"
|
||||
(cd "$W" && sha256sum -c SHA256SUMS >/dev/null 2>&1) || die "контрольные суммы копии не сходятся — копия повреждена"
|
||||
[ -s "$W/bj.pgdump" ] && [ -s "$W/bj-data.tar.gz" ] || die "в копии нет базы или данных bj"
|
||||
st stop "остановка bj и шлюза НРД"
|
||||
systemctl stop bj-lk bj-npr bj-server igate >/dev/null 2>&1
|
||||
st db "восстановление базы bj"
|
||||
# каталог копии доступен только root — дамп подаём postgres через stdin; сообщения — по-английски для разбора ошибок
|
||||
LC_ALL=C PGOPTIONS='-c lc_messages=C' runuser -u postgres -- pg_restore --clean --if-exists -d bj < "$W/bj.pgdump" >/dev/null 2>"$W/pg.err"
|
||||
# «does not exist» при --clean на пустой базе — не ошибка; остальное — ошибка
|
||||
if grep -i "error" "$W/pg.err" | grep -vqi "does not exist"; then die "база bj: $(grep -i error "$W/pg.err" | grep -vi 'does not exist' | head -1)"; fi
|
||||
if [ -s "$W/igate.pgdump" ] && runuser -u postgres -- psql -Atc "select 1 from pg_database where datname='igate'" | grep -q 1; then
|
||||
st db "восстановление базы шлюза НРД"
|
||||
LC_ALL=C PGOPTIONS='-c lc_messages=C' runuser -u postgres -- pg_restore --clean --if-exists -d igate < "$W/igate.pgdump" >/dev/null 2>&1
|
||||
fi
|
||||
st files "восстановление данных bj и шлюза НРД"
|
||||
tar -C / -xzf "$W/bj-data.tar.gz" || die "распаковка данных bj"
|
||||
[ -s "$W/igate-data.tar.gz" ] && { tar -C / -xzf "$W/igate-data.tar.gz" || die "распаковка данных шлюза НРД"; }
|
||||
# копия хранилища в восстановленных настройках есть, а пароль в неё не входит — берём тот, с которым
|
||||
# восстанавливали, и возвращаем расписание копий
|
||||
if [ -r "$CONF" ]; then
|
||||
install -m 600 /run/bj-restore.cred "$CRED"
|
||||
(. "$CONF"; schedule "${EVERY_HOURS:-6}"; [ "${ENABLED:-1}" = 1 ] && systemctl enable bj-backup.timer >/dev/null 2>&1)
|
||||
fi
|
||||
rm -f /run/bj-restore.cred
|
||||
st start "запуск служб"
|
||||
systemctl start igate >/dev/null 2>&1
|
||||
systemctl start bj-server >/dev/null 2>&1
|
||||
systemctl is-enabled bj-backup.timer >/dev/null 2>&1 && systemctl start bj-backup.timer >/dev/null 2>&1
|
||||
for u in bj-npr bj-lk; do systemctl is-enabled "$u" >/dev/null 2>&1 && systemctl start "$u" >/dev/null 2>&1; done
|
||||
rm -rf "$W" /run/bj-restore.conf
|
||||
st done "восстановлено из копии $NAME" ;;
|
||||
*) err "неизвестная команда: $cmd" ;;
|
||||
esac
|
||||
@@ -11,6 +11,8 @@ process_one(){
|
||||
case "$action" in
|
||||
quikexport-*) out=$(BJ_EXTRA="$extra" /usr/local/sbin/bj-quikexport-admin "${action#quikexport-}" "$staging" 2>&1) ;;
|
||||
component-install) out=$(/usr/local/sbin/bj-component-install "$staging" 2>&1) ;;
|
||||
# резервное копирование и восстановление (настройки bj → bj-backup-admin); параметры — JSON в extra
|
||||
backup-config|backup-test|backup-run|backup-list|backup-verify|backup-restore) out=$(BJ_EXTRA="$extra" /usr/local/sbin/bj-backup-admin "${action#backup-}" 2>&1) ;;
|
||||
# Перезапуск службы модуля из админки. Список закрытый: чужую службу
|
||||
# (и сам этот демон) отсюда тронуть нельзя.
|
||||
unit-restart)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Bridge&Join: резервная копия на сетевое хранилище
|
||||
After=network-online.target postgresql.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/bj-backup
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Bridge&Join: резервная копия по расписанию (расписание — Настройка → Резервное копирование)
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00/6:00:00
|
||||
RandomizedDelaySec=5min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user