diff --git a/setup/start/sudoers b/setup/start/sudoers index 3f41023d6..c7402dba5 100644 --- a/setup/start/sudoers +++ b/setup/start/sudoers @@ -71,4 +71,7 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/hdparm.sh Defaults!/home/yellowtent/box/src/scripts/hdparm.sh env_keep="HOME BOX_ENV" yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/logtail.sh +Defaults!/home/yellowtent/box/src/scripts/kill-child.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/kill-child.sh + cloudron-support ALL=(ALL) NOPASSWD: ALL diff --git a/src/logs.js b/src/logs.js index c643be6e9..1eee5f58a 100644 --- a/src/logs.js +++ b/src/logs.js @@ -9,6 +9,7 @@ const assert = require('assert'), TransformStream = stream.Transform; const LOGTAIL_CMD = path.join(__dirname, 'scripts/logtail.sh'); +const KILL_CHILD_CMD = path.join(__dirname, 'scripts/kill-child.sh'); class LogStream extends TransformStream { constructor(options) { @@ -68,13 +69,12 @@ function tail(filePaths, options) { if (options.follow) args.push('--follow'); if (options.sudo) { - const cp = child_process.spawn('/usr/bin/sudo', [ '-S', LOGTAIL_CMD, ...args, ...filePaths ]); - cp.terminate = () => { // see note in shell.js - child_process.spawn('kill', ['-SIGKILL', -cp.pid], { detached: true }, (error) => { - if (error) debug(`tail could not terminate`, error); + const cp = child_process.spawn('/usr/bin/sudo', [ LOGTAIL_CMD, ...args, ...filePaths ]); + cp.terminate = () => { + child_process.execFile('/usr/bin/sudo', [ KILL_CHILD_CMD, cp.pid, process.pid ], { encoding: 'utf8' }, (error, stdout, stderr) => { + if (error) debug(`tail: failed to kill children`, stdout, stderr); }); }; - cp.stdin.end(); return cp; } else { const cp = child_process.spawn('/usr/bin/tail', args.concat(filePaths)); @@ -96,7 +96,7 @@ function journalctl(unit, options) { if (options.follow) args.push('--follow'); - const cp = spawn('journalctl', args); + const cp = child_process.spawn('journalctl', args); cp.terminate = () => cp.kill('SIGKILL'); return cp; } diff --git a/src/scripts/kill-child.sh b/src/scripts/kill-child.sh new file mode 100755 index 000000000..38af0686c --- /dev/null +++ b/src/scripts/kill-child.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -eu -o pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "This script should be run as root." > /dev/stderr + exit 1 +fi + +if [[ $# -eq 0 ]]; then + echo "No arguments supplied" + exit 1 +fi + +if [[ "$1" == "--check" ]]; then + echo "OK" + exit 0 +fi + +function killtree() { + local pid=$1 + for cpid in $(pgrep -P "$pid"); do + killtree "${cpid}" || true + done + echo "kill-child: killing $pid" + kill -SIGKILL "${pid}" 2>/dev/null || true +} + +readonly target_pid="$1" +readonly expected_parent_pid="$2" + +readonly target_actual_parent_pid=$(ps -o ppid= -p "${target_pid}" 2>/dev/null | tr -d ' ') + +if [[ -z "${target_actual_parent_pid}" ]]; then + echo "kill-child: target PID ${target_pid} does not exist." + exit 1 +fi + +if [[ "${target_actual_parent_pid}" -ne "${expected_parent_pid}" ]]; then + echo "kill-child: refusing to kill — PID ${target_pid} is not a child of ${expected_parent_pid}." + exit 1 +fi + +readonly child_cmd=$(ps -o cmd= -p "${target_pid}") +echo "kill-child: kill PID ${target_pid} (command: ${child_cmd})" +killtree ${target_pid}