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:
2025-12-16 02:21:20 +08:00
parent b4932ef737
commit 3446d3d97e
6 changed files with 213 additions and 13 deletions

210
bin/runbg Executable file
View 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