Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
280094e
feat: implement ISO management functionality with disk classification…
bartrosa Jul 15, 2026
fd836ae
docs: add package comment for baremetal package to clarify purpose
bartrosa Jul 15, 2026
47ffc3c
feat: implement bootstrap functionality for essential tools and packages
bartrosa Jul 15, 2026
7eba54e
refactor: clean up imports and enhance CLI command registration
bartrosa Jul 15, 2026
64ed1b1
feat: add bootstrap essentials and ISO management commands
bartrosa Jul 15, 2026
c748bf7
refactor: standardize struct field formatting in Session type
bartrosa Jul 15, 2026
0ec23d9
feat: introduce ExitError type for enhanced error handling
bartrosa Jul 15, 2026
482fc48
feat: add exec package for running external commands
bartrosa Jul 15, 2026
30da189
refactor: standardize struct field formatting in Runner type
bartrosa Jul 15, 2026
a1eef6f
docs: add package comment to heic.go for clarity on media conversion …
bartrosa Jul 15, 2026
431a0bb
feat: implement APT and RPMOstree package managers with detection and…
bartrosa Jul 15, 2026
6149c59
refactor: improve package detection logic in platform package
bartrosa Jul 15, 2026
69fca2c
refactor: improve resource cleanup in PostgreSQL apply functions
bartrosa Jul 15, 2026
983b29e
docs: add comments to DeployMode constants for clarity on deployment …
bartrosa Jul 15, 2026
4705f45
refactor: standardize variable naming and improve resource handling
bartrosa Jul 15, 2026
49b334a
feat: implement updater package for GitHub release management
bartrosa Jul 15, 2026
ec1d6cb
refactor: standardize formatting of knownLangs map in mise.go
bartrosa Jul 15, 2026
e6a1ea9
docs: add comments to Warn and Fail functions for clarity on logging …
bartrosa Jul 15, 2026
779d4fc
fix: enhance error handling in main function to support custom exit e…
bartrosa Jul 15, 2026
65486ab
feat: implement install and uninstall scripts for homelab-cli
bartrosa Jul 15, 2026
bc425fa
chore: update golangci-lint configuration and enhance goreleaser sett…
bartrosa Jul 15, 2026
683bd06
feat: enhance homelab-cli with new features and installation scripts
bartrosa Jul 15, 2026
2d92832
chore: update .gitignore to include additional files and directories
bartrosa Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,18 @@ profile.cov

# Build artifacts
/bin/
lab

# GoReleaser / local release output
/dist/
*.deb
*.rpm
homelab-cli_*.tar.gz
checksums.txt
homelab-cli_*_checksums.txt

# Local dev toolchain (e.g. Go SDK for CI without system install)
.tools/

# Dependency directories (remove the comment below to include it)
# vendor/
Expand All @@ -30,6 +41,11 @@ go.work.sum

# env file
.env
.env.*

# OS
.DS_Store
Thumbs.db

# Editor/IDE
# .idea/
Expand Down
10 changes: 10 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ linters:
- gosec

settings:
errcheck:
exclude-functions:
- fmt.Fprintf
- fmt.Fprint
- fmt.Fprintln
gosec:
excludes:
- G204
- G301
- G304
- G306
- G302
- G110

issues:
max-same-issues: 50
27 changes: 26 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,41 @@ archives:
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"

checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
name_template: "checksums.txt"
algorithm: sha256

nfpms:
- id: lab
builds:
- lab
maintainer: "Your Name <you@example.com>"
description: "Homelab automation CLI from bare metal to GPU-served LLMs"
license: Apache-2.0
bindir: /usr/bin
section: utils
homepage: "https://github.com/bartrosa/homelab-cli"
recommends:
- mise
- podman
- distrobox
formats:
- deb
- rpm

changelog:
sort: asc
use: github
filters:
exclude:
- "^docs:"
- "^chore:"
- "^test:"
- "^ci:"

