We have made an emacs conf with profiles. And refactored lab tool to use deploy-rs
This commit is contained in:
parent
24b01ae4f0
commit
bff56e4ffc
22 changed files with 1448 additions and 176 deletions
123
documentation/SSH_DEPLOYMENT_STRATEGY.md
Normal file
123
documentation/SSH_DEPLOYMENT_STRATEGY.md
Normal file
|
@ -0,0 +1,123 @@
|
|||
# SSH Deployment Strategy - Unified sma User Approach
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the updated SSH deployment strategy for the home lab, standardizing on the `sma` user for all administrative operations and deployments.
|
||||
|
||||
## User Strategy
|
||||
|
||||
### sma User (System Administrator)
|
||||
- **Purpose**: System administration, deployment, maintenance
|
||||
- **SSH Key**: `id_ed25519_admin`
|
||||
- **Privileges**: sudo NOPASSWD, wheel group
|
||||
- **Usage**: All lab tool deployments, system maintenance
|
||||
|
||||
### geir User (Developer)
|
||||
- **Purpose**: Development work, daily usage, git operations
|
||||
- **SSH Key**: `id_ed25519_dev`
|
||||
- **Privileges**: Standard user with development tools
|
||||
- **Usage**: Development workflows, git operations
|
||||
|
||||
## Deployment Workflow
|
||||
|
||||
### From Any Machine (Workstation or Laptop)
|
||||
|
||||
1. **Both machines have sma user configured** with admin SSH key
|
||||
2. **Lab tool uses sma user consistently** for all remote operations
|
||||
3. **Deploy-rs uses sma user** for automated deployments with rollback
|
||||
|
||||
### SSH Configuration
|
||||
|
||||
The SSH configuration supports both direct access patterns:
|
||||
|
||||
```bash
|
||||
# Direct Tailscale access with sma user
|
||||
ssh sma@sleeper-service.tail807ea.ts.net
|
||||
ssh sma@grey-area.tail807ea.ts.net
|
||||
ssh sma@reverse-proxy.tail807ea.ts.net
|
||||
ssh sma@little-rascal.tail807ea.ts.net
|
||||
|
||||
# Local sma user (for deployment from laptop to workstation)
|
||||
ssh sma@localhost
|
||||
```
|
||||
|
||||
## Lab Tool Commands
|
||||
|
||||
All lab commands now work consistently from both machines:
|
||||
|
||||
```bash
|
||||
# Status checking
|
||||
lab status # Works from both workstation and laptop
|
||||
|
||||
# Deployment (using sma user automatically)
|
||||
lab deploy sleeper-service # Works from both machines
|
||||
lab deploy grey-area # Works from both machines
|
||||
lab deploy little-rascal # Deploy TO laptop FROM workstation
|
||||
lab deploy congenital-optimist # Deploy TO workstation FROM laptop
|
||||
|
||||
# Deploy-rs (with automatic rollback)
|
||||
lab deploy-rs sleeper-service
|
||||
lab hybrid-update all
|
||||
```
|
||||
|
||||
## Security Benefits
|
||||
|
||||
1. **Principle of Least Privilege**: sma user only for admin tasks
|
||||
2. **Key Separation**: Admin and development keys are separate
|
||||
3. **Consistent Access**: Same user across all machines for deployment
|
||||
4. **Audit Trail**: Clear separation between admin and development activities
|
||||
|
||||
## Machine-Specific Notes
|
||||
|
||||
### congenital-optimist (Workstation)
|
||||
- **Type**: Local deployment
|
||||
- **SSH**: Uses localhost with sma user for consistency
|
||||
- **Primary Use**: Development and deployment hub
|
||||
|
||||
### little-rascal (Laptop)
|
||||
- **Type**: Remote deployment
|
||||
- **SSH**: Tailscale hostname with sma user
|
||||
- **Primary Use**: Mobile development and deployment
|
||||
|
||||
### Remote Servers (sleeper-service, grey-area, reverse-proxy)
|
||||
- **Type**: Remote deployment
|
||||
- **SSH**: Tailscale hostnames with sma user
|
||||
- **Access**: Both workstation and laptop can deploy
|
||||
|
||||
## Migration Benefits
|
||||
|
||||
1. **Simplified Workflow**: Same commands work from both machines
|
||||
2. **Better Security**: Dedicated admin user for all system operations
|
||||
3. **Consistency**: All deployments use the same SSH user pattern
|
||||
4. **Flexibility**: Can deploy from either workstation or laptop seamlessly
|
||||
|
||||
## Testing the Setup
|
||||
|
||||
```bash
|
||||
# Test SSH connectivity with sma user
|
||||
ssh sma@sleeper-service.tail807ea.ts.net echo "Connection OK"
|
||||
ssh sma@grey-area.tail807ea.ts.net echo "Connection OK"
|
||||
ssh sma@little-rascal.tail807ea.ts.net echo "Connection OK"
|
||||
|
||||
# Test lab tool
|
||||
lab status # Should show all machines
|
||||
lab deploy sleeper-service # Should work with sma user
|
||||
|
||||
# Test deploy-rs
|
||||
lab deploy-rs sleeper-service --dry-run
|
||||
```
|
||||
|
||||
## Implementation Status
|
||||
|
||||
- ✅ SSH keys configured for sma user on all machines
|
||||
- ✅ Lab tool updated to use sma user for all operations
|
||||
- ✅ Deploy-rs configuration updated to use sma user
|
||||
- ✅ SSH client configuration updated with proper host patterns
|
||||
- 📋 Ready for testing and validation
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Test SSH connectivity from both machines to all targets
|
||||
2. Validate lab tool deployment commands
|
||||
3. Test deploy-rs functionality with sma user
|
||||
4. Update any remaining scripts that might use old SSH patterns
|
201
dotfiles/geir/emacs-config/init-nix.el
Normal file
201
dotfiles/geir/emacs-config/init-nix.el
Normal file
|
@ -0,0 +1,201 @@
|
|||
;;; init.el --- Nix-integrated modular Emacs configuration -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; A Nix-integrated, modular Emacs configuration that leverages Nix-provided tools
|
||||
;; and packages where possible, falling back to Emacs package manager only when needed.
|
||||
;; Core setup: UI, Nix integration, modular loading
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Performance optimizations
|
||||
(setq gc-cons-threshold (* 50 1000 1000))
|
||||
(add-hook 'emacs-startup-hook
|
||||
(lambda ()
|
||||
(setq gc-cons-threshold (* 2 1000 1000))
|
||||
(let ((profile (getenv "EMACS_PROFILE")))
|
||||
(message "Emacs loaded in %s with %d garbage collections (Profile: %s)."
|
||||
(format "%.2f seconds"
|
||||
(float-time
|
||||
(time-subtract after-init-time before-init-time)))
|
||||
gcs-done
|
||||
(or profile "unknown")))))
|
||||
|
||||
;; Basic UI setup - minimal but pleasant
|
||||
(setq inhibit-startup-screen t)
|
||||
(menu-bar-mode -1)
|
||||
(when (fboundp 'tool-bar-mode) (tool-bar-mode -1))
|
||||
(when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
|
||||
(set-face-attribute 'default nil :height 140)
|
||||
(setq-default cursor-type 'bar)
|
||||
|
||||
;; Nix Integration Setup
|
||||
;; Configure Emacs to use Nix-provided tools when available
|
||||
(defun nix-tool-path (tool-name)
|
||||
"Get the path to TOOL-NAME from Nix environment variables."
|
||||
(let ((env-var (concat (upcase tool-name) "_PATH")))
|
||||
(getenv env-var)))
|
||||
|
||||
;; Configure external tools to use Nix-provided binaries
|
||||
(when-let ((rg-path (nix-tool-path "rg")))
|
||||
(setq consult-ripgrep-command rg-path))
|
||||
|
||||
(when-let ((ag-path (nix-tool-path "ag")))
|
||||
(setq ag-executable ag-path))
|
||||
|
||||
(when-let ((fd-path (nix-tool-path "fd")))
|
||||
(setq find-program fd-path))
|
||||
|
||||
(when-let ((sqlite-path (nix-tool-path "sqlite")))
|
||||
(setq org-roam-database-connector 'sqlite3)
|
||||
(setq org-roam-db-executable sqlite-path))
|
||||
|
||||
;; Language Server Configuration (for Nix-provided LSP servers)
|
||||
(defun configure-nix-lsp-servers ()
|
||||
"Configure LSP to use Nix-provided language servers."
|
||||
(when (featurep 'lsp-mode)
|
||||
;; Nix LSP server
|
||||
(when-let ((nil-path (nix-tool-path "nil_lsp")))
|
||||
(setq lsp-nix-nil-server-path nil-path))
|
||||
|
||||
;; Bash LSP server
|
||||
(when-let ((bash-lsp-path (nix-tool-path "bash_lsp")))
|
||||
(setq lsp-bash-language-server-path bash-lsp-path))
|
||||
|
||||
;; YAML LSP server
|
||||
(when-let ((yaml-lsp-path (nix-tool-path "yaml_lsp")))
|
||||
(setq lsp-yaml-language-server-path yaml-lsp-path))))
|
||||
|
||||
;; Configure format-all to use Nix-provided formatters
|
||||
(defun configure-nix-formatters ()
|
||||
"Configure format-all to use Nix-provided formatters."
|
||||
(when (featurep 'format-all)
|
||||
;; Shellcheck for shell scripts
|
||||
(when-let ((shellcheck-path (nix-tool-path "shellcheck")))
|
||||
(setq format-all-formatters
|
||||
(cons `(sh (shellcheck ,shellcheck-path))
|
||||
format-all-formatters)))))
|
||||
|
||||
;; Package management setup
|
||||
;; Note: With Nix integration, we rely less on package.el
|
||||
;; Most packages come pre-installed via the flake
|
||||
(require 'package)
|
||||
(setq package-archives
|
||||
'(("melpa" . "https://melpa.org/packages/")
|
||||
("gnu" . "https://elpa.gnu.org/packages/")))
|
||||
|
||||
;; Only initialize package.el if we're not in a Nix environment
|
||||
;; In Nix environments, packages are pre-installed
|
||||
(unless (getenv "EMACS_PROFILE")
|
||||
(package-initialize)
|
||||
|
||||
;; Install use-package for non-Nix environments
|
||||
(unless (package-installed-p 'use-package)
|
||||
(package-refresh-contents)
|
||||
(package-install 'use-package)))
|
||||
|
||||
;; Configure use-package for Nix integration
|
||||
(require 'use-package)
|
||||
;; Don't auto-install packages in Nix environment - they're pre-provided
|
||||
(setq use-package-always-ensure (not (getenv "EMACS_PROFILE")))
|
||||
|
||||
;; Essential packages that should be available in all profiles
|
||||
(use-package exec-path-from-shell
|
||||
:if (memq window-system '(mac ns x))
|
||||
:config
|
||||
(exec-path-from-shell-initialize)
|
||||
;; Ensure Nix environment is properly inherited
|
||||
(exec-path-from-shell-copy-envs '("NIX_PATH" "NIX_EMACS_PROFILE")))
|
||||
|
||||
(use-package diminish)
|
||||
(use-package bind-key)
|
||||
|
||||
;; Basic editing improvements
|
||||
(use-package which-key
|
||||
:config
|
||||
(which-key-mode 1))
|
||||
|
||||
;; Load profile-specific configuration based on Nix profile
|
||||
(defun load-profile-config ()
|
||||
"Load configuration specific to the current Nix profile."
|
||||
(let ((profile (getenv "EMACS_PROFILE")))
|
||||
(pcase profile
|
||||
("server"
|
||||
(message "Loading minimal server configuration...")
|
||||
;; Minimal config - only essential features
|
||||
(setq gc-cons-threshold (* 2 1000 1000))) ; Lower memory usage
|
||||
|
||||
("laptop"
|
||||
(message "Loading laptop development configuration...")
|
||||
;; Laptop config - balanced features
|
||||
(setq auto-save-timeout 30) ; More frequent saves
|
||||
(setq lsp-idle-delay 0.3)) ; Moderate LSP responsiveness
|
||||
|
||||
("workstation"
|
||||
(message "Loading workstation configuration...")
|
||||
;; Workstation config - maximum performance
|
||||
(setq gc-cons-threshold (* 50 1000 1000)) ; Higher performance
|
||||
(setq lsp-idle-delay 0.1)) ; Fastest LSP response
|
||||
|
||||
(_
|
||||
(message "Loading default configuration...")))))
|
||||
|
||||
;; Apply profile-specific settings
|
||||
(load-profile-config)
|
||||
|
||||
;; Configure Nix integration after packages are loaded
|
||||
(add-hook 'after-init-hook #'configure-nix-lsp-servers)
|
||||
(add-hook 'after-init-hook #'configure-nix-formatters)
|
||||
|
||||
;; Org mode basic setup (always included)
|
||||
(use-package org
|
||||
:config
|
||||
(setq org-startup-indented t)
|
||||
(setq org-hide-emphasis-markers t))
|
||||
|
||||
;; Module loading system
|
||||
;; Load modules based on availability and profile
|
||||
(defvar my-modules-dir
|
||||
(expand-file-name "modules/" user-emacs-directory)
|
||||
"Directory containing modular configuration files.")
|
||||
|
||||
(defun load-module (module-name)
|
||||
"Load MODULE-NAME from the modules directory."
|
||||
(let ((module-file (expand-file-name (concat module-name ".el") my-modules-dir)))
|
||||
(when (file-exists-p module-file)
|
||||
(load-file module-file)
|
||||
(message "Loaded module: %s" module-name))))
|
||||
|
||||
;; Load modules based on profile
|
||||
(let ((profile (getenv "EMACS_PROFILE")))
|
||||
(pcase profile
|
||||
("server"
|
||||
;; Minimal modules for server
|
||||
(load-module "ui"))
|
||||
|
||||
((or "laptop" "workstation")
|
||||
;; Full module set for development machines
|
||||
(load-module "ui")
|
||||
(load-module "completion")
|
||||
(load-module "navigation")
|
||||
(load-module "development")
|
||||
(load-module "elisp-development")
|
||||
(when (string= profile "workstation")
|
||||
(load-module "claude-code")))
|
||||
|
||||
(_
|
||||
;; Default module loading (non-Nix environment)
|
||||
(load-module "ui")
|
||||
(load-module "completion")
|
||||
(load-module "navigation"))))
|
||||
|
||||
;; Display startup information
|
||||
(add-hook 'emacs-startup-hook
|
||||
(lambda ()
|
||||
(let ((profile (getenv "EMACS_PROFILE")))
|
||||
(message "=== Emacs Ready ===")
|
||||
(message "Profile: %s" (or profile "default"))
|
||||
(message "Nix Integration: %s" (if profile "enabled" "disabled"))
|
||||
(message "Modules loaded based on profile")
|
||||
(message "==================="))))
|
||||
|
||||
;;; init.el ends here
|
126
dotfiles/geir/emacs-config/modules/claude-code.el
Normal file
126
dotfiles/geir/emacs-config/modules/claude-code.el
Normal file
|
@ -0,0 +1,126 @@
|
|||
;;; claude-code.el --- Claude Code CLI integration module -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Integration with Claude Code CLI for AI-assisted coding directly in Emacs
|
||||
;; Provides terminal interface and commands for interacting with Claude AI
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Install claude-code via quelpa if not already installed
|
||||
(unless (package-installed-p 'claude-code)
|
||||
(quelpa '(claude-code :fetcher github :repo "stevemolitor/claude-code.el")))
|
||||
|
||||
;; Claude Code - AI assistant integration
|
||||
(use-package claude-code
|
||||
:ensure nil ; Already installed via quelpa
|
||||
:bind-keymap ("C-c C-c" . claude-code-command-map)
|
||||
:bind (("C-c C-c c" . claude-code)
|
||||
("C-c C-c s" . claude-code-send-command)
|
||||
("C-c C-c r" . claude-code-send-region)
|
||||
("C-c C-c b" . claude-code-send-buffer)
|
||||
("C-c C-c e" . claude-code-fix-error-at-point)
|
||||
("C-c C-c t" . claude-code-toggle)
|
||||
("C-c C-c k" . claude-code-kill)
|
||||
("C-c C-c n" . claude-code-new))
|
||||
:custom
|
||||
;; Terminal backend preference (eat is now installed via quelpa)
|
||||
(claude-code-terminal-type 'eat)
|
||||
|
||||
;; Enable desktop notifications
|
||||
(claude-code-notifications t)
|
||||
|
||||
;; Startup delay to ensure proper initialization
|
||||
(claude-code-startup-delay 1.0)
|
||||
|
||||
;; Confirm before killing Claude sessions
|
||||
(claude-code-confirm-kill t)
|
||||
|
||||
;; Use modern keybinding style
|
||||
(claude-code-newline-and-send-style 'modern)
|
||||
|
||||
:config
|
||||
;; Smart terminal detection - eat should be available via quelpa
|
||||
(defun claude-code-detect-best-terminal ()
|
||||
"Detect the best available terminal for Claude Code."
|
||||
(cond
|
||||
((package-installed-p 'eat) 'eat)
|
||||
((and (package-installed-p 'vterm)
|
||||
(or (executable-find "cmake")
|
||||
(file-exists-p "/usr/bin/cmake")
|
||||
(file-exists-p "/nix/store/*/bin/cmake")))
|
||||
'vterm)
|
||||
(t 'eat))) ; fallback to eat, should be installed
|
||||
|
||||
;; Set terminal type based on detection
|
||||
(setq claude-code-terminal-type (claude-code-detect-best-terminal))
|
||||
|
||||
;; Auto-start Claude in project root when opening coding files
|
||||
(defun claude-code-auto-start-maybe ()
|
||||
"Auto-start Claude Code if in a project and not already running."
|
||||
(when (and (derived-mode-p 'prog-mode)
|
||||
(project-current)
|
||||
(not (claude-code-running-p)))
|
||||
(claude-code)))
|
||||
|
||||
;; Optional: Auto-start when opening programming files
|
||||
;; Uncomment the next line if you want this behavior
|
||||
;; (add-hook 'prog-mode-hook #'claude-code-auto-start-maybe)
|
||||
|
||||
;; Add helpful message about Claude Code setup
|
||||
(message "Claude Code module loaded. Use C-c C-c c to start Claude, C-c C-c h for help"))
|
||||
|
||||
;; Terminal emulator for Claude Code (eat installed via quelpa in init.el)
|
||||
(use-package eat
|
||||
:ensure nil ; Already installed via quelpa
|
||||
:custom
|
||||
(eat-term-name "xterm-256color")
|
||||
(eat-kill-buffer-on-exit t))
|
||||
|
||||
;; Alternative terminal emulator (if eat fails or user prefers vterm)
|
||||
(use-package vterm
|
||||
:if (and (not (package-installed-p 'eat))
|
||||
(executable-find "cmake"))
|
||||
:custom
|
||||
(vterm-always-compile-module t)
|
||||
(vterm-kill-buffer-on-exit t)
|
||||
(vterm-max-scrollback 10000))
|
||||
|
||||
;; Transient dependency for command menus
|
||||
(use-package transient
|
||||
:ensure t)
|
||||
|
||||
;; Enhanced error handling for Claude Code integration
|
||||
(defun claude-code-send-error-context ()
|
||||
"Send error at point with surrounding context to Claude."
|
||||
(interactive)
|
||||
(if (claude-code-running-p)
|
||||
(let* ((error-line (line-number-at-pos))
|
||||
(start (max 1 (- error-line 5)))
|
||||
(end (min (line-number-at-pos (point-max)) (+ error-line 5)))
|
||||
(context (buffer-substring-no-properties
|
||||
(line-beginning-position (- start error-line))
|
||||
(line-end-position (- end error-line)))))
|
||||
(claude-code-send-command
|
||||
(format "I'm getting an error around line %d. Here's the context:\n\n```%s\n%s\n```\n\nCan you help me fix this?"
|
||||
error-line
|
||||
(or (file-name-extension (buffer-file-name)) "")
|
||||
context)))
|
||||
(message "Claude Code is not running. Start it with C-c C-c c")))
|
||||
|
||||
;; Keybinding for enhanced error context
|
||||
(global-set-key (kbd "C-c C-c x") #'claude-code-send-error-context)
|
||||
|
||||
;; Project-aware Claude instances
|
||||
(defun claude-code-project-instance ()
|
||||
"Start or switch to Claude instance for current project."
|
||||
(interactive)
|
||||
(if-let ((project (project-current)))
|
||||
(let ((default-directory (project-root project)))
|
||||
(claude-code))
|
||||
(claude-code)))
|
||||
|
||||
;; Keybinding for project-specific Claude
|
||||
(global-set-key (kbd "C-c C-c p") #'claude-code-project-instance)
|
||||
|
||||
(provide 'claude-code)
|
||||
;;; claude-code.el ends here
|
55
dotfiles/geir/emacs-config/modules/completion.el
Normal file
55
dotfiles/geir/emacs-config/modules/completion.el
Normal file
|
@ -0,0 +1,55 @@
|
|||
;;; completion.el --- Completion framework configuration -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Modern completion with Vertico, Consult, and Corfu
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Vertico - vertical completion UI
|
||||
(use-package vertico
|
||||
:init
|
||||
(vertico-mode)
|
||||
:custom
|
||||
(vertico-cycle t))
|
||||
|
||||
;; Marginalia - rich annotations in minibuffer
|
||||
(use-package marginalia
|
||||
:init
|
||||
(marginalia-mode))
|
||||
|
||||
;; Consult - enhanced search and navigation commands
|
||||
(use-package consult
|
||||
:bind (("C-s" . consult-line)
|
||||
("C-x b" . consult-buffer)
|
||||
("C-x 4 b" . consult-buffer-other-window)
|
||||
("C-x 5 b" . consult-buffer-other-frame)
|
||||
("M-y" . consult-yank-pop)
|
||||
("M-g g" . consult-goto-line)
|
||||
("M-g M-g" . consult-goto-line)
|
||||
("C-x r b" . consult-bookmark)))
|
||||
|
||||
;; Orderless - flexible completion style
|
||||
(use-package orderless
|
||||
:custom
|
||||
(completion-styles '(orderless basic))
|
||||
(completion-category-defaults nil)
|
||||
(completion-category-overrides '((file (styles partial-completion)))))
|
||||
|
||||
;; Corfu - in-buffer completion popup
|
||||
(use-package corfu
|
||||
:custom
|
||||
(corfu-cycle t)
|
||||
(corfu-auto t)
|
||||
(corfu-auto-delay 0.2)
|
||||
(corfu-auto-prefix 2)
|
||||
:init
|
||||
(global-corfu-mode))
|
||||
|
||||
;; Cape - completion at point extensions
|
||||
(use-package cape
|
||||
:init
|
||||
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
|
||||
(add-to-list 'completion-at-point-functions #'cape-file))
|
||||
|
||||
(provide 'completion)
|
||||
;;; completion.el ends here
|
40
dotfiles/geir/emacs-config/modules/development.el
Normal file
40
dotfiles/geir/emacs-config/modules/development.el
Normal file
|
@ -0,0 +1,40 @@
|
|||
;;; development.el --- Development tools configuration -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; LSP, Copilot, and other development tools
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; LSP Mode
|
||||
(use-package lsp-mode
|
||||
:hook ((prog-mode . lsp-deferred))
|
||||
:commands (lsp lsp-deferred)
|
||||
:custom
|
||||
(lsp-keymap-prefix "C-c l")
|
||||
(lsp-idle-delay 0.5)
|
||||
(lsp-log-io nil)
|
||||
(lsp-completion-provider :none) ; Use corfu instead
|
||||
:config
|
||||
(lsp-enable-which-key-integration t))
|
||||
|
||||
;; LSP UI
|
||||
(use-package lsp-ui
|
||||
:after lsp-mode
|
||||
:custom
|
||||
(lsp-ui-doc-enable t)
|
||||
(lsp-ui-doc-position 'bottom)
|
||||
(lsp-ui-sideline-enable t)
|
||||
(lsp-ui-sideline-show-hover nil))
|
||||
|
||||
;; Which Key - helpful for discovering keybindings
|
||||
(use-package which-key
|
||||
:config
|
||||
(which-key-mode 1)
|
||||
(setq which-key-idle-delay 0.3))
|
||||
|
||||
;; Magit - Git interface
|
||||
(use-package magit
|
||||
:bind ("C-x g" . magit-status))
|
||||
|
||||
(provide 'development)
|
||||
;;; development.el ends here
|
164
dotfiles/geir/emacs-config/modules/elisp-development.el
Normal file
164
dotfiles/geir/emacs-config/modules/elisp-development.el
Normal file
|
@ -0,0 +1,164 @@
|
|||
;;; elisp-development.el --- Enhanced Emacs Lisp development setup -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Specialized configuration for Emacs Lisp development
|
||||
;; This module provides enhanced development tools specifically for .el files
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Enhanced Emacs Lisp mode with better defaults
|
||||
(use-package elisp-mode
|
||||
:ensure nil ; Built-in package
|
||||
:mode "\\.el\\'"
|
||||
:hook ((emacs-lisp-mode . eldoc-mode)
|
||||
(emacs-lisp-mode . show-paren-mode)
|
||||
(emacs-lisp-mode . electric-pair-mode))
|
||||
:bind (:map emacs-lisp-mode-map
|
||||
("C-c C-e" . eval-last-sexp)
|
||||
("C-c C-b" . eval-buffer)
|
||||
("C-c C-r" . eval-region)
|
||||
("C-c C-d" . describe-function-at-point))
|
||||
:config
|
||||
;; Better indentation
|
||||
(setq lisp-indent-function 'lisp-indent-function)
|
||||
|
||||
;; Show function signatures in minibuffer
|
||||
(eldoc-mode 1))
|
||||
|
||||
;; Enhanced Elisp navigation
|
||||
(use-package elisp-slime-nav
|
||||
:hook (emacs-lisp-mode . elisp-slime-nav-mode)
|
||||
:bind (:map elisp-slime-nav-mode-map
|
||||
("M-." . elisp-slime-nav-find-elisp-thing-at-point)
|
||||
("M-," . pop-tag-mark)))
|
||||
|
||||
;; Better parentheses handling
|
||||
(use-package smartparens
|
||||
:hook (emacs-lisp-mode . smartparens-strict-mode)
|
||||
:config
|
||||
(require 'smartparens-config)
|
||||
(sp-local-pair 'emacs-lisp-mode "'" nil :actions nil)
|
||||
(sp-local-pair 'emacs-lisp-mode "`" nil :actions nil))
|
||||
|
||||
;; Rainbow delimiters for better paren visibility
|
||||
(use-package rainbow-delimiters
|
||||
:hook (emacs-lisp-mode . rainbow-delimiters-mode))
|
||||
|
||||
;; Aggressive indentation
|
||||
(use-package aggressive-indent
|
||||
:hook (emacs-lisp-mode . aggressive-indent-mode))
|
||||
|
||||
;; Enhanced help and documentation
|
||||
(use-package helpful
|
||||
:bind (("C-h f" . helpful-callable)
|
||||
("C-h v" . helpful-variable)
|
||||
("C-h k" . helpful-key)
|
||||
("C-h x" . helpful-command)
|
||||
("C-h ." . helpful-at-point)))
|
||||
|
||||
;; Live examples for Elisp functions
|
||||
(use-package elisp-demos
|
||||
:after helpful
|
||||
:config
|
||||
(advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update))
|
||||
|
||||
;; Package linting
|
||||
(use-package package-lint
|
||||
:commands package-lint-current-buffer)
|
||||
|
||||
;; Flycheck for syntax checking
|
||||
(use-package flycheck
|
||||
:hook (emacs-lisp-mode . flycheck-mode)
|
||||
:config
|
||||
;; Enhanced Emacs Lisp checking
|
||||
(setq flycheck-emacs-lisp-load-path 'inherit))
|
||||
|
||||
;; Checkdoc for documentation linting
|
||||
(use-package checkdoc
|
||||
:ensure nil ; Built-in
|
||||
:commands checkdoc)
|
||||
|
||||
;; Enhanced debugging
|
||||
(use-package edebug
|
||||
:ensure nil ; Built-in
|
||||
:bind (:map emacs-lisp-mode-map
|
||||
("C-c C-x C-d" . edebug-defun)
|
||||
("C-c C-x C-b" . edebug-set-breakpoint)))
|
||||
|
||||
;; Package development helpers
|
||||
(use-package auto-compile
|
||||
:config
|
||||
(auto-compile-on-load-mode)
|
||||
(auto-compile-on-save-mode))
|
||||
|
||||
;; Enhanced REPL interaction
|
||||
(use-package ielm
|
||||
:ensure nil ; Built-in
|
||||
:bind ("C-c C-z" . ielm)
|
||||
:config
|
||||
(add-hook 'ielm-mode-hook 'eldoc-mode))
|
||||
|
||||
;; Highlight defined functions and variables
|
||||
(use-package highlight-defined
|
||||
:hook (emacs-lisp-mode . highlight-defined-mode))
|
||||
|
||||
;; Better search and replace for symbols
|
||||
(use-package expand-region
|
||||
:bind ("C-=" . er/expand-region))
|
||||
|
||||
;; Multiple cursors for batch editing
|
||||
(use-package multiple-cursors
|
||||
:bind (("C-S-c C-S-c" . mc/edit-lines)
|
||||
("C->" . mc/mark-next-like-this)
|
||||
("C-<" . mc/mark-previous-like-this)))
|
||||
|
||||
;; Custom functions for Elisp development
|
||||
(defun elisp-eval-and-replace ()
|
||||
"Evaluate the sexp at point and replace it with its value."
|
||||
(interactive)
|
||||
(backward-kill-sexp)
|
||||
(condition-case nil
|
||||
(prin1 (eval (read (current-kill 0)))
|
||||
(current-buffer))
|
||||
(error (message "Invalid expression")
|
||||
(insert (current-kill 0)))))
|
||||
|
||||
(defun elisp-describe-thing-at-point ()
|
||||
"Show the documentation for the thing at point."
|
||||
(interactive)
|
||||
(let ((thing (symbol-at-point)))
|
||||
(cond
|
||||
((fboundp thing) (describe-function thing))
|
||||
((boundp thing) (describe-variable thing))
|
||||
(t (message "No documentation found for %s" thing)))))
|
||||
|
||||
;; Key bindings for custom functions
|
||||
(define-key emacs-lisp-mode-map (kbd "C-c C-x C-e") 'elisp-eval-and-replace)
|
||||
(define-key emacs-lisp-mode-map (kbd "C-c C-d") 'elisp-describe-thing-at-point)
|
||||
|
||||
;; Project-specific configurations
|
||||
(defun setup-elisp-project ()
|
||||
"Set up development environment for Elisp projects."
|
||||
(interactive)
|
||||
(when (and buffer-file-name
|
||||
(string-match "\\.el\\'" buffer-file-name))
|
||||
;; Add current directory to load-path for local requires
|
||||
(add-to-list 'load-path (file-name-directory buffer-file-name))
|
||||
|
||||
;; Set up package development if this looks like a package
|
||||
(when (or (file-exists-p "Cask")
|
||||
(file-exists-p "Eask")
|
||||
(string-match "-pkg\\.el\\'" buffer-file-name))
|
||||
(message "Elisp package development mode enabled"))))
|
||||
|
||||
(add-hook 'emacs-lisp-mode-hook 'setup-elisp-project)
|
||||
|
||||
;; Better compilation output
|
||||
(add-hook 'emacs-lisp-mode-hook
|
||||
(lambda ()
|
||||
(setq-local compile-command
|
||||
(format "emacs -batch -f batch-byte-compile %s"
|
||||
(shell-quote-argument buffer-file-name)))))
|
||||
|
||||
(provide 'elisp-development)
|
||||
;;; elisp-development.el ends here
|
51
dotfiles/geir/emacs-config/modules/navigation.el
Normal file
51
dotfiles/geir/emacs-config/modules/navigation.el
Normal file
|
@ -0,0 +1,51 @@
|
|||
;;; navigation.el --- Navigation and file management -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; File navigation, project management, and window management
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Dired improvements
|
||||
(use-package dired
|
||||
:ensure nil
|
||||
:custom
|
||||
(dired-listing-switches "-alhF")
|
||||
(dired-dwim-target t)
|
||||
:config
|
||||
(put 'dired-find-alternate-file 'disabled nil))
|
||||
|
||||
;; Project management
|
||||
(use-package projectile
|
||||
:config
|
||||
(projectile-mode +1)
|
||||
:bind-keymap
|
||||
("C-c p" . projectile-command-map)
|
||||
:custom
|
||||
(projectile-completion-system 'default))
|
||||
|
||||
;; Treemacs - file tree
|
||||
(use-package treemacs
|
||||
:bind (("M-0" . treemacs-select-window)
|
||||
("C-x t 1" . treemacs-delete-other-windows)
|
||||
("C-x t t" . treemacs)
|
||||
("C-x t d" . treemacs-select-directory)
|
||||
("C-x t B" . treemacs-bookmark)
|
||||
("C-x t C-t" . treemacs-find-file)
|
||||
("C-x t M-t" . treemacs-find-tag))
|
||||
:custom
|
||||
(treemacs-width 30))
|
||||
|
||||
;; Ace Window - quick window switching
|
||||
(use-package ace-window
|
||||
:bind ("M-o" . ace-window)
|
||||
:custom
|
||||
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)))
|
||||
|
||||
;; Winner mode - window configuration undo/redo
|
||||
(use-package winner
|
||||
:ensure nil
|
||||
:config
|
||||
(winner-mode 1))
|
||||
|
||||
(provide 'navigation)
|
||||
;;; navigation.el ends here
|
32
dotfiles/geir/emacs-config/modules/ui.el
Normal file
32
dotfiles/geir/emacs-config/modules/ui.el
Normal file
|
@ -0,0 +1,32 @@
|
|||
;;; ui.el --- UI configuration module -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
;; Enhanced UI configuration - themes, modeline, icons
|
||||
|
||||
;;; Code:
|
||||
|
||||
;; Doom themes
|
||||
(use-package doom-themes
|
||||
:config
|
||||
(load-theme 'doom-monokai-pro t)
|
||||
(doom-themes-visual-bell-config)
|
||||
(doom-themes-org-config))
|
||||
|
||||
;; Doom modeline
|
||||
(use-package doom-modeline
|
||||
:init (doom-modeline-mode 1)
|
||||
:custom
|
||||
(doom-modeline-height 15)
|
||||
(doom-modeline-icon t)
|
||||
(doom-modeline-buffer-file-name-style 'truncate-with-project))
|
||||
|
||||
;; All the icons
|
||||
(use-package all-the-icons
|
||||
:if (display-graphic-p)
|
||||
:config
|
||||
;; Install fonts if not already done
|
||||
(unless (find-font (font-spec :name "all-the-icons"))
|
||||
(all-the-icons-install-fonts t)))
|
||||
|
||||
(provide 'ui)
|
||||
;;; ui.el ends here
|
6
flake.lock
generated
6
flake.lock
generated
|
@ -54,11 +54,11 @@
|
|||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1751011381,
|
||||
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=",
|
||||
"lastModified": 1751271578,
|
||||
"narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7",
|
||||
"rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -261,7 +261,7 @@
|
|||
profiles.system = {
|
||||
user = "root";
|
||||
path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.little-rascal;
|
||||
sshUser = "geir";
|
||||
sshUser = "sma";
|
||||
sudo = "sudo -u";
|
||||
autoRollback = true;
|
||||
magicRollback = true;
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
|
||||
# Development tools
|
||||
../../modules/development/tools.nix
|
||||
../../modules/development/emacs.nix
|
||||
|
||||
# Emacs with workstation profile
|
||||
../../modules/development/emacs.nix
|
||||
|
||||
# AI tools
|
||||
../../modules/ai/claude-code.nix
|
||||
|
@ -61,6 +65,14 @@
|
|||
];
|
||||
};
|
||||
|
||||
# Emacs workstation configuration
|
||||
services.emacs-profiles = {
|
||||
enable = true;
|
||||
profile = "workstation";
|
||||
enableDaemon = true;
|
||||
user = "geir";
|
||||
};
|
||||
|
||||
# Enable clean seatd/greetd login
|
||||
services.seatd-clean.enable = true;
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
../../modules/virtualization/incus.nix
|
||||
../../modules/users/sma.nix
|
||||
|
||||
# Development (minimal for services host)
|
||||
../../modules/development/emacs.nix
|
||||
|
||||
# NFS client with ID mapping
|
||||
../../modules/services/nfs-client.nix
|
||||
|
||||
|
@ -43,6 +46,14 @@
|
|||
# Disks and Updates
|
||||
services.fstrim.enable = true;
|
||||
|
||||
# Emacs server configuration (minimal for services host)
|
||||
services.emacs-profiles = {
|
||||
enable = true;
|
||||
profile = "server";
|
||||
enableDaemon = false;
|
||||
user = "sma";
|
||||
};
|
||||
|
||||
# Mount remote filesystem
|
||||
fileSystems."/mnt/remote/media" = {
|
||||
device = "sleeper-service:/mnt/storage/media";
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
../../modules/common/base.nix
|
||||
../../modules/common/nix.nix
|
||||
../../modules/common/tty.nix
|
||||
../../modules/common/emacs.nix
|
||||
|
||||
# Desktop
|
||||
../../modules/desktop/niri.nix
|
||||
|
@ -25,6 +24,7 @@
|
|||
|
||||
# Development
|
||||
../../modules/development/tools.nix
|
||||
../../modules/development/emacs.nix
|
||||
../../modules/ai/claude-code.nix
|
||||
|
||||
# Users
|
||||
|
@ -79,6 +79,14 @@
|
|||
kernel.sysctl."vm.swappiness" = 180;
|
||||
};
|
||||
|
||||
# Emacs laptop configuration
|
||||
services.emacs-profiles = {
|
||||
enable = true;
|
||||
profile = "laptop";
|
||||
enableDaemon = true;
|
||||
user = "geir";
|
||||
};
|
||||
|
||||
# zram configuration
|
||||
zramSwap = {
|
||||
enable = true;
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
../../modules/network/extraHosts.nix
|
||||
../../modules/users/sma.nix
|
||||
../../modules/security/ssh-keys.nix
|
||||
|
||||
# Development (minimal for edge server)
|
||||
../../modules/development/emacs.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
|
@ -43,6 +46,14 @@
|
|||
# Tailscale for secure management access
|
||||
services.tailscale.enable = true;
|
||||
|
||||
# Emacs server configuration (minimal for edge server)
|
||||
services.emacs-profiles = {
|
||||
enable = true;
|
||||
profile = "server";
|
||||
enableDaemon = false;
|
||||
user = "sma";
|
||||
};
|
||||
|
||||
# SSH configuration - temporarily simplified for testing
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
{ config, lib, pkgs, inputs, unstable, ... }: {
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
unstable,
|
||||
...
|
||||
}: {
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
# Security modules
|
||||
|
@ -10,6 +17,9 @@
|
|||
./nfs.nix
|
||||
./services/transmission.nix
|
||||
|
||||
# Development (minimal for server)
|
||||
../../modules/development/emacs.nix
|
||||
|
||||
# User modules - server only needs sma user
|
||||
../../modules/users/sma.nix
|
||||
];
|
||||
|
@ -21,14 +31,18 @@
|
|||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
mirroredBoots = [
|
||||
{ devices = [ "nodev" ]; path = "/boot"; } ];
|
||||
{
|
||||
devices = ["nodev"];
|
||||
path = "/boot";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
boot.supportedFilesystems = [ "zfs" ];
|
||||
boot.supportedFilesystems = ["zfs"];
|
||||
boot.loader.grub.memtest86.enable = true;
|
||||
|
||||
# Add nomodeset for graphics compatibility
|
||||
boot.kernelParams = [ "nomodeset" ];
|
||||
boot.kernelParams = ["nomodeset"];
|
||||
|
||||
# ZFS services for file server
|
||||
services.zfs = {
|
||||
|
@ -36,6 +50,14 @@
|
|||
trim.enable = true;
|
||||
};
|
||||
|
||||
# Emacs server configuration (minimal)
|
||||
services.emacs-profiles = {
|
||||
enable = true;
|
||||
profile = "server";
|
||||
enableDaemon = false; # Don't run daemon on server
|
||||
user = "sma";
|
||||
};
|
||||
|
||||
# Enable ZFS auto-mounting since we're using ZFS native mountpoints
|
||||
# systemd.services.zfs-mount.enable = lib.mkForce false;
|
||||
|
||||
|
|
285
modules/development/emacs.nix
Normal file
285
modules/development/emacs.nix
Normal file
|
@ -0,0 +1,285 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.services.emacs-profiles;
|
||||
|
||||
# Emacs package configurations for different profiles
|
||||
packageSets = {
|
||||
# Essential packages for all profiles
|
||||
essential = epkgs:
|
||||
with epkgs; [
|
||||
use-package
|
||||
diminish
|
||||
bind-key
|
||||
which-key
|
||||
exec-path-from-shell # Critical for integrating with Nix environment
|
||||
];
|
||||
|
||||
# Minimal packages for server profile
|
||||
minimal = epkgs:
|
||||
with epkgs; [
|
||||
# Basic editing
|
||||
smartparens
|
||||
expand-region
|
||||
|
||||
# Essential navigation (pure Emacs, no external deps)
|
||||
vertico
|
||||
consult
|
||||
marginalia
|
||||
orderless
|
||||
|
||||
# Basic modes for config files
|
||||
nix-mode # Essential for Nix ecosystem
|
||||
yaml-mode
|
||||
markdown-mode
|
||||
|
||||
# Org mode essentials
|
||||
org
|
||||
org-roam
|
||||
];
|
||||
|
||||
# Development packages for laptop/workstation
|
||||
development = epkgs:
|
||||
with epkgs; [
|
||||
# Advanced navigation and completion
|
||||
vertico
|
||||
consult
|
||||
marginalia
|
||||
orderless
|
||||
embark
|
||||
embark-consult
|
||||
corfu
|
||||
cape
|
||||
|
||||
# Project management
|
||||
projectile
|
||||
magit
|
||||
forge
|
||||
|
||||
# Development tools
|
||||
lsp-mode
|
||||
lsp-ui
|
||||
company
|
||||
flycheck
|
||||
yasnippet
|
||||
|
||||
# Language support
|
||||
nix-mode
|
||||
rust-mode
|
||||
python-mode
|
||||
typescript-mode
|
||||
json-mode
|
||||
yaml-mode
|
||||
markdown-mode
|
||||
|
||||
# Org mode and knowledge management
|
||||
org
|
||||
org-roam
|
||||
org-roam-ui
|
||||
org-agenda
|
||||
|
||||
# UI enhancements
|
||||
doom-themes
|
||||
doom-modeline
|
||||
all-the-icons
|
||||
rainbow-delimiters
|
||||
highlight-indent-guides
|
||||
|
||||
# Editing enhancements
|
||||
smartparens
|
||||
expand-region
|
||||
multiple-cursors
|
||||
avy
|
||||
ace-window
|
||||
|
||||
# Terminal integration
|
||||
vterm
|
||||
eshell-git-prompt
|
||||
];
|
||||
|
||||
# Full workstation packages
|
||||
workstation = epkgs:
|
||||
with epkgs; [
|
||||
# All development packages plus extras
|
||||
claude-code # AI assistance (when available)
|
||||
pdf-tools
|
||||
nov # EPUB reader
|
||||
elfeed # RSS reader
|
||||
mu4e # Email (if configured)
|
||||
dired-sidebar
|
||||
treemacs
|
||||
treemacs-projectile
|
||||
treemacs-magit
|
||||
];
|
||||
};
|
||||
|
||||
# Generate Emacs configuration based on profile
|
||||
# Uses emacs-gtk to track upstream with GTK3 support for desktop profiles
|
||||
# Uses emacs-nox for server profiles (no X11/GUI dependencies)
|
||||
emacsWithProfile = profile: let
|
||||
# Choose Emacs package based on profile
|
||||
emacsPackage =
|
||||
if profile == "server"
|
||||
then pkgs.emacs-nox # No GUI for servers
|
||||
else pkgs.emacs-gtk; # GTK3 for desktops
|
||||
|
||||
# Combine package sets based on profile
|
||||
selectedPackages = epkgs:
|
||||
(packageSets.essential epkgs)
|
||||
++ (
|
||||
if profile == "server"
|
||||
then packageSets.minimal epkgs
|
||||
else if profile == "laptop"
|
||||
then packageSets.development epkgs
|
||||
else if profile == "workstation"
|
||||
then (packageSets.development epkgs) ++ (packageSets.workstation epkgs)
|
||||
else packageSets.minimal epkgs
|
||||
);
|
||||
in
|
||||
pkgs.emacsWithPackagesFromUsePackage {
|
||||
config = builtins.readFile ../../dotfiles/geir/emacs-config/init-nix.el;
|
||||
package = emacsPackage;
|
||||
extraEmacsPackages = selectedPackages;
|
||||
|
||||
# Provide external tools that Emacs will use
|
||||
# These will be available via environment variables
|
||||
override = epkgs:
|
||||
epkgs
|
||||
// {
|
||||
# External tools for Emacs integration
|
||||
external-tools =
|
||||
[
|
||||
pkgs.ripgrep # for fast searching
|
||||
pkgs.fd # for file finding
|
||||
pkgs.sqlite # for org-roam database
|
||||
pkgs.ag # the silver searcher
|
||||
pkgs.git # version control
|
||||
pkgs.direnv # environment management
|
||||
|
||||
# Language servers (when available)
|
||||
pkgs.nixd # Nix language server
|
||||
pkgs.nodePackages.bash-language-server
|
||||
pkgs.nodePackages.yaml-language-server
|
||||
pkgs.marksman # Markdown language server
|
||||
|
||||
# Formatters
|
||||
pkgs.alejandra # Nix formatter
|
||||
pkgs.shellcheck # Shell script analysis
|
||||
pkgs.shfmt # Shell script formatter
|
||||
]
|
||||
++ lib.optionals (profile != "server") [
|
||||
# Additional tools for development profiles
|
||||
pkgs.nodejs # for various language servers
|
||||
pkgs.python3 # for Python development
|
||||
pkgs.rustup # Rust toolchain
|
||||
pkgs.go # Go language
|
||||
];
|
||||
};
|
||||
};
|
||||
in {
|
||||
options.services.emacs-profiles = {
|
||||
enable = mkEnableOption "Emacs with machine-specific profiles";
|
||||
|
||||
profile = mkOption {
|
||||
type = types.enum ["server" "laptop" "workstation"];
|
||||
default = "laptop";
|
||||
description = "Emacs profile to use based on machine type";
|
||||
};
|
||||
|
||||
enableDaemon = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable Emacs daemon service";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "geir";
|
||||
description = "User to run Emacs daemon for";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Install Emacs with the selected profile
|
||||
environment.systemPackages = [
|
||||
(emacsWithProfile cfg.profile)
|
||||
];
|
||||
|
||||
# System-wide Emacs daemon (optional)
|
||||
services.emacs = mkIf cfg.enableDaemon {
|
||||
enable = true;
|
||||
package = emacsWithProfile cfg.profile;
|
||||
};
|
||||
|
||||
# Create the Emacs configuration directory structure
|
||||
environment.etc = {
|
||||
"emacs/init.el" = {
|
||||
source = ../../dotfiles/geir/emacs-config/init-nix.el;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
# Module files
|
||||
"emacs/modules/ui.el" = {
|
||||
source = ../../dotfiles/geir/emacs-config/modules/ui.el;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
"emacs/modules/completion.el" = {
|
||||
source = ../../dotfiles/geir/emacs-config/modules/completion.el;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
"emacs/modules/navigation.el" = {
|
||||
source = ../../dotfiles/geir/emacs-config/modules/navigation.el;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
"emacs/modules/development.el" = {
|
||||
source = ../../dotfiles/geir/emacs-config/modules/development.el;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
"emacs/modules/elisp-development.el" = {
|
||||
source = ../../dotfiles/geir/emacs-config/modules/elisp-development.el;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
"emacs/modules/claude-code.el" = mkIf (cfg.profile == "workstation") {
|
||||
source = ../../dotfiles/geir/emacs-config/modules/claude-code.el;
|
||||
mode = "0644";
|
||||
};
|
||||
};
|
||||
|
||||
# Environment variables for Nix integration
|
||||
environment.variables = {
|
||||
EMACS_PROFILE = cfg.profile;
|
||||
|
||||
# Tool paths for Emacs integration
|
||||
RG_PATH = "${pkgs.ripgrep}/bin/rg";
|
||||
FD_PATH = "${pkgs.fd}/bin/fd";
|
||||
SQLITE_PATH = "${pkgs.sqlite}/bin/sqlite3";
|
||||
AG_PATH = "${pkgs.ag}/bin/ag";
|
||||
|
||||
# Language servers
|
||||
NIL_LSP_PATH = "${pkgs.nixd}/bin/nixd";
|
||||
BASH_LSP_PATH = "${pkgs.nodePackages.bash-language-server}/bin/bash-language-server";
|
||||
YAML_LSP_PATH = "${pkgs.nodePackages.yaml-language-server}/bin/yaml-language-server";
|
||||
|
||||
# Formatters
|
||||
SHELLCHECK_PATH = "${pkgs.shellcheck}/bin/shellcheck";
|
||||
ALEJANDRA_PATH = "${pkgs.alejandra}/bin/alejandra";
|
||||
};
|
||||
|
||||
# Ensure the user can access the Emacs daemon
|
||||
systemd.user.services.emacs = mkIf cfg.enableDaemon {
|
||||
environment = {
|
||||
EMACS_PROFILE = cfg.profile;
|
||||
NIX_PATH = config.environment.variables.NIX_PATH or "";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -8,9 +8,7 @@
|
|||
# Editors
|
||||
zed-editor
|
||||
neovim
|
||||
emacs
|
||||
vscode
|
||||
vscodium-fhs
|
||||
|
||||
# Language servers
|
||||
nixd
|
||||
|
@ -35,12 +33,13 @@
|
|||
direnv
|
||||
gh
|
||||
github-copilot-cli
|
||||
deploy-rs
|
||||
# ai
|
||||
claude-code
|
||||
];
|
||||
|
||||
# System-wide Emacs daemon
|
||||
services.emacs.enable = true;
|
||||
# Note: Emacs is now configured via modules/development/emacs.nix
|
||||
# with machine-specific profiles
|
||||
|
||||
# Enable ZSH system-wide for development
|
||||
programs.zsh.enable = true;
|
||||
|
|
|
@ -78,6 +78,33 @@
|
|||
User sma
|
||||
IdentityFile ~/.ssh/id_ed25519_admin
|
||||
|
||||
# Direct sma user access via Tailscale for deployments
|
||||
Host sma@sleeper-service.tail807ea.ts.net
|
||||
Hostname sleeper-service.tail807ea.ts.net
|
||||
User sma
|
||||
IdentityFile ~/.ssh/id_ed25519_admin
|
||||
|
||||
Host sma@grey-area.tail807ea.ts.net
|
||||
Hostname grey-area.tail807ea.ts.net
|
||||
User sma
|
||||
IdentityFile ~/.ssh/id_ed25519_admin
|
||||
|
||||
Host sma@reverse-proxy.tail807ea.ts.net
|
||||
Hostname reverse-proxy.tail807ea.ts.net
|
||||
User sma
|
||||
IdentityFile ~/.ssh/id_ed25519_admin
|
||||
|
||||
Host sma@little-rascal.tail807ea.ts.net
|
||||
Hostname little-rascal.tail807ea.ts.net
|
||||
User sma
|
||||
IdentityFile ~/.ssh/id_ed25519_admin
|
||||
|
||||
# Localhost sma user for local deployment from laptop
|
||||
Host sma@localhost
|
||||
Hostname localhost
|
||||
User sma
|
||||
IdentityFile ~/.ssh/id_ed25519_admin
|
||||
|
||||
# Tailscale network
|
||||
Host 100.* *.tail*
|
||||
User geir
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
;; lab/deployment.scm - Deployment operations (impure)
|
||||
;; lab/deployment.scm - Deploy-rs based deployment operations
|
||||
|
||||
(define-module (lab deployment)
|
||||
#:use-module (ice-9 format)
|
||||
|
@ -7,10 +7,10 @@
|
|||
#:use-module (srfi srfi-1)
|
||||
#:use-module (utils logging)
|
||||
#:use-module (utils config)
|
||||
#:use-module (utils ssh)
|
||||
#:export (deploy-machine
|
||||
update-flake
|
||||
execute-nixos-rebuild
|
||||
deploy-all-machines
|
||||
deploy-with-rollback
|
||||
option-ref))
|
||||
|
||||
;; Helper function for option handling
|
||||
|
@ -19,26 +19,128 @@
|
|||
(let ((value (assoc-ref options key)))
|
||||
(if value value default)))
|
||||
|
||||
;; Impure function: Deploy machine configuration
|
||||
;; Main deployment function using deploy-rs
|
||||
(define (deploy-machine machine-name . args)
|
||||
"Deploy configuration to machine (impure - has side effects)"
|
||||
(let* ((mode (if (null? args) "boot" (car args)))
|
||||
"Deploy configuration to machine using deploy-rs (impure - has side effects)"
|
||||
(let* ((mode (if (null? args) "default" (car args)))
|
||||
(options (if (< (length args) 2) '() (cadr args)))
|
||||
(valid-modes '("boot" "test" "switch"))
|
||||
(dry-run (option-ref options 'dry-run #f)))
|
||||
(dry-run (option-ref options 'dry-run #f))
|
||||
(skip-checks (option-ref options 'skip-checks #f)))
|
||||
|
||||
(if (not (validate-machine-name machine-name))
|
||||
#f
|
||||
(if (not (member mode valid-modes))
|
||||
(begin
|
||||
(log-error "Invalid deployment mode: ~a" mode)
|
||||
(log-error "Valid modes: ~a" (string-join valid-modes ", "))
|
||||
#f)
|
||||
(begin
|
||||
(log-info "Starting deployment: ~a (mode: ~a)" machine-name mode)
|
||||
(execute-nixos-rebuild machine-name mode options))))))
|
||||
(log-info "Starting deploy-rs deployment: ~a" machine-name)
|
||||
(execute-deploy-rs machine-name mode options)))))
|
||||
|
||||
;; Impure function: Update flake inputs
|
||||
;; Execute deploy-rs deployment
|
||||
(define (execute-deploy-rs machine-name mode options)
|
||||
"Execute deployment using deploy-rs with automatic rollback"
|
||||
(let* ((homelab-root (get-homelab-root))
|
||||
(dry-run (option-ref options 'dry-run #f))
|
||||
(skip-checks (option-ref options 'skip-checks #f))
|
||||
(auto-rollback (option-ref options 'auto-rollback #t))
|
||||
(magic-rollback (option-ref options 'magic-rollback #t)))
|
||||
|
||||
(log-info "Deploying ~a using deploy-rs..." machine-name)
|
||||
|
||||
(if dry-run
|
||||
(begin
|
||||
(log-info "DRY RUN: Would execute deploy-rs for ~a" machine-name)
|
||||
(log-info "Command would be: deploy '.#~a'" machine-name)
|
||||
#t)
|
||||
(let* ((deploy-cmd (build-deploy-command machine-name skip-checks auto-rollback magic-rollback))
|
||||
(start-time (current-time)))
|
||||
|
||||
(log-debug "Deploy command: ~a" deploy-cmd)
|
||||
(log-info "Executing deployment with automatic rollback protection...")
|
||||
|
||||
(let* ((port (open-pipe* OPEN_READ "/bin/sh" "-c" deploy-cmd))
|
||||
(output (get-string-all port))
|
||||
(status (close-pipe port))
|
||||
(elapsed (- (current-time) start-time)))
|
||||
|
||||
(if (zero? status)
|
||||
(begin
|
||||
(log-success "Deploy-rs deployment completed successfully in ~as" elapsed)
|
||||
(log-info "Deployment output:")
|
||||
(log-info "~a" output)
|
||||
#t)
|
||||
(begin
|
||||
(log-error "Deploy-rs deployment failed (exit: ~a)" status)
|
||||
(log-error "Error output:")
|
||||
(log-error "~a" output)
|
||||
(log-info "Deploy-rs automatic rollback should have been triggered")
|
||||
#f)))))))
|
||||
|
||||
;; Build deploy-rs command with options
|
||||
(define (build-deploy-command machine-name skip-checks auto-rollback magic-rollback)
|
||||
"Build the deploy-rs command with appropriate flags"
|
||||
(let ((base-cmd (format #f "cd ~a && deploy '.#~a'" (get-homelab-root) machine-name))
|
||||
(flags '()))
|
||||
|
||||
;; Add flags based on options
|
||||
(when skip-checks
|
||||
(set! flags (cons "--skip-checks" flags)))
|
||||
|
||||
(when auto-rollback
|
||||
(set! flags (cons "--auto-rollback" flags)))
|
||||
|
||||
(when magic-rollback
|
||||
(set! flags (cons "--magic-rollback" flags)))
|
||||
|
||||
;; Combine command with flags
|
||||
(if (null? flags)
|
||||
base-cmd
|
||||
(format #f "~a ~a" base-cmd (string-join flags " ")))))
|
||||
|
||||
;; Deploy to all machines
|
||||
(define (deploy-all-machines . args)
|
||||
"Deploy to all machines using deploy-rs"
|
||||
(let* ((options (if (null? args) '() (car args)))
|
||||
(dry-run (option-ref options 'dry-run #f))
|
||||
(machines (get-all-machines)))
|
||||
|
||||
(log-info "Starting deployment to all machines (~a total)" (length machines))
|
||||
|
||||
(let ((results
|
||||
(map (lambda (machine)
|
||||
(log-info "Deploying to ~a..." machine)
|
||||
(let ((result (deploy-machine machine "default" options)))
|
||||
(if result
|
||||
(log-success "✓ ~a deployed successfully" machine)
|
||||
(log-error "✗ ~a deployment failed" machine))
|
||||
(cons machine result)))
|
||||
machines)))
|
||||
|
||||
;; Summary
|
||||
(let ((successful (filter cdr results))
|
||||
(failed (filter (lambda (r) (not (cdr r))) results)))
|
||||
(log-info "Deployment summary:")
|
||||
(log-info " Successful: ~a/~a machines" (length successful) (length machines))
|
||||
(when (not (null? failed))
|
||||
(log-error " Failed: ~a" (string-join (map car failed) ", ")))
|
||||
|
||||
;; Return true if all succeeded
|
||||
(= (length successful) (length machines))))))
|
||||
|
||||
;; Deploy with explicit rollback testing
|
||||
(define (deploy-with-rollback machine-name . args)
|
||||
"Deploy with explicit rollback capability testing"
|
||||
(let* ((options (if (null? args) '() (car args)))
|
||||
(test-rollback (option-ref options 'test-rollback #f)))
|
||||
|
||||
(log-info "Deploying ~a with rollback testing..." machine-name)
|
||||
|
||||
(if test-rollback
|
||||
(begin
|
||||
(log-info "Testing rollback mechanism (deploy will be reverted)")
|
||||
;; Deploy with magic rollback disabled to test manual rollback
|
||||
(let ((modified-options (cons '(magic-rollback . #f) options)))
|
||||
(execute-deploy-rs machine-name "default" modified-options)))
|
||||
(execute-deploy-rs machine-name "default" options))))
|
||||
|
||||
;; Update flake inputs (moved from old deployment module)
|
||||
(define (update-flake . args)
|
||||
"Update flake inputs (impure - has side effects)"
|
||||
(let* ((options (if (null? args) '() (car args)))
|
||||
|
@ -64,76 +166,3 @@
|
|||
(log-error "Flake update failed (exit: ~a)" status)
|
||||
(log-error "Error output: ~a" output)
|
||||
#f))))))
|
||||
|
||||
;; Impure function: Execute nixos-rebuild
|
||||
(define (execute-nixos-rebuild machine-name mode options)
|
||||
"Execute nixos-rebuild command (impure - has side effects)"
|
||||
(let* ((dry-run (option-ref options 'dry-run #f))
|
||||
(ssh-config (get-ssh-config machine-name))
|
||||
(is-local (and ssh-config (assoc-ref ssh-config 'is-local)))
|
||||
(homelab-root (get-homelab-root)))
|
||||
|
||||
(if is-local
|
||||
;; Local deployment
|
||||
(let ((rebuild-cmd (format #f "sudo nixos-rebuild ~a --flake ~a#~a"
|
||||
mode homelab-root machine-name)))
|
||||
(log-debug "Local rebuild command: ~a" rebuild-cmd)
|
||||
|
||||
(if dry-run
|
||||
(begin
|
||||
(log-info "DRY RUN: Would execute: ~a" rebuild-cmd)
|
||||
#t)
|
||||
(let* ((port (open-pipe* OPEN_READ "/bin/sh" "-c" rebuild-cmd))
|
||||
(output (get-string-all port))
|
||||
(status (close-pipe port)))
|
||||
|
||||
(if (zero? status)
|
||||
(begin
|
||||
(log-success "Local nixos-rebuild completed")
|
||||
#t)
|
||||
(begin
|
||||
(log-error "Local nixos-rebuild failed (exit: ~a)" status)
|
||||
#f)))))
|
||||
|
||||
;; Remote deployment
|
||||
(let* ((hostname (assoc-ref ssh-config 'hostname))
|
||||
(ssh-alias (or (assoc-ref ssh-config 'ssh-alias) hostname))
|
||||
(temp-dir "/tmp/homelab-deploy")
|
||||
(sync-cmd (format #f "rsync -av --delete ~a/ ~a:~a/"
|
||||
homelab-root ssh-alias temp-dir))
|
||||
(rebuild-cmd (format #f "ssh ~a 'cd ~a && sudo nixos-rebuild ~a --flake .#~a'"
|
||||
ssh-alias temp-dir mode machine-name)))
|
||||
|
||||
(log-debug "Remote sync command: ~a" sync-cmd)
|
||||
(log-debug "Remote rebuild command: ~a" rebuild-cmd)
|
||||
|
||||
(if dry-run
|
||||
(begin
|
||||
(log-info "DRY RUN: Would sync and rebuild remotely")
|
||||
#t)
|
||||
(begin
|
||||
;; Sync configuration
|
||||
(log-info "Syncing configuration to ~a..." machine-name)
|
||||
(let* ((sync-port (open-pipe* OPEN_READ "/bin/sh" "-c" sync-cmd))
|
||||
(sync-output (get-string-all sync-port))
|
||||
(sync-status (close-pipe sync-port)))
|
||||
|
||||
(if (zero? sync-status)
|
||||
(begin
|
||||
(log-success "Configuration synced")
|
||||
;; Execute rebuild
|
||||
(log-info "Executing nixos-rebuild on ~a..." machine-name)
|
||||
(let* ((rebuild-port (open-pipe* OPEN_READ "/bin/sh" "-c" rebuild-cmd))
|
||||
(rebuild-output (get-string-all rebuild-port))
|
||||
(rebuild-status (close-pipe rebuild-port)))
|
||||
|
||||
(if (zero? rebuild-status)
|
||||
(begin
|
||||
(log-success "Remote nixos-rebuild completed")
|
||||
#t)
|
||||
(begin
|
||||
(log-error "Remote nixos-rebuild failed (exit: ~a)" rebuild-status)
|
||||
#f))))
|
||||
(begin
|
||||
(log-error "Configuration sync failed (exit: ~a)" sync-status)
|
||||
#f)))))))))
|
||||
|
|
|
@ -22,44 +22,48 @@
|
|||
;; Pure function: Command help text
|
||||
(define (get-help-text)
|
||||
"Pure function returning help text"
|
||||
"Home Lab Tool - K.I.S.S Refactored Edition
|
||||
"Home Lab Tool - Deploy-rs Edition
|
||||
|
||||
USAGE: lab <command> [args...]
|
||||
|
||||
COMMANDS:
|
||||
status Show infrastructure status
|
||||
machines List all machines
|
||||
deploy <machine> [mode] Deploy configuration to machine
|
||||
Available modes: boot (default), test, switch
|
||||
deploy-all Deploy to all machines
|
||||
deploy <machine> [options] Deploy configuration to machine using deploy-rs
|
||||
Options: --dry-run, --skip-checks
|
||||
deploy-all [options] Deploy to all machines using deploy-rs
|
||||
update Update flake inputs
|
||||
auto-update Perform automatic system update with health checks
|
||||
auto-update-status Show auto-update service status and logs
|
||||
health [machine] Check machine health (all if no machine specified)
|
||||
ssh <machine> SSH to machine
|
||||
test-modules Test modular implementation
|
||||
ssh <machine> SSH to machine (using sma user)
|
||||
test-rollback <machine> Test deployment with rollback
|
||||
help Show this help
|
||||
|
||||
EXAMPLES:
|
||||
lab status
|
||||
lab machines
|
||||
lab deploy congenital-optimist # Deploy with boot mode (default)
|
||||
lab deploy congenital-optimist switch # Deploy and activate immediately
|
||||
lab deploy congenital-optimist test # Deploy temporarily for testing
|
||||
lab deploy-all
|
||||
lab update
|
||||
lab auto-update # Perform automatic update with reboot
|
||||
lab auto-update-status # Show auto-update logs and status
|
||||
lab health
|
||||
lab health sleeper-service
|
||||
lab ssh sleeper-service
|
||||
lab test-modules
|
||||
lab deploy congenital-optimist # Deploy with deploy-rs safety
|
||||
lab deploy sleeper-service --dry-run # Test deployment without applying
|
||||
lab deploy grey-area --skip-checks # Deploy without health checks
|
||||
lab deploy-all # Deploy to all machines
|
||||
lab deploy-all --dry-run # Test deployment to all machines
|
||||
lab update # Update flake inputs
|
||||
lab test-rollback sleeper-service # Test rollback functionality
|
||||
lab ssh sleeper-service # SSH to machine as sma user
|
||||
|
||||
This implementation follows K.I.S.S principles:
|
||||
- Modular: Each module has single responsibility
|
||||
- Functional: Pure functions separated from side effects
|
||||
- Small: Individual modules under 50 lines
|
||||
- Simple: One function does one thing well
|
||||
Deploy-rs Features:
|
||||
- Automatic rollback on deployment failure
|
||||
- Health checks after deployment
|
||||
- Magic rollback for network connectivity issues
|
||||
- Atomic deployments with safety guarantees
|
||||
- Consistent sma user for all deployments
|
||||
|
||||
This implementation uses deploy-rs for all deployments:
|
||||
- Robust: Automatic rollback protection
|
||||
- Safe: Health checks and validation
|
||||
- Consistent: Same deployment method for all machines
|
||||
- Flexible: Dry-run and skip-checks options available
|
||||
")
|
||||
|
||||
;; Pure function: Format machine list
|
||||
|
@ -109,36 +113,33 @@ Home lab root: ~a
|
|||
(log-success "Machine list complete")))
|
||||
|
||||
(define (cmd-deploy machine-name . args)
|
||||
"Deploy configuration to machine"
|
||||
(let* ((mode (if (null? args) "boot" (car args)))
|
||||
(valid-modes '("boot" "test" "switch")))
|
||||
(log-info "Deploying to machine: ~a (mode: ~a)" machine-name mode)
|
||||
(if (not (member mode valid-modes))
|
||||
(begin
|
||||
(log-error "Invalid deployment mode: ~a" mode)
|
||||
(log-error "Valid modes: ~a" (string-join valid-modes ", "))
|
||||
(format #t "Usage: lab deploy <machine> [boot|test|switch]\n"))
|
||||
"Deploy configuration to machine using deploy-rs"
|
||||
(let* ((options (parse-deploy-options args)))
|
||||
(log-info "Deploying to machine: ~a using deploy-rs" machine-name)
|
||||
(if (validate-machine-name machine-name)
|
||||
(let ((result (deploy-machine machine-name mode '())))
|
||||
(let ((result (deploy-machine machine-name "default" options)))
|
||||
(if result
|
||||
(log-success "Deployment to ~a complete (mode: ~a)" machine-name mode)
|
||||
(log-error "Deployment to ~a failed" machine-name)))
|
||||
(log-success "Deploy-rs deployment to ~a completed successfully" machine-name)
|
||||
(log-error "Deploy-rs deployment to ~a failed" machine-name)))
|
||||
(begin
|
||||
(log-error "Invalid machine: ~a" machine-name)
|
||||
(log-info "Available machines: ~a" (string-join (get-all-machines) ", ")))))))
|
||||
(log-info "Available machines: ~a" (string-join (get-all-machines) ", "))))))
|
||||
|
||||
(define (cmd-ssh machine-name)
|
||||
"SSH to machine"
|
||||
(log-info "Connecting to machine: ~a" machine-name)
|
||||
"SSH to machine using sma user"
|
||||
(log-info "Connecting to machine: ~a as sma user" machine-name)
|
||||
(if (validate-machine-name machine-name)
|
||||
(let ((ssh-config (get-ssh-config machine-name)))
|
||||
(if ssh-config
|
||||
(let ((hostname (assoc-ref ssh-config 'hostname))
|
||||
(ssh-alias (assoc-ref ssh-config 'ssh-alias))
|
||||
(ssh-user (assoc-ref ssh-config 'ssh-user))
|
||||
(is-local (assoc-ref ssh-config 'is-local)))
|
||||
(if is-local
|
||||
(log-info "Machine ~a is local - no SSH needed" machine-name)
|
||||
(let ((target (or ssh-alias hostname)))
|
||||
(begin
|
||||
(log-info "Machine ~a is local - switching to sma user locally" machine-name)
|
||||
(system "sudo -u sma -i"))
|
||||
(let ((target (format #f "~a@~a" (or ssh-user "sma") (or ssh-alias hostname))))
|
||||
(log-info "Connecting to ~a via SSH..." target)
|
||||
(system (format #f "ssh ~a" target)))))
|
||||
(log-error "No SSH configuration found for ~a" machine-name)))
|
||||
|
@ -171,20 +172,12 @@ Home lab root: ~a
|
|||
(log-error "Flake update failed"))))
|
||||
|
||||
(define (cmd-deploy-all)
|
||||
"Deploy to all machines"
|
||||
(log-info "Deploying to all machines...")
|
||||
(let* ((machines (list-machines))
|
||||
(results (map (lambda (machine)
|
||||
(log-info "Deploying to ~a..." machine)
|
||||
(let ((result (deploy-machine machine "boot" '())))
|
||||
"Deploy to all machines using deploy-rs"
|
||||
(log-info "Deploying to all machines using deploy-rs...")
|
||||
(let ((result (deploy-all-machines '())))
|
||||
(if result
|
||||
(log-success "✓ ~a deployed" machine)
|
||||
(log-error "✗ ~a failed" machine))
|
||||
result))
|
||||
machines))
|
||||
(successful (filter identity results)))
|
||||
(log-info "Deployment summary: ~a/~a successful"
|
||||
(length successful) (length machines))))
|
||||
(log-success "All deploy-rs deployments completed successfully")
|
||||
(log-error "Some deploy-rs deployments failed"))))
|
||||
|
||||
(define (cmd-health args)
|
||||
"Check machine health"
|
||||
|
@ -219,6 +212,33 @@ Home lab root: ~a
|
|||
"Show auto-update status and logs"
|
||||
(auto-update-status))
|
||||
|
||||
;; Parse deployment options from command line arguments
|
||||
(define (parse-deploy-options args)
|
||||
"Parse deployment options from command line arguments"
|
||||
(let ((options '()))
|
||||
(for-each
|
||||
(lambda (arg)
|
||||
(cond
|
||||
((string=? arg "--dry-run")
|
||||
(set! options (cons '(dry-run . #t) options)))
|
||||
((string=? arg "--skip-checks")
|
||||
(set! options (cons '(skip-checks . #t) options)))
|
||||
(else
|
||||
(log-warn "Unknown option: ~a" arg))))
|
||||
args)
|
||||
options))
|
||||
|
||||
(define (cmd-test-rollback machine-name)
|
||||
"Test deployment with rollback functionality"
|
||||
(log-info "Testing rollback deployment for machine: ~a" machine-name)
|
||||
(if (validate-machine-name machine-name)
|
||||
(let ((options '((test-rollback . #t))))
|
||||
(let ((result (deploy-with-rollback machine-name options)))
|
||||
(if result
|
||||
(log-success "Rollback test completed for ~a" machine-name)
|
||||
(log-error "Rollback test failed for ~a" machine-name))))
|
||||
(log-error "Invalid machine: ~a" machine-name)))
|
||||
|
||||
;; Main command dispatcher
|
||||
(define (dispatch-command command args)
|
||||
"Dispatch command with appropriate handler"
|
||||
|
@ -236,12 +256,20 @@ Home lab root: ~a
|
|||
(if (null? args)
|
||||
(begin
|
||||
(log-error "deploy command requires machine name")
|
||||
(format #t "Usage: lab deploy <machine> [boot|test|switch]\n"))
|
||||
(format #t "Usage: lab deploy <machine> [options]\n")
|
||||
(format #t "Options: --dry-run, --skip-checks\n"))
|
||||
(apply cmd-deploy args)))
|
||||
|
||||
('deploy-all
|
||||
(cmd-deploy-all))
|
||||
|
||||
('test-rollback
|
||||
(if (null? args)
|
||||
(begin
|
||||
(log-error "test-rollback command requires machine name")
|
||||
(format #t "Usage: lab test-rollback <machine>\n"))
|
||||
(cmd-test-rollback (car args))))
|
||||
|
||||
('update
|
||||
(cmd-update))
|
||||
|
||||
|
@ -264,6 +292,13 @@ Home lab root: ~a
|
|||
('test-modules
|
||||
(cmd-test-modules))
|
||||
|
||||
('test-rollback
|
||||
(if (null? args)
|
||||
(begin
|
||||
(log-error "test-rollback command requires machine name")
|
||||
(format #t "Usage: lab test-rollback <machine>\n"))
|
||||
(cmd-test-rollback (car args))))
|
||||
|
||||
(_
|
||||
(log-error "Unknown command: ~a" command)
|
||||
(format #t "Use 'lab help' for available commands\n")
|
||||
|
@ -272,7 +307,7 @@ Home lab root: ~a
|
|||
;; Main entry point
|
||||
(define (main args)
|
||||
"Main entry point for lab tool"
|
||||
(log-info "Home Lab Tool - K.I.S.S Refactored Edition")
|
||||
(log-info "Home Lab Tool - Deploy-rs Edition")
|
||||
|
||||
(let* ((parsed-cmd (if (> (length args) 1) (cdr args) '("help")))
|
||||
(command (string->symbol (car parsed-cmd)))
|
||||
|
|
|
@ -22,26 +22,31 @@
|
|||
(machines . ((congenital-optimist
|
||||
(type . local)
|
||||
(hostname . "localhost")
|
||||
(ssh-user . "sma")
|
||||
(services . (workstation development)))
|
||||
(sleeper-service
|
||||
(type . remote)
|
||||
(hostname . "sleeper-service.tail807ea.ts.net")
|
||||
(ssh-alias . "admin-sleeper")
|
||||
(ssh-alias . "sleeper-service.tail807ea.ts.net")
|
||||
(ssh-user . "sma")
|
||||
(services . (nfs zfs storage)))
|
||||
(grey-area
|
||||
(type . remote)
|
||||
(hostname . "grey-area.tail807ea.ts.net")
|
||||
(ssh-alias . "admin-grey")
|
||||
(ssh-alias . "grey-area.tail807ea.ts.net")
|
||||
(ssh-user . "sma")
|
||||
(services . (ollama forgejo git)))
|
||||
(reverse-proxy
|
||||
(type . remote)
|
||||
(hostname . "reverse-proxy.tail807ea.ts.net")
|
||||
(ssh-alias . "admin-reverse")
|
||||
(ssh-alias . "reverse-proxy.tail807ea.ts.net")
|
||||
(ssh-user . "sma")
|
||||
(services . (nginx proxy ssl)))
|
||||
(little-rascal
|
||||
(type . remote)
|
||||
(hostname . "little-rascal.tail807ea.ts.net")
|
||||
(ssh-alias . "little-rascal")
|
||||
(ssh-alias . "little-rascal.tail807ea.ts.net")
|
||||
(ssh-user . "sma")
|
||||
(services . (development niri desktop ai-tools)))))
|
||||
(deployment . ((default-mode . "boot")
|
||||
(timeout . 300)
|
||||
|
@ -124,10 +129,12 @@
|
|||
(if machine-config
|
||||
(let ((type (assoc-ref machine-config 'type))
|
||||
(hostname (assoc-ref machine-config 'hostname))
|
||||
(ssh-alias (assoc-ref machine-config 'ssh-alias)))
|
||||
(ssh-alias (assoc-ref machine-config 'ssh-alias))
|
||||
(ssh-user (assoc-ref machine-config 'ssh-user)))
|
||||
`((type . ,type)
|
||||
(hostname . ,hostname)
|
||||
(ssh-alias . ,ssh-alias)
|
||||
(ssh-user . ,ssh-user)
|
||||
(is-local . ,(eq? type 'local))))
|
||||
#f)))
|
||||
|
||||
|
|
34
shell.nix
Normal file
34
shell.nix
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Nix shell for Home Lab development with deploy-rs and lab-tool
|
||||
|
||||
{
|
||||
description = "Home Lab dev shell with deploy-rs and lab-tool";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
deploy-rs.url = "github:serokell/deploy-rs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, deploy-rs, ... }@inputs: let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in {
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.git
|
||||
pkgs.guile_3_0
|
||||
pkgs.guile-ssh
|
||||
pkgs.guile-json
|
||||
pkgs.guile-git
|
||||
pkgs.guile-gcrypt
|
||||
pkgs.openssh
|
||||
pkgs.nixos-rebuild
|
||||
deploy-rs.packages.${system}.deploy-rs
|
||||
(import ./packages/lab-tool/default.nix { inherit (pkgs) lib stdenv makeWrapper guile_3_0 guile-ssh guile-json guile-git guile-gcrypt openssh git nixos-rebuild; })
|
||||
];
|
||||
shellHook = ''
|
||||
echo "Dev shell: deploy-rs and lab-tool available."
|
||||
echo "Try: lab status, lab deploy <machine>, or deploy . <target>"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue