Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save ByronScottJones/9d1530c56a7b2c48cc7e444dca02b028 to your computer and use it in GitHub Desktop.

Select an option

Save ByronScottJones/9d1530c56a7b2c48cc7e444dca02b028 to your computer and use it in GitHub Desktop.
Bash Script to Generate Installation Scripts from Existing System
#Based on Reddit Post: https://www.reddit.com/r/bash/comments/1oe77jj/how_do_you_export_your_full_package_list/?share_id=PXjkqGNGLDfcl5M1X8e05
#!/usr/bin/env bash
set -euo pipefail
# ---- basics -----------------------------------------------------------------
have() { command -v "$1" &>/dev/null; }
SUDO=""
if [ "${EUID:-$(id -u)}" -ne 0 ] && have sudo; then SUDO="sudo "; fi
DOTFILES="${DOTFILES:-$HOME/.dotfiles}"
backup_dir="$DOTFILES/bak"
mkdir -p "$backup_dir"
# For tools that don't clearly separate "install" vs "enable", we still try to
# emit a best-effort install command line per item.
# ---- Debian/Ubuntu (apt) ----------------------------------------------------
if have apt; then
# Prefer dpkg-query to avoid apt headers/noise; falls back to apt list
if have dpkg-query; then
dpkg-query -W -f='${binary:Package}\n' \
| sort -u \
| sed "s/^/${SUDO}apt-get install -y /" \
> "$backup_dir/apt.install.sh"
else
apt list --installed 2>/dev/null \
| awk -F/ 'NR>1{print $1}' \
| sort -u \
| sed "s/^/${SUDO}apt-get install -y /" \
> "$backup_dir/apt.install.sh"
fi
echo "[apt] wrote $backup_dir/apt.install.sh"
fi
# ---- Arch (pacman) ----------------------------------------------------------
if have pacman; then
# Explicitly installed, not deps:
pacman -Qqe 2>/dev/null \
| sort -u \
| sed "s/^/${SUDO}pacman -S --needed --noconfirm /" \
> "$backup_dir/pacman.install.sh"
echo "[pacman] wrote $backup_dir/pacman.install.sh"
fi
# ---- RPM world (dnf/zypper fallback) ----------------------------------------
if have rpm; then
# Prefer dnf (RHEL/Fedora) else zypper (SUSE). If neither, emit rpm -i comment.
if have dnf; then
rpm -qa --qf '%{NAME}\n' \
| sort -u \
| sed "s/^/${SUDO}dnf install -y /" \
> "$backup_dir/rpm.dnf.install.sh"
echo "[rpm/dnf] wrote $backup_dir/rpm.dnf.install.sh"
elif have zypper; then
rpm -qa --qf '%{NAME}\n' \
| sort -u \
| sed "s/^/${SUDO}zypper install -y /" \
> "$backup_dir/rpm.zypper.install.sh"
echo "[rpm/zypper] wrote $backup_dir/rpm.zypper.install.sh"
else
rpm -qa \
| sort -u \
| sed "s/^/# No dnf/zypper detected. Manually fetch .rpm then: ${SUDO}rpm -i /" \
> "$backup_dir/rpm.manual.install.txt"
echo "[rpm] no dnf/zypper; wrote advisory $backup_dir/rpm.manual.install.txt"
fi
fi
# ---- Flatpak ----------------------------------------------------------------
if have flatpak; then
# System apps -> install back to system
flatpak list --system --app --columns=application \
| awk 'NF' | sort -u \
| sed "s/^/${SUDO}flatpak install -y --system /" \
> "$backup_dir/flatpak.system.install.sh"
# User apps -> install back to user
flatpak list --user --app --columns=application \
| awk 'NF' | sort -u \
| sed "s/^/flatpak install -y --user /" \
> "$backup_dir/flatpak.user.install.sh"
echo "[flatpak] wrote $backup_dir/flatpak.{system,user}.install.sh"
fi
# ---- Homebrew (macOS/Linuxbrew) --------------------------------------------
if have brew; then
# Your request already uses the canonical installer format: Brewfile
brew bundle dump --file="$backup_dir/Brewfile" --force
echo "[brew] wrote $backup_dir/Brewfile"
fi
# ---- Node.js (npm) ----------------------------------------------------------
if have npm; then
# Parseable avoids tree formatting; skip first line (prefix dir)
npm ls -g --depth=0 --parseable 2>/dev/null \
| awk 'NR>1{print $0}' \
| awk -F/ '{print $NF}' \
| sort -u \
| awk 'NF && $0 !~ /^npm$/ {print "npm install -g " $0}' \
> "$backup_dir/node.npm.install.sh"
echo "[npm] wrote $backup_dir/node.npm.install.sh"
fi
# ---- Node.js (pnpm) ---------------------------------------------------------
if have pnpm; then
pnpm ls -g --depth=0 --parseable 2>/dev/null \
| awk 'NR>1{print $0}' \
| awk -F/ '{print $NF}' \
| sort -u \
| awk 'NF {print "pnpm add -g " $0}' \
> "$backup_dir/node.pnpm.install.sh"
echo "[pnpm] wrote $backup_dir/node.pnpm.install.sh"
fi
# ---- Python (pip) -----------------------------------------------------------
# Choose pip for this Python; prefer pip3 if available
pip_cmd=""
if have pip; then pip_cmd="pip"
elif have pip3; then pip_cmd="pip3"
fi
if [ -n "${pip_cmd}" ]; then
# Freeze gives NAME==VERSION for exact restore
$pip_cmd list --format=freeze 2>/dev/null \
| grep -Ev '^(pip|setuptools|wheel)|^-$' \
| awk '{print "'"$pip_cmd"' install --upgrade " $0}' \
> "$backup_dir/python.pip.install.sh"
echo "[pip] wrote $backup_dir/python.pip.install.sh"
fi
# ---- Ruby (gem) -------------------------------------------------------------
if have gem; then
gem list --no-versions --local \
| awk 'NF {print "gem install " $0}' \
> "$backup_dir/ruby.gem.install.sh"
echo "[gem] wrote $backup_dir/ruby.gem.install.sh"
fi
# ---- pipx (Python apps in venvs) -------------------------------------------
if have pipx; then
# --short often prints one per line; keep first token as package/app
pipx list --short 2>/dev/null \
| awk '{print $1}' \
| awk 'NF {print "pipx install " $0}' \
> "$backup_dir/pipx.install.sh"
echo "[pipx] wrote $backup_dir/pipx.install.sh"
fi
# ---- GNOME Shell Extensions -------------------------------------------------
if have gnome-extensions; then
# We can reliably re-enable; true "install" typically needs a .zip or EGO
gnome-extensions list --user --enabled \
| awk 'NF {print "gnome-extensions enable " $0}' \
> "$backup_dir/gnome.ext.user.enable.sh"
gnome-extensions list --system --enabled \
| awk 'NF {print "gnome-extensions enable " $0}' \
> "$backup_dir/gnome.ext.system.enable.sh"
# If you archive zips elsewhere, set EXT_ZIP_DIR and uncomment the install line:
# awk '{print "gnome-extensions install --force " ENVIRON["EXT_ZIP_DIR"] "/" $0 ".shell-extension.zip"}'
echo "[gnome-extensions] wrote enable scripts under $backup_dir (installation zips vary by distro/source)"
fi
# ---- Containers (podman) ----------------------------------------------------
if have podman; then
podman images --format '{{.Repository}}:{{.Tag}}' \
| awk 'NF && $0 !~ /<none>:<none>/{print "podman pull " $0}' \
> "$backup_dir/podman.pull.sh"
echo "[podman] wrote $backup_dir/podman.pull.sh"
fi
# ---- Visual Studio Code Extensions -----------------------------------------
if have code; then
code --list-extensions > "$backup_dir/vscode.bak"
awk '{print "code --install-extension " $0}' "$backup_dir/vscode.bak" \
> "$backup_dir/vscode.install.sh"
echo "[vscode] wrote $backup_dir/vscode.bak and $backup_dir/vscode.install.sh"
fi
# ---- Gentoo (Portage) -------------------------------------------------------
if have emerge && [ -r /var/lib/portage/world ]; then
# world contains atoms; keep as-is for emerge
grep -v '^\s*$' /var/lib/portage/world \
| sed "s/^/${SUDO}emerge --ask --verbose /" \
> "$backup_dir/gentoo.emerge.install.sh"
echo "[gentoo] wrote $backup_dir/gentoo.emerge.install.sh"
fi
# ---- Compatibility outputs you originally had (optional) --------------------
# If you still want the plain lists alongside the install scripts, uncomment:
# apt list --installed > "$backup_dir/apt.list"
# pacman -Qe > "$backup_dir/pacman.list"
# rpm -qa > "$backup_dir/rpm.list"
# flatpak list --system --app --columns=application | sort -u > "$backup_dir/flatpak.list"
# npm list --global --depth=0 > "$backup_dir/node.npm.list"
# pnpm list --global --depth=0 > "$backup_dir/node.pnpm.list"
# $pip_cmd list --not-required > "$backup_dir/python.pip.list"
# gem list > "$backup_dir/ruby.gem.list"
# gnome-extensions list --user --active > "$backup_dir/gnome.ext.usr.list"
# gnome-extensions list --system --active > "$backup_dir/gnome.ext.sys.list"
# podman images --format '{{.Repository}}:{{.Tag}}' > "$backup_dir/podman.img.list"
echo "Done. Installer command scripts are in: $backup_dir"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment