replace ykrun with runbg for background command execution
... to solve the nvm-sh environment issue.(mostly vscode) NOTE: may also import other envvars, tbc.
This commit is contained in:
210
bin/runbg
Executable file
210
bin/runbg
Executable file
@@ -0,0 +1,210 @@
|
||||
#!/bin/bash
|
||||
#systemd-run --user --scope --slice=YukiLauncher.slice --unit="$1-$$".scope /bin/sh -c '"$@"' _ "$@"
|
||||
|
||||
# goddamn nvm-sh !!!
|
||||
# have to update PATH before launch
|
||||
PATH=$(zsh -c -i 'echo $PATH')
|
||||
export PATH
|
||||
|
||||
# start-bg.sh - robustly start a command in the background and redirect stdout+stderr to a log
|
||||
# Generated by GitHub Copilot.
|
||||
|
||||
# Features:
|
||||
# - Uses setsid (preferred) to detach from the controlling terminal.
|
||||
# - Falls back to nohup if setsid is unavailable.
|
||||
# - Runs the command with stdin closed, stdout+stderr redirected to a logfile.
|
||||
# - Sets a safe umask and changes to / to avoid blocking filesystems.
|
||||
# - Writes a pidfile next to the log for easy management.
|
||||
# - Uses positional args ($@), script PID ($$) and reports child PID ($!).
|
||||
#
|
||||
# Usage:
|
||||
# start-bg.sh [ -l LOGFILE | -d LOGDIR ] [ -m METHOD ] -- command [args...]
|
||||
#
|
||||
# Options:
|
||||
# -l, --log LOGFILE Path to log file (if omitted an auto name is used)
|
||||
# -d, --dir LOGDIR Directory for autogenerated logs (default: ./logs)
|
||||
# -m, --method METHOD Background method: setsid (default) | nohup
|
||||
# -h, --help Show this help and exit
|
||||
#
|
||||
# Examples:
|
||||
# ./start-bg.sh -- sleep 60
|
||||
# ./start-bg.sh -d /var/log/myapp -- /usr/bin/myapp --config /etc/myapp.conf
|
||||
# ./start-bg.sh -l ./my.log -- /usr/bin/myapp arg1 arg2
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
progname=$(basename "$0")
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $progname [ -l LOGFILE | -d LOGDIR ] [ -m METHOD ] -- command [args...]
|
||||
|
||||
Start "command [args...]" detached from the terminal and redirect stdout+stderr to a log.
|
||||
|
||||
METHOD:
|
||||
setsid - use setsid to start a session and detach (preferred)
|
||||
nohup - use nohup (fallback/compat)
|
||||
|
||||
When no -l is provided, a log file is auto-generated:
|
||||
<LOGDIR>/<command>.<YYYYMMDD-HHMMSS>.pid<$$>.log
|
||||
|
||||
A pidfile is written as <logfile>.pid containing the child PID.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Defaults
|
||||
logdir="/tmp/$USER/runner"
|
||||
logfile=""
|
||||
method="setsid"
|
||||
|
||||
# Parse options until the `--` separator
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-l|--log)
|
||||
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
|
||||
logfile="$2"; shift 2 ;;
|
||||
-d|--dir)
|
||||
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
|
||||
logdir="$2"; shift 2 ;;
|
||||
-m|--method)
|
||||
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
|
||||
method="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
usage ;;
|
||||
--)
|
||||
shift; break ;;
|
||||
-*)
|
||||
echo "Unknown option: $1"; usage ;;
|
||||
*)
|
||||
break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Remaining args are the command to run
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Error: no command specified."
|
||||
usage
|
||||
fi
|
||||
|
||||
# Preserve the command and args
|
||||
cmd=( "$@" )
|
||||
cmd_basename=$(basename "${cmd[0]}")
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
mkdir -p -- "${logdir}"
|
||||
|
||||
if [[ -z "${logfile}" ]]; then
|
||||
logfile="${logdir}/${cmd_basename}.${timestamp}.pid$$.log"
|
||||
fi
|
||||
|
||||
pidfile="${logfile}.pid"
|
||||
|
||||
# Validate method
|
||||
case "${method}" in
|
||||
setsid|nohup) ;;
|
||||
*)
|
||||
echo "Invalid method: ${method}. Allowed: setsid|nohup"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare environment for child
|
||||
# We construct a small wrapper for safe daemonization:
|
||||
start_with_setsid() {
|
||||
# Prefer existing setsid binary (coreutils/util-linux)
|
||||
if ! command -v setsid >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Use setsid to start the process in a new session. We:
|
||||
# - set a conservative umask
|
||||
# - change to / to avoid keeping directories busy
|
||||
# - close stdin and redirect stdout/stderr to logfile
|
||||
#
|
||||
# We run setsid directly with the command array so arguments are preserved.
|
||||
#
|
||||
# Note: redirecting >"$logfile" 2>&1 < /dev/null outside ensures the setsid child
|
||||
# inherits the redirections.
|
||||
(
|
||||
umask 022
|
||||
cd / || true
|
||||
# exec will replace the subshell with the command
|
||||
exec "${cmd[@]}"
|
||||
) >"${logfile}" 2>&1 < /dev/null &
|
||||
child_pid=$!
|
||||
|
||||
# Detach the job from this shell's job table if possible
|
||||
disown "$child_pid" 2>/dev/null || true
|
||||
|
||||
# setsid the already-forked child to start a new session. Some systems allow:
|
||||
# setsid -w <pid>
|
||||
# but that's nonportable; instead we try to re-exec the command under setsid.
|
||||
# If `setsid` can't be applied to the backgrounded PID, we respawn via setsid:
|
||||
if setsid true >/dev/null 2>&1; then
|
||||
# Respawn under setsid to ensure proper session leader if available.
|
||||
# Kill the previous child (it is still running) and restart under setsid.
|
||||
# To avoid races: only do this if cmd is still running and the pid we started is a shell wrapper.
|
||||
if kill -0 "$child_pid" 2>/dev/null; then
|
||||
# Attempt to terminate wrapper and spawn a true setsid child.
|
||||
kill "$child_pid" 2>/dev/null || true
|
||||
# Short sleep to allow process cleanup (best-effort)
|
||||
sleep 0.05
|
||||
fi
|
||||
|
||||
# Now start the real setsid-backed child with same redirections
|
||||
setsid "${cmd[@]}" >"${logfile}" 2>&1 < /dev/null &
|
||||
child_pid=$!
|
||||
disown "$child_pid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
printf '%s' "$child_pid"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_with_nohup() {
|
||||
# Use nohup as fallback; nohup will ignore HUP but doesn't start a new session.
|
||||
nohup "${cmd[@]}" >"${logfile}" 2>&1 < /dev/null &
|
||||
child_pid=$!
|
||||
disown "$child_pid" 2>/dev/null || true
|
||||
printf '%s' "$child_pid"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main launcher: pick method and start
|
||||
echo "Launching command: ${cmd[*]}"
|
||||
echo "Log file: ${logfile}"
|
||||
echo "Method: ${method}"
|
||||
|
||||
child_pid=""
|
||||
|
||||
if [[ "${method}" == "setsid" ]]; then
|
||||
# Try to start via setsid; if setsid not present, fallback to nohup
|
||||
if child_pid=$(start_with_setsid); then
|
||||
: # success
|
||||
else
|
||||
echo "setsid not available, falling back to nohup"
|
||||
child_pid=$(start_with_nohup)
|
||||
fi
|
||||
else
|
||||
child_pid=$(start_with_nohup)
|
||||
fi
|
||||
|
||||
# Write pidfile (best-effort)
|
||||
if [[ -n "${child_pid}" ]]; then
|
||||
printf '%s\n' "${child_pid}" > "${pidfile}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Reporting
|
||||
echo "Script PID: $$"
|
||||
if [[ -n "${child_pid}" ]]; then
|
||||
echo "Child PID: ${child_pid}"
|
||||
else
|
||||
echo "Child PID: (unknown)"
|
||||
fi
|
||||
echo "Pidfile: ${pidfile}"
|
||||
echo "Started at: $(date --iso-8601=seconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S%z)"
|
||||
echo
|
||||
echo "To follow the log: tail -F ${logfile}"
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user