Files
aglab.dotfiles/bin/startb

296 lines
8.7 KiB
Bash
Executable File

#!/bin/bash
# like `start /b` in Windows CMD.
# goddamn nvm-sh !!!
# have to update PATH before launch
PATH=$(zsh -c -i 'echo $PATH')
export PATH
# https://vescrity.github.io/post/systemd-desktop-suspend/
#systemd-run --user --scope --slice=YukiLauncher.slice --unit="$1-$$".scope /bin/sh -c '"$@"' _ "$@"
# Generated by GitHub Copilot.
# start-bg-journal.sh - start a command as a detached daemon and send stdout/stderr to systemd-journald
#
# Features:
# - Sends stdout+stderr to journald using systemd-cat when available.
# - Falls back to writing into a logfile if systemd-cat isn't present or if --log is passed.
# - Uses setsid to detach, closes inherited file descriptors (best-effort),
# redirects stdin to /dev/null, and execs the target command.
# - Supports PID file, umask, and a syslog identifier (tag) and priority for journald.
# - Uses positional args ($@) for the command; uses $$ in auto-generated names and prints the child PID ($!).
#
# Usage:
# start-bg-journal.sh [ -t TAG ] [ -r PRIORITY ] [ -l LOGFILE | -d LOGDIR ] [ -p PIDFILE ] [ -u UMASK ] -- command [args...]
#
# Examples:
# ./start-bg-journal.sh -t myapp -- /usr/bin/myapp --config /etc/myapp.conf
# ./start-bg-journal.sh -l ./my.log -- /usr/bin/myapp arg1 arg2
# ./start-bg-journal.sh -p /run/myapp.pid -t myapp -r info -- /usr/bin/myapp
set -euo pipefail
progname=$(basename "$0")
usage() {
cat <<EOF
Usage: $progname [ -t TAG ] [ -r PRIORITY ] [ -l LOGFILE | -d LOGDIR ] [ -p PIDFILE ] [ -u UMASK ] -- command [args...]
Start command [args...] as a background daemon, sending stdout+stderr to systemd-journald (via systemd-cat)
when available. If systemd-cat is not available or if -l/--log is provided, logs are written to a logfile.
Options:
-t, --tag TAG Tag / SYSLOG_IDENTIFIER used by systemd-cat (default: basename(command))
-r, --priority PRIO Journal priority (emerg, alert, crit, err, warning, notice, info, debug)
-l, --log LOGFILE Write stdout+stderr to LOGFILE instead of journald
-d, --dir LOGDIR Directory for autogenerated log files (default: /tmp/$USER/runner)
-p, --pidfile PATH Write daemon PID to PATH
-u, --umask UMASK Set umask for the daemon (default: 0022)
-h, --help Show this help and exit
When no -l is provided and journald is available, output goes to journald.
When -l is not provided and journald is not available, a logfile is auto-generated:
<LOGDIR>/<command>.<YYYYMMDD-HHMMSS>.pid<$$>.log
EOF
exit 1
}
# Defaults
logdir="/tmp/$USER/runner"
logfile=""
pidfile=""
umask_val="0022"
tag=""
priority=""
# Parse options up to --
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--tag)
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
tag="$2"; shift 2 ;;
-r|--priority)
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
priority="$2"; shift 2 ;;
-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 ;;
-p|--pidfile)
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
pidfile="$2"; shift 2 ;;
-u|--umask)
if [[ $# -lt 2 ]]; then echo "Missing argument for $1"; usage; fi
umask_val="$2"; shift 2 ;;
-h|--help)
usage ;;
--)
shift; break ;;
-*)
echo "Unknown option: $1"; usage ;;
*)
break ;;
esac
done
if [[ $# -eq 0 ]]; then
echo "Error: no command specified."
usage
fi
# Build command array from remaining args.
cmd=( "$@" )
cmd_basename=$(basename "${cmd[0]}")
timestamp=$(date +%Y%m%d-%H%M%S)
# If tag not set, default to command basename
if [[ -z "$tag" ]]; then
tag="${cmd_basename}"
fi
# Detect systemd-cat
systemd_cat_path=""
if command -v systemd-cat >/dev/null 2>&1; then
systemd_cat_path=$(command -v systemd-cat)
fi
# If user provided logfile explicitly, force file mode
use_journal=false
if [[ -n "${logfile}" ]]; then
use_journal=false
else
if [[ -n "${systemd_cat_path}" ]]; then
use_journal=true
else
use_journal=false
fi
fi
# Prepare logfile if needed
mkdir -p -- "${logdir}"
if ! $use_journal; then
if [[ -z "${logfile}" ]]; then
logfile="${logdir}/${cmd_basename}.${timestamp}.pid$$.log"
fi
# Ensure file exists and is writable
: > "${logfile}" || { echo "Cannot write to log file: ${logfile}"; exit 1; }
fi
# Helper: quote command for printing
quoted_cmd() {
local i out=""
for i in "$@"; do
printf -v i "%q" "$i"
out="${out} ${i}"
done
printf '%s' "${out# }"
}
# Build the inner script to run under setsid. We inject expanded variables from outer shell.
if $use_journal; then
# Compose systemd-cat args (escape tag and priority safely)
# Note: we rely on simple token expansion; tag/priority are validated minimally below.
journal_args=()
journal_args+=( "-t" )
journal_args+=( "$tag" )
if [[ -n "$priority" ]]; then
journal_args+=( "-p" )
journal_args+=( "$priority" )
fi
# Validate priority loosely (allow common names or numeric)
case "$priority" in
""|emerg|alert|crit|err|warning|notice|info|debug|0|1|2|3|4|5|6|7) ;;
*)
echo "Warning: unknown priority '$priority' — journald may reject it." >&2 ;;
esac
# Prepare a single string of quoted journal args for insertion into the -c script
# We need to protect values with single quotes if they contain special chars.
journal_args_escaped=""
for a in "${journal_args[@]}"; do
# replace every single quote with '\'"\''
a_escaped=${a//\'/\'"\'"\'}
journal_args_escaped="$journal_args_escaped '$a_escaped'"
done
inner_script=$(cat <<'EOF'
umask __UMASK__
cd / || true
# Close fds (best-effort)
if [ -d /proc/self/fd ]; then
for fdpath in /proc/self/fd/*; do
fdnum=${fdpath##*/}
case "$fdnum" in
0|1|2) continue ;;
esac
eval "exec ${fdnum}>&-" 2>/dev/null || true
done
else
maxfd=$(ulimit -n 2>/dev/null || echo 256)
case "$maxfd" in (*[!0-9]*|"") maxfd=256 ;; esac
fd=3
while [ "$fd" -le "$maxfd" ]; do
eval "exec ${fd}>&-" 2>/dev/null || true
fd=$((fd+1))
done
fi
# Redirect stdio: stdin -> /dev/null, stdout+stderr -> systemd-cat (journal)
exec </dev/null
# Note: the systemd-cat command and its args are inserted by the outer script.
exec > >( __SYSTEMD_CAT__ ) 2>&1
# Exec the target command; "$@" expands to the command and its args passed after --
exec "$@"
EOF
)
# Substitute placeholders with safe values
inner_script=${inner_script//'__UMASK__'/"$umask_val"}
# Build the systemd-cat invocation string (path + args) — shell-escaped
# We need a single token string like: /bin/systemd-cat -t 'tag' -p 'priority'
systemd_cat_cmd="$(printf '%s' "$systemd_cat_path")$journal_args_escaped"
# Replace placeholder __SYSTEMD_CAT__ with the built command
inner_script=${inner_script//'__SYSTEMD_CAT__'/"$systemd_cat_cmd"}
# Start the detached process via setsid
setsid bash -c "$inner_script" -- "${cmd[@]}" &
else
# Fallback: redirect to logfile (file mode)
inner_script=$(cat <<'EOF'
umask __UMASK__
cd / || true
# Close fds (best-effort)
if [ -d /proc/self/fd ]; then
for fdpath in /proc/self/fd/*; do
fdnum=${fdpath##*/}
case "$fdnum" in
0|1|2) continue ;;
esac
eval "exec ${fdnum}>&-" 2>/dev/null || true
done
else
maxfd=$(ulimit -n 2>/dev/null || echo 256)
case "$maxfd" in (*[!0-9]*|"") maxfd=256 ;; esac
fd=3
while [ "$fd" -le "$maxfd" ]; do
eval "exec ${fd}>&-" 2>/dev/null || true
fd=$((fd+1))
done
fi
exec </dev/null
exec > "__LOGFILE__" 2>&1
exec "$@"
EOF
)
inner_script=${inner_script//'__UMASK__'/"$umask_val"}
# Escape logfile path for safe insertion (single-quote style)
log_escaped=${logfile//\'/\'"\'"\'}
inner_script=${inner_script//'__LOGFILE__'/"'$log_escaped'"}
setsid bash -c "$inner_script" -- "${cmd[@]}" &
fi
child_pid=$!
# Try to disown if supported
if command -v disown >/dev/null 2>&1; then
disown "$child_pid" 2>/dev/null || true
fi
# Optionally write pidfile
if [[ -n "${pidfile}" ]]; then
mkdir -p -- "$(dirname "${pidfile}")"
printf '%s\n' "${child_pid}" > "${pidfile}"
fi
echo "Started command: $(quoted_cmd "${cmd[@]}")"
echo "Script PID: $$"
echo "Daemon PID: ${child_pid}"
if $use_journal; then
echo "Output destination: systemd-journald (via $(printf '%s' "$systemd_cat_path"))"
echo "Journal tag: ${tag}"
if [[ -n "${priority}" ]]; then
echo "Journal priority: ${priority}"
fi
else
echo "Log file: ${logfile}"
fi
if [[ -n "${pidfile}" ]]; then
echo "PID file: ${pidfile}"
fi
echo "Started at: $(date --iso-8601=seconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S%z)"
if ! $use_journal; then
echo
echo "To follow the log: tail -F ${logfile}"
else
echo
echo "To view logs for this tag: journalctl -t ${tag} -f"
fi