release:
draft: false
prerelease: auto
mode: replace

# brews:
# - # TODO: add Homebrew tap after repository is public and naming is final
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - YYYY-MM-DD

### Added

- Release plumbing: GoReleaser `.deb`, `.rpm`, `.tar.gz`, and `checksums.txt` for linux/darwin amd64/arm64.
- `scripts/install.sh` and `scripts/uninstall.sh` for curl-based installs with SHA256 verification.
- `lab self-update` with `--check`, `--version`, `--pre-release`, and `--yes`.
- `lab iso list|download|disks|write` — ISO catalog, verified downloads, USB disk listing, and safe `dd` writes (Linux).
- `lab bootstrap essentials` for Ubuntu and Fedora Silverblue with idempotent sections.
- Package manager adapters: `internal/pkgmgr` (`apt`, `rpm-ostree`, detect).
- Testable exec runner: `internal/exec`.

### Changed

- GoReleaser config: nfpms, changelog filters, release mode.
- GitHub release workflow unchanged in trigger but documents full artifact set.

## [Unreleased]

### Added
Expand Down
48 changes: 42 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,50 @@ Module: `github.com/bartrosa/homelab-cli`

| Area | Commands | Notes |
|------|----------|--------|
| **Bootstrap** | `bootstrap laptop\|server\|profile\|list` | Embedded YAML profiles (macOS, Linux, Silverblue, Ubuntu server) |
| **Bootstrap** | `bootstrap laptop\|server\|profile\|list\|essentials` | Profiles + `essentials` for Ubuntu/Silverblue |
| **Packages** | `pkg install\|ensure\|list` | brew, apt, dnf, rpm-ostree |
| **Toolchains** | `toolchain install\|list\|use` | via [mise](https://mise.jdx.dev/) |
| **Services** | `services up\|down\|list\|logs\|ensure` | homelab `ml-stack` compose |
| **Server** | `server run`, `server deploy` | SSH + rsync; deploy can provision PG and start compose |
| **PostgreSQL** | `postgres apply` | Idempotent apply from `instances.yaml` (pgx) |
| **Bare metal** | `baremetal install` | Qdrant, Milvus, ClickHouse on Linux |
| **System** | `system usb list`, `system usb` | Bootable USB; ISOs discovered from Ubuntu/Fedora mirrors |
| **ISO** | `iso list`, `iso download`, `iso disks`, `iso write` | Cache ISOs, list disks, burn USB (Linux) |
| **SSH** | `ssh connect`, `ssh sync` | Host inventory from config |
| **Repos** | `repos backup` | GitLab account mirror (homelab Python script today) |
| **Templates** | `templates list\|new` | Copy `project-initiators/` from homelab |
| **Media** | `media heic` | HEIC→JPEG via `heif-convert` |
| **Meta** | `version` | Build metadata |
| **Meta** | `version`, `self-update` | Build metadata and in-place upgrades |

**Planned (stubs):** `cluster`, `gpu`, `models`, `mlops`, `vector`, `pipelines`, `agents`, `obs`, `logs`, `mcp`, most of `repos` beyond backup.

Full command tables: [`docs/commands.md`](docs/commands.md).

## Installation

### Build from source
### One-liner install

```bash
curl -sSL https://raw.githubusercontent.com/bartrosa/homelab-cli/main/scripts/install.sh | bash
```

Pin a version or install to `$HOME/.local`:

```bash
curl -sSL https://raw.githubusercontent.com/bartrosa/homelab-cli/main/scripts/install.sh | bash -s -- --version v0.2.0
curl -sSL https://raw.githubusercontent.com/bartrosa/homelab-cli/main/scripts/install.sh | bash -s -- --prefix "$HOME/.local"
```

### Upgrading

```bash
lab self-update
lab self-update --check # exit 0 if current, 3 if update available
```

### Alternatives

**Build from source**

```bash
git clone https://github.com/bartrosa/homelab-cli.git
Expand All @@ -48,15 +71,28 @@ make install # or: make build && ./bin/lab

Requires **Go 1.25+**.

### `go install`
**go install**

```bash
go install github.com/bartrosa/homelab-cli/cmd/lab@latest
```

### Releases
**Release packages**

Tagged releases publish binaries via GoReleaser. You can also use `scripts/install.sh` when release artifacts are available.
Tagged releases publish `.tar.gz`, `.deb`, and `.rpm` via GoReleaser on the [Releases](https://github.com/bartrosa/homelab-cli/releases) page.

## Provisioning a new machine

```bash
# On an existing Linux host:
lab iso list
lab iso download ubuntu-desktop
lab iso disks
lab iso write ~/.cache/homelab-cli/iso/ubuntu-24.04.3-desktop-amd64.iso --to /dev/sdb

# After booting the fresh OS and installing lab:
lab bootstrap essentials
```

## Quick start

Expand Down
13 changes: 12 additions & 1 deletion cmd/lab/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
package main

import (
"github.com/bartrosa/homelab-cli/internal/cli"
"context"
"errors"
"os"
"os/signal"
"syscall"

"github.com/bartrosa/homelab-cli/internal/cli"

"github.com/bartrosa/homelab-cli/internal/clierrors"
)

func main() {
Expand All @@ -16,6 +20,13 @@ func main() {
stop()

if err != nil {
var exitErr *clierrors.ExitError
if errors.As(err, &exitErr) {
if exitErr.Msg != "" {
cli.PrintCommandError(err)
}
os.Exit(exitErr.ExitCode())
}
cli.PrintCommandError(err)
os.Exit(1)
}
Expand Down
20 changes: 20 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,32 @@ Run `lab <command> --help` for flags. Global flags apply to all commands: `--con
| `lab bootstrap server` | Ubuntu server profile + optional homelab `install-server-deps.sh` step. | ✅ |
| `lab bootstrap profile <name>` | Run built-in or config-defined profile. | ✅ |
| `lab bootstrap list` | List embedded profiles. | ✅ |
| `lab bootstrap essentials` | Baseline packages for Ubuntu or Silverblue (sections: system-update, cli-basics, …). | ✅ |

Built-in profiles: `laptop-macos`, `laptop-linux`, `silverblue-laptop`, `server-ubuntu`.

```bash
lab bootstrap laptop --dry-run
lab bootstrap essentials --dry-run --target silverblue
lab bootstrap profile dgx-spark # from config bootstrap.profiles
```

### `lab iso`

| Command | Description | Status |
|---------|-------------|--------|
| `lab iso list` | Supported distros and resolved versions. | ✅ |
| `lab iso download <distro>` | Download and verify ISO to cache. | ✅ |
| `lab iso disks` | List block devices; USB vs SYSTEM (Linux). | ✅ |
| `lab iso write <iso> --to <device>` | Burn ISO with safety checks (Linux). | ✅ |

Flow: `list` → `download` → `disks` → `write`.

```bash
lab iso download ubuntu-desktop
lab iso write ~/.cache/homelab-cli/iso/ubuntu-24.04.3-desktop-amd64.iso --to /dev/sdb
```

### `lab pkg`

| Command | Description | Status |
Expand Down Expand Up @@ -187,8 +205,10 @@ lab media heic ~/Pictures/import --quality 95 --force
| Command | Description | Status |
|---------|-------------|--------|
| `lab version` | Build version, commit, date (`--output text\|json`). | ✅ |
| `lab self-update` | Install latest release from GitHub (`--check`, `--version`, `--pre-release`). | ✅ |

```bash
lab version --output json
lab self-update --check
lab --log-level debug services list
```
1 change: 1 addition & 0 deletions internal/baremetal/clickhouse.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package baremetal installs data-plane services directly on Linux hosts.
package baremetal

import (
Expand Down
61 changes: 61 additions & 0 deletions internal/bootstrap/distrobox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package bootstrap

import (
"bufio"
"context"
"fmt"
"os"
"strings"

"github.com/bartrosa/homelab-cli/internal/exec"
)

// SetupDistrobox installs distrobox and optionally creates homelab-dev container.
func SetupDistrobox(ctx context.Context, opts EssentialsOptions) (bool, error) {
pkg, _ := PackageFor("distrobox", "rpm-ostree")
installed, err := opts.Runner.RunWithOutput(ctx, "which", "distrobox")
reboot := false
if err != nil || strings.TrimSpace(installed) == "" {
if err := opts.Runner.Run(ctx, "rpm-ostree", "install", "--idempotent", "-y", pkg); err != nil {
return false, err
}
reboot = true
}

create := opts.Yes
if !create && !opts.Yes {
fmt.Fprint(opts.Stdout, "Create distrobox homelab-dev container? [y/N]: ")
var ans string
_, _ = fmt.Scanln(&ans)
create = strings.EqualFold(strings.TrimSpace(ans), "y") || strings.EqualFold(strings.TrimSpace(ans), "yes")
}
if !create {
return reboot, nil
}

if err := opts.Runner.Run(ctx, "distrobox", "create", "--name", "homelab-dev", "--image", "quay.io/fedora/fedora:41"); err != nil {
return reboot, fmt.Errorf("distrobox create: %w", err)
}
_ = opts.Runner.Run(ctx, "distrobox", "enter", "homelab-dev", "--", "sudo", "dnf", "install", "-y", "@development-tools", "git", "curl")
_ = opts.Runner.Run(ctx, "distrobox", "enter", "homelab-dev", "--", "sh", "-c", "curl https://mise.run | sh")
home, _ := os.UserHomeDir()
misePath := home + "/.local/share/mise/bin/mise"
exportPath := home + "/.local/bin"
_ = opts.Runner.Run(ctx, "distrobox-export", "--bin", misePath, "--export-path", exportPath)
return reboot, nil
}

// EnsureFlathub adds Flathub remote if missing.
func EnsureFlathub(ctx context.Context, runner exec.Runner) error {
out, err := runner.RunWithOutput(ctx, "flatpak", "remote-list")
if err != nil {
return err
}
sc := bufio.NewScanner(strings.NewReader(out))
for sc.Scan() {
if strings.Contains(sc.Text(), "flathub") {
return nil
}
}
return runner.Run(ctx, "flatpak", "remote-add", "--if-not-exists", "flathub", "https://flathub.org/repo/flathub.flatpakrepo")
}
43 changes: 43 additions & 0 deletions internal/bootstrap/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package bootstrap

import (
"context"
"fmt"
"io"
"os/user"

"github.com/bartrosa/homelab-cli/internal/exec"
)

// InstallDocker sets up Docker CE on Ubuntu via official apt repository.
func InstallDocker(ctx context.Context, runner exec.Runner, stdout, _ io.Writer) error {
if _, err := runner.RunWithOutput(ctx, "docker", "--version"); err == nil {
fmt.Fprintln(stdout, "docker already installed")
return addUserToDockerGroup(ctx, runner, stdout)
}

script := `set -e
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo ${VERSION_CODENAME}) stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
`
if err := runner.Run(ctx, "sudo", "bash", "-c", script); err != nil {
return fmt.Errorf("docker install: %w", err)
}
return addUserToDockerGroup(ctx, runner, stdout)
}

func addUserToDockerGroup(ctx context.Context, runner exec.Runner, stdout io.Writer) error {
u, err := user.Current()
if err != nil {
return err
}
if err := runner.Run(ctx, "sudo", "usermod", "-aG", "docker", u.Username); err != nil {
return err
}
fmt.Fprintln(stdout, "Added user to docker group — re-login or run: newgrp docker")
return nil
}
Loading
Loading