Last active
March 12, 2026 22:27
-
-
Save EdwardKerckhof/0f0bee87cfc82f0073e1dd7f63c3c437 to your computer and use it in GitHub Desktop.
Homelab CLI installer
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
| # Install the homelab CLI from the private GitHub repo. | |
| # | |
| # Usage (remote): | |
| # gh api repos/EdwardKerckhof/homelab-v2/contents/cli/install.ps1 -H "Accept: application/vnd.github.raw" | iex | |
| # | |
| # Usage (local, from a cloned repo): | |
| # .\cli\install.ps1 | |
| # | |
| # On a fresh Windows machine, this script auto-installs git, python, and pipx | |
| # via winget before installing the CLI. | |
| param( | |
| [string]$Version = "main" | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| $Repo = "EdwardKerckhof/homelab-v2" | |
| $HttpsUrl = "git+https://github.com/$Repo.git@$Version#subdirectory=cli" | |
| $SshUrl = "git+ssh://git@github.com/$Repo.git@$Version#subdirectory=cli" | |
| $PkgSpec = "homelab-cli @ $HttpsUrl" | |
| $PkgSpecSsh = "homelab-cli @ $SshUrl" | |
| # ── Helpers ─────────────────────────────────────────────────────────────────── | |
| function Has-Winget { | |
| return [bool](Get-Command winget -ErrorAction SilentlyContinue) | |
| } | |
| function Winget-Install { | |
| param([string]$PackageId, [string]$Name) | |
| if (-not (Has-Winget)) { | |
| throw "$Name is required. Install it manually or install winget first." | |
| } | |
| Write-Host "==> Installing $Name via winget..." | |
| winget install --id $PackageId --accept-source-agreements --accept-package-agreements --silent | |
| # Refresh PATH for current session | |
| $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine") | |
| $userPath = [Environment]::GetEnvironmentVariable("Path", "User") | |
| $env:Path = "$machinePath;$userPath" | |
| } | |
| # ── Ensure git ──────────────────────────────────────────────────────────────── | |
| function Ensure-Git { | |
| if (Get-Command git -ErrorAction SilentlyContinue) { return } | |
| Winget-Install "Git.Git" "Git" | |
| if (-not (Get-Command git -ErrorAction SilentlyContinue)) { | |
| throw "Git installed but not on PATH. Restart your terminal and re-run." | |
| } | |
| } | |
| # ── Ensure Python 3.10+ ────────────────────────────────────────────────────── | |
| function Find-Python { | |
| foreach ($cmd in @("python3", "python", "py")) { | |
| if (Get-Command $cmd -ErrorAction SilentlyContinue) { | |
| $ver = & $cmd -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null | |
| if ($ver) { | |
| $parts = $ver -split '\.' | |
| if ([int]$parts[0] -ge 3 -and [int]$parts[1] -ge 10) { | |
| return $cmd | |
| } | |
| } | |
| } | |
| } | |
| return $null | |
| } | |
| function Ensure-Python { | |
| $script:Python = Find-Python | |
| if ($script:Python) { return } | |
| Winget-Install "Python.Python.3.13" "Python 3.13" | |
| $script:Python = Find-Python | |
| if (-not $script:Python) { | |
| throw "Python 3.10+ installed but not on PATH. Restart your terminal and re-run." | |
| } | |
| } | |
| # ── Ensure GitHub CLI ───────────────────────────────────────────────────────── | |
| function Ensure-Gh { | |
| if (Get-Command gh -ErrorAction SilentlyContinue) { return } | |
| Write-Host "==> GitHub CLI (gh) is required to access the private repo." | |
| Winget-Install "GitHub.cli" "GitHub CLI" | |
| if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { | |
| throw "gh installed but not on PATH. Restart your terminal and re-run." | |
| } | |
| } | |
| function Ensure-GhAuth { | |
| $authStatus = gh auth status 2>&1 | |
| if ($LASTEXITCODE -eq 0) { return } | |
| Write-Host "" | |
| Write-Host " You need to log in to GitHub so the installer can access the private repo." | |
| Write-Host "" | |
| gh auth login | |
| $authCheck = gh auth status 2>&1 | |
| if ($LASTEXITCODE -ne 0) { throw "GitHub authentication failed." } | |
| } | |
| # ── Ensure pipx ────────────────────────────────────────────────────────────── | |
| function Ensure-Pipx { | |
| if (Get-Command pipx -ErrorAction SilentlyContinue) { return } | |
| Write-Host "==> Installing pipx..." | |
| & $script:Python -m pip install --user pipx | |
| & $script:Python -m pipx ensurepath | |
| # Refresh PATH for current session | |
| $userPath = [Environment]::GetEnvironmentVariable("Path", "User") | |
| $env:Path = "$userPath;$env:Path" | |
| if (-not (Get-Command pipx -ErrorAction SilentlyContinue)) { | |
| throw "pipx installed but not on PATH. Restart your terminal and re-run." | |
| } | |
| } | |
| # ── Pre-flight ──────────────────────────────────────────────────────────────── | |
| Write-Host "==> Homelab CLI installer" | |
| Ensure-Git | |
| Ensure-Python | |
| Write-Host " Python: $Python ($(& $Python --version 2>&1))" | |
| Ensure-Gh | |
| Ensure-GhAuth | |
| # Configure git to use gh for HTTPS auth (pipx install clones via git) | |
| gh auth setup-git | |
| # ── Install or upgrade ──────────────────────────────────────────────────────── | |
| Ensure-Pipx | |
| $pipxList = pipx list --short 2>$null | |
| if ($pipxList -match '(?m)^homelab-cli ') { | |
| Write-Host "==> homelab-cli already installed - upgrading..." | |
| pipx upgrade homelab-cli | |
| } else { | |
| Write-Host "==> Installing homelab CLI with pipx..." | |
| try { | |
| pipx install $PkgSpec | |
| } catch { | |
| Write-Host "==> HTTPS install failed - trying SSH..." | |
| pipx install $PkgSpecSsh | |
| } | |
| } | |
| Write-Host "" | |
| Write-Host "Done! Run 'homelab' to get started." |
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
| #!/usr/bin/env bash | |
| # Install the homelab CLI from the private GitHub repo. | |
| # | |
| # Usage (remote — pick one): | |
| # bash <(gh api repos/EdwardKerckhof/homelab-v2/contents/cli/install.sh -H "Accept: application/vnd.github.raw") | |
| # curl -fsSL -H "Authorization: token $GITHUB_TOKEN" https://raw.githubusercontent.com/EdwardKerckhof/homelab-v2/main/cli/install.sh | bash | |
| # | |
| # Usage (local, from a cloned repo): | |
| # bash cli/install.sh | |
| # | |
| # On a fresh Ubuntu/Debian or macOS, this script auto-installs git, python3, | |
| # and pipx before installing the CLI. | |
| set -euo pipefail | |
| # ── Parse arguments ─────────────────────────────────────────────────────────── | |
| VERSION="main" | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --version) VERSION="$2"; shift 2 ;; | |
| *) echo "Unknown option: $1" >&2; exit 1 ;; | |
| esac | |
| done | |
| REPO="EdwardKerckhof/homelab-v2" | |
| HTTPS_URL="git+https://github.com/${REPO}.git@${VERSION}#subdirectory=cli" | |
| SSH_URL="git+ssh://git@github.com/${REPO}.git@${VERSION}#subdirectory=cli" | |
| PKG_SPEC="homelab-cli @ ${HTTPS_URL}" | |
| PKG_SPEC_SSH="homelab-cli @ ${SSH_URL}" | |
| # ── Helpers ─────────────────────────────────────────────────────────────────── | |
| die() { echo "Error: $*" >&2; exit 1; } | |
| # Detect package manager | |
| detect_pkg_manager() { | |
| if command -v apt-get &>/dev/null; then | |
| PKG_MGR="apt" | |
| elif command -v dnf &>/dev/null; then | |
| PKG_MGR="dnf" | |
| elif command -v brew &>/dev/null; then | |
| PKG_MGR="brew" | |
| else | |
| PKG_MGR="" | |
| fi | |
| } | |
| # Run with sudo if needed (skips if already root) | |
| maybe_sudo() { | |
| if [[ $EUID -eq 0 ]]; then | |
| "$@" | |
| elif command -v sudo &>/dev/null; then | |
| sudo "$@" | |
| else | |
| die "Need root privileges to install packages. Run as root or install sudo." | |
| fi | |
| } | |
| pkg_install() { | |
| local pkg="$1" | |
| echo "==> Installing $pkg..." | |
| case "$PKG_MGR" in | |
| apt) maybe_sudo apt-get update -qq && maybe_sudo apt-get install -y -qq "$pkg" ;; | |
| dnf) maybe_sudo dnf install -y -q "$pkg" ;; | |
| brew) brew install "$pkg" ;; | |
| *) die "No supported package manager found. Install $pkg manually." ;; | |
| esac | |
| } | |
| # ── Ensure git ──────────────────────────────────────────────────────────────── | |
| ensure_git() { | |
| if command -v git &>/dev/null; then return; fi | |
| pkg_install git | |
| command -v git &>/dev/null || die "Failed to install git." | |
| } | |
| # ── Ensure Python 3.10+ ────────────────────────────────────────────────────── | |
| ensure_python() { | |
| for cmd in python3 python; do | |
| if command -v "$cmd" &>/dev/null; then | |
| local ver | |
| ver=$("$cmd" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') | |
| local major=${ver%%.*} minor=${ver##*.} | |
| if (( major >= 3 && minor >= 10 )); then | |
| PYTHON="$cmd" | |
| return | |
| fi | |
| fi | |
| done | |
| # Auto-install | |
| case "$PKG_MGR" in | |
| apt) pkg_install python3 && pkg_install python3-venv ;; | |
| dnf) pkg_install python3 ;; | |
| brew) pkg_install python@3 ;; | |
| *) die "Python 3.10+ is required. Install it manually." ;; | |
| esac | |
| # Re-check after install | |
| for cmd in python3 python; do | |
| if command -v "$cmd" &>/dev/null; then | |
| local ver | |
| ver=$("$cmd" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') | |
| local major=${ver%%.*} minor=${ver##*.} | |
| if (( major >= 3 && minor >= 10 )); then | |
| PYTHON="$cmd" | |
| return | |
| fi | |
| fi | |
| done | |
| die "Python 3.10+ is required but could not be installed." | |
| } | |
| # ── Ensure GitHub CLI ───────────────────────────────────────────────────────── | |
| ensure_gh() { | |
| if command -v gh &>/dev/null; then return; fi | |
| echo "==> GitHub CLI (gh) is required to access the private repo." | |
| case "$PKG_MGR" in | |
| apt) | |
| # Official gh repo for Debian/Ubuntu | |
| maybe_sudo mkdir -p /etc/apt/keyrings | |
| curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ | |
| | maybe_sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null | |
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ | |
| | maybe_sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null | |
| maybe_sudo apt-get update -qq && maybe_sudo apt-get install -y -qq gh | |
| ;; | |
| dnf) pkg_install gh ;; | |
| brew) brew install gh ;; | |
| *) die "Install the GitHub CLI manually: https://cli.github.com" ;; | |
| esac | |
| command -v gh &>/dev/null || die "Failed to install gh." | |
| } | |
| ensure_gh_auth() { | |
| if gh auth status &>/dev/null; then return; fi | |
| echo "" | |
| echo " You need to log in to GitHub so the installer can access the private repo." | |
| echo "" | |
| gh auth login | |
| gh auth status &>/dev/null || die "GitHub authentication failed." | |
| } | |
| # ── Ensure pipx ────────────────────────────────────────────────────────────── | |
| ensure_pipx() { | |
| if command -v pipx &>/dev/null; then return; fi | |
| echo "==> Installing pipx..." | |
| # On Debian/Ubuntu, prefer the system package (avoids PEP 668) | |
| if [[ "$PKG_MGR" == "apt" ]]; then | |
| pkg_install pipx | |
| elif [[ "$PKG_MGR" == "dnf" ]]; then | |
| pkg_install pipx | |
| elif [[ "$PKG_MGR" == "brew" ]]; then | |
| brew install pipx | |
| else | |
| "$PYTHON" -m pip install --user pipx --break-system-packages 2>/dev/null \ | |
| || "$PYTHON" -m pip install --user pipx \ | |
| || die "Failed to install pipx." | |
| fi | |
| "$PYTHON" -m pipx ensurepath 2>/dev/null || true | |
| export PATH="$HOME/.local/bin:$PATH" | |
| command -v pipx &>/dev/null || die "pipx installed but not on PATH. Restart your terminal and re-run." | |
| } | |
| # ── Pre-flight ──────────────────────────────────────────────────────────────── | |
| echo "==> Homelab CLI installer" | |
| detect_pkg_manager | |
| ensure_git | |
| ensure_python | |
| echo " Python: $PYTHON ($($PYTHON --version 2>&1))" | |
| ensure_gh | |
| ensure_gh_auth | |
| # Configure git to use gh for HTTPS auth (pipx install clones via git) | |
| gh auth setup-git | |
| # ── Install or upgrade ──────────────────────────────────────────────────────── | |
| ensure_pipx | |
| if pipx list --short 2>/dev/null | grep -q '^homelab-cli '; then | |
| echo "==> homelab-cli already installed — upgrading..." | |
| pipx upgrade homelab-cli || true | |
| else | |
| echo "==> Installing homelab CLI with pipx..." | |
| if ! pipx install "$PKG_SPEC"; then | |
| echo "==> HTTPS install failed — trying SSH..." | |
| pipx install "$PKG_SPEC_SSH" | |
| fi | |
| fi | |
| echo "" | |
| echo "Done! Run 'homelab' to get started." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment