Beacon config, beacon integration into emacs

This commit is contained in:
2026-06-01 11:43:42 +03:00
parent e271872cae
commit c0f72be869
6 changed files with 240 additions and 50 deletions
+3
View File
@@ -11,4 +11,7 @@
}; };
modules = [../nixos/configuration-beacon.nix]; modules = [../nixos/configuration-beacon.nix];
}; };
config.flake.packages.x86_64-linux.beacon-image =
config.flake.nixosConfigurations.beacon.config.system.build.image;
} }
+160
View File
@@ -157,6 +157,166 @@
(kbd "C") 'azos/caldav-sync) (kbd "C") 'azos/caldav-sync)
#+end_src #+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 * Provide
#+begin_src emacs-lisp #+begin_src emacs-lisp
+44 -43
View File
@@ -7,10 +7,22 @@
... ...
}: { }: {
imports = [ imports = [
"${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix" "${modulesPath}/virtualisation/disk-image.nix"
suiteModules.nixos.attic suiteModules.nixos.attic
]; ];
image.format = "raw";
boot.initrd.availableKernelModules = [
"xhci_pci"
"usb_storage"
"uas"
"ahci"
"nvme"
"usbhid"
"sd_mod"
];
nixpkgs.hostPlatform = "x86_64-linux"; nixpkgs.hostPlatform = "x86_64-linux";
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
nixpkgs.config.cudaSupport = true; nixpkgs.config.cudaSupport = true;
@@ -28,11 +40,13 @@
]; ];
}; };
security.sudo.wheelNeedsPassword = false;
networking.hostName = "beacon"; networking.hostName = "beacon";
time.timeZone = "Asia/Jerusalem"; time.timeZone = "Asia/Jerusalem";
# NetworkManager is enabled by installation-cd-minimal; configure WiFi + static IP networking.networkmanager.enable = true;
# via a keyfile so it activates automatically on boot. hardware.enableRedistributableFirmware = true;
networking.useDHCP = false; networking.useDHCP = false;
environment.etc."NetworkManager/system-connections/Zakobar.nmconnection" = { environment.etc."NetworkManager/system-connections/Zakobar.nmconnection" = {
mode = "0600"; mode = "0600";
@@ -62,47 +76,27 @@
''; '';
}; };
# Storage drive (ext4, label "storage") provides persistent nix store and data dir. # Swap is created on first boot after boot.growPartition expands the root
# Prerequisites — run once on the storage drive before first boot: # partition to the full drive size. Non-blocking: a failure just means no
# mkfs.ext4 -L storage /dev/sdX # swap on this boot (retried next boot once the partition has grown).
# mount /dev/sdX /mnt/storage systemd.services.beacon-swap = {
# mkdir -p /mnt/storage/nix-rw/store /mnt/storage/nix-rw/work /mnt/storage/data description = "Create and activate swapfile";
# umount /mnt/storage after = ["systemd-growfs@-.service" "local-fs.target"];
# The drive is required to boot; boot halts if it is not plugged in. wantedBy = ["multi-user.target"];
fileSystems."/mnt/storage" = { serviceConfig = {
device = "/dev/disk/by-label/storage"; Type = "oneshot";
fsType = "ext4"; RemainAfterExit = true;
neededForBoot = true; };
options = ["noatime"]; script = ''
if [ ! -f /swapfile ]; then
dd if=/dev/zero of=/swapfile bs=1M count=16384 status=progress
chmod 600 /swapfile
${pkgs.util-linux}/bin/mkswap /swapfile
fi
${pkgs.util-linux}/bin/swapon /swapfile
'';
}; };
# Redirect the live CD's tmpfs rw-store to the storage drive so nix store
# writes survive across boots and don't consume RAM.
fileSystems."/nix/.rw-store" = lib.mkForce {
device = "/mnt/storage/nix-rw";
fsType = "none";
options = ["bind"];
depends = ["/mnt/storage"];
neededForBoot = true;
};
fileSystems."/data" = {
device = "/mnt/storage/data";
fsType = "none";
options = ["bind"];
depends = ["/mnt/storage"];
};
swapDevices = [
{
device = "/mnt/storage/swapfile";
size = 16384;
}
];
# Ensure ext4 is available in initrd for the storage drive
boot.initrd.kernelModules = ["ext4"];
# NVIDIA RTX 4050 — Ada Lovelace supports open kernel modules # NVIDIA RTX 4050 — Ada Lovelace supports open kernel modules
services.xserver.videoDrivers = ["nvidia"]; services.xserver.videoDrivers = ["nvidia"];
hardware.nvidia = { hardware.nvidia = {
@@ -112,7 +106,7 @@
}; };
hardware.graphics.enable = true; hardware.graphics.enable = true;
services.getty.autologinUser = lib.mkForce "aner"; services.getty.autologinUser = "aner";
services.openssh = { services.openssh = {
enable = true; enable = true;
@@ -135,8 +129,15 @@
rsync rsync
tmux tmux
vim vim
wget
rclone
pciutils
nvtopPackages.nvidia
cudaPackages.cudatoolkit cudaPackages.cudatoolkit
cudaPackages.cudnn
cudaPackages.nccl
python3 python3
direnv
]; ];
azos.attic.enable = true; azos.attic.enable = true;
+28 -5
View File
@@ -22,7 +22,11 @@
suiteModules.nixos.attic suiteModules.nixos.attic
]; ];
boot.loader.systemd-boot.enable = true; boot.loader.grub = {
enable = true;
efiSupport = true;
device = "nodev";
};
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
nixpkgs = { nixpkgs = {
@@ -71,7 +75,7 @@
users.users.aner = { users.users.aner = {
isNormalUser = true; isNormalUser = true;
description = "Aner Zakobar"; description = "Aner Zakobar";
extraGroups = ["networkmanager" "wheel" "audio" "video"]; extraGroups = ["networkmanager" "wheel" "audio" "video" "seat" "input"];
packages = with pkgs; []; packages = with pkgs; [];
}; };
@@ -91,11 +95,29 @@
}; };
specialisation = { specialisation = {
hyprland = { steam-big-picture = {
configuration = { configuration = {
home-manager.users.aner = {pkgs, ...}: { environment.systemPackages = [pkgs.sway];
environment.etc."sway-steam.conf".text = ''
output * bg #000000 solid_color
default_border none
default_floating_border none
exec ${pkgs.dbus}/bin/dbus-run-session -- ${pkgs.bash}/bin/bash -c "steam -tenfoot; ${pkgs.sway}/bin/swaymsg exit"
'';
services.seatd.enable = true;
services.greetd.settings = lib.mkForce {
terminal.vt = 1;
default_session = {
command = "${pkgs.tuigreet}/bin/tuigreet --time --user-menu --cmd '/home/aner/.login.sh'";
user = "greeter";
};
initial_session = {
command = "${pkgs.sway}/bin/sway --config /etc/sway-steam.conf";
user = "aner";
};
};
home-manager.users.aner = {lib, ...}: {
azos.suites.exwm.enable = lib.mkForce false; azos.suites.exwm.enable = lib.mkForce false;
azos.suites.hyprland.enable = true;
}; };
}; };
}; };
@@ -111,6 +133,7 @@
killall killall
brightnessctl brightnessctl
exfatprogs exfatprogs
gamescope
]; ];
fonts.enableDefaultPackages = true; fonts.enableDefaultPackages = true;
+4 -1
View File
@@ -13,6 +13,9 @@ pkgs.mkShell {
"nix flake update --flake '.?submodules=1'") "nix flake update --flake '.?submodules=1'")
(pkgs.writeShellScriptBin (pkgs.writeShellScriptBin
"azos-beacon-build-image" "azos-beacon-build-image"
"nix build '.?submodules=1#nixosConfigurations.beacon.config.system.build.isoImage'") "nix build '.?submodules=1#packages.x86_64-linux.beacon-image'")
(pkgs.writeShellScriptBin
"azos-beacon-remote-update"
"nixos-rebuild switch --flake '.?submodules=1#beacon' --target-host aner@192.168.1.200 --build-host aner@192.168.1.200 --sudo")
]; ];
} }