#!/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 </..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 >( __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 "__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