{"id":30413091,"url":"https://github.com/longcipher/nudeploy","last_synced_at":"2026-02-09T22:32:25.846Z","repository":{"id":309235373,"uuid":"1035449441","full_name":"longcipher/nudeploy","owner":"longcipher","description":"Effortless systemd deployments with Nushell, inspired by Ansible","archived":false,"fork":false,"pushed_at":"2026-01-03T18:09:41.000Z","size":96,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-07T11:20:07.832Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Nushell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/longcipher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-10T12:30:33.000Z","updated_at":"2026-01-03T18:09:45.000Z","dependencies_parsed_at":"2025-08-10T19:14:35.315Z","dependency_job_id":null,"html_url":"https://github.com/longcipher/nudeploy","commit_stats":null,"previous_names":["longcipher/nudeploy"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/longcipher/nudeploy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/longcipher%2Fnudeploy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/longcipher%2Fnudeploy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/longcipher%2Fnudeploy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/longcipher%2Fnudeploy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/longcipher","download_url":"https://codeload.github.com/longcipher/nudeploy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/longcipher%2Fnudeploy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29283608,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-09T21:57:15.303Z","status":"ssl_error","status_checked_at":"2026-02-09T21:57:11.537Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-22T02:33:56.871Z","updated_at":"2026-02-09T22:32:25.838Z","avatar_url":"https://github.com/longcipher.png","language":"Nushell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nudeploy\n\n[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/longcipher/nudeploy)\n[![Context7](https://img.shields.io/badge/Website-context7.com-blue)](https://context7.com/longcipher/nudeploy)\n\n![hpx](https://socialify.git.ci/longcipher/nudeploy/image?font=Source+Code+Pro\u0026language=1\u0026name=1\u0026owner=1\u0026pattern=Circuit+Board\u0026theme=Auto)\n\nA tiny, stable, idempotent deployment helper to roll out a systemd service to multiple remote hosts using:\n\n- Nushell for orchestration\n- SSH/SCP for remote execution and file transfer\n\nIt reuses your existing SSH config at `~/.ssh/config`. No extra SSH config in this repo.\n\n## Configuration: one TOML file\n\nDefine hosts and services in a single TOML config (defaults to `./nudeploy.toml`). Example:\n\n```toml\n[[hosts]]\nname = \"localhost\"\nip = \"127.0.0.1\"\nport = 22\nuser = \"akagi201\"\nshell = \"zsh\" # Optional: override shell wrapper (default \"sh\") to load correct profile/env\nenable = true\ngroup = \"prod\"\n\n[[services]]\nname = \"axon\"\nsrc_dir = \"./axon\"\ndst_dir = \"/home/akagi201/axon\"\nunit_file = \"axon.service\"  # relative to src_dir or absolute\nsync_files = [\n  { from = \"foo.conf\", to = \"bar.conf\" },\n  # Support downloading on remote directly:\n  # { from = \"https://example.com/file.conf\", to = \"bar.conf\" }\n  # Optional per-file chmod (applied with sudo after sync); defaults to \"0644\"\n  # { from = \"bin/myapp\", to = \"bin/myapp\", chmod = \"0755\" },\n]\nrestart = true   # restart service when files changed\nenable = true    # enable service (default true)\n```\n\nYou can target hosts by group or explicitly via `--hosts hostA,hostB`.\n\n## Requirements\n\n- macOS or Linux\n- Nushell v0.90+ (newer preferred)\n- bash (for the CLI wrapper)\n- ssh, scp\n- Remote machines run systemd and have sudo available if you need to install into /etc\n- Remote tools: one of sha256sum | shasum | openssl must be available (most distros have at least one)\n\n## Install\n\n### Quick Install (Bash script)\n\nThis installs `nudeploy` to `~/.local/bin`.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/longcipher/nudeploy/main/install.sh | bash\n```\n\nEnsure `~/.local/bin` is in your `$PATH`.\n\n### Manual Install\n\n- Ensure Nushell is installed:\n  - brew install nushell\n- Make the wrapper executable:\n  - chmod +x nudeploy/nudeploy.sh\n\n### Install via nupm (for Nu users)\n\nThe repo includes `nupm.nuon`. You can install via nupm and get a `nudeploy` bin on PATH (points to `nudeploy.nu`).\n\n```shell\n# Local install from the current directory (install nupm first per its docs)\nnu -c 'nupm install --path .'\n\n# Or install from Git (example)\nnu -c 'nupm install --git https://github.com/longcipher/nudeploy'\n\n# Now you can run it directly (nupm exposes `nudeploy` on PATH)\nnudeploy --help\n```\n\nNote: After nupm install, `nudeploy` runs `nudeploy.nu`. The Bash wrapper `nudeploy.sh` still works standalone.\n\n## Usage\n\n- Plan (no changes; shows what would change):\n\n```shell\n# All enabled services\nnudeploy deploy --dry-run --group prod\n# Single service\nnudeploy deploy --dry-run --service axon --group prod\n```\n\n- Deploy to a group (idempotent):\n\n```shell\n# All enabled services\nnudeploy deploy --group prod --sudo\n# Single service\nnudeploy deploy --service axon --group prod --sudo\n```\n\n- Deploy to specific hosts:\n\n```shell\nnudeploy deploy --service axon --hosts host1,host2 --sudo\n```\n\n- Check status:\n\n```shell\n# All enabled services\nnudeploy status --group prod\n# Single service\nnudeploy status --service axon --group prod\n```\n\n- Restart without redeploying files:\n\n```shell\n# All enabled services\nnudeploy restart --hosts host1\n# Single service\nnudeploy restart --service axon --hosts host1\n```\n\n- List hosts (enabled by default):\n\n```shell\nnudeploy hosts --group prod\n```\n\n- Run a shell command on targets:\n\n```shell\nnudeploy exec \"uname -a\" --group prod\n```\n\n- Run a playbook (script execution, stop on error):\n\n```shell\n# Run silently (shows only last command output)\nnudeploy exec playbooks/arch.sh --group prod\n\n# Run verbosely (shows every command and its output)\nnudeploy exec playbooks/arch.sh --group prod -v\n```\n\n- Download artifacts locally (curl + extract):\n\n```shell\n# All enabled downloads in config\nnudeploy download\n\n# Only selected names\nnudeploy download --name openobserve\n\n# Alternate config file\nnudeploy download --config ./nudeploy.toml\n```\n\n## Options\n\n- --config: Path to config TOML (default: ./nudeploy.toml)\n- --service: Service name from config (optional for deploy/status/restart). If omitted, acts on all services with `enable = true`.\n- --group: Hosts group\n- --hosts: Comma-separated hostnames (SSH Host aliases)\n- --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.\n- --json: Emit JSON output suitable for CI\n- --name: For `download`, comma-separated artifact names to fetch (defaults to all enabled)\n- --dry-run: For `deploy`, show what would change without applying (formerly `plan`)\n\n## Idempotency strategy\n\n- Files are uploaded only if remote checksum differs\n- Optional chmod is enforced after sync as the SSH user when `chmod` is set on an item\n- Systemd daemon-reload runs only when unit changed\n- Service is enabled once if not enabled\n- Service is restarted only when changes detected (or restart mode forces it)\n\n## Notes\n\n- nudeploy does not install software on remote machines; it only pushes your service unit/config and manages systemd\n- Per-file permissions: set `chmod = \"0755\"` on binaries you need to execute; default mode is `0644`.\n- 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.\n- For sudo prompts, passwordless sudo is recommended for automation\n\n### Playbooks\n\nPlaybooks are shell scripts (Bash syntax) that nudeploy executes remotely over a single SSH session.\n\n- **Single Session**: Context is preserved between lines (e.g., `cd /tmp` affects subsequent commands).\n- **Automatic Error Handling**: `set -e` is automatically prepended, so execution stops immediately if any command fails.\n- **Verbose Mode (`-v`)**: Shows every executed command (prefixed with `👉 [Line N]`) and its output interleaved.\n- **Quiet Mode (default)**: Shows only the output of the *last* executed command (useful for status checks).\n\nExample `playbooks/bootstrap.sh`:\n\n```bash\n# Context is preserved\ncd /tmp\ncurl -L -o app.tar.gz https://example.com/app.tar.gz\n\n# Variables work\nAPP_DIR=\"/opt/app\"\nmkdir -p \"$APP_DIR\"\ntar -xzf app.tar.gz -C \"$APP_DIR\"\n\n# Logic works\nif ! id -u deploy \u003e/dev/null 2\u003e\u00261; then\n    useradd -m -s /bin/bash deploy\nfi\n```\n\n## Dev tips\n\n- The Bash wrapper only parses CLI; all orchestration lives in Nushell\n\n## Quick start\n\n1. Install prerequisites (macOS):\n\n```shell\nbrew install nushell\n```\n\n1. Ensure the CLI is executable and callable:\n\n```shell\nchmod +x nudeploy/nudeploy.sh\n./nudeploy/nudeploy.sh --help\n```\n\n1. Define your config at `./nudeploy.toml` with [[hosts]] and [[services]]. Host names are arbitrary labels; you can also set ip/user/port.\n\n```shell\n./nudeploy/nudeploy.sh plan \\\n  --service example-service \\\n  --group all \\\n  --sudo\n```\n\nWhen you’re ready:\n\n```shell\n./nudeploy/nudeploy.sh deploy \\\n  --service example-service \\\n  --group all \\\n  --sudo\n```\n\nTip: If Nushell is not on PATH or is named differently, set `NU=/path/to/nu` before running.\n\n## Model and behavior\n\n- Unit file is copied to `/etc/systemd/system/\u003cservice\u003e.service`.\n- sync_files entries copy local files (hash-compared) or download URLs on the remote; only changed files are installed.\n- Idempotent: files only update on hash change; `daemon-reload` only when unit changes; enable once; restart on change when `restart=true`.\n\n## End-to-end example\n\nUse the included example config/service to get a feel for the workflow:\n\n```shell\n# Plan the changes (no writes)\n./nudeploy/nudeploy.sh plan \\\n  --service axon \\\n  --group prod \\\n  --sudo\n\n# Apply changes idempotently\n./nudeploy/nudeploy.sh deploy \\\n  --service axon \\\n  --group prod \\\n  --sudo\n\n# Check status\n./nudeploy/nudeploy.sh status \\\n  --service example-service \\\n  --group all\n```\n\nOutputs:\n\n- Plan shows which items would be uploaded per host.\n- Deploy uploads/downloads only when checksums differ, reloads systemd if unit changed, enables once, and restarts only when needed.\n- Status reports enabled/active states, plus a few systemctl properties in JSON mode.\n\n\n## JSON output for CI\n\nUse `--json` to emit structured records per host that you can pipe to `jq` or parse in CI:\n\n```shell\n./nudeploy/nudeploy.sh deploy \\\n  --service axon \\\n  --group prod \\\n  --sudo \\\n  --json\n```\n\nYou can fail a CI job if any host failed or if changes are found (policy dependent). Example:\n\n```shell\n./nudeploy/nudeploy.sh deploy --service foo --group all --sudo --json \\\n  | jq -e 'all(.[]; .ok? // true)'\n```\n\n## Output details\n\nPlan prints a detailed summary plus per-file actions by default. Use `--json` for structured data.\n\nNote: The main entry is `nudeploy.sh`. You can symlink it to `nudeploy` to match the examples above.\n\n## Troubleshooting\n\n- First SSH to a host prompts for key: we use `StrictHostKeyChecking=accept-new` which will trust new hosts on first connect.\n- Permission denied writing files: destinations must be writable by the SSH user. Pre-create directories/files with proper ownership if needed.\n- Permission denied (systemctl): configure passwordless sudo for systemctl for your deployment user, or run with a TTY if prompts are needed.\n- Remote host missing hasher: needs one of `sha256sum`, `shasum`, or `openssl`. Install `coreutils` or `perl` packages accordingly.\n- Remote not systemd: this tool targets systemd-based Linux. Non-systemd hosts aren’t supported.\n- Unit not restarting: with `restart = true`, restarts occur when files changed; otherwise not.\n- File destinations: ensure the destination parent directory exists or is creatable; we auto-create with `mkdir -p` when needed.\n- 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.\n\n## Environment variables\n\n- `NU`: path to Nushell executable. Defaults to `nu` on PATH.\n\n## Release with nupm\n\nThe project includes `nupm.nuon`:\n\n```nu\n{\n  name: \"nudeploy\",\n  version: \"0.1.0\",\n  description: \"Idempotent systemd deploy helper over SSH using Nushell\",\n  license: \"MIT\",\n  bins: { nudeploy: \"nudeploy.nu\" },\n  modules: [\"lib.nu\"],\n}\n```\n\nExample release flow (using nupm install from Git tags):\n\n```shell\n# Tag a release (version must match `nupm.nuon`)\ngit tag v0.1.0 \u0026\u0026 git push origin v0.1.0\n\n# Verify install from the Git tag\nnu -c 'nupm install --git https://github.com/longcipher/nudeploy --tag v0.1.0'\n\n# When bumping versions:\n# 1) Update `version` in nupm.nuon\n# 2) Update README examples\n# 3) Re-tag and push\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flongcipher%2Fnudeploy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flongcipher%2Fnudeploy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flongcipher%2Fnudeploy/lists"}