# nudeploy ## Introduction nudeploy is a lightweight, idempotent deployment orchestration tool for managing systemd services across multiple remote hosts using SSH. Built with Nushell, it provides a declarative approach to service deployment without requiring agent installation on target machines. The tool leverages existing SSH configurations and focuses on safe, repeatable deployments by comparing file checksums before copying and only restarting services when actual changes are detected. The core functionality centers around a single TOML configuration file that defines hosts, services, and sync rules. nudeploy handles the complete lifecycle: uploading service files, managing systemd unit files, ensuring correct permissions, and controlling service states. It supports various deployment scenarios including dry-run planning, incremental updates, and playbook execution for system provisioning. The idempotent design ensures that repeated deployments are safe and only apply changes when necessary, making it ideal for continuous deployment pipelines and infrastructure automation. ## API Reference and Functions ### Deploy Command Idempotently deploys services to remote hosts by syncing files, installing systemd units, and managing service states based on checksum comparison. ```bash # Deploy all enabled services to production group with sudo privileges nudeploy deploy --group prod --sudo # Deploy specific service to selected hosts nudeploy deploy --service axon --hosts server1,server2 --sudo # Dry-run to preview changes without applying them nudeploy deploy --dry-run --service axon --group prod # Deploy with JSON output for CI/CD integration nudeploy deploy --service helix --group web --sudo --json | jq -e 'all(.[]; .changed? // false)' ``` ### Status Command Retrieves systemd service status information from remote hosts including enabled state, active state, and detailed systemd properties. ```bash # Check status of all enabled services in production group nudeploy status --group prod # Check specific service status across multiple hosts nudeploy status --service axon --hosts host1,host2 # Get JSON output with detailed systemd properties nudeploy status --service helix --group all --json ``` ### Restart Command Restarts services on target hosts without syncing files or checking for changes. ```bash # Restart all enabled services on specific host nudeploy restart --hosts host1 --sudo # Restart specific service across a group nudeploy restart --service axon --group prod --sudo # Restart with JSON output nudeploy restart --service helix --hosts server1 --sudo --json ``` ### Hosts Command Lists configured hosts with their connection details and group membership. ```bash # List all enabled hosts nudeploy hosts # List hosts in specific group nudeploy hosts --group prod # Output as JSON for processing nudeploy hosts --json | jq '.[] | select(.group == "prod")' ``` ### Exec Command Executes shell commands or playbook files on remote hosts with optional sudo privileges. ```bash # Run single command on all hosts in group nudeploy exec "uname -a" --group prod # Execute command with sudo nudeploy exec "systemctl status sshd" --hosts host1 --sudo # Run playbook file (executes line by line, stops on first error) nudeploy exec playbooks/arch_up.nu --group prod --sudo # Execute with JSON output to capture results nudeploy exec "df -h" --hosts server1,server2 --json ``` ### Download Command Downloads and extracts artifacts on remote hosts using curl or wget. ```bash # Download all enabled artifacts defined in config nudeploy download --group prod # Download specific artifacts by name nudeploy download --name openobserve --hosts server1 --sudo # Download with custom config file nudeploy download --config ./custom-nudeploy.toml --group all ``` ### Copy Command Copies files from one location to another on remote hosts with checksum comparison to avoid unnecessary transfers. ```bash # Copy all enabled items to remote hosts nudeploy copy --group prod # Copy specific items by name nudeploy copy --name serve --hosts host1,host2 --sudo # Copy with JSON output nudeploy copy --name binary --group all --sudo --json ``` ### Configuration File Defines hosts, services, downloads, and copy operations in a single TOML file. ```toml # nudeploy.toml - Complete configuration example # Define target hosts [[hosts]] name = "webserver1" ip = "192.168.1.10" port = 22 user = "deploy" shell = "bash" # Optional: sh (default), bash, or zsh enable = true group = "web" [[hosts]] name = "dbserver" ip = "192.168.1.20" port = 2222 user = "admin" shell = "zsh" enable = true group = "database" # Define services to deploy [[services]] name = "myapp" src_dir = "./myapp" dst_dir = "/opt/myapp" unit_file = "myapp.service" # Relative to src_dir or absolute path sync_files = [ { from = "config.yaml", to = "config.yaml" }, { from = "bin/myapp", to = "bin/myapp", chmod = "0755" }, { from = "https://example.com/data.json", to = "data.json" }, ] restart = true # Restart service when files change enable = true # Enable service on deployment [[services]] name = "nginx" src_dir = "./nginx" dst_dir = "/etc/nginx" unit_file = "/etc/systemd/system/nginx.service" sync_mode = "rsync" # Use rsync instead of scp (default: scp) sync_files = [ { from = "nginx.conf", to = "nginx.conf", chmod = "0644" }, { from = "sites-enabled/", to = "sites-enabled/" }, ] restart = true enable = true # Define downloads (artifacts to fetch on remote) [[downloads]] name = "openobserve" version = "v0.15.0" url = "https://downloads.openobserve.ai/releases/openobserve/v0.15.0/openobserve-v0.15.0-linux-amd64.tar.gz" enable = true dst_dir = "/opt/openobserve" extract = true # Auto-extract tar.gz, tar.xz, zip, tar, gz, xz # Define copy operations (remote-to-remote file copies) [[copy]] name = "serve-binary" src = "/home/builder/serve-rs/target/release/serve" dst = "/home/deploy/bin/serve" mode = "0755" enable = true ``` ### Nushell Library Functions Core functions exported from lib.nu for programmatic use or custom scripts. ```nu # Load configuration from TOML file use lib.nu * let cfg = (load_config "./nudeploy.toml") # Returns: { hosts: [...], services: [...] } # Select target hosts based on filters let targets = (select_targets $cfg "prod" "") # Returns: list of host records matching group "prod" # Build service metadata for deployment let svc = (resolve_service $cfg "myapp") let meta = (build_service_meta $svc) # Returns: normalized service metadata with paths and sync items # Execute SSH command on host let result = (ssh_run $host "systemctl status myapp" --sudo=true) # Returns: { exit_code: 0, stdout: "...", stderr: "..." } # Calculate local file SHA256 hash let hash = (local_sha256 "/path/to/file") # Returns: "abc123..." (hash string) # Calculate remote file SHA256 hash let remote_hash = (remote_sha256 $host "/remote/path" --sudo=false) # Returns: "abc123..." or "MISSING" or "ERROR" # Upload file if changed (idempotent) let result = (compare_and_upload $host $meta "/local/file" "/remote/file" "0644" --sudo=false) # Returns: { changed: true, reason: "copied" } or { changed: false, reason: "up-to-date" } # Deploy service to single host let result = (deploy_host $meta $host --sudo=true) # Returns: { host: "server1", changed: true, events: [...], error: "" } # Check service status on host let status = (status_host $meta $host --sudo=true) # Returns: { host: "server1", enabled: true, active: true, raw: {...} } # Parse playbook file into executable steps let steps = (parse_playbook "./playbooks/setup.sh") # Returns: [{ line: 1, cmd: "sudo apt update" }, ...] # Execute playbook on host let result = (play_host $host $steps --sudo=true) # Returns: { host: "server1", ok: true, failed_line: null, events: [...] } ``` ### Install Script Automated installation to ~/.local/bin for quick setup. ```bash # Install from GitHub (remote) curl -fsSL https://raw.githubusercontent.com/longcipher/nudeploy/main/install.sh | bash # Install from local repository cd nudeploy chmod +x install.sh ./install.sh # Verify installation nudeploy --help # Ensure ~/.local/bin is in PATH echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` ### SSH Connection Management nudeploy uses SSH connection multiplexing for improved performance across multiple commands. ```nu # Establish persistent SSH connection (5-minute keep-alive) ssh_connect $host # Creates control socket at /tmp/nudeploy-{user}_{host}_{port}.sock # Subsequent commands reuse the connection automatically let res1 = (ssh_run $host "echo test1") let res2 = (ssh_run $host "echo test2") let res3 = (ssh_run $host "echo test3") # All three commands use the same connection for speed ``` ### Playbook Execution Playbooks are text files with shell commands executed sequentially, stopping on first failure. ```bash # playbooks/bootstrap.sh # Update package manager sudo apt-get update -y # Install dependencies sudo apt-get install -y curl wget git # Create application user id -u appuser >/dev/null 2>&1 || sudo useradd -m -s /bin/bash appuser # Create application directory sudo mkdir -p /opt/myapp sudo chown appuser:appuser /opt/myapp # Execute playbook nudeploy exec playbooks/bootstrap.sh --group all --sudo ``` ### Error Handling and Permissions Proper error handling for common deployment scenarios. ```bash # Handle permission denied errors # Ensure destinations are writable or use --sudo flag nudeploy deploy --service myapp --group prod --sudo # Configure passwordless sudo on remote hosts echo "deploy ALL=(ALL) NOPASSWD: /bin/systemctl" | sudo tee /etc/sudoers.d/deploy # Handle missing remote tools # Ensure sha256sum, shasum, or openssl is installed ssh user@host "which sha256sum || sudo apt-get install -y coreutils" # Verify unit file before deployment systemd-analyze verify ./myapp/myapp.service # Check SSH connectivity before deployment ssh -o ConnectTimeout=5 user@host "echo OK" ``` ## Summary and Integration Patterns nudeploy excels in scenarios requiring repeatable, verifiable deployments of systemd services across multiple Linux hosts. The primary use case is continuous deployment pipelines where applications packaged as systemd units need to be rolled out to staging, production, or edge computing environments. Its idempotent nature makes it safe to run repeatedly in automated workflows, with the --dry-run flag enabling preview-before-apply patterns. The JSON output mode integrates seamlessly with CI/CD systems like GitHub Actions, GitLab CI, or Jenkins, allowing teams to parse deployment results and fail builds on errors. The tool also serves well for infrastructure bootstrap scenarios through playbook execution, combining system provisioning commands with service deployment in a single workflow. Integration patterns typically involve version-controlled configuration repositories where nudeploy.toml files define infrastructure as code alongside service files and systemd units. Teams can maintain separate configurations for different environments (dev, staging, production) and use git branches or tags to control deployments. The download command enables pulling pre-built binaries from artifact repositories, while the copy command facilitates promoting builds from build servers to runtime locations. SSH multiplexing provides efficient execution across large server fleets, and the group-based targeting allows rolling deployments by deploying to canary groups before full production rollout. The tool's minimal dependencies (just Nushell and SSH) make it suitable for air-gapped environments and embedded systems where heavy orchestration tools would be impractical.