Merge claude/1
This commit is contained in:
commit
2d9450fed4
2
init.sh
2
init.sh
@ -720,7 +720,7 @@ initUsers()
|
||||
initRootHelpers()
|
||||
{
|
||||
local helper helper_src helper_dst helper_tmp
|
||||
for helper in libreportal-ownership libreportal-dns libreportal-ssh-access; do
|
||||
for helper in libreportal-ownership libreportal-dns libreportal-ssh-access libreportal-socket libreportal-svc; do
|
||||
helper_src="$script_dir/scripts/system/$helper"
|
||||
helper_dst="/usr/local/sbin/$helper"
|
||||
if [[ ! -f "$helper_src" ]]; then
|
||||
|
||||
@ -119,6 +119,14 @@ runResolv() { _runRootHelper libreportal-dns "$@"; }
|
||||
# key-add <b64>|key-remove <fp>|pw-set <on|off>}
|
||||
runSshAccess() { _runRootHelper libreportal-ssh-access "$@"; }
|
||||
|
||||
# Docker-socket read perms for the type switcher: {rootless|rooted} {on|off}
|
||||
# (exit 3 = socket absent).
|
||||
runSocket() { _runRootHelper libreportal-socket "$@"; }
|
||||
|
||||
# Install/refresh the systemd task-processor unit (root generates the unit from
|
||||
# config; no caller-supplied content): {install|enable|restart|start|status}
|
||||
runSvc() { _runRootHelper libreportal-svc "$@"; }
|
||||
|
||||
# Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc
|
||||
# edits). Needs real root in both modes; funnelled through one place so it can
|
||||
# later be confined to a scoped sudoers allowlist.
|
||||
|
||||
@ -13,22 +13,21 @@ dockerSwitcherSetSocketPermissions()
|
||||
|
||||
isHeader "Docker Socket Checker"
|
||||
|
||||
# The chmods run in the root-owned socket helper (runSocket), which computes
|
||||
# the socket paths itself and returns 3 when the socket is absent — so the
|
||||
# *_found flags (read downstream by stop_docker/swap_docker_type) come from
|
||||
# its exit code, no privileged `test -e` needed.
|
||||
if [[ $CFG_DOCKER_INSTALL_TYPE == "rooted" ]]; then
|
||||
if [[ $docker_rootless_exist == "false" ]]; then
|
||||
# if File exists
|
||||
if runSystem test -e "$docker_rootless_socket"; then
|
||||
local result=$(runSystem chmod o-r "$docker_rootless_socket")
|
||||
if runSocket rootless off; then
|
||||
checkSuccess "Removing read permissions from Rootless docker socket."
|
||||
docker_rootless_found="true"
|
||||
else
|
||||
#isSuccessful "Rootless socket not found, no need to do anything with rootless setup."
|
||||
docker_rootless_found="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
# if File exists
|
||||
if runSystem test -e "$docker_rooted_socket"; then
|
||||
local result=$(runSystem chmod +r "$docker_rooted_socket")
|
||||
if runSocket rooted on; then
|
||||
checkSuccess "Adding read permissions to Rooted docker socket."
|
||||
docker_rooted_found="true"
|
||||
else
|
||||
@ -38,19 +37,14 @@ dockerSwitcherSetSocketPermissions()
|
||||
fi
|
||||
|
||||
if [[ $CFG_DOCKER_INSTALL_TYPE == "rootless" ]]; then
|
||||
# if File exists
|
||||
if runSystem test -e "$docker_rooted_socket"; then
|
||||
local result=$(runSystem chmod o-r "$docker_rooted_socket")
|
||||
if runSocket rooted off; then
|
||||
checkSuccess "Removing read permissions from Rooted docker socket."
|
||||
docker_rooted_found="true"
|
||||
else
|
||||
#isSuccessful "Rooted socket not found, no need to do anything with rooted setup."
|
||||
docker_rooted_found="false"
|
||||
fi
|
||||
|
||||
# if File exists
|
||||
if runSystem test -e "$docker_rootless_socket"; then
|
||||
local result=$(runSystem chmod +r "$docker_rootless_socket")
|
||||
if runSocket rootless on; then
|
||||
checkSuccess "Adding read permissions to Rootless docker socket."
|
||||
docker_rootless_found="true"
|
||||
else
|
||||
|
||||
@ -10,7 +10,7 @@ fixPermissionsBeforeStart()
|
||||
fi
|
||||
|
||||
fixAppFolderPermissions;
|
||||
changeRootOwnedFile $docker_dir/$db_file $sudo_user_name
|
||||
runOwnership db-own
|
||||
|
||||
# The regenerable WebUI dir is reconciled to the mode's container owner via
|
||||
# the shared helper (same code path as install + switch). Third-party app
|
||||
@ -22,12 +22,12 @@ fixPermissionsBeforeStart()
|
||||
|
||||
# Traefik
|
||||
if [ -f "${containers_dir}traefik/etc/certs/acme.json" ]; then
|
||||
updateFileOwnership "${containers_dir}traefik/etc/certs/acme.json" $docker_install_user $docker_install_user
|
||||
runOwnership app-file traefik etc/certs/acme.json
|
||||
local result=$(runFileOp chmod 600 "${containers_dir}traefik/etc/certs/acme.json")
|
||||
checkSuccess "Set permissions to acme.json file for traefik"
|
||||
fi
|
||||
if [ -f "${containers_dir}traefik/etc/traefik.yml" ]; then
|
||||
updateFileOwnership "${containers_dir}traefik/etc/traefik.yml" $docker_install_user $docker_install_user
|
||||
runOwnership app-file traefik etc/traefik.yml
|
||||
local result=$(runFileOp chmod 600 "${containers_dir}traefik/etc/traefik.yml")
|
||||
checkSuccess "Set permissions to traefik.yml file for traefik"
|
||||
fi
|
||||
|
||||
@ -130,6 +130,14 @@ webui() {
|
||||
[[ -d "$WEBUI_DIR" ]] && chown -R "$cowner:$cowner" "$WEBUI_DIR"
|
||||
}
|
||||
|
||||
# Ensure the apps DB is manager-owned + world-readable (reclaims a stray
|
||||
# root/other-owned DB; the WebUI container reads it).
|
||||
db_own() {
|
||||
[[ -f "$DB_PATH" ]] || return 0
|
||||
chown "$MANAGER:$MANAGER" "$DB_PATH"
|
||||
chmod o+r "$DB_PATH"
|
||||
}
|
||||
|
||||
# Structural containers/ top dir only -> container owner + traversable.
|
||||
containers_top() {
|
||||
local mode cowner; mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||
@ -151,14 +159,29 @@ app_data_nobody() {
|
||||
[[ -d "$d/data" ]] && chown -R 65534:65534 "$d/data"
|
||||
}
|
||||
|
||||
# Chown one LibrePortal-managed file under an app dir to the container owner
|
||||
# (e.g. traefik acme.json / traefik.yml the container creates as another uid).
|
||||
# relpath is validated: no traversal, no absolute path, safe charset only.
|
||||
app_file() {
|
||||
local d rel mode cowner
|
||||
d="$(_app_dir "${1:-}")" || return 1
|
||||
rel="${2:-}"
|
||||
[[ -n "$rel" && "$rel" != /* && "$rel" != *..* && "$rel" =~ ^[A-Za-z0-9._/-]+$ ]] \
|
||||
|| { echo "libreportal-ownership: invalid relpath" >&2; return 1; }
|
||||
mode="$(_mode)"; cowner="$(_container_owner "$mode")"
|
||||
[[ -e "$d/$rel" ]] && chown "$cowner:$cowner" "$d/$rel"
|
||||
}
|
||||
|
||||
action="${1:-}"; shift 2>/dev/null || true
|
||||
case "$action" in
|
||||
reconcile) reconcile "${1:-}";;
|
||||
traversal) traversal;;
|
||||
containers-top) containers_top;;
|
||||
db-own) db_own;;
|
||||
app-perms) app_perms;;
|
||||
webui) webui;;
|
||||
taskdir) taskdir;;
|
||||
app-data-nobody) app_data_nobody "${1:-}";;
|
||||
*) echo "usage: libreportal-ownership {reconcile [mode]|traversal|containers-top|app-perms|webui|taskdir|app-data-nobody <app>}" >&2; exit 2;;
|
||||
app-file) app_file "${1:-}" "${2:-}";;
|
||||
*) echo "usage: libreportal-ownership {reconcile [mode]|traversal|containers-top|db-own|app-perms|webui|taskdir|app-data-nobody <app>|app-file <app> <relpath>}" >&2; exit 2;;
|
||||
esac
|
||||
|
||||
38
scripts/system/libreportal-socket
Normal file
38
scripts/system/libreportal-socket
Normal file
@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# LibrePortal docker-socket permission helper — the only root-privileged chmod of
|
||||
# the docker sockets the manager may trigger (the type switcher hides/exposes the
|
||||
# inactive/active mode's socket). Installed root:root 0755 to /usr/local/sbin by
|
||||
# init.sh. Self-contained; the socket paths are computed here (never caller-
|
||||
# supplied), so the scoped sudoers can allow it instead of blanket `sudo chmod`.
|
||||
#
|
||||
# Exit: 0 = socket found + chmod'd, 3 = socket absent (caller treats as not-found).
|
||||
|
||||
set -u
|
||||
|
||||
[[ $EUID -eq 0 ]] || { echo "libreportal-socket: must run as root" >&2; exit 1; }
|
||||
|
||||
DB_CFG="/docker/configs/general/general_docker_install"
|
||||
ROOTED_SOCK="/var/run/docker.sock"
|
||||
|
||||
_rootless_sock() {
|
||||
local u uid
|
||||
u=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
|
||||
[[ -n "$u" ]] || return 1
|
||||
uid=$(id -u "$u" 2>/dev/null) || return 1
|
||||
printf '/run/user/%s/docker.sock' "$uid"
|
||||
}
|
||||
|
||||
which="${1:-}"; state="${2:-}"
|
||||
case "$which" in
|
||||
rootless) sock="$(_rootless_sock)" || exit 3 ;;
|
||||
rooted) sock="$ROOTED_SOCK" ;;
|
||||
*) echo "usage: libreportal-socket {rootless|rooted} {on|off}" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
[[ -e "$sock" ]] || exit 3
|
||||
|
||||
case "$state" in
|
||||
on) chmod +r "$sock" ;;
|
||||
off) chmod o-r "$sock" ;;
|
||||
*) echo "usage: libreportal-socket {rootless|rooted} {on|off}" >&2; exit 2 ;;
|
||||
esac
|
||||
92
scripts/system/libreportal-svc
Normal file
92
scripts/system/libreportal-svc
Normal file
@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# LibrePortal task-processor systemd helper — the only root-privileged management
|
||||
# of the libreportal.service unit the manager may trigger. Installed root:root
|
||||
# 0755 to /usr/local/sbin by init.sh. Self-contained: it GENERATES the unit from
|
||||
# config (mode + install-user uid + the baked manager name + fixed script paths)
|
||||
# — it does NOT accept unit content from the caller (that would be root: an
|
||||
# arbitrary systemd unit runs anything as root). So the scoped sudoers can allow
|
||||
# it instead of blanket `sudo tee /etc/systemd/...` + `sudo systemctl`.
|
||||
#
|
||||
# Idempotent: only rewrites + daemon-reloads + restarts when the unit changed,
|
||||
# else just ensures it's enabled + running (no needless restart of in-flight work).
|
||||
|
||||
set -u
|
||||
|
||||
[[ $EUID -eq 0 ]] || { echo "libreportal-svc: must run as root" >&2; exit 1; }
|
||||
|
||||
MANAGER="__MANAGER__"
|
||||
[[ "$MANAGER" == "__MANAGER__" || -z "$MANAGER" ]] && MANAGER="libreportal"
|
||||
|
||||
SERVICE_FILE="/etc/systemd/system/libreportal.service"
|
||||
INSTALL_SCRIPTS_DIR="/docker/install/scripts"
|
||||
TASK_PROCESSOR="$INSTALL_SCRIPTS_DIR/crontab/task/crontab_task_processor.sh"
|
||||
DB_CFG="/docker/configs/general/general_docker_install"
|
||||
|
||||
_mode() {
|
||||
local m
|
||||
m=$(grep -h '^CFG_DOCKER_INSTALL_TYPE=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
|
||||
echo "${m:-rootless}"
|
||||
}
|
||||
|
||||
_gen_unit() {
|
||||
local env_block=""
|
||||
if [[ "$(_mode)" == "rootless" ]]; then
|
||||
local u uid
|
||||
u=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
|
||||
uid=$(id -u "${u:-dockerinstall}" 2>/dev/null)
|
||||
if [[ -n "$uid" ]]; then
|
||||
env_block="Environment=DOCKER_HOST=unix:///run/user/${uid}/docker.sock
|
||||
Environment=XDG_RUNTIME_DIR=/run/user/${uid}"
|
||||
fi
|
||||
fi
|
||||
cat <<EOF
|
||||
[Unit]
|
||||
Description=LibrePortal Task Processor
|
||||
After=network.target
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$MANAGER
|
||||
Group=$MANAGER
|
||||
WorkingDirectory=$INSTALL_SCRIPTS_DIR
|
||||
ExecStart=$TASK_PROCESSOR start_script
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
SyslogIdentifier=libreportal
|
||||
${env_block}
|
||||
|
||||
# Security
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
}
|
||||
|
||||
install_unit() {
|
||||
local desired current=""
|
||||
desired="$(_gen_unit)"
|
||||
[[ -f "$SERVICE_FILE" ]] && current="$(cat "$SERVICE_FILE" 2>/dev/null)"
|
||||
if [[ "$desired" != "$current" ]]; then
|
||||
printf '%s\n' "$desired" > "$SERVICE_FILE"
|
||||
systemctl daemon-reload
|
||||
systemctl enable libreportal.service >/dev/null 2>&1
|
||||
systemctl restart libreportal.service
|
||||
echo "updated"
|
||||
else
|
||||
systemctl enable libreportal.service >/dev/null 2>&1
|
||||
systemctl is-active --quiet libreportal.service || systemctl start libreportal.service
|
||||
echo "unchanged"
|
||||
fi
|
||||
}
|
||||
|
||||
action="${1:-}"
|
||||
case "$action" in
|
||||
install) install_unit ;;
|
||||
enable) systemctl enable libreportal.service >/dev/null 2>&1 ;;
|
||||
restart) systemctl restart libreportal.service ;;
|
||||
start) systemctl start libreportal.service ;;
|
||||
status) systemctl is-active libreportal.service ;;
|
||||
*) echo "usage: libreportal-svc {install|enable|restart|start|status}" >&2; exit 2 ;;
|
||||
esac
|
||||
@ -13,11 +13,11 @@ installLibrePortalWebUITaskService()
|
||||
{
|
||||
[[ "$CFG_REQUIREMENT_WEBUI_SERVICE" == "true" ]] || return 0
|
||||
|
||||
local service_file="/etc/systemd/system/libreportal.service"
|
||||
local task_processor_script="$install_scripts_dir/crontab/task/crontab_task_processor.sh"
|
||||
local task_dir="$containers_dir/libreportal/frontend/data/tasks"
|
||||
|
||||
# Point the processor at the task dir (idempotent).
|
||||
# Point the processor at the task dir (idempotent). This edits the
|
||||
# manager-owned install tree, so no privilege is needed.
|
||||
if [ -f "$task_processor_script" ]; then
|
||||
sed -i "s|TASK_DIR=\".*\"|TASK_DIR=\"$task_dir\"|g" "$task_processor_script"
|
||||
chmod +x "$task_processor_script"
|
||||
@ -25,66 +25,23 @@ installLibrePortalWebUITaskService()
|
||||
isNotice "Task processor script not found"
|
||||
fi
|
||||
|
||||
# Rootless docker exposes the daemon at /run/user/<uid>/docker.sock and depends
|
||||
# on XDG_RUNTIME_DIR being set. Systemd units don't inherit user bashrc, so
|
||||
# without these Environment= lines the processor would fall back to
|
||||
# /var/run/docker.sock (which rootless does not create). The rootless daemon
|
||||
# runs as the DOCKER INSTALL USER, so its socket lives in THAT user's runtime
|
||||
# dir (matches dockerCommandRunInstallUser). Rooted gets no extras — the
|
||||
# default /var/run path is already correct.
|
||||
local service_env_block=""
|
||||
if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then
|
||||
local docker_install_uid
|
||||
docker_install_uid="$(id -u "$CFG_DOCKER_INSTALL_USER")"
|
||||
service_env_block="Environment=DOCKER_HOST=unix:///run/user/${docker_install_uid}/docker.sock
|
||||
Environment=XDG_RUNTIME_DIR=/run/user/${docker_install_uid}"
|
||||
fi
|
||||
|
||||
local desired
|
||||
desired="$(cat <<EOF
|
||||
[Unit]
|
||||
Description=LibrePortal Task Processor
|
||||
After=network.target
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$sudo_user_name
|
||||
Group=$sudo_user_name
|
||||
WorkingDirectory=$install_scripts_dir
|
||||
ExecStart=$task_processor_script start_script
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
SyslogIdentifier=libreportal
|
||||
${service_env_block}
|
||||
|
||||
# Security
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
)"
|
||||
|
||||
local current=""
|
||||
[[ -f "$service_file" ]] && current="$(runSystem cat "$service_file" 2>/dev/null)"
|
||||
|
||||
if [[ "$desired" != "$current" ]]; then
|
||||
printf '%s\n' "$desired" | runSystem tee "$service_file" > /dev/null
|
||||
runSystem systemctl daemon-reload
|
||||
runSystem systemctl enable libreportal.service >/dev/null 2>&1
|
||||
runSystem systemctl restart libreportal.service
|
||||
# The unit itself is generated + installed by the root-owned svc helper (it
|
||||
# reads the mode + install-user uid from config to build the rootless
|
||||
# DOCKER_HOST/XDG_RUNTIME_DIR Environment= lines). Idempotent: only restarts on
|
||||
# an actual change, so a rooted<->rootless switch re-reads the new mode without
|
||||
# bouncing the processor on routine re-runs.
|
||||
local svc_result
|
||||
svc_result="$(runSvc install)"
|
||||
if [[ "$svc_result" == "updated" ]]; then
|
||||
isSuccessful "LibrePortal task processor service installed/updated ($CFG_DOCKER_INSTALL_TYPE)."
|
||||
else
|
||||
# Unit already correct — ensure it's enabled + running, without a restart.
|
||||
runSystem systemctl enable libreportal.service >/dev/null 2>&1
|
||||
runSystem systemctl is-active --quiet libreportal.service || runSystem systemctl start libreportal.service
|
||||
isSuccessful "LibrePortal task processor service already up to date."
|
||||
fi
|
||||
|
||||
# Drop the legacy crontab entry if present (superseded by the service).
|
||||
if sudo -u "$sudo_user_name" crontab -l 2>/dev/null | grep -q "task_processor.sh"; then
|
||||
sudo -u "$sudo_user_name" crontab -l 2>/dev/null | grep -v "task_processor.sh" | sudo -u "$sudo_user_name" crontab -
|
||||
# Drop the legacy crontab entry if present (superseded by the service). We are
|
||||
# the manager, so operate on its own crontab directly.
|
||||
if crontab -l 2>/dev/null | grep -q "task_processor.sh"; then
|
||||
crontab -l 2>/dev/null | grep -v "task_processor.sh" | crontab -
|
||||
isNotice "Removed task processor from crontab"
|
||||
fi
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user