devcfg is a CLI TUI written in Go that lets you configure a Linux/macOS machine after an SSH connection, without any built-in SSH logic.
Workflow:
- Connect manually via SSH
- Download and run
devcfg - Follow the interactive TUI workflow
- Configure your environment (tools, git, docker…)
- Everything runs locally on the remote machine
✨ Fully Customizable —
devcfguses a simple JSON tool registry that you can modify to add your own tools, creating custom environment configurations for your team or organization. See 🎨 Customization for details.
- 🚀 Quick Start
- 🎮 TUI Navigation
- 🪜 Workflow Steps
- 🏗️ Architecture
- ⚙️ Tool Model (tools.json)
- 🧠 Resolver Priority
- 🎨 Customization
- 📦 Build from Source
- 🧪 Testing
- 🛠️ Dev Setup (Contributing)
- 📦 CI/CD
- 🌍 Distribution
- 🧩 Philosophy
The installation script automatically downloads a pre-built binary if available, or builds from source if not. It installs the binary to ~/.local/bin and updates your PATH:
curl -fsSL https://raw.githubusercontent.com/I3-rett/devcfg/main/install.sh | bashThe script will:
- Download or build the
devcfgbinary - Install it to
~/.local/bin(creating the directory if needed) - Add
~/.local/binto your PATH in your shell configuration file (.bashrc,.zshrc, etc.) - Provide instructions for making
devcfgimmediately available
After installation, either:
- Open a new terminal, or
- Run
source ~/.bashrc(or~/.zshrcfor zsh)
Then you can run devcfg from anywhere.
# Linux (amd64)
curl -fsSL https://github.com/I3-rett/devcfg/releases/latest/download/devcfg-linux-amd64 -o devcfg \
&& chmod +x devcfg && ./devcfg
# macOS (Apple Silicon)
curl -fsSL https://github.com/I3-rett/devcfg/releases/latest/download/devcfg-darwin-arm64 -o devcfg \
&& chmod +x devcfg && ./devcfgNote: If you get a "Not Found" error, releases may not be published yet. Use the install script or Option 2 below.
Requirements: Go 1.24+
git clone https://github.com/I3-rett/devcfg.git
cd devcfg
go build -o devcfg .
./devcfg| Key | Action |
|---|---|
↑ / k |
Move cursor up |
↓ / j |
Move cursor down |
SPACE |
Toggle selection (checkbox / radio) |
ENTER on item |
Toggle selection |
ENTER on Continue |
Validate step and proceed |
Tab / Shift+Tab |
Navigate form fields (Git step) |
q / Ctrl+C |
Quit |
Interactive checklist of tools to install. Uses the system package manager (brew/apt) or a fallback script.
Available tools: git, neovim, docker, lazydocker, nvm, python3, curl, tmux, htop, ripgrep, fzf, zsh, bat, tssh, lazygit
Step 1/3 — Tools Installation
[ ] git Version control system
[✓] neovim Hyperextensible text editor
▶ [ ] docker Container platform
[ ] nvm Node Version Manager
...
╭──────────────╮
│ Continue │
╰──────────────╯
Form to set git config --global identity.
user.nameuser.email- GPG signing toggle (
commit.gpgsign)
Automatic checks:
- Docker installation detected
- Docker daemon status (via
systemctl is-active docker) - User membership in the
dockergroup (offerssudo usermod -aG docker $USER)
devcfg/
├── main.go Entry point
├── internal/
│ ├── system/detect.go OS + package manager detection
│ ├── registry/
│ │ ├── registry.go Tool registry (go:embed)
│ │ └── tools.json Tool definitions (18 tools)
│ ├── resolver/resolver.go brew / apt / fallback selection
│ ├── executor/executor.go Command runner (stdout+stderr capture)
│ └── tui/
│ ├── app.go Root Bubble Tea model (step orchestrator)
│ ├── tuistyles/styles.go Lipgloss theme (purple/teal)
│ └── steps/
│ ├── tools.go Step 1 — Tools checklist
│ ├── git.go Step 2 — Git config form
│ └── docker.go Step 3 — Docker checks
└── .github/workflows/release.yml CI/CD: build + publish release
| Layer | Package | Role |
|---|---|---|
| System | internal/system |
Detect OS (macos, ubuntu, debian, linux) and package manager (brew, apt, none) via runtime.GOOS and /etc/os-release |
| Registry | internal/registry |
Load tool definitions from embedded tools.json; expose List() and Find(name) |
| Resolver | internal/resolver |
Select install command: brew → apt → fallback script |
| Executor | internal/executor |
Run arbitrary commands with os/exec, capture combined stdout+stderr |
| TUI | internal/tui |
Multi-step Bubble Tea workflow with lipgloss styling |
{
"name": "neovim",
"description": "Hyperextensible text editor",
"brew": "neovim",
"apt": "neovim",
"fallback": ""
}Each tool carries its own package name per package manager. No OS coupling in the tool definition itself.
brew available + brew package defined → brew install <pkg>
apt available + apt package defined → sudo apt-get install -y <pkg>
fallback script defined → sh -c "<script>"
otherwise → error: no install method
devcfg is designed to be easily customizable to fit your specific development environment needs. The tool registry is defined in a simple JSON file that you can modify to add, remove, or update tools.
To add your own tools to the registry:
- Edit the tool registry — Modify
internal/registry/tools.jsonto include your custom tool definitions - Define installation methods — Specify how the tool should be installed on different systems:
brew: Homebrew package name (for macOS and Linux with Homebrew)apt: APT package name (for Debian/Ubuntu systems)fallback: Shell script command for systems without package managers
- Rebuild the binary — Run
go build -o devcfg .to embed the updated registry - Deploy — Use your customized binary on your target machines
Each tool in tools.json follows this structure:
{
"name": "tool-name",
"description": "Human-readable description",
"binary": "executable-name",
"brew": "homebrew-package-name",
"apt": "apt-package-name",
"fallback": "curl -fsSL https://example.com/install.sh | bash",
"requires": ["dependency-tool-1", "dependency-tool-2"]
}Field Details:
name— Unique identifier for the tool (required)description— Shown in the TUI checklist (required)binary— Executable name to check if installed (leave empty for non-binary tools like fonts or configs)binaryAliases— Array of alternative binary names to check (e.g.,["bat", "batcat"])brew— Homebrew formula/cask nameapt— APT package namefallback— Shell command to install when package managers aren't availablerequires— Array of tool names that must be installed first (optional)
Add a custom development tool:
{
"name": "mycli",
"description": "My custom CLI tool",
"binary": "mycli",
"brew": "myorg/tap/mycli",
"apt": "",
"fallback": "curl -fsSL https://mycli.dev/install.sh | bash"
}Add a tool with dependencies:
{
"name": "my-neovim-config",
"description": "My Neovim configuration",
"binary": "",
"brew": "",
"apt": "",
"fallback": "git clone https://github.com/me/my-nvim-config ~/.config/nvim",
"requires": ["neovim", "git"]
}- Team-specific tooling — Create a company-wide
devcfgwith your organization's standard tools - Role-specific environments — Build different configurations for backend, frontend, DevOps, etc.
- Reproducible setups — Ensure every team member has the same development environment
- Easy onboarding — New team members can set up their machines with a single command
The registry is embedded at build time using Go's go:embed directive, making your customized binary completely self-contained and portable.
git clone https://github.com/I3-rett/devcfg.git
cd devcfg
go build -o devcfg .
./devcfgRequirements: Go 1.24+
Dependencies:
github.com/charmbracelet/bubbletea— TUI frameworkgithub.com/charmbracelet/lipgloss— terminal stylinggithub.com/charmbracelet/bubbles— text inputs
Tests in this project follow standard Go conventions and are co-located with the packages they cover (*_test.go in the same directory).
- Table-driven tests — use
[]struct{ name, input, want }slices and iterate witht.Run(tc.name, ...)to keep cases readable and easy to extend. - No external test framework — only the standard
testingpackage. Helpers fromtesting/iotestoros/execstubs where relevant. - Isolation — unit tests must not make real network calls or perform persistent filesystem mutations. Use
t.TempDir()for temporary files, keep filesystem writes confined there, and restore env variables witht.Setenv(). - One assertion per sub-test — keep each
t.Runfocused; avoid asserting unrelated things together. - Error paths covered — every function that returns an
errormust have at least one test case that triggers the error branch. - Deterministic — tests must not rely on timing, random values, or test-execution order.
| Package | Priority | Notes |
|---|---|---|
internal/system |
High | Mock filesystem for /etc/os-release; mock PATH for binary detection |
internal/registry |
High | Validate List() length + fields; Find() hit and miss |
internal/resolver |
High | All package-manager × tool combinations; fallback; error case |
internal/executor |
Medium | Real subprocess (echo); empty args; failing command |
internal/tui |
Low | Pure View/Init smoke tests; no interactive input required |
# All packages
go test ./...
# With race detector (recommended in CI)
go test -race ./...
# With coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out- Go 1.24+
git
# 1. Fork then clone the repo
git clone https://github.com/<your-fork>/devcfg.git
cd devcfg
# 2. Download dependencies
go mod download
# 3. Build the binary
go build -o devcfg .
# 4. Run locally
./devcfggo run .devcfg/
├── main.go Entry point
├── go.mod / go.sum Module definition and lock file
└── internal/
├── system/detect.go OS + package manager detection
├── registry/
│ ├── registry.go Tool registry (go:embed)
│ └── tools.json Tool definitions
├── resolver/resolver.go brew / apt / fallback selection
├── executor/executor.go Command runner
└── tui/
├── app.go Root Bubble Tea model (step orchestrator)
├── tuistyles/styles.go Lipgloss theme
└── steps/
├── tools.go Step 1 — Tools checklist
├── git.go Step 2 — Git config form
└── docker.go Step 3 — Docker checks
- Add a new tool — edit
internal/registry/tools.json(rebuild required to embed the updated file). See 🎨 Customization for detailed instructions. - Add a new TUI step — create a new file under
internal/tui/steps/, implement thetea.Modelinterface, and wire it up ininternal/tui/app.go. - Change styling — update the Lipgloss theme in
internal/tui/tuistyles/styles.go.
- Create a feature branch:
git checkout -b feat/my-change - Make and commit your changes
- Open a pull request against
main
GitHub Actions workflow (.github/workflows/release.yml) triggers on v* tag pushes and:
- Builds
devcfg-linux-amd64(cross-compiled on ubuntu-latest) - Builds
devcfg-darwin-arm64(cross-compiled on ubuntu-latest) - Creates a GitHub Release with both binaries
Pre-built binaries are available in GitHub Releases once a version tag is pushed.
# Linux (amd64)
curl -fsSL https://github.com/I3-rett/devcfg/releases/latest/download/devcfg-linux-amd64 -o devcfg \
&& chmod +x devcfg && ./devcfg
# macOS (Apple Silicon)
curl -fsSL https://github.com/I3-rett/devcfg/releases/latest/download/devcfg-darwin-arm64 -o devcfg \
&& chmod +x devcfg && ./devcfgNote: If no releases have been published yet, build from source following the instructions in the Quick Start section.
- SSH-external — no internal SSH logic; runs where you are
- Local execution only — all commands run on the target machine
- Structured workflow — not just a package installer
- Keyboard-first UX — inspired by
tsshstyle - Deterministic + minimal — explicit steps, clear feedback
- Extensible via registry — the bundled tool registry is defined in
tools.json; changes require rebuilding the binary