feat: implement SSH + rsync deployment method
- Extract deploy-rs code into separate module (lab/deploy-rs.scm) - Create new SSH + rsync deployment module (lab/ssh-deploy.scm) - Make SSH + rsync the default deployment method - Update help text and examples - Add options: --boot, --test, --use-deploy-rs - Supports same workflow as manual: rsync + nixos-rebuild --flake This provides a faster, simpler deployment method that matches the manual workflow: sudo nixos-rebuild --flake /path#machine
This commit is contained in:
parent
59d287a543
commit
3599f278a7
4 changed files with 394 additions and 151 deletions
140
packages/lab-tool/lab/deploy-rs.scm
Normal file
140
packages/lab-tool/lab/deploy-rs.scm
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
;; lab/deploy-rs.scm - Deploy-rs based deployment operations (extracted)
|
||||||
|
|
||||||
|
(define-module (lab deploy-rs)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
#:use-module (ice-9 popen)
|
||||||
|
#:use-module (ice-9 textual-ports)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (utils logging)
|
||||||
|
#:use-module (utils config)
|
||||||
|
#:export (deploy-machine-deploy-rs
|
||||||
|
deploy-all-machines-deploy-rs
|
||||||
|
deploy-with-rollback
|
||||||
|
option-ref))
|
||||||
|
|
||||||
|
;; Helper function for option handling
|
||||||
|
(define (option-ref options key default)
|
||||||
|
"Get option value with default fallback"
|
||||||
|
(let ((value (assoc-ref options key)))
|
||||||
|
(if value value default)))
|
||||||
|
|
||||||
|
;; Main deployment function using deploy-rs
|
||||||
|
(define (deploy-machine-deploy-rs machine-name . 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)))
|
||||||
|
(dry-run (option-ref options 'dry-run #f))
|
||||||
|
(skip-checks (option-ref options 'skip-checks #f)))
|
||||||
|
|
||||||
|
(if (not (validate-machine-name machine-name))
|
||||||
|
#f
|
||||||
|
(begin
|
||||||
|
(log-info "Starting deploy-rs deployment: ~a" machine-name)
|
||||||
|
(execute-deploy-rs machine-name mode options)))))
|
||||||
|
|
||||||
|
;; 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-info "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=true" flags)))
|
||||||
|
|
||||||
|
(when magic-rollback
|
||||||
|
(set! flags (cons "--magic-rollback=true" flags)))
|
||||||
|
|
||||||
|
;; Combine command with flags
|
||||||
|
(if (null? flags)
|
||||||
|
base-cmd
|
||||||
|
(format #f "~a ~a" base-cmd (string-join (reverse flags) " ")))))
|
||||||
|
|
||||||
|
;; Deploy to all machines
|
||||||
|
(define (deploy-all-machines-deploy-rs . 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-deploy-rs 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))))
|
|
@ -1,4 +1,4 @@
|
||||||
;; lab/deployment.scm - Deploy-rs based deployment operations
|
;; lab/deployment.scm - Unified deployment operations (SSH + rsync by default)
|
||||||
|
|
||||||
(define-module (lab deployment)
|
(define-module (lab deployment)
|
||||||
#:use-module (ice-9 format)
|
#:use-module (ice-9 format)
|
||||||
|
@ -7,162 +7,61 @@
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
#:use-module (utils logging)
|
#:use-module (utils logging)
|
||||||
#:use-module (utils config)
|
#:use-module (utils config)
|
||||||
|
#:use-module (lab ssh-deploy)
|
||||||
|
#:use-module (lab deploy-rs)
|
||||||
#:export (deploy-machine
|
#:export (deploy-machine
|
||||||
update-flake
|
update-flake
|
||||||
deploy-all-machines
|
deploy-all-machines
|
||||||
deploy-with-rollback
|
deploy-with-rollback
|
||||||
option-ref))
|
option-ref))
|
||||||
|
|
||||||
;; Helper function for option handling
|
;; Helper function for option handling (re-exported from ssh-deploy)
|
||||||
(define (option-ref options key default)
|
(define (option-ref options key default)
|
||||||
"Get option value with default fallback"
|
"Get option value with default fallback"
|
||||||
(let ((value (assoc-ref options key)))
|
(let ((value (assoc-ref options key)))
|
||||||
(if value value default)))
|
(if value value default)))
|
||||||
|
|
||||||
;; Main deployment function using deploy-rs
|
;; Main deployment function - SSH by default, deploy-rs optional
|
||||||
(define (deploy-machine machine-name . args)
|
(define (deploy-machine machine-name . args)
|
||||||
"Deploy configuration to machine using deploy-rs (impure - has side effects)"
|
"Deploy configuration to machine using SSH + rsync (default) or deploy-rs (optional)"
|
||||||
(let* ((mode (if (null? args) "default" (car args)))
|
(let* ((mode (if (null? args) "default" (car args)))
|
||||||
(options (if (< (length args) 2) '() (cadr args)))
|
(options (if (< (length args) 2) '() (cadr args)))
|
||||||
(dry-run (option-ref options 'dry-run #f))
|
(use-deploy-rs (option-ref options 'use-deploy-rs #f)))
|
||||||
(skip-checks (option-ref options 'skip-checks #f)))
|
|
||||||
|
|
||||||
(if (not (validate-machine-name machine-name))
|
(if (not (validate-machine-name machine-name))
|
||||||
#f
|
#f
|
||||||
|
(if use-deploy-rs
|
||||||
(begin
|
(begin
|
||||||
(log-info "Starting deploy-rs deployment: ~a" machine-name)
|
(log-info "Using deploy-rs deployment method")
|
||||||
(execute-deploy-rs machine-name mode options)))))
|
(deploy-machine-deploy-rs machine-name mode options))
|
||||||
|
|
||||||
;; 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
|
(begin
|
||||||
(log-info "DRY RUN: Would execute deploy-rs for ~a" machine-name)
|
(log-info "Using SSH + rsync deployment method")
|
||||||
(log-info "Command would be: deploy '.#~a'" machine-name)
|
(deploy-machine-ssh machine-name mode options))))))
|
||||||
#t)
|
|
||||||
(let* ((deploy-cmd (build-deploy-command machine-name skip-checks auto-rollback magic-rollback))
|
|
||||||
(start-time (current-time)))
|
|
||||||
|
|
||||||
(log-info "Deploy command: ~a" deploy-cmd)
|
;; Deploy to all machines - delegate to appropriate module
|
||||||
(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=true" flags)))
|
|
||||||
|
|
||||||
(when magic-rollback
|
|
||||||
(set! flags (cons "--magic-rollback=true" flags)))
|
|
||||||
|
|
||||||
;; Combine command with flags
|
|
||||||
(if (null? flags)
|
|
||||||
base-cmd
|
|
||||||
(format #f "~a ~a" base-cmd (string-join (reverse flags) " ")))))
|
|
||||||
|
|
||||||
;; Deploy to all machines
|
|
||||||
(define (deploy-all-machines . args)
|
(define (deploy-all-machines . args)
|
||||||
"Deploy to all machines using deploy-rs"
|
"Deploy to all machines using SSH + rsync (default) or deploy-rs (optional)"
|
||||||
(let* ((options (if (null? args) '() (car args)))
|
(let* ((options (if (null? args) '() (car args)))
|
||||||
(dry-run (option-ref options 'dry-run #f))
|
(use-deploy-rs (option-ref options 'use-deploy-rs #f)))
|
||||||
(machines (get-all-machines)))
|
|
||||||
|
|
||||||
(log-info "Starting deployment to all machines (~a total)" (length machines))
|
(if use-deploy-rs
|
||||||
|
(begin
|
||||||
|
(log-info "Using deploy-rs for all machines")
|
||||||
|
(deploy-all-machines-deploy-rs options))
|
||||||
|
(begin
|
||||||
|
(log-info "Using SSH + rsync for all machines")
|
||||||
|
(deploy-all-machines-ssh options)))))
|
||||||
|
|
||||||
(let ((results
|
;; Deploy with rollback testing - only available with deploy-rs
|
||||||
(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)
|
(define (deploy-with-rollback machine-name . args)
|
||||||
"Deploy with explicit rollback capability testing"
|
"Deploy with explicit rollback capability testing (deploy-rs only)"
|
||||||
(let* ((options (if (null? args) '() (car args)))
|
(let* ((options (if (null? args) '() (car args)))
|
||||||
(test-rollback (option-ref options 'test-rollback #f)))
|
(modified-options (cons '(use-deploy-rs . #t) options)))
|
||||||
|
|
||||||
(log-info "Deploying ~a with rollback testing..." machine-name)
|
(log-info "Rollback testing requires deploy-rs - switching to deploy-rs mode")
|
||||||
|
(deploy-with-rollback machine-name modified-options)))
|
||||||
|
|
||||||
(if test-rollback
|
;; Update flake inputs - delegate to ssh-deploy module
|
||||||
(begin
|
(define update-flake
|
||||||
(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)"
|
"Update flake inputs (impure - has side effects)"
|
||||||
(let* ((options (if (null? args) '() (car args)))
|
(@ (lab ssh-deploy) update-flake))
|
||||||
(dry-run (option-ref options 'dry-run #f)))
|
|
||||||
|
|
||||||
(log-info "Updating flake inputs...")
|
|
||||||
|
|
||||||
(if dry-run
|
|
||||||
(begin
|
|
||||||
(log-info "DRY RUN: Would execute: nix flake update")
|
|
||||||
#t)
|
|
||||||
(let* ((homelab-root (get-homelab-root))
|
|
||||||
(update-cmd (format #f "cd ~a && nix flake update" homelab-root))
|
|
||||||
(port (open-pipe* OPEN_READ "/bin/sh" "-c" update-cmd))
|
|
||||||
(output (get-string-all port))
|
|
||||||
(status (close-pipe port)))
|
|
||||||
|
|
||||||
(if (zero? status)
|
|
||||||
(begin
|
|
||||||
(log-success "Flake inputs updated successfully")
|
|
||||||
#t)
|
|
||||||
(begin
|
|
||||||
(log-error "Flake update failed (exit: ~a)" status)
|
|
||||||
(log-error "Error output: ~a" output)
|
|
||||||
#f))))))
|
|
||||||
|
|
198
packages/lab-tool/lab/ssh-deploy.scm
Normal file
198
packages/lab-tool/lab/ssh-deploy.scm
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
;; lab/ssh-deploy.scm - SSH + rsync + nixos-rebuild deployment operations
|
||||||
|
|
||||||
|
(define-module (lab ssh-deploy)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
#:use-module (ice-9 popen)
|
||||||
|
#:use-module (ice-9 textual-ports)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (utils logging)
|
||||||
|
#:use-module (utils config)
|
||||||
|
#:use-module (utils ssh)
|
||||||
|
#:export (deploy-machine-ssh
|
||||||
|
deploy-all-machines-ssh
|
||||||
|
update-flake
|
||||||
|
sync-config-to-machine
|
||||||
|
option-ref))
|
||||||
|
|
||||||
|
;; Helper function for option handling
|
||||||
|
(define (option-ref options key default)
|
||||||
|
"Get option value with default fallback"
|
||||||
|
(let ((value (assoc-ref options key)))
|
||||||
|
(if value value default)))
|
||||||
|
|
||||||
|
;; Main SSH deployment function
|
||||||
|
(define (deploy-machine-ssh machine-name . args)
|
||||||
|
"Deploy configuration to machine using SSH + rsync + nixos-rebuild"
|
||||||
|
(let* ((mode (if (null? args) "default" (car args)))
|
||||||
|
(options (if (< (length args) 2) '() (cadr args)))
|
||||||
|
(dry-run (option-ref options 'dry-run #f))
|
||||||
|
(boot-mode (option-ref options 'boot #f)))
|
||||||
|
|
||||||
|
(if (not (validate-machine-name machine-name))
|
||||||
|
#f
|
||||||
|
(begin
|
||||||
|
(log-info "Starting SSH deployment: ~a" machine-name)
|
||||||
|
(execute-ssh-deploy machine-name mode options)))))
|
||||||
|
|
||||||
|
;; Execute SSH-based deployment
|
||||||
|
(define (execute-ssh-deploy machine-name mode options)
|
||||||
|
"Execute deployment using SSH + rsync + nixos-rebuild"
|
||||||
|
(let* ((homelab-root (get-homelab-root))
|
||||||
|
(dry-run (option-ref options 'dry-run #f))
|
||||||
|
(boot-mode (option-ref options 'boot #f))
|
||||||
|
(test-mode (option-ref options 'test #f))
|
||||||
|
(remote-path "/tmp/home-lab-config"))
|
||||||
|
|
||||||
|
(log-info "Deploying ~a using SSH + rsync + nixos-rebuild..." machine-name)
|
||||||
|
|
||||||
|
(if dry-run
|
||||||
|
(begin
|
||||||
|
(log-info "DRY RUN: Would sync config and rebuild ~a" machine-name)
|
||||||
|
(log-info "Would execute: rsync + nixos-rebuild --flake /tmp/home-lab-config#~a" machine-name)
|
||||||
|
#t)
|
||||||
|
(let ((start-time (current-time)))
|
||||||
|
|
||||||
|
;; Step 1: Sync configuration to remote machine
|
||||||
|
(log-info "Step 1: Syncing configuration to ~a:~a" machine-name remote-path)
|
||||||
|
(if (sync-config-to-machine machine-name remote-path)
|
||||||
|
;; Step 2: Execute nixos-rebuild on remote machine
|
||||||
|
(begin
|
||||||
|
(log-info "Step 2: Executing nixos-rebuild on ~a" machine-name)
|
||||||
|
(execute-remote-rebuild machine-name remote-path boot-mode test-mode start-time))
|
||||||
|
(begin
|
||||||
|
(log-error "Failed to sync configuration to ~a" machine-name)
|
||||||
|
#f))))))
|
||||||
|
|
||||||
|
;; Sync configuration to remote machine
|
||||||
|
(define (sync-config-to-machine machine-name remote-path)
|
||||||
|
"Sync Home-lab configuration to remote machine using rsync"
|
||||||
|
(let* ((homelab-root (get-homelab-root))
|
||||||
|
(ssh-config (get-ssh-config machine-name)))
|
||||||
|
|
||||||
|
(if (not ssh-config)
|
||||||
|
(begin
|
||||||
|
(log-error "No SSH configuration found for ~a" machine-name)
|
||||||
|
#f)
|
||||||
|
(if (assoc-ref ssh-config 'is-local)
|
||||||
|
;; Local "sync" - just ensure path exists
|
||||||
|
(begin
|
||||||
|
(log-debug "Local machine ~a, copying to ~a" machine-name remote-path)
|
||||||
|
(let* ((cp-cmd (format #f "sudo mkdir -p ~a && sudo cp -r ~a/* ~a/"
|
||||||
|
remote-path homelab-root remote-path))
|
||||||
|
(status (system cp-cmd)))
|
||||||
|
(if (zero? status)
|
||||||
|
(begin
|
||||||
|
(log-debug "Local configuration copied successfully")
|
||||||
|
#t)
|
||||||
|
(begin
|
||||||
|
(log-error "Local configuration copy failed (exit: ~a)" status)
|
||||||
|
#f))))
|
||||||
|
;; Remote sync using rsync
|
||||||
|
(let* ((hostname (assoc-ref ssh-config 'hostname))
|
||||||
|
(ssh-alias (assoc-ref ssh-config 'ssh-alias))
|
||||||
|
(user (assoc-ref ssh-config 'user))
|
||||||
|
(identity-file (assoc-ref ssh-config 'identity-file))
|
||||||
|
(target (if user (format #f "~a@~a" user (or ssh-alias hostname)) (or ssh-alias hostname)))
|
||||||
|
(key-arg (if identity-file (format #f "-i ~a" identity-file) ""))
|
||||||
|
(rsync-cmd (format #f "rsync -avz --delete -e 'ssh ~a' ~a/ ~a:~a/"
|
||||||
|
key-arg homelab-root target remote-path)))
|
||||||
|
|
||||||
|
(log-debug "Rsync command: ~a" rsync-cmd)
|
||||||
|
(let* ((port (open-pipe* OPEN_READ "/bin/sh" "-c" rsync-cmd))
|
||||||
|
(output (get-string-all port))
|
||||||
|
(status (close-pipe port)))
|
||||||
|
|
||||||
|
(if (zero? status)
|
||||||
|
(begin
|
||||||
|
(log-debug "Configuration synced successfully")
|
||||||
|
(log-debug "Rsync output: ~a" output)
|
||||||
|
#t)
|
||||||
|
(begin
|
||||||
|
(log-error "Configuration sync failed (exit: ~a)" status)
|
||||||
|
(log-error "Rsync error: ~a" output)
|
||||||
|
#f))))))))
|
||||||
|
|
||||||
|
;; Execute nixos-rebuild on remote machine
|
||||||
|
(define (execute-remote-rebuild machine-name remote-path boot-mode test-mode start-time)
|
||||||
|
"Execute nixos-rebuild on the remote machine"
|
||||||
|
(let* ((rebuild-mode (cond
|
||||||
|
(test-mode "test")
|
||||||
|
(boot-mode "boot")
|
||||||
|
(else "switch")))
|
||||||
|
(rebuild-cmd (format #f "sudo nixos-rebuild ~a --flake ~a#~a"
|
||||||
|
rebuild-mode remote-path machine-name)))
|
||||||
|
|
||||||
|
(log-info "Executing: ~a" rebuild-cmd)
|
||||||
|
|
||||||
|
(call-with-values
|
||||||
|
(lambda () (run-remote-command machine-name rebuild-cmd))
|
||||||
|
(lambda (success output)
|
||||||
|
(let ((elapsed (- (current-time) start-time)))
|
||||||
|
(if success
|
||||||
|
(begin
|
||||||
|
(log-success "SSH deployment completed successfully in ~as" elapsed)
|
||||||
|
(log-info "Rebuild output:")
|
||||||
|
(log-info "~a" output)
|
||||||
|
#t)
|
||||||
|
(begin
|
||||||
|
(log-error "SSH deployment failed (exit code indicates failure)")
|
||||||
|
(log-error "Rebuild error output:")
|
||||||
|
(log-error "~a" output)
|
||||||
|
#f)))))))
|
||||||
|
|
||||||
|
;; Deploy to all machines using SSH
|
||||||
|
(define (deploy-all-machines-ssh . args)
|
||||||
|
"Deploy to all machines using SSH + rsync + nixos-rebuild"
|
||||||
|
(let* ((options (if (null? args) '() (car args)))
|
||||||
|
(dry-run (option-ref options 'dry-run #f))
|
||||||
|
(machines (get-all-machines)))
|
||||||
|
|
||||||
|
(log-info "Starting SSH deployment to all machines (~a total)" (length machines))
|
||||||
|
|
||||||
|
(let ((results
|
||||||
|
(map (lambda (machine)
|
||||||
|
(log-info "Deploying to ~a..." machine)
|
||||||
|
(let ((result (deploy-machine-ssh 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 "SSH 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))))))
|
||||||
|
|
||||||
|
;; Update flake inputs
|
||||||
|
(define (update-flake . args)
|
||||||
|
"Update flake inputs (impure - has side effects)"
|
||||||
|
(let* ((options (if (null? args) '() (car args)))
|
||||||
|
(dry-run (option-ref options 'dry-run #f)))
|
||||||
|
|
||||||
|
(log-info "Updating flake inputs...")
|
||||||
|
|
||||||
|
(if dry-run
|
||||||
|
(begin
|
||||||
|
(log-info "DRY RUN: Would execute: nix flake update")
|
||||||
|
#t)
|
||||||
|
(let* ((homelab-root (get-homelab-root))
|
||||||
|
(update-cmd (format #f "cd ~a && nix flake update" homelab-root))
|
||||||
|
(port (open-pipe* OPEN_READ "/bin/sh" "-c" update-cmd))
|
||||||
|
(output (get-string-all port))
|
||||||
|
(status (close-pipe port)))
|
||||||
|
|
||||||
|
(if (zero? status)
|
||||||
|
(begin
|
||||||
|
(log-success "Flake inputs updated successfully")
|
||||||
|
#t)
|
||||||
|
(begin
|
||||||
|
(log-error "Flake update failed (exit: ~a)" status)
|
||||||
|
(log-error "Error output: ~a" output)
|
||||||
|
#f))))))
|
|
@ -22,49 +22,55 @@
|
||||||
;; Pure function: Command help text
|
;; Pure function: Command help text
|
||||||
(define (get-help-text)
|
(define (get-help-text)
|
||||||
"Pure function returning help text"
|
"Pure function returning help text"
|
||||||
"Home Lab Tool - Deploy-rs Edition
|
"Home Lab Tool - SSH + Rsync Edition
|
||||||
|
|
||||||
USAGE: lab <command> [args...]
|
USAGE: lab <command> [args...]
|
||||||
|
|
||||||
COMMANDS:
|
COMMANDS:
|
||||||
status Show infrastructure status
|
status Show infrastructure status
|
||||||
machines List all machines
|
machines List all machines
|
||||||
deploy <machine> [options] Deploy configuration to machine using deploy-rs
|
deploy <machine> [options] Deploy configuration to machine using SSH + rsync + nixos-rebuild
|
||||||
Options: --dry-run, --skip-checks
|
Options: --dry-run, --boot, --test, --use-deploy-rs
|
||||||
deploy-all [options] Deploy to all machines using deploy-rs
|
deploy-all [options] Deploy to all machines using SSH + rsync + nixos-rebuild
|
||||||
update Update flake inputs
|
update Update flake inputs
|
||||||
auto-update Perform automatic system update with health checks
|
auto-update Perform automatic system update with health checks
|
||||||
auto-update-status Show auto-update service status and logs
|
auto-update-status Show auto-update service status and logs
|
||||||
health [machine] Check machine health (all if no machine specified)
|
health [machine] Check machine health (all if no machine specified)
|
||||||
ssh <machine> SSH to machine (using sma user)
|
ssh <machine> SSH to machine (using sma user)
|
||||||
test-rollback <machine> Test deployment with rollback
|
test-rollback <machine> Test deployment with rollback (uses deploy-rs)
|
||||||
help Show this help
|
help Show this help
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
lab status
|
lab status
|
||||||
lab machines
|
lab machines
|
||||||
lab deploy congenital-optimist # Deploy with deploy-rs safety
|
lab deploy little-rascal # Deploy with SSH + rsync (default)
|
||||||
lab deploy sleeper-service --dry-run # Test deployment without applying
|
lab deploy little-rascal --dry-run # Test deployment without applying
|
||||||
lab deploy grey-area --skip-checks # Deploy without health checks
|
lab deploy little-rascal --boot # Deploy but only activate on next boot
|
||||||
|
lab deploy little-rascal --test # Deploy but don't make permanent
|
||||||
|
lab deploy little-rascal --use-deploy-rs # Use deploy-rs instead of SSH method
|
||||||
lab deploy-all # Deploy to all machines
|
lab deploy-all # Deploy to all machines
|
||||||
lab deploy-all --dry-run # Test deployment to all machines
|
lab deploy-all --dry-run # Test deployment to all machines
|
||||||
lab update # Update flake inputs
|
lab update # Update flake inputs
|
||||||
lab test-rollback sleeper-service # Test rollback functionality
|
lab test-rollback sleeper-service # Test rollback functionality (deploy-rs)
|
||||||
lab ssh sleeper-service # SSH to machine as sma user
|
lab ssh sleeper-service # SSH to machine as sma user
|
||||||
|
|
||||||
Deploy-rs Features:
|
SSH + Rsync Features (Default):
|
||||||
|
- Fast: Only syncs changed files with rsync
|
||||||
|
- Simple: Uses standard nixos-rebuild workflow
|
||||||
|
- Reliable: Same command workflow as manual deployment
|
||||||
|
- Flexible: Supports boot, test, and switch modes
|
||||||
|
|
||||||
|
Deploy-rs Features (Optional with --use-deploy-rs):
|
||||||
- Automatic rollback on deployment failure
|
- Automatic rollback on deployment failure
|
||||||
- Health checks after deployment
|
- Health checks after deployment
|
||||||
- Magic rollback for network connectivity issues
|
- Magic rollback for network connectivity issues
|
||||||
- Atomic deployments with safety guarantees
|
- Atomic deployments with safety guarantees
|
||||||
- Consistent sma user for all deployments
|
|
||||||
|
|
||||||
This implementation uses deploy-rs for all deployments:
|
This implementation uses SSH + rsync + nixos-rebuild by default:
|
||||||
- Robust: Automatic rollback protection
|
- Fast: Efficient file synchronization
|
||||||
- Safe: Health checks and validation
|
- Simple: Standard NixOS deployment workflow
|
||||||
- Consistent: Same deployment method for all machines
|
- Consistent: Same user (sma) for all operations
|
||||||
- Flexible: Dry-run and skip-checks options available
|
- Flexible: Multiple deployment modes available"
|
||||||
")
|
|
||||||
|
|
||||||
;; Pure function: Format machine list
|
;; Pure function: Format machine list
|
||||||
(define (format-machine-list machines)
|
(define (format-machine-list machines)
|
||||||
|
@ -323,4 +329,4 @@ Home lab root: ~a
|
||||||
|
|
||||||
;; Run main function if script is executed directly
|
;; Run main function if script is executed directly
|
||||||
(when (and (defined? 'command-line) (not (null? (command-line))))
|
(when (and (defined? 'command-line) (not (null? (command-line))))
|
||||||
(main (command-line)))
|
(main (command-line))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue