Skip to content

longcipher/nudeploy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nudeploy

DeepWiki Context7

hpx

A tiny, stable, idempotent deployment helper to roll out a systemd service to multiple remote hosts using:

  • Nushell for orchestration
  • SSH/SCP for remote execution and file transfer

It reuses your existing SSH config at ~/.ssh/config. No extra SSH config in this repo.

Configuration: one TOML file

Define hosts and services in a single TOML config (defaults to ./nudeploy.toml). Example:

[[hosts]]
name = "localhost"
ip = "127.0.0.1"
port = 22
user = "akagi201"
shell = "zsh" # Optional: override shell wrapper (default "sh") to load correct profile/env
enable = true
group = "prod"

[[services]]
name = "axon"
src_dir = "./axon"
dst_dir = "/home/akagi201/axon"
unit_file = "axon.service"  # relative to src_dir or absolute
sync_files = [
  { from = "foo.conf", to = "bar.conf" },
  # Support downloading on remote directly:
  # { from = "https://example.com/file.conf", to = "bar.conf" }
  # Optional per-file chmod (applied with sudo after sync); defaults to "0644"
  # { from = "bin/myapp", to = "bin/myapp", chmod = "0755" },
]
restart = true   # restart service when files changed
enable = true    # enable service (default true)

You can target hosts by group or explicitly via --hosts hostA,hostB.

Requirements

  • macOS or Linux
  • Nushell v0.90+ (newer preferred)
  • bash (for the CLI wrapper)
  • ssh, scp
  • Remote machines run systemd and have sudo available if you need to install into /etc
  • Remote tools: one of sha256sum | shasum | openssl must be available (most distros have at least one)

Install

Quick Install (Bash script)

This installs nudeploy to ~/.local/bin.

curl -fsSL https://raw.githubusercontent.com/longcipher/nudeploy/main/install.sh | bash

Ensure ~/.local/bin is in your $PATH.

Manual Install

  • Ensure Nushell is installed:
    • brew install nushell
  • Make the wrapper executable:
    • chmod +x nudeploy/nudeploy.sh

Install via nupm (for Nu users)

The repo includes nupm.nuon. You can install via nupm and get a nudeploy bin on PATH (points to nudeploy.nu).

# Local install from the current directory (install nupm first per its docs)
nu -c 'nupm install --path .'

# Or install from Git (example)
nu -c 'nupm install --git https://github.com/longcipher/nudeploy'

# Now you can run it directly (nupm exposes `nudeploy` on PATH)
nudeploy --help

Note: After nupm install, nudeploy runs nudeploy.nu. The Bash wrapper nudeploy.sh still works standalone.

Usage

  • Plan (no changes; shows what would change):
# All enabled services
nudeploy deploy --dry-run --group prod
# Single service
nudeploy deploy --dry-run --service axon --group prod
  • Deploy to a group (idempotent):
# All enabled services
nudeploy deploy --group prod --sudo
# Single service
nudeploy deploy --service axon --group prod --sudo
  • Deploy to specific hosts:
nudeploy deploy --service axon --hosts host1,host2 --sudo
  • Check status:
# All enabled services
nudeploy status --group prod
# Single service
nudeploy status --service axon --group prod
  • Restart without redeploying files:
# All enabled services
nudeploy restart --hosts host1
# Single service
nudeploy restart --service axon --hosts host1
  • List hosts (enabled by default):
nudeploy hosts --group prod
  • Run a shell command on targets:
nudeploy exec "uname -a" --group prod
  • Run a playbook (script execution, stop on error):
# Run silently (shows only last command output)
nudeploy exec playbooks/arch.sh --group prod

# Run verbosely (shows every command and its output)
nudeploy exec playbooks/arch.sh --group prod -v
  • Download artifacts locally (curl + extract):
# All enabled downloads in config
nudeploy download

# Only selected names
nudeploy download --name openobserve

# Alternate config file
nudeploy download --config ./nudeploy.toml

Options

  • --config: Path to config TOML (default: ./nudeploy.toml)
  • --service: Service name from config (optional for deploy/status/restart). If omitted, acts on all services with enable = true.
  • --group: Hosts group
  • --hosts: Comma-separated hostnames (SSH Host aliases)
  • --sudo: Use sudo for systemd actions (daemon-reload/enable/start/restart) and installing unit files into /etc. All other file and directory operations run as the SSH user.
  • --json: Emit JSON output suitable for CI
  • --name: For download, comma-separated artifact names to fetch (defaults to all enabled)
  • --dry-run: For deploy, show what would change without applying (formerly plan)

Idempotency strategy

  • Files are uploaded only if remote checksum differs
  • Optional chmod is enforced after sync as the SSH user when chmod is set on an item
  • Systemd daemon-reload runs only when unit changed
  • Service is enabled once if not enabled
  • Service is restarted only when changes detected (or restart mode forces it)

Notes

  • nudeploy does not install software on remote machines; it only pushes your service unit/config and manages systemd
  • Per-file permissions: set chmod = "0755" on binaries you need to execute; default mode is 0644.
  • Local download subcommand reads download_dir and [[downloads]] from your config, fetches with curl, extracts by suffix (tar.gz/tgz, tar.xz, zip, tar, gz, xz), and removes archives after extraction.
  • For sudo prompts, passwordless sudo is recommended for automation

Playbooks

Playbooks are shell scripts (Bash syntax) that nudeploy executes remotely over a single SSH session.

  • Single Session: Context is preserved between lines (e.g., cd /tmp affects subsequent commands).
  • Automatic Error Handling: set -e is automatically prepended, so execution stops immediately if any command fails.
  • Verbose Mode (-v): Shows every executed command (prefixed with 👉 [Line N]) and its output interleaved.
  • Quiet Mode (default): Shows only the output of the last executed command (useful for status checks).

Example playbooks/bootstrap.sh:

# Context is preserved
cd /tmp
curl -L -o app.tar.gz https://example.com/app.tar.gz

# Variables work
APP_DIR="/opt/app"
mkdir -p "$APP_DIR"
tar -xzf app.tar.gz -C "$APP_DIR"

# Logic works
if ! id -u deploy >/dev/null 2>&1; then
    useradd -m -s /bin/bash deploy
fi

Dev tips

  • The Bash wrapper only parses CLI; all orchestration lives in Nushell

Quick start

  1. Install prerequisites (macOS):
brew install nushell
  1. Ensure the CLI is executable and callable:
chmod +x nudeploy/nudeploy.sh
./nudeploy/nudeploy.sh --help
  1. Define your config at ./nudeploy.toml with [[hosts]] and [[services]]. Host names are arbitrary labels; you can also set ip/user/port.
./nudeploy/nudeploy.sh plan \
  --service example-service \
  --group all \
  --sudo

When you’re ready:

./nudeploy/nudeploy.sh deploy \
  --service example-service \
  --group all \
  --sudo

Tip: If Nushell is not on PATH or is named differently, set NU=/path/to/nu before running.

Model and behavior

  • Unit file is copied to /etc/systemd/system/<service>.service.
  • sync_files entries copy local files (hash-compared) or download URLs on the remote; only changed files are installed.
  • Idempotent: files only update on hash change; daemon-reload only when unit changes; enable once; restart on change when restart=true.

End-to-end example

Use the included example config/service to get a feel for the workflow:

# Plan the changes (no writes)
./nudeploy/nudeploy.sh plan \
  --service axon \
  --group prod \
  --sudo

# Apply changes idempotently
./nudeploy/nudeploy.sh deploy \
  --service axon \
  --group prod \
  --sudo

# Check status
./nudeploy/nudeploy.sh status \
  --service example-service \
  --group all

Outputs:

  • Plan shows which items would be uploaded per host.
  • Deploy uploads/downloads only when checksums differ, reloads systemd if unit changed, enables once, and restarts only when needed.
  • Status reports enabled/active states, plus a few systemctl properties in JSON mode.

JSON output for CI

Use --json to emit structured records per host that you can pipe to jq or parse in CI:

./nudeploy/nudeploy.sh deploy \
  --service axon \
  --group prod \
  --sudo \
  --json

You can fail a CI job if any host failed or if changes are found (policy dependent). Example:

./nudeploy/nudeploy.sh deploy --service foo --group all --sudo --json \
  | jq -e 'all(.[]; .ok? // true)'

Output details

Plan prints a detailed summary plus per-file actions by default. Use --json for structured data.

Note: The main entry is nudeploy.sh. You can symlink it to nudeploy to match the examples above.

Troubleshooting

  • First SSH to a host prompts for key: we use StrictHostKeyChecking=accept-new which will trust new hosts on first connect.
  • Permission denied writing files: destinations must be writable by the SSH user. Pre-create directories/files with proper ownership if needed.
  • Permission denied (systemctl): configure passwordless sudo for systemctl for your deployment user, or run with a TTY if prompts are needed.
  • Remote host missing hasher: needs one of sha256sum, shasum, or openssl. Install coreutils or perl packages accordingly.
  • Remote not systemd: this tool targets systemd-based Linux. Non-systemd hosts aren’t supported.
  • Unit not restarting: with restart = true, restarts occur when files changed; otherwise not.
  • File destinations: ensure the destination parent directory exists or is creatable; we auto-create with mkdir -p when needed.
  • PATH issues in exec: If commands like mise are missing, ensure they are in ~/.profile (for sh/bash) or ~/.zprofile (for zsh). You can also set shell = "zsh" in [[hosts]] config to use zsh as the wrapper shell.

Environment variables

  • NU: path to Nushell executable. Defaults to nu on PATH.

Release with nupm

The project includes nupm.nuon:

{
  name: "nudeploy",
  version: "0.1.0",
  description: "Idempotent systemd deploy helper over SSH using Nushell",
  license: "MIT",
  bins: { nudeploy: "nudeploy.nu" },
  modules: ["lib.nu"],
}

Example release flow (using nupm install from Git tags):

# Tag a release (version must match `nupm.nuon`)
git tag v0.1.0 && git push origin v0.1.0

# Verify install from the Git tag
nu -c 'nupm install --git https://github.com/longcipher/nudeploy --tag v0.1.0'

# When bumping versions:
# 1) Update `version` in nupm.nuon
# 2) Update README examples
# 3) Re-tag and push

About

Effortless systemd deployments with Nushell, inspired by Ansible

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published