-
Notifications
You must be signed in to change notification settings - Fork 0
feat(airc daemon): Windows support via HKCU Run-key autostart (no admin) #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4856,6 +4856,7 @@ _daemon_os() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "linux" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MINGW*|MSYS*|CYGWIN*) echo "windows" ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *) echo "unknown" ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -4893,6 +4894,8 @@ _daemon_installed() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ -f "$HOME/Library/LaunchAgents/com.cambriantech.airc.plist" ] && return 0 ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linux|wsl) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ -f "$HOME/.config/systemd/user/airc.service" ] && return 0 ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| windows) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" //v airc-monitor >/dev/null 2>&1 && return 0 ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -4906,6 +4909,7 @@ cmd_daemon_install() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "$os" in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| darwin) _daemon_install_launchd "$airc_bin" "$scope" ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linux|wsl) _daemon_install_systemd "$airc_bin" "$scope" "$os" ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| windows) _daemon_install_schtasks "$airc_bin" "$scope" ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| *) die "Daemon install not supported on $(uname -s). Manual workaround: run 'airc connect' under your platform's preferred autostart mechanism." ;; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esac | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -4970,6 +4974,94 @@ PLIST | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo " will pick up gh credentials on next restart." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _daemon_install_schtasks() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Windows daemon via HKCU Run key (no admin). Mirrors launchd / | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # systemd: per-user autostart at logon, restarts airc connect on | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # exit, logs to $scope/daemon.log. Joel 2026-04-28: "fix the monitor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # man / i cant go to bed till this is fixed" — Windows had no daemon | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # path, `nohup airc connect &` doesn't survive the launching shell | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # on MINGW64 (Git Bash kills the child when the parent bash exits). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Why Run-key instead of Task Scheduler: schtasks //SC ONLOGON | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # requires admin even for per-user tasks (UAC prompt + "Access is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # denied" without). HKCU\...\Run writes to user-scope hive, no admin, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # works identically (fires at user logon). Path-of-least-friction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # per Joel: "i just want whatever is least hassle and also robust". | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local airc_bin="$1" scope="$2" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local entry_name="airc-monitor" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Find Git Bash. The launcher .bat bridges from cmd.exe (Run key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # context) into bash (where airc actually runs). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local bash_exe="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for c in 'C:\Program Files\Git\bin\bash.exe' 'C:\Program Files (x86)\Git\bin\bash.exe' "$HOME/AppData/Local/Programs/Git/bin/bash.exe"; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local check_path; check_path=$(echo "$c" | sed 's|\\|/|g; s|^C:|/c|') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -f "$c" ] || [ -f "$check_path" ]; then bash_exe="$c"; break; fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ -z "$bash_exe" ] && die "bash.exe not found at any standard Git for Windows path. Install Git for Windows + re-run." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Convert paths to Windows form; cmd.exe can't read /c/Users/... . | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| local airc_bin_win scope_win | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if command -v cygpath >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| airc_bin_win=$(cygpath -w "$airc_bin") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope_win=$(cygpath -w "$scope") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| airc_bin_win=$(printf '%s' "$airc_bin" | sed 's|^/\([a-z]\)/|\U\1:\\\\|; s|/|\\\\|g') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope_win=$(printf '%s' "$scope" | sed 's|^/\([a-z]\)/|\U\1:\\\\|; s|/|\\\\|g') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Stage a launcher .bat in $scope. Loops with 5s pause for airc-crash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # auto-restart (matches launchd KeepAlive=true / systemd Restart=always). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Uses `start /B` for the bash invocation so the cmd.exe wrapper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # doesn't pop a visible console window at logon. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+5014
to
+5015
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Uses `start /B` for the bash invocation so the cmd.exe wrapper | |
| # doesn't pop a visible console window at logon. | |
| # Invokes bash.exe directly from the .bat; no `start /B` wrapper is | |
| # used for the bash launch. |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the generated .bat, use the quoted set "VAR=value" form for environment variables (e.g. AIRC_HOME) so paths containing special cmd.exe metacharacters (like &, (, ), ^) or trailing spaces can't break the script or be interpreted as additional commands at logon.
| set AIRC_HOME=$scope_win | |
| set AIRC_BACKGROUND_OK=1 | |
| set "AIRC_HOME=$scope_win" | |
| set "AIRC_BACKGROUND_OK=1" |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generated airc-daemon.bat never redirects airc connect stdout/stderr to $scope/daemon.log, but airc daemon log and the install/status output claim $scope/daemon.log is the log location. Either redirect the bash invocation output to daemon.log (and stderr to daemon.err) or adjust the user-facing messages/cmd_daemon_log behavior for Windows so it matches reality.
| "$bash_exe" -lc "exec '$airc_bin_win' connect" | |
| "$bash_exe" -lc "exec '$airc_bin_win' connect" >> "$scope_win\\daemon.log" 2>> "$scope_win\\daemon.err" |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Windows uninstall path only matches/kills processes whose cmdline contains airc-daemon.bat, which targets the launcher cmd.exe/batch but not the spawned bash/airc connect processes. Consider reusing the existing process-tree helpers (e.g. proc_children, proc_cmdline, or pidfile-based teardown) to reliably terminate the whole daemon process tree.
| # Match on the launcher .bat path so we don't kill foreground | |
| # `airc join` running in the user's terminal. | |
| local scope; scope=$(_daemon_scope) | |
| if ps -ef 2>/dev/null | grep 'airc-daemon.bat' | grep -v grep >/dev/null; then | |
| ps -ef | grep 'airc-daemon.bat' | grep -v grep | awk '{print $2}' | while read pid; do | |
| kill "$pid" 2>/dev/null || true | |
| done | |
| echo " ✓ Killed running daemon launcher process(es)" | |
| # Match the launcher first, but use Windows tree termination so | |
| # spawned `bash` / `airc connect` children are also stopped. Then | |
| # clean up any orphaned daemon children that still reference this | |
| # daemon scope in their command line. | |
| local scope; scope=$(_daemon_scope) | |
| local killed_any=0 | |
| local pid | |
| while read -r pid; do | |
| [ -n "$pid" ] || continue | |
| if command -v taskkill >/dev/null 2>&1; then | |
| taskkill //PID "$pid" //T //F >/dev/null 2>&1 || kill "$pid" 2>/dev/null || true | |
| else | |
| kill "$pid" 2>/dev/null || true | |
| fi | |
| killed_any=1 | |
| done < <(ps -ef 2>/dev/null | awk '/airc-daemon\.bat/ && !/awk/ {print $2}') | |
| while read -r pid; do | |
| [ -n "$pid" ] || continue | |
| if command -v taskkill >/dev/null 2>&1; then | |
| taskkill //PID "$pid" //T //F >/dev/null 2>&1 || kill "$pid" 2>/dev/null || true | |
| else | |
| kill "$pid" 2>/dev/null || true | |
| fi | |
| killed_any=1 | |
| done < <(ps -ef 2>/dev/null | awk -v scope="$scope" ' | |
| index($0, scope) && index($0, "airc connect") && $0 !~ /airc-daemon\.bat/ && !/awk/ {print $2} | |
| ') | |
| if [ "$killed_any" -eq 1 ]; then | |
| echo " ✓ Killed running daemon process tree" |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments mention using start /B (and later rely on behavior attributed to start /B), but the implementation uses start ... /MIN and the .bat directly invokes bash without start /B. Please update the comments to match the actual behavior so future debugging doesn’t chase the wrong process model.
| # launcher .bat spawns bash + airc-connect then exits, so we | |
| # look for the airc-connect process (PPID=1 = orphaned-into- | |
| # init, which is what `start /B` produces on Windows). Falling | |
| # back to airc.pid lookup if that fails. | |
| # launcher .bat starts bash running `airc connect` and then | |
| # exits, so we look for the surviving airc-connect process. | |
| # In the MSYS/Git Bash `ps` view this may appear orphaned | |
| # (often PPID=1). Fall back to airc.pid lookup if that fails. |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$scope/airc.pid contains multiple PIDs separated by spaces (see cmd_connect writes "$$ $PAIR_PID ..."), but this code strips all whitespace and then kill -0 checks a concatenated number (e.g. "123 456" -> "123456"). Parse the first PID field instead (or iterate fields) so Windows status can reliably detect a live daemon via the pidfile fallback.
| pidfile_pid=$(head -1 "$scope/airc.pid" 2>/dev/null | tr -d '[:space:]') | |
| pidfile_pid=$(awk 'NR == 1 { print $1; exit }' "$scope/airc.pid" 2>/dev/null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bash_exeis written verbatim into the generated.bat. If the Git-for-Windows install is found via the$HOME/.../Git/bin/bash.execandidate,bash_exewill be a POSIX path like/c/Users/.../bash.exe, whichcmd.exe(Run-key context) typically can't execute. Convert the discovered bash path to a Windows path (e.g. viacygpath -w) before embedding it into the batch file so both system-wide and per-user Git installs work.