#!/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 </..pid<$$>.log A pidfile is written as .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 # 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