Skip to content

Commit 72e87e7

Browse files
committed
draft
0 parents  commit 72e87e7

52 files changed

Lines changed: 3371 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/validate.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: validate
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
validate:
9+
runs-on: ubuntu-24.04
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: oven-sh/setup-bun@v2
13+
- name: Install dependencies
14+
run: |
15+
sudo apt-get update
16+
sudo apt-get install -y ansible
17+
bun install
18+
bun run build
19+
ansible-galaxy collection install -r requirements.yml
20+
- name: Script validation
21+
run: |
22+
bash -n install.sh
23+
test -x dist/terrariumctl
24+
- name: Ansible syntax
25+
run: ansible-playbook -i ansible/inventory.ini ansible/site.yml --syntax-check

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist/
2+
node_modules/
3+
bun.lock
4+
bun.lockb

README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Terrarium
2+
3+
Terrarium bootstraps a single Ubuntu 24.04 VPS into a ZFS-backed LXD host for isolated workloads.
4+
5+
The intended entrypoint is:
6+
7+
```bash
8+
curl -fsSL https://raw.githubusercontent.com/terion-name/terrarium/refs/heads/main/install.sh | bash
9+
```
10+
11+
The shell bootstrap is intentionally thin. It downloads a compiled `terrariumctl` bundle from GitHub Releases when possible, clones the Terrarium repo into `/opt/terrarium`, stages the bundled binary into that checkout, and falls back to a source build only when you target a branch-like `--ref` such as `main`.
12+
13+
Terrarium provisions the host with:
14+
15+
- Cockpit
16+
- `45Drives/cockpit-zfs`
17+
- `45Drives/cockpit-S3ObjectBroswer`
18+
- LXD with the built-in web UI
19+
- ZFS
20+
- `sanoid` and optional `syncoid`
21+
- Traefik for public management endpoints
22+
- Optional self-hosted ZITADEL at `auth.<domain>`
23+
- `devsec.hardening` OS and SSH hardening
24+
25+
## Supported Host
26+
27+
- Ubuntu Server 24.04 LTS
28+
- Single-host install only
29+
- LXC containers only
30+
31+
## Install Modes
32+
33+
Interactive:
34+
35+
```bash
36+
curl -fsSL https://raw.githubusercontent.com/terion-name/terrarium/refs/heads/main/install.sh | bash -s -- --interactive
37+
```
38+
39+
Non-interactive:
40+
41+
```bash
42+
curl -fsSL https://raw.githubusercontent.com/terion-name/terrarium/refs/heads/main/install.sh | bash -s -- \
43+
--non-interactive \
44+
--email admin@example.com \
45+
--idp-mode zitadel-self-hosted \
46+
--storage-mode loop \
47+
--yes
48+
```
49+
50+
## Storage Strategy
51+
52+
Recommended:
53+
54+
- Attach a dedicated block volume and use `--storage-mode disk`.
55+
56+
Fallback:
57+
58+
- If the VPS only has the default root disk, Terrarium can create a file-backed ZFS pool with `--storage-mode loop`.
59+
60+
Partition mode:
61+
62+
- `--storage-mode partition` is intended for an existing safe partition target or a non-root whole disk that Terrarium can partition.
63+
- Terrarium does not try to shrink the mounted root filesystem.
64+
65+
## Public Endpoints
66+
67+
By default, Terrarium exposes:
68+
69+
- `https://manage.<dashed-public-ip>.traefik.me` for Cockpit
70+
- `https://lxd.<dashed-public-ip>.traefik.me` for the LXD API and UI
71+
- `https://auth.<dashed-public-ip>.traefik.me` for self-hosted ZITADEL when `--idp-mode zitadel-self-hosted` is enabled
72+
73+
You can override the domains with:
74+
75+
- `--domain`
76+
- `--manage-domain`
77+
- `--lxd-domain`
78+
- `--auth-domain`
79+
80+
## Reconfiguration
81+
82+
The installer keeps the checked out repository at `/opt/terrarium` and writes the resolved config to `/etc/terrarium/config.yaml`.
83+
84+
After installation:
85+
86+
```bash
87+
terrariumctl status
88+
terrariumctl backup list
89+
terrariumctl backup export
90+
terrariumctl backup restore --source s3 --instance app --at 2026-04-12T12 --as-new app-restore
91+
terrariumctl reconfigure
92+
terrariumctl proxy sync
93+
terrariumctl idp sync
94+
terrariumctl setdomain example.com
95+
```
96+
97+
`terrariumctl setdomain` updates the persisted root domain, derives `manage.`, `lxd.`, and `auth.` subdomains unless you override them, and then re-runs the full Ansible reconciliation so Traefik, LXD, and ZITADEL pick up the new external hostnames.
98+
99+
When self-hosted ZITADEL is enabled, Terrarium generates the initial admin password at `/etc/terrarium/secrets/zitadel_admin_password`.
100+
101+
## LXC Proxy Labels
102+
103+
Terrarium can sync LXC `user.proxy` labels into Traefik every minute.
104+
105+
Examples:
106+
107+
```bash
108+
lxc config set my-app user.proxy "https://app.example.com:3000,http://app-insecure.example.com:3000"
109+
lxc config set game user.proxy "tcp://25565:25565,udp://19132:19132"
110+
```
111+
112+
Rules:
113+
114+
- `https://domain[:container_port][/path]` creates HTTP-to-HTTPS redirect plus a TLS router.
115+
- `http://domain[:container_port][/path]` creates an HTTP router only.
116+
- `tcp://hostport:containerport` exposes a raw TCP port through Traefik.
117+
- `udp://hostport:containerport` exposes a raw UDP port through Traefik.
118+
- Dynamic TCP/UDP host ports are also opened and closed in UFW automatically by the sync job.
119+
- If the container does not have a global IPv4 address yet, the route is skipped until it does.
120+
121+
## Development
122+
123+
Validate locally:
124+
125+
```bash
126+
bun install
127+
bun run build
128+
bash -n install.sh
129+
ansible-playbook -i ansible/inventory.ini ansible/site.yml --syntax-check
130+
```

ansible.cfg

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[defaults]
2+
inventory = ansible/inventory.ini
3+
roles_path = ansible/roles
4+
collections_path = .ansible/collections:~/.ansible/collections:/usr/share/ansible/collections
5+
host_key_checking = False
6+
retry_files_enabled = False
7+
interpreter_python = auto_silent
8+
stdout_callback = default
9+
10+
[privilege_escalation]
11+
become = True

ansible/inventory.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[terrarium]
2+
localhost ansible_connection=local
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
terrarium_sanoid_recursion_root: "{{ terrarium_lxd_pool_name }}/containers"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- name: reload systemd
2+
ansible.builtin.systemd:
3+
daemon_reload: true
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
- name: Install backup packages
2+
ansible.builtin.apt:
3+
name:
4+
- awscli
5+
- mbuffer
6+
- sanoid
7+
- zstd
8+
state: present
9+
10+
- name: Ensure backup directories exist
11+
ansible.builtin.file:
12+
path: "{{ item }}"
13+
state: directory
14+
mode: "0755"
15+
loop:
16+
- /etc/sanoid
17+
- "{{ terrarium_state_dir }}/catalog"
18+
- "{{ terrarium_state_dir }}/lastsnapshots"
19+
- "{{ terrarium_state_dir }}/restore"
20+
21+
- name: Render sanoid config
22+
ansible.builtin.template:
23+
src: sanoid.conf.j2
24+
dest: /etc/sanoid/sanoid.conf
25+
mode: "0644"
26+
27+
- name: Render terrarium S3 env file
28+
ansible.builtin.copy:
29+
dest: /etc/terrarium/s3.env
30+
mode: "0600"
31+
content: |
32+
AWS_ACCESS_KEY_ID={{ terrarium_s3_access_key }}
33+
AWS_SECRET_ACCESS_KEY={{ terrarium_s3_secret_key }}
34+
AWS_DEFAULT_REGION={{ terrarium_s3_region }}
35+
when: terrarium_enable_s3
36+
37+
- name: Install S3 backup systemd service
38+
ansible.builtin.template:
39+
src: terrarium-s3-backup.service.j2
40+
dest: /etc/systemd/system/terrarium-s3-backup.service
41+
mode: "0644"
42+
when: terrarium_enable_s3
43+
notify: reload systemd
44+
45+
- name: Install S3 backup timer
46+
ansible.builtin.copy:
47+
dest: /etc/systemd/system/terrarium-s3-backup.timer
48+
mode: "0644"
49+
content: |
50+
[Unit]
51+
Description=Run Terrarium S3 exports
52+
53+
[Timer]
54+
OnCalendar=hourly
55+
Persistent=true
56+
57+
[Install]
58+
WantedBy=timers.target
59+
when: terrarium_enable_s3
60+
notify: reload systemd
61+
62+
- name: Enable S3 backup timer
63+
ansible.builtin.systemd:
64+
name: terrarium-s3-backup.timer
65+
enabled: true
66+
state: started
67+
when: terrarium_enable_s3
68+
69+
- name: Disable S3 backup timer when not configured
70+
ansible.builtin.systemd:
71+
name: terrarium-s3-backup.timer
72+
enabled: false
73+
state: stopped
74+
failed_when: false
75+
when: not terrarium_enable_s3
76+
77+
- name: Install syncoid service
78+
ansible.builtin.template:
79+
src: terrarium-syncoid.service.j2
80+
dest: /etc/systemd/system/terrarium-syncoid.service
81+
mode: "0644"
82+
when: terrarium_enable_syncoid
83+
notify: reload systemd
84+
85+
- name: Install syncoid timer
86+
ansible.builtin.copy:
87+
dest: /etc/systemd/system/terrarium-syncoid.timer
88+
mode: "0644"
89+
content: |
90+
[Unit]
91+
Description=Run Terrarium syncoid replication
92+
93+
[Timer]
94+
OnCalendar=hourly
95+
Persistent=true
96+
97+
[Install]
98+
WantedBy=timers.target
99+
when: terrarium_enable_syncoid
100+
notify: reload systemd
101+
102+
- name: Enable syncoid timer
103+
ansible.builtin.systemd:
104+
name: terrarium-syncoid.timer
105+
enabled: true
106+
state: started
107+
when: terrarium_enable_syncoid
108+
109+
- name: Disable syncoid timer when not configured
110+
ansible.builtin.systemd:
111+
name: terrarium-syncoid.timer
112+
enabled: false
113+
state: stopped
114+
failed_when: false
115+
when: not terrarium_enable_syncoid
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[{{ terrarium_sanoid_recursion_root }}]
2+
use_template = terrarium
3+
recursive = yes
4+
process_children_only = yes
5+
6+
[template_terrarium]
7+
frequently = 0
8+
hourly = 24
9+
daily = 14
10+
monthly = 3
11+
yearly = 0
12+
autosnap = yes
13+
autoprune = yes
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[Unit]
2+
Description=Export Terrarium ZFS snapshots to S3
3+
After=network-online.target snap.lxd.daemon.service
4+
Wants=network-online.target
5+
6+
[Service]
7+
Type=oneshot
8+
EnvironmentFile=/etc/terrarium/s3.env
9+
ExecStart=/usr/local/bin/terrariumctl backup export

0 commit comments

Comments
 (0)