One command - up - refreshes, upgrades and cleans your whole system.
Works on Linux, macOS and Windows. Auto-detects every package
manager you have, runs them in the right order, gives you one clean summary
at the end. No YAML, no daemons.
New to package managers? Read the Why eMerger? section first. In a hurry? Jump to Quickstart.
- Why eMerger?
- Platforms at a glance
- Quickstart
- What
upactually does - Requirements
- Installation
- Uninstallation
- Update / self-update
- User manual
- Running
- Flags, quick reference
- Combining flags
- Interactive mode
- Dry-run & verbose
- Quiet levels
- Security-only updates
- Dev toolchains
- Firmware (Linux)
- Parallel mode
- Snapshots (Linux)
- Mirrors refresh (Linux)
- Reboot handling
- Resume after interruption
- Package diff & changelog
- Reports
- History & errors
- Doctor
- Configuration
- Integration
- Auto-update (unattended)
- Cookbook / Recipes
- Safety & security
- Files & paths
- Supported package managers
- Exit codes
- Troubleshooting
- FAQ
- Glossary
- Development
- License
📄 For a printable, all-in-one reference see doc/documentation.pdf.
A modern machine gets its software from many different places at once:
the distro's own package manager (apt, dnf, pacman, zypper,
softwareupdate, winget...), an app-store layer (flatpak, snap,
brew --cask, choco, scoop), a user-level store (brew, mas),
language ecosystems (npm, pip, cargo, gem, pnpm...) and, on
Linux, firmware via fwupdmgr. Each has its own syntax, its own cache,
its own notion of "security update" and its own definition of "clean".
Keeping all of them up to date by hand is tedious and error-prone. Writing a personal wrapper that works on three operating systems is a weekend project most people never finish.
eMerger is that wrapper, generalised. Type up and it will:
- detect every package manager installed on the host
- ask for
sudo(or trigger UAC) only if something actually needs it - take an optional snapshot so you can audit the run afterwards
- run refresh / upgrade / clean, in the right order, with retries
- wipe user caches and the trash, optionally
- print one summary with per-manager result, disk freed, run duration and a reboot advisory
- export the same summary as JSON, Markdown or a Prometheus file
It does this with no daemon, no YAML, no Python/Ruby runtime. Just Bash on Unix, PowerShell on Windows.
What eMerger is NOT:
- Not a package manager itself. It drives the ones you already have.
- Not a configuration management system. No desired state, no manifest. If you want that, use Ansible, Salt or NixOS.
- Not a service. It can install a weekly timer, but it does not run in the background.
| Linux | macOS | Windows | |
|---|---|---|---|
| Entry point | src/emerger.sh |
src/emerger.sh |
src/emerger.ps1 |
| Shell | bash 3.2+ | bash 3.2+ (system default) | PowerShell 5.1+ |
| Setup | ./setup.sh |
./setup.sh |
.\setup.ps1 |
| Uninstall | ./uninstall.sh |
./uninstall.sh |
.\uninstall.ps1 |
| Auto-update | systemd user timer / cron | launchd-compat cron | Task Scheduler |
| Elevation | sudo |
sudo |
UAC relaunch |
| TUI menu | yes (-i) |
yes (-i) |
no |
| Parallel mode | yes | yes | no (always serial) |
| Snapshots | snapper/timeshift/btrfs | no | no (System Restore is manual) |
| Config dir | ~/.config/emerger/ |
~/.config/emerger/ |
%APPDATA%\emerger\ |
| State dir | ~/.local/state/emerger/ |
~/.local/state/emerger/ |
%LOCALAPPDATA%\emerger\state\ |
Feature parity is kept for the core flow (detect → upgrade → clean → summary). Platform-specific features are documented below and clearly labelled.
No commitment. Nothing gets upgraded until you say so.
Linux / macOS
git clone https://github.com/MasterCruelty/eMerger
cd eMerger
./setup.sh
# open a new shell (or: source ~/.bashrc)
up --help
up -n # preview: shows what would run
up # real runWindows (PowerShell)
git clone https://github.com/MasterCruelty/eMerger
cd eMerger
.\setup.ps1
# open a new PowerShell window (or: . $PROFILE)
up --help
up -n # preview
up # real runOn a Debian/Ubuntu desktop with Flatpak installed, expect something like:
_____ __ __
| ___| | \/ | eMerger v2.0.0
| |__ | |\/| | one command for the whole system
|____| |_| |_|
Ubuntu 24.04 o x86_64 o 2026-04-16 10:12
eMerger v2.0.0 o github.com/MasterCruelty/eMerger
[1/3] apt OK (38 upgraded, 0 removed, 0 new)
[2/3] flatpak OK (5 refreshed)
[3/3] fwupd SKIP (--firmware not set)
+---------------------------------------------+
| eMerger - summary |
| duration 42s |
| freed 15 MiB |
| reboot not required |
| errors 0 |
+---------------------------------------------+
Do not run
sudo up. eMerger will ask forsudoitself, only for the managers that need it. Runningsudo upwould install user-level packages (e.g.pip --user, Homebrew,npm -g) into root's home.
Each full run performs, in order:
- Load
config.sh(orconfig.ps1) and any--profile. - Parse CLI flags. CLI always wins over config and profile defaults.
- Acquire a global exclusive lock (
/tmp/emerger.lockon Unix). - Print logo, OS info line and timestamp (skippable with
-nl/-ni). - Warn on low battery and on low free disk space.
- Cache
sudocredentials (Unix) or relaunch elevated (Windows) if any detected manager needs it. - Snapshot installed packages (for the post-run diff).
- Run
pre.dhooks. - For each detected manager: refresh → upgrade → clean.
- Optionally clean user cache /
%TEMP%and trash / Recycle Bin. - Run
post.dhooks. - Compute the installed-packages diff.
- Print boxed summary + reboot advisory.
- Emit desktop notification if the session has a display (or if BurntToast is installed on Windows).
- Exit 0 on success, 3 if any manager failed, 4 if a reboot is pending
and
--reboot-exitwas passed.
Linux
- bash ≥ 3.2, coreutils, git, sudo.
- Optional:
gum/whiptail,notify-send,curl,flock,reflector,netselect-apt,snapper/timeshift/btrfs-progs,fwupdmgr.
macOS
- bash ≥ 3.2 (the system
/bin/bashworks out of the box; no Homebrew bash needed). - Xcode Command Line Tools (for git).
- Optional: Homebrew,
mas(brew install mas).
Windows
- PowerShell 5.1 (built-in on Win10+) or 7+.
- Git for Windows (for
up --self-update). - Optional:
winget,scoop,choco,PSWindowsUpdate(Install-Module PSWindowsUpdate -Scope CurrentUser),BurntToast(Install-Module BurntToast) for toast notifications.
eMerger never installs these for you - it only uses what's there.
git clone https://github.com/MasterCruelty/eMerger
cd eMerger
./setup.shsetup.sh does exactly this:
- Adds
alias up='bash /path/to/eMerger/src/emerger.sh'to your shell rc (bash, zsh, fish - whichever you use). - Makes
src/emerger.shexecutable. - Installs shell completions under:
~/.local/share/bash-completion/completions/up~/.zsh/completions/_up~/.config/fish/completions/up.fish
- Creates
~/.config/emerger/{config.sh,ignore.list,hooks/,profiles.d/}.
After install, open a new shell (or source ~/.bashrc).
Identical script, same flow:
git clone https://github.com/MasterCruelty/eMerger
cd eMerger
./setup.shmacOS specifics handled automatically:
- The alias is written to
~/.zshrcfirst (zsh is the default shell since Catalina). - If Homebrew is installed, completions go to
$(brew --prefix)/etc/bash_completion.d/and$(brew --prefix)/share/zsh/site-functions/. softwareupdate(native macOS updater) is auto-detected alongsidebrew,brew --cask, andmas.
git clone https://github.com/MasterCruelty/eMerger
cd eMerger
.\setup.ps1setup.ps1 does:
- Sets
ExecutionPolicytoRemoteSignedforCurrentUserif it wasRestrictedorUndefined. No admin needed for this. - Adds a
function up { & "…\emerger.ps1" @args }block to your$PROFILE.CurrentUserAllHosts(soupworks from any host: cmd-hosted PS, ISE, Terminal…). - Scaffolds
%APPDATA%\emerger\{config.ps1,hooks\,profiles.d\}.
After install, open a new PowerShell window (or . $PROFILE).
Important:
setup.ps1does not require admin. Package manager runs that need admin will trigger a UAC prompt via automatic elevation.
All three platforms: just point an alias/function at the entry point.
# bash/zsh/fish - macOS or Linux
alias up='bash /absolute/path/to/eMerger/src/emerger.sh'# PowerShell - Windows
function up { & "C:\path\to\eMerger\src\emerger.ps1" @args }# Linux / macOS
./uninstall.sh# Windows
.\uninstall.ps1Removes the shell alias / up function, any cronjob, systemd user timer or
scheduled task. Keeps your config and state directories so you don't
lose history or hooks. Delete those paths manually if you want a clean
wipe:
rm -rf ~/.config/emerger ~/.cache/emerger ~/.local/state/emergerThe repo itself is not removed - rm -rf or Remove-Item the directory
when you're done.
Three equivalent ways:
up -up # flag formor
up --self-updateor
./update.sh # Linux / macOS
.\update.ps1 # WindowsThis does a git pull --ff-only inside the repo and shows the commit range.
Refuses non-fast-forward pulls so local changes never silently vanish.
For automatic updates of eMerger itself, put it in post.d hook (see
Hooks).
up # Linux / macOSup # WindowsFull list of flags: up --help.
Authoritative list: up --help. Highlights:
| Flag | Meaning | Platforms |
|---|---|---|
-n, --dry-run |
Preview, don't run | all |
-v, --verbose |
Stream output live | all |
-q / -qq / -qqq |
Quieter | all |
-y, --yes |
Assume yes | all |
-i, --interactive |
Menu UI | Linux/macOS |
--security |
Security-only | all (where supported) |
--dev |
Include dev toolchains | all |
--firmware |
fwupdmgr |
Linux |
--parallel |
Run user-space concurrently | Linux/macOS |
--profile NAME |
Load a profile | all |
--snapshot |
snapper/timeshift/btrfs | Linux |
--refresh-mirrors |
re-rank mirrors | Linux |
--resume |
Skip completed managers | Linux/macOS |
--reboot |
Reboot if required | all |
--changed |
Package diff | Linux/macOS |
--changelog PKG |
Upstream changelog | Linux/macOS |
--report FILE |
Export Markdown | Linux/macOS |
--doctor |
Health check | all |
--history |
Recent runs | all |
--errors / -err |
Log tail | all |
--no-emoji |
ASCII only | all |
--json |
Machine-readable summary | all |
--reboot-exit |
Exit 4 if reboot is required | all |
--rollback |
Revert last snapshot | Linux |
--download-only / --offline |
Prefetch, don't install | Linux/macOS (apt/dnf/pacman/zypper) |
--only LIST |
Keep only these managers | all |
--except LIST |
Skip these managers | all |
--metrics FILE |
Prometheus textfile export | all |
-up |
Self-update | all |
-au |
Install auto-update | all |
Flags are independent tokens - mix and match as many as you need, in any
order. Short bundling -nv → -n -v is supported for the letters
{h V n v q y i w}. Compound short flags (-nl, -ni, -qq, -up,
-err, ...) and long flags pass through unchanged.
up -n -v # dry-run + live stream (preview a full run)
up -y -q --security # unattended security-only, minimal output
up --dev --parallel -v # dev toolchains + user-space concurrency, verbose
up --snapshot --reboot -y # snapshot first, reboot at the end if needed
up --profile server --resume # resume an interrupted headless run
up -n --dev --firmware # preview a full dev + firmware run, no side effects
up -qq -y -nl -ni --security # exactly what the scheduled timer does
up --refresh-mirrors -y -v # re-rank mirrors then upgrade, watch it live
up --changed --report out.md # show diff and export it in one shotup -y -q --security # Windows, unattended security-only
up --dev -v # Windows, include dev toolchains, verbose
up -n --security # Windows, preview a security-only runFlags that take a value (--profile NAME, --changelog PKG,
--report FILE, --metrics FILE, --only LIST, --except LIST) must
keep their argument adjacent; everything else is position-free. CLI flags
always win over config file and profile defaults, so you can override a
profile on the fly:
up --profile work --dev # work profile, but force dev toolchains this runup -iMenu via gum (pretty), whiptail (classic), or plain read-loop.
Windows does not ship a TUI - use flags directly or a profile.
up -n # see what would happen (safe, no sudo)
up -v # stream pkg-manager output live
up -n -v # bothExample of -n output:
$ up -n
[dry] sudo apt update
[dry] sudo apt upgrade -y
[dry] sudo apt autoremove -y
[dry] sudo apt clean
[dry] flatpak update -y
[dry] flatpak uninstall --unused -y
- default - full UI with box and spinner
-q- hide muted/info lines-qq- only step titles + one-line summary (great for systemd logs)-qqq- no output at all; exit code is the only signal
up --security -y- Linux:
apt(viaunattended-upgrade),dnf(--security),zypper(patch --category security). - macOS:
softwareupdate --install --recommended. - Windows:
PSWindowsUpdaterespects KB severity if the module supports it.
Other managers ignore the flag.
Opt-in (every platform):
up --devUpdates:
| Tool | Command |
|---|---|
rustup |
rustup self update && rustup update stable |
cargo |
cargo install-update -a |
npm |
npm update -g |
pnpm |
pnpm -g update |
pip |
user-site upgrades |
gem |
gem update |
These never run under sudo. Missing toolchains are silently skipped.
up --firmwareRuns fwupdmgr refresh && fwupdmgr update -y --no-reboot-check. Windows
firmware is handled by vendor tools (Dell Command Update, Lenovo Vantage)
and is out of scope. macOS firmware is handled by softwareupdate.
up --parallelUser-space managers that don't touch / run concurrently (flatpak,
snap, brew, mas, dev tools). System managers stay serial. Windows
side is currently always serial.
up --snapshotTries in order: snapper, timeshift, btrfs subvolume snapshot. Windows
users: enable System Restore manually; eMerger doesn't trigger it.
up --refresh-mirrors- Arch:
reflector --latest 20 --sort rate. - Debian/Ubuntu:
netselect-apt. - Fedora: handled by
fastestmirrorplugin automatically; no-op here.
After a run, eMerger checks for reboot flags:
- Linux:
/var/run/reboot-required,needs-restarting -r. - Windows: registry keys (
CBS RebootPending,WindowsUpdate\RebootRequired,PendingFileRenameOperations).
To reboot on demand:
up --reboot # reboots if required, no-op otherwiseIf you kill a run mid-way:
up --resumeskips every manager that successfully completed in the last interrupted run.
State lives in ~/.local/state/emerger/resume. Linux/macOS only.
Every run records installed packages before/after. View it:
up --changedExample output:
$ up --changed
~ firefox 123.0.1 -> 124.0
~ libc6 2.39-0 -> 2.39-1
+ flatpak-xdg-utils 1.0.6
- old-package 2.1
Legend: + added, - removed, ~ upgraded. Linux/macOS.
Read a single package's upstream changelog:
up --changelog firefoxDispatches to apt changelog, dnf changelog/updateinfo, pacman -Qi,
or brew log.
up --report report.mdMarkdown export of the last run: JSON summary, managers, reboot advisory, full package diff as a table.
up --history # last 10 runs
up --errors # tail of ERROR lines from the logup --doctorAudits:
- shell / PowerShell version
- sudo cache / admin status
- disk space
- network reachability
- state dir writability
- per-manager native health (
dpkg --audit,pacman -Dk,brew doctor, …) - pending reboot flag
- (Windows) ExecutionPolicy
Exits non-zero if issues are found. Always attach up --doctor output
when reporting a bug.
Linux / macOS - ~/.config/emerger/config.sh (sourced before arg parsing):
# Defaults
ARG_DEV=1 # always include dev toolchains
ARG_WEATHER=1 # always show weather
ARG_PARALLEL=1 # user-space managers in parallel
# Thresholds
DISK_MIN_FREE_MB=2048 # require >= 2 GB on /
RETRY_MAX=3 # transient-error retries
# Scheduling
QUIET_HOURS="23:00-07:00" # skip scheduled runs inside this windowWindows - %APPDATA%\emerger\config.ps1 (dot-sourced before arg parsing):
$script:ArgsGlobal.Dev = $true
$script:ArgsGlobal.Security = $true
$script:ArgsGlobal.NoTrash = $true| Variable | Default | Meaning |
|---|---|---|
ARG_DEV |
0 | include dev toolchains by default |
ARG_FIRMWARE |
0 | include fwupdmgr by default |
ARG_NO_FIRMWARE |
0 | force-skip firmware |
ARG_SECURITY |
0 | security-only by default |
ARG_YES |
0 | assume yes by default |
ARG_PARALLEL |
0 | parallel user-space by default |
ARG_WEATHER |
0 | show weather widget |
ARG_NO_EMOJI |
0 | force ASCII glyphs |
ARG_NO_CACHE |
0 | skip user cache cleaning |
ARG_NO_TRASH |
0 | skip trash cleaning |
ARG_NO_LOGO |
0 | hide logo |
ARG_NO_INFO |
0 | hide system info line |
QUIET_LEVEL |
0 | 0=full UI, 1=-q, 2=-qq, 3=-qqq |
DISK_MIN_FREE_MB |
1024 | abort/warn below this many MiB |
BATTERY_MIN_PCT |
20 | warn below this battery percent |
RETRY_MAX |
2 | transient-error retries |
RETRY_DELAY |
3 | seconds between retries |
QUIET_HOURS |
(unset) | "HH:MM-HH:MM" - skip scheduled runs inside window |
EMERGER_CACHE_TTL |
86400 | detection cache TTL in seconds, 0 disables |
CLI flags always win.
ARG_SECURITY=1inconfig.shplusup --devon the CLI will run security updates and dev toolchains.
Profiles are config snippets scoped to a name.
up --profile work
up --list-profilesShipped defaults in share/profiles/:
| Profile | Meant for |
|---|---|
work |
laptop at work - security, unattended, no cache/trash |
home |
desktop at home - everything, dev toolchains, parallel |
server |
headless - -qq, security, no prompts |
safe |
pre-presentation - security only, no big downloads |
Each platform looks for its own extension:
- Unix →
share/profiles/<name>.sh - Windows →
share/profiles/<name>.ps1
User profiles go in ~/.config/emerger/profiles.d/ (Unix) or
%APPDATA%\emerger\profiles.d\ (Windows) and shadow the shipped ones.
Example custom profile (~/.config/emerger/profiles.d/train.sh):
# description: on a train, prefetch only, keep the fans quiet
ARG_DOWNLOAD_ONLY=1
ARG_YES=1
ARG_QUIET=1
QUIET_LEVEL=2
ARG_NO_TRASH=1
ARG_NO_CACHE=1Then up --profile train does a silent prefetch.
Drop executable scripts in hooks/pre.d/ (before updates) or
hooks/post.d/ (after). They run alphabetically. A failing hook emits a
warning but never aborts the run.
- Unix:
*.sh, run under bash. - Windows:
*.ps1, dot-sourced under PowerShell.
Example 1 - backup dotfiles before every run:
# ~/.config/emerger/hooks/pre.d/10-backup-dotfiles.sh
#!/usr/bin/env bash
set -e
rsync -a --delete ~/.config/ ~/backups/dotfiles/Example 2 - Slack notification after every run:
# ~/.config/emerger/hooks/post.d/99-slack.sh
#!/usr/bin/env bash
state=~/.local/state/emerger
payload=$(tail -1 "$state/history.jsonl")
curl -sS -X POST -H 'Content-Type: application/json' \
-d "{\"text\":\"eMerger: $payload\"}" "$SLACK_WEBHOOK_URL"Example 3 - copy last log to clipboard (Windows):
# %APPDATA%\emerger\hooks\post.d\10-log-to-clipboard.ps1
$log = Join-Path $env:LOCALAPPDATA 'emerger\state\emerger.log'
Get-Content $log -Tail 40 | Set-ClipboardExample 4 - export Prometheus metrics automatically:
# ~/.config/emerger/hooks/post.d/90-prom.sh
#!/usr/bin/env bash
up --metrics /var/lib/node_exporter/textfile_collector/emerger.promHooks run with the privileges that invoked
up. If you need root-owned side effects, write them inpost.dand guard withsudo -nor a targeted sudoers entry.
~/.config/emerger/ignore.list - one package per line, # comments ok.
Honored natively by pacman (--ignore=). For others it is
advisory - you still need to hold them via:
- apt:
sudo apt-mark hold <pkg> - dnf:
sudo dnf versionlock add <pkg> - zypper:
sudo zypper al <pkg>
Set QUIET_HOURS="HH:MM-HH:MM" in config.sh. When a scheduled run starts
inside that window and -y is set (i.e. from the timer), eMerger exits
immediately. Interactive runs always proceed. Windows across midnight is
supported (e.g. "23:00-07:00"). Linux/macOS.
Drop a bash script in ~/.config/emerger/managers.d/<name>.sh to add support
for a package manager without touching the repo. A minimal plugin:
PM_PLUGIN_SLUG=mytool
pm_mytool_detect() { command -v mytool >/dev/null 2>&1; }
pm_mytool_needs_sudo() { return 1; } # optional, default: no sudo
pm_mytool_parallel() { return 0; } # optional, default: serial
pm_mytool_dev() { return 1; } # optional, default: not gated by --dev
pm_mytool_icon() { printf '🔌'; } # optional
pm_mytool_run() {
run_cmd "mytool refresh" mytool refresh || return 1
run_cmd "mytool upgrade" mytool upgrade -y || return 1
}A complete, copy-pasteable template lives in
share/plugins/example.sh.
Plugins are registered at the same level as native managers: they honour
--only, --except, --parallel, --dev, the detection cache, hooks and
the summary. They are invoked from inside pkg_run, so run_cmd automatically
gives them --dry-run, retry, logging and live-log handling for free.
The detection cache is keyed by manager slug and lives at
~/.cache/emerger/detected. TTL defaults to 1 day; override via
EMERGER_CACHE_TTL=<seconds> in config.sh (0 disables caching). After
installing or removing a package manager, run up -rc to clear the cache.
Linux/macOS only. Windows plugins are not yet supported.
up --jsonEmits a single-line JSON object on stdout. The logo, info line and summary
box are all suppressed, so the output is safe to pipe into jq or consume
from a CI job:
{"ts":"2026-04-14T07:24:31Z","duration":42,"freed_kb":15360,
"errors":0,"reboot":0,
"managers":[{"name":"apt","result":"ok"},{"name":"flatpak","result":"ok"}]}Every run also appends one such line to
~/.local/state/emerger/history.jsonl (one JSON per line). Fields:
| Field | Meaning |
|---|---|
ts |
ISO 8601 UTC timestamp of the run start |
duration |
total wall-clock seconds |
freed_kb |
cache and trash bytes freed, in KiB |
errors |
number of managers that returned non-zero |
reboot |
1 if a reboot is required, else 0 |
managers |
array of {name, result} entries |
up --metrics /var/lib/node_exporter/textfile_collector/emerger.promReads the most recent entry from history.jsonl and renders a Prometheus
textfile-collector snapshot. Exported gauges:
emerger_last_run_timestamp_secondsemerger_last_run_duration_secondsemerger_last_run_freed_bytesemerger_last_run_errorsemerger_reboot_requiredemerger_manager_ok{manager="..."}(one per manager from the last run)
Does not trigger a run - invoke it from a post.d hook or from your timer
after up completes.
By default eMerger always exits 0 on success even when a reboot is pending
(the summary box prints REBOOT RECOMMENDED). Pass --reboot-exit to turn
that into exit code 4 instead, so an orchestrator can react:
up -y --reboot-exit
rc=$?
case $rc in
0) ;; # done, no reboot needed
3) notify-send "eMerger: some managers failed" ;;
4) systemctl reboot ;; # clean, reboot required
esacup --download-only -y # or --offlineRefreshes indexes and downloads the pending upgrade set but does not
install it. Supported natively on pacman (-Syuw), apt/apt-get
(--download-only), dnf (--downloadonly) and zypper
(update --download-only). Other managers ignore the flag.
Typical use cases:
- laptop on a slow/metered connection at a café: prefetch while online, install later at the office
- servers in a maintenance window: prestage packages, then flip to install-only when the change ticket opens
- pre-flight for
--snapshot: confirm the whole update set is downloaded before taking a snapshot
up --only apt,flatpak # only these managers (comma-separated)
up --except snap,fwupd # everything that would run, minus theseThe filters are applied after detection and after --dev/--firmware
gating, so:
--only XwithXnot detected is a no-op (nothing runs).--exceptwins over--onlywhen both mention the same name.
Compose with profiles (up --profile work --only apt) to restrict a
profile on the fly.
up --rollbackReverts to the most recent eMerger-created snapshot. Dispatches to:
- snapper: native
snapper -c root rollback <num>. Grep-finds the last snapshot whose description starts witheMerger pre-update. A reboot is required to apply the rollback (snapper semantics, not ours). - timeshift: hands off to
timeshift --restore(interactive by design; we don't pass--yes- rollback is too destructive for that). - raw btrfs: refuses to swap subvolumes automatically, prints the path
of the latest snapshot under
/.snapshots/emerger/so you can do it manually.
Combine with --snapshot for a safe update cycle:
up --snapshot -y || up --rollbackSingle-letter short flags can be bundled:
up -nv # == up -n -v
up -ynv # == up -y -n -v
up -qv # == up -q -vOnly flags whose letters are all in the set {h V n v q y i w} bundle.
Compound short flags (-nl, -ni, -nc, -nt, -qq, -qqq, -up,
-au, -err, -rc) and long flags (--foo) pass through unchanged.
up -au # Linux / macOSup -au # Windows- Linux: systemd user timer (preferred) at
~/.config/systemd/user/emerger.{service,timer}; cron fallback. - macOS: cron fallback (
crontab -l), or uselaunchdmanually. - Windows:
Register-ScheduledTask -TaskName eMerger. Weekly, Sunday 10:00, ±1h randomized delay.
The scheduled run always uses -y -q -nl -ni.
Manage:
# Linux
systemctl --user status emerger.timer
systemctl --user disable emerger.timer
# Windows
Get-ScheduledTask eMerger
Unregister-ScheduledTask eMergerTo avoid night-time runs, pair with QUIET_HOURS in config.sh.
On Linux, systemd user timers do not fire unless a login session is open for the user. Run
loginctl enable-linger $USERonce (as root) to make them fire in the background.
Concrete, copy-pasteable snippets for common situations.
up -vSimple. Live output, everything updated, cache and trash wiped at the
end. Add --firmware once a month on Linux.
One-off setup:
cat > ~/.config/emerger/config.sh <<'EOF'
ARG_SECURITY=1
ARG_YES=1
ARG_NO_CACHE=1
ARG_NO_TRASH=1
QUIET_HOURS="08:00-18:00"
EOF
up -auPair with a Prometheus hook for dashboards:
cat > ~/.config/emerger/hooks/post.d/90-prom.sh <<'EOF'
#!/usr/bin/env bash
up --metrics /var/lib/node_exporter/textfile_collector/emerger.prom
EOF
chmod +x ~/.config/emerger/hooks/post.d/90-prom.shup --dev --firmware --parallel -v
# or, as a profile:
up --profile homeThe day before a presentation: security patches, no big downloads, no reboot.
up --profile safePrefetch when on good Wi-Fi:
up --download-only -yInstall later, even without network (the cache is already warm):
up -yup --only apt -y -qq --reboot-exit
rc=$?
if [[ $rc -eq 4 ]]; then echo "reboot required"; exit 0; fi
exit $rcSnapshot, update, rollback on failure, reboot on success:
set -e
up --snapshot -y || { up --rollback; exit 1; }
up -y --reboot-exit
rc=$?
if [[ $rc -eq 4 ]]; then systemctl reboot; fiup --only brew,mas -vup --except choco -vup -n | grep -i firefox # what would happen to it
up --changelog firefox # what changed upstreamAdd to ~/.config/emerger/ignore.list:
nvidia-driver-535
Then tell apt explicitly:
sudo apt-mark hold nvidia-driver-535(pacman respects the file natively.)
- eMerger is a client-side tool. No network listener, no daemon, no persistent background process.
- It reads and writes only three locations:
~/.config/emerger/,~/.cache/emerger/,~/.local/state/emerger/, plus the global lock at/tmp/emerger.lockon Unix. sudocredentials are cached bysudoitself, not by eMerger. When the run ends, the cached credentials expire on the usualsudoschedule (5 minutes by default).- On Windows, UAC elevation happens only when a detected manager actually needs admin. The elevation is logged.
- Hooks and plugins run as the invoking user. They can do anything the user can do - only install hooks from sources you trust.
- The only network calls eMerger itself makes are (a) the optional
weather widget via
wttr.in, (b)git pullduring--self-update, (c)fwupdmgr refreshwhen--firmwareis active. Everything else is delegated to the package manager you asked for. - No telemetry.
history.jsonlstays on your machine unless a hook you wrote ships it elsewhere.
| Path | Purpose |
|---|---|
~/.config/emerger/config.sh |
User defaults |
~/.config/emerger/profiles.d/ |
User profiles |
~/.config/emerger/hooks/pre.d/, post.d/ |
Hooks |
~/.config/emerger/ignore.list |
Ignore list (pacman native) |
~/.config/emerger/managers.d/*.sh |
User-defined manager plugins |
~/.cache/emerger/detected |
Detection cache (TTL: EMERGER_CACHE_TTL, default 86400s) |
~/.local/state/emerger/emerger.log |
Log (rotated at 2000 lines) |
~/.local/state/emerger/history.jsonl |
One JSON per run |
~/.local/state/emerger/pkgs.before |
Pre-run package snapshot |
~/.local/state/emerger/pkgs.after |
Post-run package snapshot |
~/.local/state/emerger/pkgs.diff |
Last-run package diff |
~/.local/state/emerger/resume |
Resume cursor |
/tmp/emerger.lock |
Global lock (flock) |
| Path | Purpose |
|---|---|
%APPDATA%\emerger\config.ps1 |
User defaults |
%APPDATA%\emerger\profiles.d\ |
User profiles |
%APPDATA%\emerger\hooks\pre.d\, post.d\ |
Hooks |
%LOCALAPPDATA%\emerger\cache\ |
Detection cache |
%LOCALAPPDATA%\emerger\state\emerger.log |
Log |
%LOCALAPPDATA%\emerger\state\history.jsonl |
Run history |
Linux - system (need sudo):
pacman, apt/apt-get, dnf, yum, zypper, xbps, apk, eopkg,
emerge, nixos-rebuild, fwupdmgr, snap.
Linux - AUR (no sudo): yay, paru.
Linux - user-space: flatpak, nix-env.
macOS: softwareupdate (native), brew, brew --cask, mas.
Windows: winget, scoop, choco, PSWindowsUpdate, wsl --update.
Dev toolchains (all platforms, opt-in with --dev): rustup,
cargo install-update, npm, pnpm, pip (user), gem.
Want another one? Add a case branch to
src/lib/packages.sh (Unix) or
src/pslib/Packages.ps1 (Windows) - they're
simple table-driven dispatchers. Or drop a plugin.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | runtime failure (sudo, lock, disk, interrupted) |
| 2 | argument parsing error |
| 3 | one or more package managers returned non-zero |
| 4 | reboot required (only emitted with --reboot-exit) |
Useful for CI / cron wrappers:
up -y -q || case $? in
3) notify-send "eMerger: some managers failed" ;;
*) logger -t eMerger "fatal $?" ;;
esacup -y -q
if ($LASTEXITCODE -eq 3) { Write-Warning "Some managers failed" }"Another eMerger run is in progress" - stale flock on
/tmp/emerger.lock. Check ps for stragglers then remove it.
Emoji renders as boxes - --no-emoji, or set LANG / Windows Terminal
font to a Unicode-capable one.
Spinner disappears on terminal resize - harmless; the live-log width recomputes every 120ms.
up --self-update aborts with "non fast-forward" - you have local
commits on top of main. Rebase or reset manually; eMerger refuses to
clobber them.
notify-send (Linux) doesn't appear - no DISPLAY /
WAYLAND_DISPLAY in the environment (typical for cron). Use systemd user
timer instead; it inherits the session.
A manager fails with exit code 3 - run up --errors to tail the log,
then up --doctor to inspect the environment. Most failures come from a
manager's own health state (for example dpkg interrupted); run the
native repair command (sudo dpkg --configure -a, sudo pacman -Dk)
before retrying.
Nothing happens on scheduled runs - check
systemctl --user status emerger.timer or the Windows Task Scheduler
entry. On Linux, user timers don't fire without an open session unless
you run loginctl enable-linger $USER.
(Windows) "up : The term 'up' is not recognized" - your PowerShell
profile didn't load. Run . $PROFILE or open a new window. If still
missing, re-run .\setup.ps1.
(Windows) "cannot be loaded because running scripts is disabled" -
ExecutionPolicy is Restricted. Run as user:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned(Windows) Elevation fails silently - you canceled the UAC prompt. The
script logs relaunching elevated then exits; the elevated window does
the actual work.
Pacman keeps asking about ignored packages - ignore.list is passed
as --ignore=; pacman still prints the warning line. Upstream behavior.
"command not found: gum" or "whiptail" - the interactive menu (-i)
falls back to a plain read loop when neither is installed. Install
gum for a nicer UI or ignore the
warning.
Is eMerger safe for production servers?
Yes, but use it in security-only, unattended mode: up --profile server.
Pair with --reboot-exit so your orchestrator decides when to reboot.
Does up install packages I don't already have?
No. up only invokes managers that are already installed and only asks
them to upgrade what is already installed. No new software is added
unless a package upgrade pulls in a new dependency.
Does up reboot my machine?
Only with --reboot. Without it, the summary prints a reboot advisory
and the run exits normally.
Can I use up without sudo?
Yes, in user-only mode: up --only flatpak,brew,npm,pip,cargo (adjust to
what you have). User-level managers never need elevation.
Can I use up inside a Docker image build?
Yes. Use up --only apt -y -qq (or the relevant manager) and ignore the
reboot advisory. Avoid --firmware, --snapshot and interactive flags.
What happens if two up runs start at the same time?
The second one aborts with exit code 1 ("Another eMerger run is in
progress"). The lock is a plain flock on /tmp/emerger.lock.
How do I completely remove eMerger? Run the uninstaller, then:
rm -rf ~/.config/emerger ~/.cache/emerger ~/.local/state/emerger
rm -rf /path/to/eMerger # the repo itselfWhere is my data? All local, all in the paths listed in Files & paths. Nothing leaves your machine unless a hook you wrote ships it.
Can I contribute a new package manager? Absolutely - see Development. Most contributions are a 20-line dispatch branch plus a test case.
Why is the terminal flashing during --parallel -v?
Each user-space manager streams its own output concurrently. Drop -v
or narrow the parallel set with --only if you want a calm terminal.
- Dry-run: a simulation. The tool prints the commands it would run without actually running them.
- Hook: a user script that runs before (
pre.d) or after (post.d) the update flow. - Manager (package manager): software like
apt,pacman,brew,wingetthat installs, upgrades and removes programs on your machine. - Parallel mode: concurrent execution of user-space managers.
- Plugin: a user-provided manager definition. Registers a new manager without modifying the repository.
- Profile: a named bundle of default flags, loaded via
--profile NAME. - Resume cursor: a file that records which managers completed
successfully, consumed by
--resume. - Snapshot: a read-only filesystem checkpoint taken before the upgrade. Linux only (snapper/timeshift/btrfs).
- TUI: Terminal User Interface. The interactive menu (
-i) on Unix. - UAC: User Account Control, the Windows elevation prompt. eMerger relaunches itself elevated when needed.
eMerger/
├── src/
│ ├── emerger.sh # Unix entry (Linux + macOS)
│ ├── emerger.ps1 # Windows entry
│ ├── lib/ # bash libs
│ ├── pslib/ # PowerShell libs
│ └── logo/
├── share/profiles/ # shipped profiles (*.sh + *.ps1)
├── share/plugins/ # example plugins
├── completions/ # bash/zsh/fish completions
├── tests/ # bats tests
├── man/up.1 # man page
├── doc/ # printable documentation
├── setup.sh setup.ps1
├── uninstall.sh uninstall.ps1
├── update.sh update.ps1
├── VERSION
└── .github/workflows/ci.yml
| File | Role |
|---|---|
ui.sh |
Colors, glyphs, spinner, box, live-log monitor |
log.sh |
Structured logging (rotated) |
sys.sh |
OS/shell/battery/disk detection |
run.sh |
Command runner (dry-run, retry, progress) |
args.sh |
Argument parser |
packages.sh |
Per-manager dispatcher |
clean.sh |
Cache and trash cleaners |
hooks.sh |
User hook runner |
update.sh |
Self-update + cron/timer setup |
notify.sh |
Desktop notifications |
summary.sh |
Final banner, history persistence |
tui.sh |
Interactive menu |
lock.sh |
Global flock |
retry.sh |
Retry on transient failures |
reboot.sh |
Reboot-required detection |
diff.sh |
Package snapshots + diff |
disk.sh |
Disk-space precheck |
snapshot.sh |
snapper/timeshift/btrfs |
mirrors.sh |
Mirror rank/refresh |
resume.sh |
Resume cursor |
doctor.sh |
--doctor |
changelog.sh |
--changelog PKG |
report.sh |
Markdown export |
wizard.sh |
First-run wizard |
profiles.sh |
Profile loader |
progress.sh |
Output summary + highlight |
estimate.sh |
Step ETA from history |
ignore.sh |
Ignore list loader |
plugins.sh |
User plugin loader |
metrics.sh |
Prometheus textfile export |
| File | Role |
|---|---|
UI.ps1 |
Colors, glyphs, box, step |
Log.ps1 |
Structured logging |
Sys.ps1 |
OS, admin, battery, disk, UAC elevation |
Args.ps1 |
Arg parser (shift + regex) |
Packages.ps1 |
Manager dispatcher + Run-Cmd |
Clean.ps1 |
%TEMP% and Recycle Bin |
Hooks.ps1 |
hooks\{pre,post}.d\*.ps1 |
Update.ps1 |
git pull self-update + Task Scheduler |
Notify.ps1 |
BurntToast (optional) |
Summary.ps1 |
Final box, history, reboot detection |
Doctor.ps1 |
--doctor |
Profiles.ps1 |
Profile loader |
Help.txt |
Help text |
sudo apt-get install bats shellcheck
bats tests/
shellcheck -S error src/emerger.sh src/lib/*.sh setup.sh uninstall.sh update.shBoth run on push via .github/workflows/ci.yml.
Unix - edit src/lib/packages.sh:
- Add its name to
PKG_MANAGERS. - Add a branch in
_pkg_detect_raw. - Add a branch in
pkg_runwithrun_cmdcalls. - If it doesn't need sudo, exclude it from
pkg_needs_sudo. - Optional: emoji in
pkg_icon, output parser inprogress.sh.
Windows - edit src/pslib/Packages.ps1:
- Add its name to
$script:PKG_MANAGERS(orPKG_DEV). - Add a
Pkg-Detectcase. - Add a
Pkg-Runcase withRun-Cmdcalls. - If it needs admin, add it to
Pkg-Need-Admin.
See CONTRIBUTING.md. Open issues and PRs against
dev. Include up --doctor output and the relevant chunk of the log when
reporting bugs.
See LICENSE.
Weather line via wttr.in.
