返回资讯中心
外部精选
软件工程
#开发者实时流

Dudu, back up and restore your dev setup on macOS and Linux with one command

Back up your whole dev setup in one command and put it back on a new machine in another. Packages, runtimes, dotfiles and terminal, on macOS and Linux.

Hacker News 最新fontnelle2026/10/18 · 19 天前22 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

Back up your whole dev setup in one command and put it back on a new machine in another. Packages, runtimes, dotfiles and terminal, on macOS and Linux.

Everything you installed from the terminal: Homebrew, apt/dnf/pacman, Node versions, global npm/pnpm/yarn/bun packages, pyenv/pipx/uv, gems, cargo, go tools, VS Code and Cursor extensions

Your terminal, as it was: zsh, Oh My Zsh and plugins, Powerlevel10k/Starship, Terminal.app and iTerm2 profiles, Ghostty, Kitty, WezTerm, Nerd Fonts, git and SSH config

Saved where you already are: a local folder, iCloud Drive, Google Drive and Apple Notes, several at once

Secrets stay home: tokens in your dotfiles are masked, key files are never copied, and dudu secrets moves keys out of your .zshrc

Encrypted if you want: AES-256 with a password kept in your Keychain

Safe to restore: a backup is treated as untrusted, checked against its checksums, and never overwrites a file without keeping a copy

Runs by itself: a daily backup with dudu schedule on

Speaks your language: English, Português (Brasil) and Español, picked from your system

Pure bash, no dependencies: works with the bash 3.2 that ships with macOS

Quick Start

Install via Homebrew

brew install fontnelle/tap/dudu

Or via script

curl -fsSL https://raw.githubusercontent.com/fontnelle/dudu/main/install.sh | bash

The script verifies the release against its SHA256SUMS. Options: | bash -s -- 0.1.0 for a version, | bash -s -- --prefix ~/.local/bin for where the command goes.

dudu # interactive menu dudu backup # save everything to your destinations dudu restore # on the new machine: reinstall everything dudu status # what changed since the last backup dudu outdated # updates for brew, npm, pip, gems, Node LTS... then update all or pick dudu secrets # keys found in your config files, and moving them to ~/.secrets dudu schedule on # a backup every day (12:30, or: dudu schedule on 08:00) dudu config # destinations, language, encryption, schedule

Every command takes --dry-run and --only brew,node,.... restore also takes --from <machine> (a backup from another computer), --file <folder> (a backup copied by hand) and --yes. --debug traces every step into ~/.cache/dudu/dudu.log (needs bash 4.1+).

brew install fontnelle/tap/dudu # or the script, on a Mac without Homebrew yet dudu config # the same destination as the old machine dudu restore --dry-run # see what will happen dudu restore

The restore runs in order: Homebrew first (which brings pyenv, go, fonts...), then the shell and git config, Node versions, global packages, editors and macOS preferences. Every step is idempotent: what is already installed is skipped, and a file that differs is kept as <file>.dudu-bak.

Destination Where Notes

Local folder ~/dudu-backups or any folder Default

iCloud Drive iCloud Drive/dudu macOS

Google Drive Google Drive/My Drive/dudu Drive for desktop on macOS. On Linux, a synced folder (dudu config set gdrive_path ~/GoogleDrive/dudu) or rclone (dudu config set rclone_remote gdrive:dudu)

Apple Notes the note dudu backup - <machine> in the dudu folder macOS. A readable summary plus the whole backup embedded. The first save asks for permission to control Notes

Pick one or more with dudu config or dudu config set destination icloud,notes. Each backup goes to all of them, and one failing does not stop the others. restore and status read the newest backup found across them. Folders keep the latest 5 backups per machine (dudu config set keep 10).

Collector Saves Restores with

brew Brewfile (formulae, casks, taps, Mac App Store) brew bundle install (installs Homebrew if needed)

syspkgs manually installed apt/dnf/pacman packages, flatpak, snap the system package manager

shell .zshrc, .zprofile, .bashrc, .p10k.zsh, starship.toml, tmux, vim, fish, nvim, Oh My Zsh custom plugins/themes (as git URLs) copies files, installs Oh My Zsh, clones plugins

git .gitconfig, global gitignore, ~/.ssh/config, gh config copies files

terminal Terminal.app and iTerm2 profiles, Ghostty, Alacritty, Kitty, WezTerm, the list of Nerd Fonts defaults import, copies files

node nvm (or fnm/volta) versions and the default installs nvm, nvm install each version

npm pnpm yarn bun global packages (per Node version with nvm), .npmrc npm install -g ...

python pyenv versions, pipx and uv tools, pip --user pyenv install, pipx install, uv tool install

ruby cargo go gems/rbenv, cargo install crates, go install tools the same tools

vscode cursor extensions, settings, keybindings, snippets code --install-extension

macos a curated set of Dock, Finder, keyboard and screenshot preferences defaults write

Skip collectors with dudu config set exclude macos,ruby, or keep only some with dudu config set include brew,node,shell.

dudu config set encrypt on asks for a password (12+ characters) and keeps it in the macOS Keychain (on Linux, secret-tool or ~/.config/dudu/key with mode 600). Each backup then becomes one backup.enc (AES-256-CBC, PBKDF2 with 200,000 rounds, via openssl) plus a MAC checked before anything is decrypted. Only manifest.json (machine name, date, which tools) and a generic RESTORE.md stay readable. On a new machine, dudu restore asks for the password. Without it the backup cannot be restored, so keep it in your password manager.

Anyone with access to the iCloud or Google Drive account can change a backup folder there, so dudu treats it as untrusted on restore:

With encryption on, the MAC check comes first: a changed backup is refused before anything runs.

Every backup carries a SHA256SUMS. Saves go to a .partial folder, are checked, then renamed, so an interrupted save never becomes the backup restore uses. Notes are read back and checked, and the previous note is kept as "(previous)".

restore and status check the checksums and fall back to the previous backup when the newest is damaged.

A tool that fails or times out keeps its copy from the last good backup, and the backup says so instead of "complete".

keep is never less than 1, and pruning never removes the newest complete backup or the one just saved.

Two machines with the same name back up to separate folders, told apart by a hashed hardware id.

One dudu runs at a time. Ctrl-C stops the running step and everything it started, and removes temp files.

Exit codes

dudu schedule on [HH:MM] adds a daily backup: a LaunchAgent on macOS, a systemd user timer on Linux. It runs without a terminal, so it uses the stored backup password, and a failure shows a notification. If you use Apple Notes, run one backup by hand first so macOS can ask for the permission. dudu schedule status shows it, dudu schedule off removes it.

dudu speaks English, Português (Brasil) and Español. It follows your system language (LANG, or System Settings on macOS) with nothing to set up, and the first run says which one it picked.

dudu config set language es # en, pt-BR, es, or auto to follow the system again DUDU_LANG=en dudu status # one command in another language

Or from the menu: Settings › Language. A backup's RESTORE.md (and its Apple Notes note) is written in the language of the machine that made it. Tab completion descriptions in zsh follow the same setting.

dudu completion zsh > "${fpath[1]}/_dudu" # zsh tab completion echo 'eval "$(dudu completion bash)"' >> ~/.bashrc # bash tab completion echo "source $(dudu completion highlight --path)" >> ~/.zshrc # zsh highlighting

Homebrew installs the tab completion for you. With fast-syntax-highlighting or zsh-syntax-highlighting, dudu backup shows the subcommand in yellow while you type, and a typo like dudu bakcup in red.

Each tool is one file in lib/collectors/<name>.sh:

foo_detect() { has foo; } # is it on this machine? foo_backup() { foo list > "$1/list.txt" || return 1; summary "$1" "$(tn "%d thing" "%d things" "$n")"; } foo_restore() { act foo install ...; } # act runs, or prints in --dry-run foo_outdated() { foo outdated; } # optional: "name current latest" per line foo_upgrade() { foo upgrade "$1"; } # optional: updates one package

Then add the name to DUDU_COLLECTORS in lib/core/common.sh (the order is the restore order). A backup function returns non-zero when its tool fails, so dudu keeps the last good copy. Pass package lists through missing_pkgs and config files through save_file and restore_home <dir> <allowed paths>: they validate names, mask secrets and keep .dudu-bak copies.

make check # shellcheck + bats + translation catalogs make install # link this checkout as ~/.local/bin/dudu

Code must run on bash 3.2 (the macOS default): no associative arrays, no mapfile, no ${var,,}, and ${VAR} before any non-ASCII character. Messages are written in English as t "text %s" "$value" (or tn for plurals) and translated in lib/i18n/pt-BR.sh and lib/i18n/es.sh; make check fails when a message is missing from a catalog. CI runs the tests on macOS, bash 3.2, Ubuntu, Fedora and Arch.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →