Refactor setup workflow

This commit is contained in:
MangoPig
2026-05-31 23:24:18 +01:00
parent 5cefa4019b
commit 58531bf579
24 changed files with 478 additions and 103 deletions

268
Scripts/base.sh Normal file
View File

@@ -0,0 +1,268 @@
#!/bin/bash
# Path: Scripts/base.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
RED='\033[1;31m'
NC='\033[0m'
DOTFILES_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REPO_ROOT="$(dirname "$DOTFILES_DIR")"
source "$DOTFILES_DIR/lib/distro.sh"
echo -e "${BLUE} LOG:${YELLOW} Initializing Base System Layer...${NC}"
# Confirm Architecture
ARCH=$(uname -m)
if [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "aarch64" ]; then
echo -e "${RED} ERROR: Unsupported architecture: $ARCH${NC}"
exit 1
fi
# Package Installation
PACKAGES=(
curl wget git sudo
zsh tmux
unzip tar gzip
build-essential
openssl
python bison mercurial
ripgrep fd bat fzf jq
btop httpie gnupg
zoxide stow direnv
bind nmap socat tcpdump net-tools
strace gdb hexyl
ninja-build libcurl4-openssl-dev
just
)
FINAL_PACKAGES=()
for pkg in "${PACKAGES[@]}"; do
case "$pkg" in
"build-essential")
if is_arch_family; then
FINAL_PACKAGES+=(base-devel)
elif is_debian_family; then
FINAL_PACKAGES+=(build-essential)
elif is_fedora_family; then
FINAL_PACKAGES+=(gcc gcc-c++ make patch)
fi
continue
;;
"python")
if is_arch_family; then
FINAL_PACKAGES+=(python)
elif is_debian_family; then
FINAL_PACKAGES+=(python3 python3-pip python3-venv)
elif is_fedora_family; then
FINAL_PACKAGES+=(python3 python3-pip)
fi
continue
;;
"fd")
if is_arch_family; then
FINAL_PACKAGES+=(fd)
elif is_debian_family || is_fedora_family; then
FINAL_PACKAGES+=(fd-find)
fi
continue
;;
"bat")
FINAL_PACKAGES+=(bat)
continue
;;
"openssl")
if is_arch_family; then
FINAL_PACKAGES+=(openssl)
elif is_debian_family; then
FINAL_PACKAGES+=(libssl-dev)
elif is_fedora_family; then
FINAL_PACKAGES+=(openssl openssl-devel)
fi
continue
;;
"bind")
if is_arch_family; then
FINAL_PACKAGES+=(bind)
elif is_debian_family; then
FINAL_PACKAGES+=(dnsutils)
elif is_fedora_family; then
FINAL_PACKAGES+=(bind-utils)
fi
continue
;;
"ninja-build")
if is_arch_family; then
FINAL_PACKAGES+=(ninja)
elif is_debian_family || is_fedora_family; then
FINAL_PACKAGES+=(ninja-build)
fi
continue
;;
"libcurl4-openssl-dev")
if is_arch_family; then
FINAL_PACKAGES+=(curl)
elif is_debian_family; then
FINAL_PACKAGES+=(libcurl4-openssl-dev)
elif is_fedora_family; then
FINAL_PACKAGES+=(libcurl-devel)
fi
continue
;;
"gnupg")
if is_fedora_family; then
FINAL_PACKAGES+=(gnupg2)
else
FINAL_PACKAGES+=(gnupg)
fi
continue
;;
*)
esac
FINAL_PACKAGES+=("$pkg")
done
if is_debian_family; then
FINAL_PACKAGES+=(ca-certificates bsdmainutils pkg-config cmake)
fi
if is_fedora_family; then
FINAL_PACKAGES+=(ca-certificates pkgconf-pkg-config cmake)
FINAL_PACKAGES+=(R-core gcc-gfortran bzip2 bzip2-devel readline-devel sqlite sqlite-devel tk-devel libffi-devel xz xz-devel ncurses-devel zlib-devel findutils llvm)
fi
echo -e "${BLUE} LOG:${YELLOW} Installing: ${NC}${FINAL_PACKAGES[*]}"
install_status=0
install_packages "${FINAL_PACKAGES[@]}" || install_status=$?
if [ "$install_status" -eq 42 ]; then
exit 0
elif [ "$install_status" -ne 0 ]; then
exit "$install_status"
fi
if is_debian_family || is_fedora_family; then
echo -e "${BLUE} LOG:${YELLOW} Fixing fd/bat binary names when needed...${NC}"
[ -f /usr/bin/fdfind ] && sudo ln -sf /usr/bin/fdfind /usr/local/bin/fd
[ -f /usr/bin/batcat ] && sudo ln -sf /usr/bin/batcat /usr/local/bin/bat
fi
# Installing pinned repo-managed CLI binaries
bash "$REPO_ROOT/Scripts/bin/install.sh" --all
if ! command -v rclone &> /dev/null; then
echo -e "${BLUE} LOG:${YELLOW} Installing Rclone CLI...${NC}"
case "$ARCH" in
x86_64)
RCLONE_ARCH="amd64"
;;
aarch64)
RCLONE_ARCH="arm64"
;;
esac
TEMP_DIR="$(mktemp -d)"
RCLONE_ZIP="$TEMP_DIR/rclone.zip"
curl -fLsS "https://downloads.rclone.org/rclone-current-linux-${RCLONE_ARCH}.zip" -o "$RCLONE_ZIP"
unzip -q "$RCLONE_ZIP" -d "$TEMP_DIR"
install -m 755 "$TEMP_DIR"/rclone-*-linux-"${RCLONE_ARCH}"/rclone "$HOME/.local/bin/rclone"
rm -rf "$TEMP_DIR"
fi
if ! command -v earthly &> /dev/null; then
echo -e "${BLUE} LOG:${YELLOW} Installing Earthly CLI...${NC}"
case "$ARCH" in
x86_64)
EARTHLY_ARCH="amd64"
;;
aarch64)
EARTHLY_ARCH="arm64"
;;
esac
curl -fLsS "https://github.com/earthly/earthly/releases/latest/download/earthly-linux-${EARTHLY_ARCH}" \
-o "$HOME/.local/bin/earthly"
chmod +x "$HOME/.local/bin/earthly"
fi
# Docker Installation
if command -v docker &> /dev/null; then
echo -e "${GREEN} LOG: Docker is already installed.${NC}"
else
if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null; then
echo -e "${RED} LOG: WSL Detected! Skipping Native Docker.${NC}"
echo -e "${RED} >>> Please install Docker Desktop on Windows.${NC}"
else
echo -e "${BLUE} LOG:${YELLOW} Installing Native Docker...${NC}"
if is_arch_family; then
sudo pacman -S --noconfirm --needed docker docker-compose
sudo systemctl enable --now docker
elif is_debian_family; then
curl -fsSL https://get.docker.com | sh
elif is_fedora_family; then
if is_atomic_fedora; then
echo -e "${YELLOW} NOTE:${NC} Skipping native Docker auto-install on rpm-ostree systems."
echo -e "${YELLOW} NOTE:${NC} After reboot, install your preferred container runtime separately if needed."
else
sudo dnf install -y moby-engine docker-compose
sudo systemctl enable --now docker
fi
fi
# Add user to group
if getent group docker >/dev/null 2>&1; then
sudo usermod -aG docker $(whoami)
fi
fi
fi
# 4. Zsh & Configuration
if [ ! -d "$HOME/.oh-my-zsh" ]; then
echo -e "${BLUE} LOG:${YELLOW} Installing Oh My Zsh...${NC}"
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
fi
# Plugins
ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
mkdir -p "$ZSH_CUSTOM/plugins"
[ ! -d "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting" ] && git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting"
[ ! -d "$ZSH_CUSTOM/plugins/zsh-autosuggestions" ] && git clone https://github.com/zsh-users/zsh-autosuggestions "$ZSH_CUSTOM/plugins/zsh-autosuggestions"
# 5. Git Credentials (WSL Bridge)
if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null; then
GCM_WIN="/mnt/c/Program Files/Git/mingw64/bin/git-credential-manager.exe"
[ -f "$GCM_WIN" ] && git config --global credential.helper "! \"$GCM_WIN\""
else
git config --global credential.helper 'cache --timeout=43200'
fi
# 6. Cleanup & Secrets
rm -f "$HOME/.zshrc" "$HOME/.zsh_aliases"
mkdir -p "$REPO_ROOT/Zsh"
touch "$REPO_ROOT/Zsh/.zsh_secrets"
# 7. Set Shell
TARGET_SHELL="$(command -v zsh)"
CURRENT_LOGIN_SHELL="$(getent passwd "$(whoami)" | cut -d: -f7)"
if [ "$CURRENT_LOGIN_SHELL" != "$TARGET_SHELL" ]; then
if command -v chsh >/dev/null 2>&1; then
sudo chsh -s "$TARGET_SHELL" "$(whoami)"
elif command -v usermod >/dev/null 2>&1; then
sudo usermod -s "$TARGET_SHELL" "$(whoami)"
else
echo -e "${YELLOW} NOTE:${NC} Could not find chsh/usermod. Please change your login shell to $TARGET_SHELL manually."
fi
fi
echo -e "${GREEN} LOG: Base System Setup Complete.${NC}"

99
Scripts/bin/install.sh Normal file
View File

@@ -0,0 +1,99 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
MANIFEST="$REPO_ROOT/Bins/versions.json"
TARGET_DIR="$HOME/.local/bin"
ensure_deps() {
local missing=()
for cmd in jq curl tar; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
printf 'Missing required commands: %s\n' "${missing[*]}" >&2
exit 1
fi
}
detect_arch() {
case "$(uname -m)" in
x86_64) printf 'x86_64\n' ;;
aarch64|arm64) printf 'aarch64\n' ;;
*)
printf 'Unsupported architecture: %s\n' "$(uname -m)" >&2
exit 1
;;
esac
}
install_tool() {
local tool="$1"
local arch version owner repo asset binary_rel url tmp_dir archive_path extracted_path target_path
arch="$(detect_arch)"
if ! jq -e --arg tool "$tool" '.[$tool]' "$MANIFEST" >/dev/null; then
printf 'Unsupported tool: %s\n' "$tool" >&2
exit 1
fi
version="$(jq -r --arg tool "$tool" '.[$tool].version' "$MANIFEST")"
owner="$(jq -r --arg tool "$tool" '.[$tool].owner' "$MANIFEST")"
repo="$(jq -r --arg tool "$tool" '.[$tool].repo' "$MANIFEST")"
asset="$(jq -r --arg tool "$tool" --arg arch "$arch" '.[$tool].linux[$arch].asset' "$MANIFEST")"
binary_rel="$(jq -r --arg tool "$tool" --arg arch "$arch" '.[$tool].linux[$arch].binary' "$MANIFEST")"
if [ -z "$asset" ] || [ "$asset" = "null" ] || [ -z "$binary_rel" ] || [ "$binary_rel" = "null" ]; then
printf 'No Linux asset mapping for %s on %s\n' "$tool" "$arch" >&2
exit 1
fi
mkdir -p "$TARGET_DIR"
tmp_dir="$(mktemp -d)"
archive_path="$tmp_dir/$asset"
url="https://github.com/$owner/$repo/releases/download/$version/$asset"
printf 'Installing %s %s\n' "$tool" "$version"
curl -fL "$url" -o "$archive_path"
tar -xzf "$archive_path" -C "$tmp_dir"
extracted_path="$tmp_dir/$binary_rel"
if [ ! -f "$extracted_path" ]; then
printf 'Expected binary not found after extraction: %s\n' "$binary_rel" >&2
rm -rf "$tmp_dir"
exit 1
fi
target_path="$TARGET_DIR/$tool"
install -m 755 "$extracted_path" "$target_path"
rm -rf "$tmp_dir"
printf 'Installed %s -> %s\n' "$tool" "$target_path"
}
main() {
ensure_deps
if [ "${1:-}" = "--all" ]; then
while IFS= read -r tool; do
install_tool "$tool"
done < <(jq -r 'keys[]' "$MANIFEST")
exit 0
fi
if [ -z "${1:-}" ]; then
printf 'Usage: %s [--all|tool]\n' "$0" >&2
exit 1
fi
install_tool "$1"
}
main "$@"

135
Scripts/bin/update.sh Normal file
View File

@@ -0,0 +1,135 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
MANIFEST="$REPO_ROOT/Bins/versions.json"
ensure_deps() {
local missing=()
for cmd in jq curl python3; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
printf 'Missing required commands: %s\n' "${missing[*]}" >&2
exit 1
fi
}
github_api() {
local path="$1"
local auth_args=()
if [ -n "${GITHUB_TOKEN:-}" ]; then
auth_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
fi
curl -fsSL \
-H "Accept: application/vnd.github+json" \
"${auth_args[@]}" \
"https://api.github.com${path}"
}
list_versions() {
local tool="$1" owner repo
if ! jq -e --arg tool "$tool" '.[$tool]' "$MANIFEST" >/dev/null; then
printf 'Unsupported tool: %s\n' "$tool" >&2
exit 1
fi
owner="$(jq -r --arg tool "$tool" '.[$tool].owner' "$MANIFEST")"
repo="$(jq -r --arg tool "$tool" '.[$tool].repo' "$MANIFEST")"
github_api "/repos/$owner/$repo/releases?per_page=100" | jq -r '.[].tag_name'
}
select_version() {
local tool="$1"
local versions
versions="$(list_versions "$tool")"
if [ -z "$versions" ]; then
printf 'No releases found for %s\n' "$tool" >&2
exit 1
fi
if command -v fzf >/dev/null 2>&1; then
printf '%s\n' "$versions" | fzf --prompt="Select ${tool} version > " --height=20 --reverse
else
printf '%s\n' "$versions" | sed -n '1,20p' >&2
printf 'fzf is not installed, so pass a version explicitly.\n' >&2
exit 1
fi
}
update_version() {
local tool="$1" version="$2"
local tmp_file
if [ -z "$version" ]; then
printf 'No version selected for %s\n' "$tool" >&2
exit 1
fi
tmp_file="$(mktemp)"
python3 - "$MANIFEST" "$tool" "$version" "$tmp_file" <<'PY'
import json
import pathlib
import sys
manifest_path = pathlib.Path(sys.argv[1])
tool = sys.argv[2]
version = sys.argv[3]
tmp_path = pathlib.Path(sys.argv[4])
data = json.loads(manifest_path.read_text())
if tool not in data:
raise SystemExit(f"Unsupported tool: {tool}")
data[tool]["version"] = version
tmp_path.write_text(json.dumps(data, indent=2) + "\n")
PY
mv "$tmp_file" "$MANIFEST"
printf 'Pinned %s to %s\n' "$tool" "$version"
}
main() {
local mode="update" tool version
ensure_deps
if [ "${1:-}" = "--list" ]; then
mode="list"
shift
fi
tool="${1:-}"
version="${2:-}"
if [ -z "$tool" ]; then
printf 'Usage: %s [--list] tool [version]\n' "$0" >&2
exit 1
fi
case "$mode" in
list)
list_versions "$tool"
;;
update)
if [ -z "$version" ]; then
version="$(select_version "$tool")"
fi
update_version "$tool" "$version"
;;
esac
}
main "$@"

38
Scripts/cpp.sh Normal file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
# Path: Scripts/cpp.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
NC='\033[0m'
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$SCRIPT_DIR/lib/distro.sh"
echo -e "${BLUE} LOG:${YELLOW} Setting up C++ Tooling (LLVM/Clang)...${NC}"
if is_arch_family; then
CPP_PACKAGES=(clang cmake ninja lldb gdb)
elif is_debian_family || is_fedora_family; then
CPP_PACKAGES=(clang cmake ninja-build lldb gdb)
else
echo -e "${RED} ERROR:${NC} Unsupported OS: $OS"
exit 1
fi
install_status=0
install_packages "${CPP_PACKAGES[@]}" || install_status=$?
if [ "$install_status" -eq 42 ]; then
exit 0
elif [ "$install_status" -ne 0 ]; then
exit "$install_status"
fi
echo -e "${GREEN} SUCCESS:${NC} C++ Environment Ready."
echo -e " - Compiler: $(clang --version | head -n 1)"
echo -e " - Builder: $(cmake --version | head -n 1)"
echo -e " - Debugger: $(lldb --version | head -n 1)"

80
Scripts/go.sh Normal file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
# Path: Scripts/go.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
RED='\033[1;31m'
NC='\033[0m'
export GVM_ROOT="$HOME/.programming/go"
BOOTSTRAP_GO="$GVM_ROOT/bootstrap"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64)
GO_BOOTSTRAP_ARCH="amd64"
;;
aarch64)
GO_BOOTSTRAP_ARCH="arm64"
;;
*)
echo -e "${RED} ERROR:${NC} Unsupported architecture for Go bootstrap: $ARCH"
exit 1
;;
esac
echo -e "${BLUE} LOG:${YELLOW} Setting up Go and GVM in ${GVM_ROOT}...${NC}"
if [ ! -d "$GVM_ROOT/scripts" ]; then
echo -e "${BLUE} LOG:${YELLOW} Cloning GVM...${NC}"
git clone https://github.com/moovweb/gvm.git "$GVM_ROOT"
rm -rf "$GVM_ROOT/.git"
echo -e "${BLUE} LOG:${YELLOW} Configuring GVM scripts...${NC}"
cp "$GVM_ROOT/scripts/gvm-default" "$GVM_ROOT/scripts/gvm"
sed -i "s|^GVM_ROOT=.*|GVM_ROOT=\"$GVM_ROOT\"|" "$GVM_ROOT/scripts/gvm"
echo -e "${GREEN} SUCCESS:${NC} GVM cloned and configured."
else
echo -e "${GREEN} SKIP:${NC} GVM already installed."
fi
if [ ! -d "$BOOTSTRAP_GO" ]; then
echo -e "${BLUE} LOG:${YELLOW} Downloading Bootstrap Go...${NC}"
mkdir -p "$BOOTSTRAP_GO"
wget -q "https://go.dev/dl/go1.20.5.linux-${GO_BOOTSTRAP_ARCH}.tar.gz" -O /tmp/go-bootstrap.tar.gz
tar -C "$BOOTSTRAP_GO" -xzf /tmp/go-bootstrap.tar.gz --strip-components=1
rm /tmp/go-bootstrap.tar.gz
echo -e "${GREEN} SUCCESS:${NC} Bootstrap Go installed."
else
echo -e "${GREEN} SKIP:${NC} Bootstrap Go already exists."
fi
export PATH="$BOOTSTRAP_GO/bin:$PATH"
export GOROOT_BOOTSTRAP="$BOOTSTRAP_GO"
set +e
source "$GVM_ROOT/scripts/gvm"
set -e
if ! command -v gvm &> /dev/null; then
echo -e "${RED} ERROR:${NC} GVM failed to load."
exit 1
fi
TARGET_VER="go1.24.11"
if ! gvm list | grep -q "$TARGET_VER"; then
echo -e "${BLUE} LOG:${YELLOW} Installing ${TARGET_VER}...${NC}"
gvm install "$TARGET_VER" --prefer-binary
fi
gvm use "$TARGET_VER" --default
echo -e "${GREEN} SUCCESS:${NC} Go setup completed."

131
Scripts/lib/distro.sh Normal file
View File

@@ -0,0 +1,131 @@
#!/bin/bash
# Shared distro helpers for install scripts.
SCRIPT_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="${REPO_ROOT:-$(dirname "$(dirname "$SCRIPT_LIB_DIR")")}"
REBOOT_MARKER="${REBOOT_MARKER:-$REPO_ROOT/.setup-reboot-required}"
if [ -f /etc/os-release ]; then
. /etc/os-release
OS="${ID}"
OS_LIKE="${ID_LIKE:-}"
else
echo "Unable to detect operating system: /etc/os-release not found."
return 1 2>/dev/null || exit 1
fi
is_arch_family() {
[[ "$OS" == "arch" || "$OS" == "manjaro" || " $OS_LIKE " == *" arch "* ]]
}
is_debian_family() {
[[ "$OS" == "ubuntu" || "$OS" == "debian" || " $OS_LIKE " == *" debian "* ]]
}
is_fedora_family() {
[[ "$OS" == "fedora" || "$OS" == "bazzite" || " $OS_LIKE " == *" fedora "* || " $OS_LIKE " == *" rhel "* ]]
}
is_atomic_fedora() {
is_fedora_family && command -v rpm-ostree >/dev/null 2>&1 && [ -f /run/ostree-booted ]
}
mark_reboot_required() {
touch "$REBOOT_MARKER"
echo ""
echo " REBOOT REQUIRED: Fedora atomic package layering finished."
echo " Please reboot, then rerun the same make target."
echo ""
}
get_atomic_requested_packages() {
local python_bin=""
local candidate
for candidate in python3 python /usr/libexec/platform-python; do
if command -v "$candidate" >/dev/null 2>&1; then
python_bin="$candidate"
break
fi
done
if [ -z "$python_bin" ]; then
return 0
fi
rpm-ostree status --json 2>/dev/null | "$python_bin" -c '
import json, sys
try:
data = json.load(sys.stdin)
except Exception:
raise SystemExit(0)
for deployment in data.get("deployments", []):
if deployment.get("booted"):
for key in ("requested-packages", "requested-local-packages"):
for package in deployment.get(key, []):
if package:
print(package)
break
'
}
install_packages() {
local packages=("$@")
if [ ${#packages[@]} -eq 0 ]; then
return 0
fi
if is_arch_family; then
sudo pacman -S --noconfirm --needed "${packages[@]}"
return 0
fi
if is_debian_family; then
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "${packages[@]}"
return 0
fi
if is_fedora_family; then
if is_atomic_fedora; then
local requested_pkg
local missing_packages=()
declare -A requested_packages=()
local pkg
while IFS= read -r requested_pkg; do
[ -n "$requested_pkg" ] && requested_packages["$requested_pkg"]=1
done < <(get_atomic_requested_packages)
for pkg in "${packages[@]}"; do
if rpm -q "$pkg" >/dev/null 2>&1; then
continue
fi
if [ -n "${requested_packages[$pkg]:-}" ]; then
continue
fi
missing_packages+=("$pkg")
done
if [ ${#missing_packages[@]} -eq 0 ]; then
return 0
fi
sudo rpm-ostree install "${missing_packages[@]}"
mark_reboot_required
return 42
fi
sudo dnf install -y "${packages[@]}"
return 0
fi
echo "Unsupported OS: $OS"
return 1
}

39
Scripts/node.sh Normal file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# Path: Scripts/node.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
NC='\033[0m'
export NVM_DIR="$HOME/.programming/node"
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
echo -e "${BLUE} LOG:${YELLOW} Setting up Node.js (NVM) in ${NVM_DIR}...${NC}"
if [ ! -d "$NVM_DIR" ]; then
mkdir -p "$NVM_DIR"
echo -e "${BLUE} LOG:${YELLOW} Installing NVM to custom path...${NC}"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | PROFILE=/dev/null bash
fi
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
if command -v nvm &> /dev/null; then
echo -e "${BLUE} LOG:${YELLOW} Installing Node LTS...${NC}"
nvm install --lts --no-progress
nvm use --lts
echo -e "${BLUE} LOG:${YELLOW} Enabling Corepack (pnpm/yarn)...${NC}"
corepack enable
echo -e "${GREEN} LOG: Node setup complete. $(node -v)${NC}"
echo -e "${GREEN} LOG: Package Managers: pnpm $(pnpm -v), yarn $(yarn -v)${NC}"
else
echo -e "${RED} ERROR: NVM failed to load from $NVM_DIR${NC}"
exit 1
fi

118
Scripts/provision.sh Normal file
View File

@@ -0,0 +1,118 @@
#!/bin/bash
# Path: Scripts/provision.sh
set -e
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
RED='\033[1;31m'
NC='\033[0m'
DEFAULT_USER="mangopig"
DEFAULT_UID="1000"
DEFAULT_GID="1000"
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$SCRIPT_DIR/lib/distro.sh"
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Please run this script as root.${NC}"
exit 1
fi
echo -e "${YELLOW}LOG: Detecting OS...${NC}"
echo -e "${YELLOW}LOG: Determining primary IPv4 address...${NC}"
if command -v hostname >/dev/null 2>&1 && hostname -I >/dev/null 2>&1; then
IP=$(hostname -I | awk '{print $1}')
elif command -v ip >/dev/null 2>&1; then
IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/ {print $NF; exit}')
elif command -v ifconfig >/dev/null 2>&1; then
IP=$(ifconfig | awk '/inet /{print $2}' | grep -v '^127' | head -n1)
else
IP="127.0.0.1"
fi
echo -e "${YELLOW}LOG: Updating system and installing base tools...${NC}"
if is_arch_family; then
pacman -Sy --noconfirm git make curl zsh sudo
SUDO_GROUP="wheel"
elif is_debian_family; then
apt-get update
apt-get install -y git make curl sudo zsh
SUDO_GROUP="sudo"
elif is_fedora_family; then
if is_atomic_fedora; then
echo -e "${RED}Provisioning on Fedora atomic systems is not supported by this root script.${NC}"
echo -e "${RED}Use the normal user workflow and run make setup after login instead.${NC}"
exit 1
fi
dnf install -y git make curl sudo zsh
SUDO_GROUP="wheel"
else
echo -e "${RED}Unsupported OS: $OS${NC}"
exit 1
fi
ZSH_PATH="$(command -v zsh)"
if [ -t 0 ]; then
echo -e "${GREEN}---------------------------------------${NC}"
echo -e "${GREEN} USER PROVISIONING WIZARD ${NC}"
echo -e "${GREEN}---------------------------------------${NC}"
read -p "Enter Username (default: $DEFAULT_USER): " INPUT_USER
USERNAME=${INPUT_USER:-$DEFAULT_USER}
read -p "Enter UID (default: $DEFAULT_UID): " INPUT_UID
USER_UID=${INPUT_UID:-$DEFAULT_UID}
read -p "Enter GID (default: $DEFAULT_GID): " INPUT_GID
USER_GID=${INPUT_GID:-$DEFAULT_GID}
else
echo -e "${YELLOW}LOG: Non-interactive session detected. Using defaults.${NC}"
USERNAME=${USERNAME:-$DEFAULT_USER}
USER_UID=${USER_UID:-$DEFAULT_UID}
USER_GID=${USER_GID:-$DEFAULT_GID}
fi
if getent group "$USER_GID" >/dev/null; then
echo -e "${YELLOW}LOG: Group with GID $USER_GID already exists. Using it.${NC}"
else
echo -e "${YELLOW}LOG: Creating group $USERNAME with GID $USER_GID...${NC}"
groupadd -g "$USER_GID" "$USERNAME"
fi
if id "$USERNAME" &>/dev/null; then
echo -e "${YELLOW}LOG: User $USERNAME already exists. Skipping creation.${NC}"
else
echo -e "${YELLOW}LOG: Creating user $USERNAME...${NC}"
useradd -m -u "$USER_UID" -g "$USER_GID" -G "$SUDO_GROUP" -s "$ZSH_PATH" "$USERNAME"
echo -e "${GREEN}LOG: Setting password for $USERNAME...${NC}"
if [ -t 0 ]; then
echo -e "${GREEN}LOG: Setting password for $USERNAME...${NC}"
passwd "$USERNAME"
else
echo -e "${YELLOW}LOG: Non-interactive: Setting password to '$USERNAME'...${NC}"
echo "$USERNAME:$USERNAME" | chpasswd
fi
fi
echo -e "${YELLOW}LOG: Configuring passwordless sudo...${NC}"
echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/90-$USERNAME"
chmod 0440 "/etc/sudoers.d/90-$USERNAME"
if is_arch_family || is_fedora_family; then
sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers
fi
echo -e "${YELLOW}LOG: Cloning dotfiles for $USERNAME...${NC}"
sudo -u "$USERNAME" git clone https://git.mangopig.tech/MangoPig/Dot-Zsh.git "/home/$USERNAME/Config/Dot-Zsh"
echo -e "${GREEN}✅ Server Provisioned!"
echo -e "${GREEN} sudo su - $USERNAME${NC} OR logout and SSH back in as $USERNAME@$IP.${NC}"
echo -e "${GREEN} Then run cd ~/Config/Dot-Zsh && make setup ${NC}"

78
Scripts/python.sh Normal file
View File

@@ -0,0 +1,78 @@
#!/bin/bash
# Path: Scripts/python.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
RED='\033[1;31m'
NC='\033[0m'
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$SCRIPT_DIR/lib/distro.sh"
# Install Build Dependencies
if is_arch_family; then
echo -e "${BLUE} LOG:${YELLOW} Installing Arch build dependencies...${NC}"
PYTHON_BUILD_DEPS=(base-devel openssl zlib xz tk libffi bzip2 git)
elif is_debian_family; then
echo -e "${BLUE} LOG:${YELLOW} Installing Debian build dependencies...${NC}"
PYTHON_BUILD_DEPS=(make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev git)
elif is_fedora_family; then
echo -e "${BLUE} LOG:${YELLOW} Installing Fedora build dependencies...${NC}"
PYTHON_BUILD_DEPS=(make gcc gcc-c++ patch zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz xz-devel ncurses-devel findutils git)
else
echo -e "${RED} ERROR:${NC} Unsupported OS: $OS"
exit 1
fi
install_status=0
install_packages "${PYTHON_BUILD_DEPS[@]}" || install_status=$?
if [ "$install_status" -eq 42 ]; then
exit 0
elif [ "$install_status" -ne 0 ]; then
exit "$install_status"
fi
# Define the Black Box Location
export PYENV_ROOT="$HOME/.programming/python/pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
echo -e "${BLUE} LOG:${YELLOW} Setting up Pyenv in ${PYENV_ROOT}...${NC}"
# Install Pyenv
if [ ! -d "$PYENV_ROOT" ]; then
echo -e "${BLUE} LOG:${YELLOW} Cloning Pyenv...${NC}"
git clone https://github.com/pyenv/pyenv.git "$PYENV_ROOT"
echo -e "${BLUE} LOG:${YELLOW} Compiling Pyenv dynamic bash extension...${NC}"
cd "$PYENV_ROOT" && src/configure && make -C src
echo -e "${GREEN} SUCCESS:${NC} Pyenv installed."
else
echo -e "${GREEN} SKIP:${NC} Pyenv already installed."
fi
# Initialize Pyenv for this script session
eval "$(pyenv init -)"
# Install Miniforge (The Community Standard)
TARGET_VER="miniforge3-latest"
if ! pyenv versions | grep -q "$TARGET_VER"; then
echo -e "${BLUE} LOG:${YELLOW} Installing ${TARGET_VER}...${NC}"
echo -e "${YELLOW} NOTE: This avoids Anaconda licensing issues and uses conda-forge default.${NC}"
pyenv install "$TARGET_VER"
echo -e "${GREEN} SUCCESS:${NC} Miniforge installed."
else
echo -e "${GREEN} SKIP:${NC} Miniforge already installed."
fi
# Set Global Default
pyenv global "$TARGET_VER"
echo -e "${GREEN} SUCCESS:${NC} Python setup completed. Default is now Miniforge (Conda)."

132
Scripts/r.sh Normal file
View File

@@ -0,0 +1,132 @@
#!/bin/bash
# Path: Scripts/r.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
RED='\033[1;31m'
NC='\033[0m'
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$SCRIPT_DIR/lib/distro.sh"
PROG_DIR="$HOME/.programming"
R_ROOT="$PROG_DIR/r"
R_BIN_DIR="$R_ROOT/bin"
R_LIB_DIR="$R_ROOT/library"
mkdir -p "$R_BIN_DIR" "$R_LIB_DIR"
write_r_wrapper() {
local target_name="$1"
local command_body="$2"
cat > "$R_BIN_DIR/$target_name" <<EOF
#!/bin/bash
set -e
export R_LIBS_USER="$R_LIB_DIR"
$command_body "\$@"
EOF
chmod +x "$R_BIN_DIR/$target_name"
}
echo -e "${BLUE} LOG:${YELLOW} Detecting R installation strategy for $OS...${NC}"
if is_arch_family; then
echo -e "${BLUE} LOG:${YELLOW} Arch Linux detected. Using system R (Pacman)...${NC}"
# Install R + Build Tools
install_status=0
install_packages r gcc-fortran curl tar || install_status=$?
if [ "$install_status" -eq 42 ]; then
exit 0
elif [ "$install_status" -ne 0 ]; then
exit "$install_status"
fi
echo -e "${GREEN} SUCCESS:${NC} R installed via Pacman."
R_BIN="R"
write_r_wrapper "R" 'exec "$(command -v R)"'
write_r_wrapper "Rscript" 'exec "$(command -v Rscript)"'
elif is_debian_family; then
echo -e "${BLUE} LOG:${YELLOW} Installing Debian build dependencies...${NC}"
install_status=0
install_packages gfortran curl tar ca-certificates || install_status=$?
if [ "$install_status" -eq 42 ]; then
exit 0
elif [ "$install_status" -ne 0 ]; then
exit "$install_status"
fi
if ! command -v rig >/dev/null 2>&1; then
echo -e "${BLUE} LOG:${YELLOW} Installing Rig for Debian/Ubuntu...${NC}"
DEB_ARCH=$(dpkg --print-architecture)
case "$DEB_ARCH" in
amd64|arm64)
;;
*)
echo -e "${RED} ERROR:${NC} Unsupported Debian architecture for Rig: $DEB_ARCH"
exit 1
;;
esac
LATEST_REL_DATA=$(curl -fsSL https://api.github.com/repos/r-lib/rig/releases/latest)
RIG_URL=$(echo "$LATEST_REL_DATA" | grep -o "https://[^\"]*r-rig_[^\"]*_${DEB_ARCH}\.deb" | head -n 1)
if [ -z "$RIG_URL" ]; then
echo -e "${RED} ERROR:${NC} Could not find Rig .deb download URL for architecture: $DEB_ARCH"
exit 1
fi
TEMP_DEB=$(mktemp --suffix=.deb)
curl -fLsS "$RIG_URL" -o "$TEMP_DEB"
sudo apt-get install -y "$TEMP_DEB"
rm -f "$TEMP_DEB"
fi
TARGET_VER="release"
if ! rig list | grep -q "release"; then
echo -e "${BLUE} LOG:${YELLOW} Installing R (${TARGET_VER})...${NC}"
sudo rig add "$TARGET_VER" --without-cran-mirror
sudo rig default "$TARGET_VER"
fi
R_BIN="rig run"
write_r_wrapper "R" 'exec rig run'
write_r_wrapper "Rscript" 'exec rig run Rscript'
elif is_fedora_family; then
echo -e "${BLUE} LOG:${YELLOW} Fedora detected. Using system R...${NC}"
install_status=0
install_packages R-core gcc-gfortran curl tar || install_status=$?
if [ "$install_status" -eq 42 ]; then
exit 0
elif [ "$install_status" -ne 0 ]; then
exit "$install_status"
fi
R_BIN="R"
write_r_wrapper "R" 'exec "$(command -v R)"'
write_r_wrapper "Rscript" 'exec "$(command -v Rscript)"'
else
echo -e "${RED} ERROR:${NC} Unsupported OS: $OS"
exit 1
fi
export R_LIBS_USER="$R_LIB_DIR"
echo -e "${BLUE} LOG:${YELLOW} Installing 'renv' package in the user R library...${NC}"
$R_BIN -e 'user_lib <- path.expand(Sys.getenv("R_LIBS_USER", unset = "~/.programming/r/library")); dir.create(user_lib, recursive = TRUE, showWarnings = FALSE); .libPaths(c(user_lib, .libPaths())); if (!require("renv", quietly=TRUE)) install.packages("renv", lib = user_lib, repos = "https://cloud.r-project.org")'
echo -e "${GREEN} SUCCESS:${NC} R setup completed."

41
Scripts/rust.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Path: Scripts/rust.sh
set -e
BLUE='\033[1;34m'
YELLOW='\033[1;33m'
GREEN='\033[1;32m'
NC='\033[0m'
export RUSTUP_HOME="$HOME/.programming/rust/multirust"
export CARGO_HOME="$HOME/.programming/rust/cargo"
echo -e "${BLUE} LOG:${YELLOW} Setting up Rust (Rustup) in ${CARGO_HOME}...${NC}"
if [ ! -f "$CARGO_HOME/bin/rustup" ]; then
echo -e "${BLUE} LOG:${YELLOW} Installing Rustup to custom path...${NC}"
mkdir -p "$RUSTUP_HOME" "$CARGO_HOME"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
else
echo -e "${GREEN} LOG: Rustup already installed in custom path.${NC}"
fi
if [ -f "$CARGO_HOME/env" ]; then
source "$CARGO_HOME/env"
fi
if command -v cargo &> /dev/null; then
echo -e "${BLUE} LOG:${YELLOW} Updating toolchain...${NC}"
rustup default stable
rustup update
echo -e "${BLUE} LOG:${YELLOW} Installing rust-src for 'Go to Definition'...${NC}"
rustup component add rust-src
echo -e "${GREEN} LOG: Rust setup complete. $(cargo --version)${NC}"
else
echo -e "${RED} ERROR: Cargo not found in PATH. Check CARGO_HOME.${NC}"
exit 1
fi

68
Scripts/setup.sh Normal file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
REBOOT_MARKER="$REPO_ROOT/.setup-reboot-required"
REPO_SECRET_FILE="$REPO_ROOT/Zsh/.zsh_secrets"
HOME_SECRET_FILE="$HOME/.zsh_secrets"
handle_reboot_marker() {
if [ -f "$REBOOT_MARKER" ]; then
rm -f "$REBOOT_MARKER"
echo "Package layering finished. Reboot, then rerun the same command."
exit 0
fi
}
sync_repo_managed_secret_file() {
mkdir -p "$(dirname "$REPO_SECRET_FILE")"
if [ -L "$HOME_SECRET_FILE" ]; then
return 0
fi
if [ -f "$HOME_SECRET_FILE" ]; then
if [ ! -f "$REPO_SECRET_FILE" ] || [ ! -s "$REPO_SECRET_FILE" ]; then
mv "$HOME_SECRET_FILE" "$REPO_SECRET_FILE"
return 0
fi
if cmp -s "$HOME_SECRET_FILE" "$REPO_SECRET_FILE"; then
rm -f "$HOME_SECRET_FILE"
return 0
fi
echo "Conflict: both $HOME_SECRET_FILE and $REPO_SECRET_FILE exist with different contents."
echo "Please merge them manually, then rerun the command."
exit 1
fi
touch "$REPO_SECRET_FILE"
}
main() {
rm -f "$REBOOT_MARKER"
bash "$SCRIPT_DIR/base.sh"
handle_reboot_marker
bash "$SCRIPT_DIR/node.sh"
bash "$SCRIPT_DIR/go.sh"
bash "$SCRIPT_DIR/rust.sh"
bash "$SCRIPT_DIR/python.sh"
handle_reboot_marker
bash "$SCRIPT_DIR/r.sh"
handle_reboot_marker
sync_repo_managed_secret_file
stow --dir="$REPO_ROOT" -D Zsh --target="$HOME" 2>/dev/null || true
stow --dir="$REPO_ROOT" Zsh --target="$HOME"
echo "Full setup completed."
}
main "$@"