Skip to content

Commit 87115fb

Browse files
authored
Merge pull request #271 from eshtek/dev
dev -> main
2 parents 6a6e13c + 778c2c6 commit 87115fb

31 files changed

Lines changed: 691 additions & 210 deletions

docs/.vitepress/sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const sidebar: DefaultTheme.SidebarItem[] = [
155155
collapsed: true,
156156
items: [
157157
// auto-generated-release-notes-start
158+
{ text: '2026-05-20', link: '/release-notes/command-deck/2026-05-20' },
158159
{ text: '2026-05-13', link: '/release-notes/command-deck/2026-05-13' },
159160
{ text: '2026-04-21', link: '/release-notes/command-deck/2026-04-21' },
160161
{ text: '2026-03-27', link: '/release-notes/command-deck/2026-03-27' },

docs/features/apps/install-scripts/advanced/debugging.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ If an install scripts fails, this will help:
2727

2828
#### Permission Errors
2929
- **Symptom**: App fails to start, logs show permission denied errors
30-
- **Solution**: Add appropriate entries to `ensure_permissions_exists` in your install script
31-
- **Example**: PostgreSQL requires specific user/group permissions
30+
- **Solution**: Add the `owner` field to the relevant entry in `ensure_directories_exists` with the correct TrueNAS user and group (e.g., `{ "user": "netdata", "group": "docker" }`)
31+
- **Example**: PostgreSQL data directories typically require `"owner": { "user": "netdata", "group": "docker" }`
3232

3333
#### Missing Directories
3434
- **Symptom**: App fails during installation, "directory not found" errors

docs/features/apps/install-scripts/curated/index.md

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,27 @@
33
<!-- curated:index:start -->
44
| App | Download | Size | Last Modified |
55
|---|---|---:|---|
6-
| `bazarr` | [bazarr.json](/install-scripts/bazarr.json) | 1.3 KB | 2026-03-15 |
7-
| `drawio` | [drawio.json](/install-scripts/drawio.json) | 757 B | 2026-03-15 |
8-
| `emby` | [emby.json](/install-scripts/emby.json) | 2.2 KB | 2026-03-15 |
9-
| `handbrake` | [handbrake.json](/install-scripts/handbrake.json) | 1.8 KB | 2026-03-15 |
10-
| `home-assistant` | [home-assistant.json](/install-scripts/home-assistant.json) | 1.6 KB | 2026-03-19 |
11-
| `immich` | [immich.json](/install-scripts/immich.json) | 1.7 KB | 2026-03-15 |
12-
| `jellyfin` | [jellyfin.json](/install-scripts/jellyfin.json) | 2.2 KB | 2026-03-15 |
13-
| `lidarr` | [lidarr.json](/install-scripts/lidarr.json) | 1.4 KB | 2026-03-15 |
14-
| `nextcloud` | [nextcloud.json](/install-scripts/nextcloud.json) | 3.5 KB | 2026-03-15 |
15-
| `peanut` | [peanut.json](/install-scripts/peanut.json) | 834 B | 2026-03-15 |
16-
| `plex` | [plex.json](/install-scripts/plex.json) | 3.4 KB | 2025-12-12 |
17-
| `prowlarr` | [prowlarr.json](/install-scripts/prowlarr.json) | 766 B | 2025-12-04 |
18-
| `qbittorrent` | [qbittorrent.json](/install-scripts/qbittorrent.json) | 1.0 KB | 2025-12-04 |
19-
| `radarr` | [radarr.json](/install-scripts/radarr.json) | 1.2 KB | 2025-12-04 |
20-
| `scrutiny` | [scrutiny.json](/install-scripts/scrutiny.json) | 1.3 KB | 2026-03-15 |
21-
| `sonarr` | [sonarr.json](/install-scripts/sonarr.json) | 1.2 KB | 2025-12-04 |
22-
| `syncthing` | [syncthing.json](/install-scripts/syncthing.json) | 2.5 KB | 2026-05-05 |
6+
| `bazarr` | [bazarr.json](/install-scripts/bazarr.json) | 1.4 KB | 2026-05-15 |
7+
| `blinko` | [blinko.json](/install-scripts/blinko.json) | 1.3 KB | 2026-05-20 |
8+
| `dozzle` | [dozzle.json](/install-scripts/dozzle.json) | 614 B | 2026-05-18 |
9+
| `drawio` | [drawio.json](/install-scripts/drawio.json) | 634 B | 2026-05-15 |
10+
| `emby` | [emby.json](/install-scripts/emby.json) | 2.3 KB | 2026-05-15 |
11+
| `excalidraw` | [excalidraw.json](/install-scripts/excalidraw.json) | 614 B | 2026-05-18 |
12+
| `handbrake` | [handbrake.json](/install-scripts/handbrake.json) | 1.9 KB | 2026-05-15 |
13+
| `home-assistant` | [home-assistant.json](/install-scripts/home-assistant.json) | 1.4 KB | 2026-05-15 |
14+
| `immich` | [immich.json](/install-scripts/immich.json) | 1.6 KB | 2026-05-15 |
15+
| `jellyfin` | [jellyfin.json](/install-scripts/jellyfin.json) | 2.3 KB | 2026-05-15 |
16+
| `lidarr` | [lidarr.json](/install-scripts/lidarr.json) | 1.4 KB | 2026-05-15 |
17+
| `lubelogger` | [lubelogger.json](/install-scripts/lubelogger.json) | 1.5 KB | 2026-05-18 |
18+
| `navidrome` | [navidrome.json](/install-scripts/navidrome.json) | 5.1 KB | 2026-05-18 |
19+
| `nextcloud` | [nextcloud.json](/install-scripts/nextcloud.json) | 3.4 KB | 2026-05-20 |
20+
| `peanut` | [peanut.json](/install-scripts/peanut.json) | 911 B | 2026-05-15 |
21+
| `plex` | [plex.json](/install-scripts/plex.json) | 3.4 KB | 2026-05-15 |
22+
| `portracker` | [portracker.json](/install-scripts/portracker.json) | 894 B | 2026-05-18 |
23+
| `prowlarr` | [prowlarr.json](/install-scripts/prowlarr.json) | 781 B | 2026-05-15 |
24+
| `qbittorrent` | [qbittorrent.json](/install-scripts/qbittorrent.json) | 1.0 KB | 2026-05-15 |
25+
| `radarr` | [radarr.json](/install-scripts/radarr.json) | 1.3 KB | 2026-05-15 |
26+
| `scrutiny` | [scrutiny.json](/install-scripts/scrutiny.json) | 1.4 KB | 2026-05-15 |
27+
| `sonarr` | [sonarr.json](/install-scripts/sonarr.json) | 1.3 KB | 2026-05-15 |
28+
| `syncthing` | [syncthing.json](/install-scripts/syncthing.json) | 2.6 KB | 2026-05-15 |
2329
<!-- curated:index:end -->

docs/features/apps/install-scripts/overview.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ For apps not yet curated or when you need to customize the configuration:
4949
### Best Practices and Common Pitfalls
5050

5151
#### Best Practices
52+
- **Use V4 format** — all new install scripts must use `"version": 4`
5253
- **Always use `$LOCATION()` macros** for paths instead of hardcoded paths
5354
- **Use `$HOST_PATH()` and `$MOUNTED_HOST_PATH()`** for storage configuration instead of manual object creation
54-
- **Include necessary directories** in `ensure_directories_exists` - assume no directories exist
55-
- **Set proper permissions** with `ensure_permissions_exists` for apps that require specific user/group access
55+
- **All directory entries must be objects** — bare strings in `ensure_directories_exists` are no longer supported in V4
56+
- **Declare ownership** with the `owner` field for apps that require specific user/group ownership (e.g., `"postgres"`, `"apps"`)
57+
- **Add `snapshot` config** on data and config directories to enable automatic pre-update ZFS snapshots
5658
- **Use `$MEMORY()` for dynamic memory allocation** to ensure apps work across different system configurations
5759
- **Reference TrueNAS app schemas** from the [official apps repository](https://github.com/truenas/apps) for `app_values` structure
5860

docs/features/apps/install-scripts/reference/schema.md

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ Install scripts are JSON objects with the following structure. Scripts can use v
44

55
## Root Properties
66

7-
- **`version`** (required): Schema version. Must be `3` or higher (currently latest supported version).
7+
- **`version`** (required): Schema version. Must be `4` (current required version). Versions 1-3 are deprecated.
88
- **`script`** (required): Metadata about the install script itself
99
- **`version`** (required): Semantic version of this install script (e.g., "1.0.0", "2.1.3")
1010
- **`updateCompatibility`** (optional): Semver range expression defining which script versions can update to this version (e.g., ">=1.0.0" allows updates from any version 1.0.0 or higher, "^2.0.0" allows updates from 2.x.x versions). Supports all [semver range syntax](https://www.npmjs.com/package/semver#ranges) including `>=`, `>`, `<`, `<=`, `^`, `~`, and complex ranges like `">=1.0.0 <3.0.0"`
1111
- **`changeLog`** (optional): Description of changes in this version of the script
1212
- **`requirements`** (required): System requirements that are validated before installation
1313
- **`installation_questions`** (optional): Array of questions to ask the user during installation
14-
- **`ensure_directories_exists`** (optional): Array of directories to create before installation
15-
- **`ensure_permissions_exists`** (optional): Array of permission modifications for specific paths
14+
- **`ensure_directories_exists`** (optional): Array of directory entry objects to create before installation, with optional ownership and snapshot declarations
1615
- **`app_values`** (required): Configuration object passed directly to TrueNAS API
1716

1817
## Available Macros
@@ -83,8 +82,8 @@ Locations are folder paths configured in HexOS Settings → Locations. Each loca
8382
"locations": ["ApplicationsPerformance", "Photos", "Media"]
8483
},
8584
"ensure_directories_exists": [
86-
"$LOCATION(ApplicationsPerformance)/immich/config",
87-
"$LOCATION(Photos)/immich"
85+
{ "path": "$LOCATION(ApplicationsPerformance)/immich/config", "owner": { "user": "apps" } },
86+
{ "path": "$LOCATION(Photos)/immich", "owner": { "user": "apps" } }
8887
],
8988
"app_values": {
9089
"storage": {
@@ -149,7 +148,7 @@ Network ports that the application will use. HexOS can validate port availabilit
149148

150149
```json
151150
{
152-
"version": 2,
151+
"version": 4,
153152
"requirements": {
154153
"locations": [
155154
"ApplicationsPerformance",
@@ -234,19 +233,43 @@ Reference question responses in your `app_values` using the `$QUESTION(key)` syn
234233

235234
Question responses can be used in conditional logic with the `$IF` macro. See the [$IF macro documentation](/features/apps/install-scripts/reference/macros#if-condition-truevalue-falsevalue) for examples of using questions in conditional expressions.
236235

237-
## Directory Creation
238-
- **String format**: Simple path string
239-
- **Object format**:
240-
- `path`: Directory path (required)
241-
- `network_share`: Boolean, whether to expose as network share
242-
- `posix`: Boolean, whether to use POSIX permissions
243-
244-
## Permission Management
245-
Required for apps that need specific user/group permissions (like PostgreSQL).
246-
- `path`: Directory path to modify
247-
- `username`: User to grant access to
248-
- `access`: Access level ("read", "write", etc.)
249-
- `posix`: Object with additional POSIX settings (e.g., `groupname`)
236+
## Directory Creation, Ownership, and Snapshots
237+
238+
Each entry in `ensure_directories_exists` is an object with the following properties:
239+
240+
- `path` (required): Directory path, typically using `$LOCATION()` macros
241+
- `network_share` (optional): Boolean, whether to expose as a network share
242+
- `owner` (optional): Object specifying the TrueNAS user and group that should own this directory
243+
- `user` (required): TrueNAS username (e.g., `"apps"`, `"netdata"`)
244+
- `group` (optional): TrueNAS group name (e.g., `"docker"`). If omitted, uses the user's default group
245+
- `snapshot` (optional): Object with an `id` field. When present, HexOS snapshots this dataset before app updates so support can assist with restoring your application and data if something goes wrong
246+
- `id` (required): Identifier included in the snapshot name and metadata (e.g., `"db"`, `"config"`)
247+
248+
**Example:**
249+
```json
250+
{
251+
"ensure_directories_exists": [
252+
{ "path": "$LOCATION(Photos)", "network_share": true },
253+
{ "path": "$LOCATION(ApplicationsPerformance)", "network_share": true },
254+
{ "path": "$LOCATION(Photos)/immich", "owner": { "user": "apps" }, "snapshot": { "id": "data" } },
255+
{ "path": "$LOCATION(ApplicationsPerformance)/immich/postgres_data", "owner": { "user": "netdata", "group": "docker" }, "snapshot": { "id": "db" } },
256+
{ "path": "$LOCATION(ApplicationsPerformance)/immich/config", "owner": { "user": "apps" }, "snapshot": { "id": "config" } }
257+
]
258+
}
259+
```
260+
261+
**How `owner` works:**
262+
- HexOS calls `user.get_user_obj` and optionally `group.get_group_obj` on the TrueNAS system to resolve usernames and group names to numeric uid/gid
263+
- After `app.update` completes, HexOS verifies and repairs ownership on declared paths if TrueNAS changed it
264+
- If a path has a POSIX1E ACL (legacy), HexOS automatically migrates it to NFS4 with `aclmode: PASSTHROUGH`, snapshots the dataset first as a rollback point, then applies the canonical ACL with the declared uid/gid
265+
- Only applies to app-specific paths (4+ path segments, e.g., `/mnt/pool/location/app/data`) — location roots are never modified
266+
- Paths without `owner` are created with default permissions and not tracked for repair
267+
268+
**How `snapshot` works:**
269+
- Before any app update, HexOS snapshots each dataset with a `snapshot` config so support can assist with restoring your application and data if something goes wrong
270+
- Snapshots are named `hexos-app-{appId}-{id}-{timestamp}` and stamped with metadata: `hexos:purpose`, `hexos:app`, `hexos:snapshot_id`, `hexos:path`
271+
- Only the latest 3 snapshots per app per dataset are kept — older snapshots are automatically pruned after each new snapshot
272+
- Only applies to app-specific paths (4+ path segments) — location roots are never snapshotted
250273

251274
## App Values
252275
This object is passed directly to TrueNAS's app installation API. The structure varies by application and corresponds to the app's configuration schema in the [TrueNAS apps repository](https://github.com/truenas/apps). For example, you can see Plex's schema for the `storage` property [here](https://github.com/truenas/apps/blob/1d2a6e9811f9af2ceae6529cc094a432a7da4e96/trains/stable/plex/app_versions.json#L422).

docs/public/install-scripts/bazarr.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"version": 3,
2+
"version": 4,
33
"script": {
4-
"version": "1.0.1",
5-
"changeLog": "Updated requirements"
4+
"version": "1.1.0",
5+
"changeLog": "Upgraded to V4 install script format"
66
},
77
"requirements": {
88
"locations": [
@@ -37,7 +37,10 @@
3737
"path": "$LOCATION(Shows)",
3838
"network_share": true
3939
},
40-
"$LOCATION(ApplicationsPerformance)/bazarr/config"
40+
{
41+
"path": "$LOCATION(ApplicationsPerformance)/bazarr/config",
42+
"snapshot": { "id": "config" }
43+
}
4144
],
4245
"app_values": {
4346
"storage": {
@@ -61,4 +64,4 @@
6164
}
6265
}
6366
}
64-
}
67+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"version": 4,
3+
"script": {
4+
"version": "1.0.0",
5+
"changeLog": "Initial"
6+
},
7+
"requirements": {
8+
"locations": ["ApplicationsPerformance", "ApplicationsCapacity"],
9+
"specifications": ["2CORE", "256MB"],
10+
"permissions": ["READ_WRITE_LOCATIONS"],
11+
"ports": [30412]
12+
},
13+
"ensure_directories_exists": [
14+
{
15+
"path": "$LOCATION(ApplicationsPerformance)",
16+
"network_share": true
17+
},
18+
{
19+
"path": "$LOCATION(ApplicationsCapacity)",
20+
"network_share": true
21+
},
22+
{ "path": "$LOCATION(ApplicationsCapacity)/blinko/data", "owner": { "user": "apps" }, "snapshot": { "id": "data" } },
23+
{ "path": "$LOCATION(ApplicationsPerformance)/blinko/postgres_data", "owner": { "user": "netdata", "group": "docker" }, "snapshot": { "id": "db" } }
24+
],
25+
"app_values": {
26+
"blinko": {
27+
"db_password": "$RANDOM_STRING(7)",
28+
"nextauth_secret": "$RANDOM_STRING(7)",
29+
"additional_envs": []
30+
},
31+
"network": {
32+
"web_port": {
33+
"bind_mode": "published",
34+
"port_number": 30412
35+
}
36+
},
37+
"storage": {
38+
"data": "$HOST_PATH($LOCATION(ApplicationsCapacity)/blinko/data)",
39+
"postgres_data": "$HOST_PATH($LOCATION(ApplicationsPerformance)/blinko/postgres_data)"
40+
},
41+
"resources": {
42+
"limits": {
43+
"cpus": 2,
44+
"memory": "$MEMORY(10%, 4096)"
45+
}
46+
}
47+
}
48+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"version": 4,
3+
"script": {
4+
"version": "1.0.0",
5+
"changeLog": "initial"
6+
},
7+
"requirements": {
8+
"locations": [],
9+
"specifications": ["2CORE", "1024MB"],
10+
"permissions": [],
11+
"ports": [30064]
12+
},
13+
"ensure_directories_exists": [],
14+
"app_values": {
15+
"network": {
16+
"web_port": {
17+
"bind_mode": "published",
18+
"port_number": 30064
19+
}
20+
},
21+
"resources": {
22+
"limits": {
23+
"cpus": 2,
24+
"memory": "$MEMORY(5%, 1024)"
25+
}
26+
}
27+
}
28+
}
Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
{
2-
"version": 3,
3-
"script": {
4-
"version": "1.0.1",
5-
"changeLog": "Updated requirements"
2+
"version": 4,
3+
"script": {
4+
"version": "1.1.0",
5+
"changeLog": "Upgraded to V4 install script format"
6+
},
7+
"requirements": {
8+
"locations": [],
9+
"specifications": ["2CORE", "1024MB"],
10+
"permissions": [],
11+
"ports": [30090, 30091]
12+
},
13+
"ensure_directories_exists": [],
14+
"app_values": {
15+
"network": {
16+
"http_port": {
17+
"bind_mode": "published",
18+
"port_number": 30090
19+
},
20+
"https_port": {
21+
"bind_mode": "published",
22+
"port_number": 30091
23+
}
624
},
7-
"requirements": {
8-
"locations": [],
9-
"specifications": ["2CORE", "1024MB"],
10-
"permissions": [],
11-
"ports": [30090, 30091]
12-
},
13-
"ensure_directories_exists": [],
14-
"app_values": {
15-
"network": {
16-
"http_port": {
17-
"bind_mode": "published",
18-
"port_number": 30090
19-
},
20-
"https_port": {
21-
"bind_mode": "published",
22-
"port_number": 30091
23-
}
24-
},
25-
"resources": {
26-
"limits": {
27-
"cpus": 2,
28-
"memory": "$MEMORY(5%, 1024)"
29-
}
30-
}
25+
"resources": {
26+
"limits": {
27+
"cpus": 2,
28+
"memory": "$MEMORY(5%, 1024)"
29+
}
3130
}
32-
}
31+
}
32+
}

docs/public/install-scripts/emby.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"version": 3,
2+
"version": 4,
33
"script": {
4-
"version": "1.0.2",
5-
"changeLog": "Updated requirements"
4+
"version": "1.1.0",
5+
"changeLog": "Upgraded to V4 install script format"
66
},
77
"requirements": {
88
"locations": ["ApplicationsPerformance", "ApplicationsCapacity", "Media", "Photos", "Music", "Movies", "Shows", "Videos"],
@@ -43,9 +43,9 @@
4343
"path": "$LOCATION(Videos)",
4444
"network_share": true
4545
},
46-
"$LOCATION(ApplicationsPerformance)/emby/config",
47-
"$LOCATION(ApplicationsPerformance)/emby/cache",
48-
"$LOCATION(ApplicationsPerformance)/emby/transcode"
46+
{ "path": "$LOCATION(ApplicationsPerformance)/emby/config", "snapshot": { "id": "config" } },
47+
{ "path": "$LOCATION(ApplicationsPerformance)/emby/cache" },
48+
{ "path": "$LOCATION(ApplicationsPerformance)/emby/transcode" }
4949
],
5050
"app_values": {
5151
"storage": {
@@ -79,4 +79,4 @@
7979
"gpus": "$GPU_CONFIG()"
8080
}
8181
}
82-
}
82+
}

0 commit comments

Comments
 (0)