Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ dist/
completions/
manpages/
igloo_test

# Worktrees
.worktrees/
207 changes: 47 additions & 160 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,198 +1,85 @@
# 🏔️ Igloo
# Igloo

**Build cozy development environments in seconds** ❄️
**Isolated development containers for GTK apps**

Igloo is a CLI tool that creates isolated Linux development containers using [Incus](https://linuxcontainers.org/incus/). Think of it as your personal igloo in the frozen tundra of system configuration chaos—warm, safe, and exactly how you like it.
Igloo creates Linux development containers using [Incus](https://linuxcontainers.org/incus/) with automatic display passthrough. Two commands, zero configuration.

## ✨ Features

- 🐧 **Multi-distro support** — Ubuntu, Debian, Fedora, or Arch Linux
- 🏠 **Home away from home** — Your home directory and project files are automatically mounted
- 🖥️ **GUI apps just work** — Wayland and X11 passthrough with optional GPU acceleration
- 👤 **Seamless user mapping** — Same UID/GID as your host, no permission headaches
- 📜 **Custom init scripts** — Automate your environment setup
- ⚡ **Fast iteration** — Destroy and rebuild in seconds

## 🚀 Quick Start
## Quick Start

```bash
# Initialize a new igloo in your project directory
cd ~/projects/my-awesome-app
igloo init

# Enter your cozy development environment
igloo enter

# When you're done for the day
igloo stop

# Start fresh? No problem!
igloo destroy
igloo init
cd ~/projects/my-gtk-app
igloo # creates and enters the container
```

## 📦 Installation
That's it. Igloo auto-detects your host OS, creates a matching container, mounts your project directory, copies your dotfiles, and sets up display passthrough with GPU support.

### From Source
When you're done with the container:

```bash
git clone https://github.com/frostyard/igloo.git
cd igloo
make build
sudo cp igloo /usr/local/bin/
```

### Prerequisites

- [Incus](https://linuxcontainers.org/incus/docs/main/installing/) installed and configured
- Your user added to the `incus` group

## 🎛️ Commands

| Command | Description |
| --------------- | ---------------------------------- |
| `igloo init` | Create a new igloo environment |
| `igloo enter` | Enter the igloo (starts if needed) |
| `igloo stop` | Stop the running igloo |
| `igloo status` | Show environment status |
| `igloo remove` | Remove container, keep config |
| `igloo destroy` | Remove everything |

## ⚙️ Configuration

Running `igloo init` creates a `.igloo/` directory with your configuration:

```
.igloo/
├── igloo.ini # Main configuration
└── scripts/ # Init scripts (run during provisioning)
└── 00-example.sh.example
```

### igloo.ini

```ini
[container]
image = images:debian/trixie/cloud
name = igloo-myproject

[packages]
install = git, vim, curl

[mounts]
home = true
project = true

[display]
enabled = true
gpu = true

[symlinks]
paths = .gitconfig, .ssh, .config/nvim
igloo destroy
```

### Init Scripts 📜
## Setup Script

Drop shell scripts in `.igloo/scripts/` to customize your environment:
Optionally create a `.igloo.sh` in your project root to install dependencies on first creation:

```bash
# .igloo/scripts/01-install-tools.sh
#!/bin/bash
apt-get install -y nodejs npm
npm install -g yarn
apt-get update
apt-get install -y build-essential libgtk-4-dev meson ninja-build
```

Scripts run in lexicographical order, so use numbered prefixes like `01-`, `02-`, etc.
To reprovision after changing `.igloo.sh`, run `igloo destroy` then `igloo`.

### Symlinks 🔗
## Commands

The `[symlinks]` section lets you link files or folders from your host home directory (`~/host/`) to the container's home (`~/`). This is perfect for sharing dotfiles!
| Command | Description |
| --- | --- |
| `igloo` | Enter the container (creates it if needed) |
| `igloo destroy` | Remove the container completely |
| `igloo --no-gui` | Enter without display/GPU passthrough |

**Default symlinks** (created automatically with `igloo init`):
## What Happens on First Run

```ini
[symlinks]
paths = .gitconfig, .ssh, .bashrc, .profile, .bash_profile
```
1. Detects your host distro from `/etc/os-release` (follows `ID_LIKE` for derivatives)
2. Creates an Incus container with matching image
3. Maps your user (same UID/GID, sudo access)
4. Mounts the project directory at the same absolute path
5. Copies dotfiles (`.gitconfig`, `.ssh/`, `.bashrc`, `.profile`, `.bash_profile`)
6. Sets up X11/Wayland display passthrough with GPU
7. Runs `.igloo.sh` if it exists
8. Drops you into a shell

Each path listed will create a symlink: `~/<path>` → `~/host/<path>`. If a file doesn't exist on your host, it's silently skipped—no errors!
On subsequent runs, it just enters the existing container (starting it if stopped) and refreshes display passthrough.

Add more paths as needed:
## Installation

```ini
[symlinks]
paths = .gitconfig, .ssh, .bashrc, .profile, .bash_profile, .config/nvim, .vimrc
```

## 🎨 Flags & Options

### igloo init

```bash
igloo init --distro ubuntu --release noble # Use Ubuntu Noble
igloo init --distro fedora --release 43 # Use Fedora 43
igloo init --name my-dev-box # Custom container name
igloo init --packages "go,nodejs,python3" # Pre-install packages
```

### igloo destroy

```bash
igloo destroy # Remove container and .igloo directory
igloo destroy --keep-config # Keep .igloo directory for later
igloo destroy --force # Force remove without stopping
```

## 🗂️ Directory Layout (Inside the Container)

```
/home/youruser/
├── host/ # Your host home directory
└── workspace/
└── myproject/ # Your project directory (where you ran igloo init)
```

## 💡 Tips & Tricks

### Run GUI Apps

```bash
igloo enter
code . # VS Code just works!
firefox # Browse the web
```

### Use Your Host's Git Config
### Prerequisites

Your home directory is mounted, so `~/.gitconfig` is already available!
- [Incus](https://linuxcontainers.org/incus/docs/main/installing/) installed and configured
- Your user added to the `incus` group

### Quick Rebuild
### From Source

```bash
igloo destroy && igloo init # Fresh start in ~30 seconds
git clone https://github.com/frostyard/igloo.git
cd igloo
make build
sudo cp igloo /usr/local/bin/
```

### Multiple Projects, Multiple Igloos

Each project directory can have its own igloo. They're completely isolated!
## Tips

## 🤝 Contributing

Contributions are welcome! Feel free to open issues and pull requests.
- Each project directory gets its own container (`igloo-<dirname>`)
- GUI apps just work inside the container (GTK, Qt, Firefox, VS Code, etc.)
- The `--no-gui` flag is useful when SSHed into the machine or running headless builds
- Multiple projects can each have their own igloo, completely isolated

## Credits

Igloo stands on the shoulders of giants:

- **[Distrobox](https://github.com/89luca89/distrobox)** — The original inspiration for seamless container-based development environments
- **[Blincus](https://blincus.dev)** — blincus is igloo version 0.
- **[Distrobox](https://github.com/89luca89/distrobox)** -- The original inspiration
- **[Blincus](https://blincus.dev)** -- Blincus is igloo version 0

## License

MIT License — build all the igloos you want! 🏔️

---

<p align="center">
<i>Stay frosty, friends!</i> ❄️🐧
</p>
MIT
66 changes: 19 additions & 47 deletions cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,52 @@ import (
"fmt"
"os"

"github.com/frostyard/igloo/internal/config"
"github.com/frostyard/igloo/internal/host"
"github.com/frostyard/igloo/internal/incus"
"github.com/frostyard/igloo/internal/ui"
"github.com/spf13/cobra"
)

func destroyCmd() *cobra.Command {
var force bool
var keepConfig bool

cmd := &cobra.Command{
Use: "destroy",
Short: "Destroy the igloo development environment",
Long: `Destroy removes the igloo container completely.
By default, it also removes the .igloo configuration directory.`,
Example: ` # Destroy the igloo environment
igloo destroy

# Force destroy without confirmation
igloo destroy --force

# Keep the .igloo directory
igloo destroy --keep-config`,
Short: "Destroy the igloo container for this project",
Long: `Removes the igloo container completely. The project files are untouched.`,
Example: ` # Destroy the container
igloo destroy`,
RunE: func(cmd *cobra.Command, args []string) error {
return runDestroy(force, keepConfig)
return runDestroy()
},
}

cmd.Flags().BoolVarP(&force, "force", "f", false, "Force destroy without stopping first")
cmd.Flags().BoolVar(&keepConfig, "keep-config", false, "Keep the .igloo configuration directory")

return cmd
}

func runDestroy(force, keepConfig bool) error {
func runDestroy() error {
styles := ui.NewStyles()
client := incus.NewClient()

// Load config
cfg, err := config.Load(config.ConfigPath())
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
return fmt.Errorf("failed to get current directory: %w", err)
}
name := host.ContainerName(cwd)

client := incus.NewClient()

// Check if instance exists
exists, err := client.InstanceExists(cfg.Container.Name)
exists, err := client.InstanceExists(name)
if err != nil {
return fmt.Errorf("failed to check instance: %w", err)
}

if exists {
fmt.Println(styles.Info(fmt.Sprintf("Destroying container %s...", cfg.Container.Name)))
if err := client.Delete(cfg.Container.Name, force); err != nil {
return fmt.Errorf("failed to destroy instance: %w", err)
}
fmt.Println(styles.Success(fmt.Sprintf("Container %s destroyed", cfg.Container.Name)))
} else {
fmt.Println(styles.Warning(fmt.Sprintf("Container %s does not exist", cfg.Container.Name)))
}

// Remove stored config hash
if err := config.RemoveStoredHash(cfg.Container.Name); err != nil {
fmt.Println(styles.Warning(fmt.Sprintf("Could not remove stored hash: %v", err)))
if !exists {
fmt.Println(styles.Warning(fmt.Sprintf("Container %s does not exist", name)))
return nil
}

// Remove .igloo directory unless --keep-config
if !keepConfig {
fmt.Println(styles.Info("Removing .igloo directory..."))
if err := os.RemoveAll(config.ConfigDir); err != nil {
return fmt.Errorf("failed to remove .igloo directory: %w", err)
}
fmt.Println(styles.Info(fmt.Sprintf("Destroying container %s...", name)))
if err := client.Delete(name, true); err != nil {
return fmt.Errorf("failed to destroy instance: %w", err)
}

fmt.Println(styles.Success("Igloo environment destroyed"))
fmt.Println(styles.Success(fmt.Sprintf("Container %s destroyed", name)))
return nil
}
Loading
Loading