feat(ansible): add package exporter and update playbooks/packages

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
This commit is contained in:
Rodney Osodo
2026-02-10 21:24:44 +03:00
parent 925c2ef1ef
commit 8bdeb15991
5 changed files with 492 additions and 91 deletions
+16 -18
View File
@@ -1,14 +1,11 @@
.PHONY: ping
ping:
ping: ## Run a ping test to the server
ansible-playbook playbooks/ping.yaml --connection=local
.PHONY: prep
prep:
prep: ## Install ansible collections
ansible-galaxy collection install community.general
ansible-galaxy collection install kewlfft.aur
.PHONY: setup-desktop
setup-desktop: prep
setup-desktop: prep ## Setup desktop environment (runs prep)
ansible-playbook playbooks/yay.yaml --connection=local --ask-become-pass
ansible-playbook playbooks/pacman.yaml --connection=local --ask-become-pass
ansible-playbook playbooks/packages/desktop.yaml --connection=local --ask-become-pass
@@ -18,19 +15,20 @@ setup-desktop: prep
ansible-playbook playbooks/go.yaml --connection=local
ansible-playbook playbooks/rust.yaml --connection=local
.PHONY: install-desktop
install-desktop: prep
install-desktop: prep ## Install desktop packages (runs prep)
ansible-playbook playbooks/packages/desktop.yaml --connection=local --ask-become-pass
ansible-playbook playbooks/go.yaml --connection=local
ansible-playbook playbooks/rust.yaml --connection=local
.PHONY: help
help:
@echo "This Makefile is used to install dotfiles"
@echo ""
@echo "Usage:"
@echo " make ping - ping the server"
@echo " make prep - install ansible collections"
@echo " make setup-desktop - install desktop packages(runs prep)"
@echo " make install-desktop - install desktop packages(runs prep)"
@echo " make help - show this help message"
export-packages: ## Export installed packages to playbooks/packages/desktop.txt
pacman -Qqe > playbooks/packages/desktop.txt
export-go-packages: ## Export installed go packages to playbooks/go.yaml
python3 scripts/export_packages.py go playbooks/go.yaml
export-cargo-packages: ## Export installed cargo packages to playbooks/rust.yaml
python3 scripts/export_packages.py cargo playbooks/rust.yaml
help: ## Show this help message
@which awk > /dev/null || (echo "awk not found. Please install it from https://www.gnu.org/software/gawk/manual/gawk.html" && exit 1)
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-28s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
+16 -11
View File
@@ -10,17 +10,22 @@
- name: Install Golang Tools
ansible.builtin.shell: |
go install golang.org/x/tools/cmd/godoc@latest
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/google/pprof@latest
go install golang.org/x/tools/gopls@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/go-critic/go-critic/cmd/gocritic@latest
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/orlangure/gocovsh@latest
go install fortio.org/fortio@latest
go install github.com/KnockOutEZ/diffdeck/cmd/diffdeck@latest
go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest
go install go.uber.org/nilaway/cmd/nilaway@latest
go install github.com/go-critic/go-critic/cmd/gocritic@latest
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/google/pprof@latest
go install github.com/oligot/go-mod-upgrade@latest
go install github.com/vektra/mockery/v2@latest
go install github.com/orlangure/gocovsh@latest
go install github.com/ramonvermeulen/whosthere@latest
go install github.com/rayomqio/benchmq@latest
go install github.com/vektra/mockery/v3@latest
go install go.uber.org/nilaway/cmd/nilaway@latest
go install golang.org/x/tools/cmd/godoc@latest
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/tools/gopls@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install mvdan.cc/sh/v3/cmd/shfmt@latest
+165 -59
View File
@@ -1,19 +1,35 @@
7zip
act
alacritty
amd-ucode
amdgpu_top-bin
ansible
arduino-cli
arduino-ide
arduino-ide-bin
asciinema
atuin
bandwhich
baobab
base
base-devel
bat
bc
beekeeper-studio-bin
biome-bin
bind
biome
blueman
bluez-utils
brave-bin
brightnessctl
btop
btrfs-progs
bun-bin
ccache
cloud-sql-proxy
clang
claude-code
cloud-utils
cmake
cmatrix
commitizen-go
ctop
dbeaver
@@ -22,155 +38,245 @@ dmidecode
docker
docker-buildx
docker-compose
dog
dolphin
downgrade
dunst
easyeda-bin
efibootmgr
epiphany
eslint
esp-idf
esptool
evince
eza
fd
flameshot
fzf
gdm
ghostty
gimp
git
git-delta
git-lfs
github-cli
gnome-backgrounds
gnome-calculator
gnome-calendar
gnome-characters
gnome-clocks
gnome-color-manager
gnome-console
gnome-control-center
gnome-disk-utility
gnome-font-viewer
gnome-keyring
gnome-logs
gnome-menus
gnome-music
gnome-remote-desktop
gnome-session
gnome-settings-daemon
gnome-shell
gnome-shell-extensions
gnome-software
gnome-system-monitor
gnome-text-editor
gnome-tweaks
gnome-user-docs
gnome-user-share
gnome-weather
go
golangci-lint
google-chrome
google-cloud-cli
google-cloud-cli-gke-gcloud-auth-plugin
goreleaser
graphviz
grilo-plugins
grim
grub
grub-customizer
guestfs-tools
gvfs
gvfs-afc
gvfs-dnssd
gvfs-goa
gvfs-google
gvfs-gphoto2
gvfs-mtp
gvfs-nfs
gvfs-onedrive
gvfs-smb
gvfs-wsdd
helm
html-xml-utils
htop
httpie
hyperfine
hyprland
hyprpicker
inetutils
inkscape
ipython
jdk-openjdk
iwd
jq
just
k6
kconfig
k9s
keychain
kicad
kicad-library
kicad-library-3d
kitty
kompose
kubectl
lazydocker
lazygit
lazysql
lens-bin
libc++
libguestfs
libvirt
less
libvirt-glib
libvirt-python
localsearch
linux
linux-firmware
linux-lts
llvm
localsend-bin
loupe
malcontent
markdownlint-cli
minicom
mosquitto
msedit
mtr
nano
nautilus
neofetch
neovim
network-manager-applet
networkmanager
ngrok
ninja
nodejs
nmap
nodejs-npm-upgrade
nvm
obs-studio
obsidian
openbsd-netcat
openssl-1.1
opencode-bin
openocd
openvpn
openvpn3
orca
otf-codenewroman-nerd
otf-font-awesome
pacman-contrib
pandoc-cli
parallel
pavucontrol
platformio-core
pnpm
polkit-kde-agent
postman-bin
power-profiles-daemon
pre-commit
preload
protobuf-c
psutils
pulseaudio
pulseaudio-alsa
pulseaudio-bluetooth
pulseaudio-jack
putty
pyenv
pyenv-virtualenv
python-pip
python-psycopg2
python-pywal16
python36
qbittorrent
qemu-base
qemu-desktop
qemu-full
qt5-wayland
qt6-wayland
quota-tools
rancher-k3d
redisinsight-bin
reflector
rpi-imager
rtl-sdr-librtlsdr-git
rtl-wmbus-git
ruff
rustup
rye
rygel
sddm
shellcheck
skopeo
slack-desktop
slurp
smartmontools
snapshot
sof-firmware
sshpass
staticcheck
stm32cubeide
stow
sushi
swaync
swtpm
swww
syncthing
tailscale
teams
tecla
terraform
thefuck
tinygo
tldr
tmux
totem
tree
ttf-cousine
ttf-cousine-nerd
ttf-hack-nerd
ttf-meslo-nerd
ttf-ms-fonts
unrar
upx
uv
vim
virt-install
virt-manager
virt-viewer
visual-studio-code-bin
vlc
vlc-plugins-all
vulkan-radeon
w3m
wabt
wakatime
wasmer
wasmtime
waybar
wget
whois
wireless_tools
wl-clipboard
wlogout
woff2-font-awesome
wofi
xdg-desktop-portal-gnome
xdg-desktop-portal-hyprland
xdg-user-dirs-gtk
xdg-utils
xf86-video-amdgpu
xf86-video-ati
xorg-server
xorg-xinit
yarn
yay
yelp
zed
zellij
zen-browser-bin
zephyr
zig
zip
zram-generator
zsh
bat
lsof
downgrade
make
npm
rsync
vim
p7zip
tar
curl
wget
preload
kicad
kicad-library
kicad-library-3d
mosquitto-clients
mosquitto
less
syncthing
k9s
lazygit
goreleaser
golangci-lint
waybar
ttf-font-awesome
reflector
nvim-treesitter
ripgrep
fd
keychain
stow
atuin
otf-font-awesome
shellcheck
power-profiles-daemon
wlogout
swaync
swww
python-pywal16
wl-clipboard
auto-cpufreq
+19 -3
View File
@@ -16,6 +16,22 @@
rustup component add clippy cargo rustfmt rust-std
- name: Install Cargo Tools
ansible.builtin.shell:
cmd: cargo install du-dust fd-find ripgrep cargo-generate jwt-cli wasm-pack git-cliff
executable: /bin/bash
ansible.builtin.shell: |
cargo install --force TidyTUI
cargo install --force cargo-dist
cargo install --force cargo-generate
cargo install --force cargo-tarpaulin
cargo install --force du-dust
cargo install --force dua
cargo install --force dua-cli
cargo install --force fd-find
cargo install --force git-cliff
cargo install --force gqlint
cargo install --force jwt-cli
cargo install --force lumen
cargo install --force medium-to-markdown
cargo install --force mini-redis
cargo install --force ripgrep
cargo install --force sleek
cargo install --force trippy
cargo install --force wasm-pack
+276
View File
@@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""
Collect `go install` / `cargo install` commands from shell histories and
atuin, then surgically update the ansible.builtin.shell block in the
corresponding playbook YAML file.
Usage:
python3 scripts/export_packages.py go playbooks/go.yaml
python3 scripts/export_packages.py cargo playbooks/rust.yaml
"""
import re
import subprocess
import sys
from pathlib import Path
def _atuin(prefix: str) -> list[str]:
try:
out = subprocess.check_output(
["atuin", "search", prefix],
stderr=subprocess.DEVNULL,
text=True,
)
except (FileNotFoundError, subprocess.CalledProcessError):
return []
lines = []
for line in out.splitlines():
parts = line.split("\t", 2)
if len(parts) >= 2:
lines.append(parts[1])
return lines
def _zsh_history(prefix: str) -> list[str]:
path = Path.home() / ".zsh_history"
if not path.exists():
return []
lines = []
for raw in path.read_text(errors="replace").splitlines():
# strip zsh extended_history timestamp prefix `: 1234567890:0;`
cmd = re.sub(r"^:[^;]*;", "", raw)
# find the first occurrence of the prefix command in the line
idx = cmd.find(prefix)
if idx != -1:
lines.append(cmd[idx:])
return lines
def _bash_history(prefix: str) -> list[str]:
path = Path.home() / ".bash_history"
if not path.exists():
return []
return [
l for l in path.read_text(errors="replace").splitlines() if l.startswith(prefix)
]
def _existing_yaml(yaml_path: Path, prefix: str) -> list[str]:
if not yaml_path.exists():
return []
return [
l.strip()
for l in yaml_path.read_text().splitlines()
if l.strip().startswith(prefix)
]
def collect_raw(prefix: str, yaml_path: Path) -> list[str]:
raw: list[str] = []
raw += _existing_yaml(yaml_path, prefix)
raw += _atuin(prefix)
raw += _zsh_history(prefix)
raw += _bash_history(prefix)
cleaned = []
for line in raw:
line = line.rstrip("\\").rstrip().strip('"')
if line.startswith(prefix):
cleaned.append(line)
return cleaned
_GO_HOST_RE = re.compile(r"^[a-zA-Z0-9_-]+\.[a-zA-Z]")
def parse_go_install(line: str) -> str | None:
"""
Normalise a `go install` line to `@latest`, preserve flags like -tags.
Returns None if the line doesn't look like a valid Go module install.
"""
parts = line.split()
# parts[0]='go', parts[1]='install', parts[2..n-1]=flags, parts[n]=pkg
if len(parts) < 3:
return None
pkg = parts[-1]
if not _GO_HOST_RE.match(pkg):
return None
# normalise version
pkg = re.sub(r"@[^@]+$", "@latest", pkg)
# collect flags (everything between 'install' and the package)
flags: list[str] = []
i = 2
while i < len(parts) - 1:
if parts[i].startswith("-"):
flags.append(parts[i])
# include the flag value if next token isn't a flag or a module path
if (
i + 1 < len(parts) - 1
and not parts[i + 1].startswith("-")
and not _GO_HOST_RE.match(parts[i + 1])
):
i += 1
flags.append(parts[i])
i += 1
flag_str = (" " + " ".join(flags)) if flags else ""
return f"go install{flag_str} {pkg}"
def collect_go(yaml_path: Path) -> list[str]:
seen: set[str] = set()
result: list[str] = []
for raw in collect_raw("go install", yaml_path):
parsed = parse_go_install(raw)
if parsed and parsed not in seen:
seen.add(parsed)
result.append(parsed)
return sorted(result)
def parse_cargo_install(line: str) -> list[dict]:
"""
Parse a `cargo install` line into a list of canonical install descriptors:
{'type': 'git', 'url': ..., 'bin': ...}
{'type': 'locked', 'pkg': ...}
{'type': 'force', 'pkg': ...}
Multi-package lines (e.g. `cargo install a b c --force`) are expanded.
"""
parts = line.split()
if len(parts) < 3:
return []
git_url = ""
git_bin = ""
locked = False
pkgs: list[str] = []
i = 2
while i < len(parts):
t = parts[i]
if t == "--git" and i + 1 < len(parts):
git_url = parts[i + 1]
i += 2
if i < len(parts) and not parts[i].startswith("-"):
git_bin = parts[i]
i += 1
elif t == "--locked":
locked = True
i += 1
elif t.startswith("-"):
i += 1
# skip flag value
if i < len(parts) and not parts[i].startswith("-"):
i += 1
else:
pkgs.append(t)
i += 1
if git_url:
return [{"type": "git", "url": git_url, "bin": git_bin}]
results = []
for pkg in pkgs:
if not pkg or pkg == "install":
continue
results.append({"type": "locked" if locked else "force", "pkg": pkg})
return results
def collect_cargo(yaml_path: Path) -> list[str]:
# keyed by package name; locked > force > plain
force: dict[str, bool] = {} # pkg -> True
locked: set[str] = set()
git: set[str] = set()
for raw in collect_raw("cargo install", yaml_path):
if "--list" in raw:
continue
for item in parse_cargo_install(raw):
if item["type"] == "git":
entry = f"cargo install --git {item['url']}"
if item["bin"]:
entry += f" {item['bin']}"
git.add(entry)
elif item["type"] == "locked":
locked.add(item["pkg"])
force.pop(item["pkg"], None)
elif item["type"] == "force":
if item["pkg"] not in locked:
force[item["pkg"]] = True
result: list[str] = []
result += sorted(git)
result += [f"cargo install --locked {p}" for p in sorted(locked)]
result += [f"cargo install --force {p}" for p in sorted(force)]
return result
def update_yaml(yaml_path: Path, task_name: str, new_lines: list[str]) -> None:
text = yaml_path.read_text()
indent = " "
new_body = "".join(f"{indent}{l}\n" for l in new_lines)
pattern_block = (
rf"(?m)( - name: {re.escape(task_name)}\n"
rf" ansible\.builtin\.shell: \|\n)"
rf"((?:{indent}[^\n]*\n)*)"
)
# Match dict form: ansible.builtin.shell:\n cmd: ...\n executable: ...
pattern_dict = (
rf"(?m)( - name: {re.escape(task_name)}\n)"
rf" ansible\.builtin\.shell:\n"
rf" cmd:.*\n"
rf" executable: /bin/bash\n"
)
if re.search(pattern_block, text):
result = re.sub(pattern_block, lambda m: m.group(1) + new_body, text)
elif re.search(pattern_dict, text):
result = re.sub(
pattern_dict,
lambda m: (
m.group(1)
+ " ansible.builtin.shell: |\n"
+ new_body
+ " args:\n executable: /bin/bash\n"
),
text,
)
else:
print(
f"ERROR: could not find task '{task_name}' in {yaml_path}", file=sys.stderr
)
sys.exit(1)
yaml_path.write_text(result)
def main() -> None:
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <go|cargo> <yaml_path>", file=sys.stderr)
sys.exit(1)
mode = sys.argv[1]
yaml_path = Path(sys.argv[2])
if mode == "go":
print(
"Collecting go install commands from atuin, zsh_history and bash_history..."
)
lines = collect_go(yaml_path)
update_yaml(yaml_path, "Install Golang Tools", lines)
elif mode == "cargo":
print(
"Collecting cargo install commands from atuin, zsh_history and bash_history..."
)
lines = collect_cargo(yaml_path)
update_yaml(yaml_path, "Install Cargo Tools", lines)
else:
print(f"Unknown mode: {mode}", file=sys.stderr)
sys.exit(1)
print(f"Written to {yaml_path}")
if __name__ == "__main__":
main()