Created
October 24, 2025 13:56
-
-
Save ByronScottJones/9d1530c56a7b2c48cc7e444dca02b028 to your computer and use it in GitHub Desktop.
Bash Script to Generate Installation Scripts from Existing System
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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