Beacon config, beacon integration into emacs
This commit is contained in:
@@ -157,6 +157,166 @@
|
||||
(kbd "C") 'azos/caldav-sync)
|
||||
#+end_src
|
||||
|
||||
** Beacon Remote Jobs
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defvar azos/beacon/host "aner@192.168.1.200"
|
||||
"SSH connection for beacon machine.")
|
||||
|
||||
(defvar azos/beacon/max-logs 5
|
||||
"Number of most-recent logs to keep per project on beacon.")
|
||||
|
||||
(defvar azos/beacon/keymap (make-sparse-keymap)
|
||||
"Keymap for beacon remote job commands (M-o b prefix).")
|
||||
|
||||
(define-key azos/global-minor-mode/open-keymap (kbd "b") azos/beacon/keymap)
|
||||
|
||||
(defun azos/beacon--project-root ()
|
||||
(or (and (fboundp 'projectile-project-root) (projectile-project-root))
|
||||
(error "Not in a projectile project")))
|
||||
|
||||
(defun azos/beacon--project-name (root)
|
||||
(file-name-nondirectory (directory-file-name root)))
|
||||
|
||||
(defun azos/beacon--list-python-files (root)
|
||||
(split-string
|
||||
(shell-command-to-string
|
||||
(format "cd %s && git ls-files | grep '\\.py$'" (shell-quote-argument root)))
|
||||
"\n" t))
|
||||
|
||||
(defun azos/beacon--list-executables (root)
|
||||
(split-string
|
||||
(shell-command-to-string
|
||||
(format "cd %s && git ls-files | while IFS= read -r f; do [ -x \"$f\" ] && echo \"$f\"; done"
|
||||
(shell-quote-argument root)))
|
||||
"\n" t))
|
||||
|
||||
(defun azos/beacon--ssh-run (script)
|
||||
"Pipe SCRIPT as bash to beacon over SSH stdin. Error on non-zero exit."
|
||||
(with-temp-buffer
|
||||
(insert script)
|
||||
(let ((ret (call-process-region (point-min) (point-max) "ssh" nil t nil
|
||||
azos/beacon/host "bash")))
|
||||
(unless (zerop ret)
|
||||
(error "Beacon SSH error (exit %d): %s" ret (string-trim (buffer-string)))))))
|
||||
|
||||
(defun azos/beacon--ssh-query (cmd &optional allow-exit-one)
|
||||
"Run CMD on beacon, return output string. Error on failure.
|
||||
With ALLOW-EXIT-ONE, exit code 1 is also treated as success (for tmux ls)."
|
||||
(with-temp-buffer
|
||||
(let ((ret (call-process "ssh" nil t nil azos/beacon/host cmd)))
|
||||
(unless (or (zerop ret) (and allow-exit-one (= ret 1)))
|
||||
(error "Beacon SSH error (exit %d): %s" ret (string-trim (buffer-string)))))
|
||||
(buffer-string)))
|
||||
|
||||
(defun azos/beacon--dispatch (root cmd label)
|
||||
"Sync ROOT to beacon and run CMD in project dir under direnv.
|
||||
LABEL names the tmux session and log file."
|
||||
(let* ((project-name (azos/beacon--project-name root))
|
||||
(timestamp (format-time-string "%Y%m%d-%H%M%S"))
|
||||
(session (format "%s-%s-%s" project-name label timestamp))
|
||||
(log-name (format "%s-%s.log" label timestamp)))
|
||||
(message "Syncing %s to beacon..." project-name)
|
||||
(azos/beacon--ssh-run (format "mkdir -p ~/beacon-projects/%s\n" project-name))
|
||||
(with-temp-buffer
|
||||
(let ((ret (call-process-shell-command
|
||||
(format "bash -c %s"
|
||||
(shell-quote-argument
|
||||
(format "cd %s && git ls-files | rsync -avz --files-from=- . %s:~/beacon-projects/%s/"
|
||||
root azos/beacon/host project-name)))
|
||||
nil t nil)))
|
||||
(unless (zerop ret)
|
||||
(error "Beacon rsync failed (exit %d): %s" ret (string-trim (buffer-string))))))
|
||||
(azos/beacon--ssh-run
|
||||
(format "RDIR=~/beacon-projects/%s
|
||||
LDIR=~/beacon-logs/%s
|
||||
mkdir -p \"$LDIR\"
|
||||
direnv allow \"$RDIR\"
|
||||
tmux new-session -d -s %s -- bash -c \"cd $RDIR && direnv exec . %s 2>&1 | tee $LDIR/%s\"
|
||||
"
|
||||
project-name project-name
|
||||
(shell-quote-argument session)
|
||||
cmd log-name))
|
||||
(azos/beacon--ssh-run
|
||||
(format "find ~/beacon-logs/%s -name '*.log' -printf '%%T@ %%p\\n' 2>/dev/null | sort -rn | tail -n +%d | cut -d' ' -f2- | xargs -r rm -f\n"
|
||||
project-name (1+ azos/beacon/max-logs)))
|
||||
(message "Beacon job started: %s" session)))
|
||||
|
||||
(defun azos/beacon/run-python ()
|
||||
"Sync current project to beacon and run a selected Python file."
|
||||
(interactive)
|
||||
(let* ((root (azos/beacon--project-root))
|
||||
(files (azos/beacon--list-python-files root))
|
||||
(file (completing-read "Python file: " files nil t))
|
||||
(label (file-name-sans-extension (file-name-nondirectory file))))
|
||||
(azos/beacon--dispatch root (format "python3 %s" file) label)))
|
||||
|
||||
(defun azos/beacon/run-exec ()
|
||||
"Sync current project to beacon and run a selected executable."
|
||||
(interactive)
|
||||
(let* ((root (azos/beacon--project-root))
|
||||
(files (azos/beacon--list-executables root))
|
||||
(file (completing-read "Executable: " files nil t))
|
||||
(label (file-name-sans-extension (file-name-nondirectory file))))
|
||||
(azos/beacon--dispatch root (format "./%s" file) label)))
|
||||
|
||||
(defun azos/beacon/run-command ()
|
||||
"Sync current project to beacon and run an arbitrary command."
|
||||
(interactive)
|
||||
(let* ((root (azos/beacon--project-root))
|
||||
(cmd (read-string "Command: "))
|
||||
(label (replace-regexp-in-string
|
||||
"[^a-zA-Z0-9-]" "-"
|
||||
(substring cmd 0 (min 20 (length cmd))))))
|
||||
(azos/beacon--dispatch root cmd label)))
|
||||
|
||||
(defun azos/beacon/list-jobs ()
|
||||
"Show all running beacon tmux sessions."
|
||||
(interactive)
|
||||
(async-shell-command
|
||||
(format "ssh %s 'tmux ls 2>/dev/null || echo \"No jobs running\"'" azos/beacon/host)
|
||||
"*beacon-jobs*"))
|
||||
|
||||
(defun azos/beacon/kill-job ()
|
||||
"Kill a beacon tmux session selected interactively."
|
||||
(interactive)
|
||||
(let* ((output (azos/beacon--ssh-query "tmux ls -F '#S' 2>/dev/null" t))
|
||||
(sessions (split-string (string-trim output) "\n" t))
|
||||
(session (completing-read "Kill job: " sessions nil t)))
|
||||
(azos/beacon--ssh-run (format "tmux kill-session -t %s\n" (shell-quote-argument session)))
|
||||
(message "Killed beacon job: %s" session)))
|
||||
|
||||
(defun azos/beacon--tail-path (path)
|
||||
(async-shell-command
|
||||
(format "ssh %s %s" azos/beacon/host
|
||||
(shell-quote-argument (format "tail -f %s" path)))
|
||||
(format "*beacon-tail-%s*" (file-name-nondirectory path))))
|
||||
|
||||
(defun azos/beacon/tail-log (&optional arg)
|
||||
"Tail the most recent beacon log.
|
||||
With prefix ARG, prompt for a pattern then select from matches."
|
||||
(interactive "P")
|
||||
(let* ((sorted-output (azos/beacon--ssh-query
|
||||
"find ~/beacon-logs -name '*.log' -printf '%T@ %P\n' 2>/dev/null | sort -rn | cut -d' ' -f2-"))
|
||||
(logs (split-string (string-trim sorted-output) "\n" t)))
|
||||
(when (null logs) (error "No logs found on beacon"))
|
||||
(if (not arg)
|
||||
(azos/beacon--tail-path (format "~/beacon-logs/%s" (car logs)))
|
||||
(let* ((pattern (read-string "Log pattern: "))
|
||||
(matches (seq-filter (lambda (l) (string-match-p pattern l)) logs))
|
||||
(log (if (= (length matches) 1)
|
||||
(car matches)
|
||||
(completing-read "Tail log: " matches nil t))))
|
||||
(azos/beacon--tail-path (format "~/beacon-logs/%s" log))))))
|
||||
|
||||
(define-key azos/beacon/keymap (kbd "p") #'azos/beacon/run-python)
|
||||
(define-key azos/beacon/keymap (kbd "e") #'azos/beacon/run-exec)
|
||||
(define-key azos/beacon/keymap (kbd "c") #'azos/beacon/run-command)
|
||||
(define-key azos/beacon/keymap (kbd "l") #'azos/beacon/list-jobs)
|
||||
(define-key azos/beacon/keymap (kbd "k") #'azos/beacon/kill-job)
|
||||
(define-key azos/beacon/keymap (kbd "t") #'azos/beacon/tail-log)
|
||||
#+end_src
|
||||
|
||||
* Provide
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
|
||||
Reference in New Issue
Block a user