Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bc5249b
lubelogger install script
acknologia May 1, 2026
f0707b3
navidrome install script
acknologia May 1, 2026
4f5efdd
Update install script schema to V4 with better permission management …
jerodfritz May 15, 2026
b3fb20f
Update install script owner schema for explicit user and group
jerodfritz May 15, 2026
e8991b0
pgdata->db for clarity
jerodfritz May 15, 2026
4a99e28
Merge pull request #240 from eshtek/feature/install-script-changes
jerodfritz May 15, 2026
0e8da8f
blinko v4 script
acknologia May 18, 2026
61840ce
dozzle v4 script
acknologia May 18, 2026
33c3d0b
excalidraw v4 script
acknologia May 18, 2026
91b4e07
portracker v4 script
acknologia May 18, 2026
ccf15f5
updated to v4
acknologia May 18, 2026
94a7cbb
v4 script
acknologia May 18, 2026
ad5869e
Merge pull request #258 from acknologia/blinko-v4-script
acknologia May 18, 2026
90fe1db
Merge branch 'dev' into dozzle-v4-script
acknologia May 18, 2026
409298c
Merge pull request #255 from acknologia/dozzle-v4-script
acknologia May 18, 2026
8dae7a7
Merge pull request #254 from acknologia/excalidraw-v4-script
acknologia May 18, 2026
8acfcb1
Merge branch 'dev' into lubelogger-install-script
acknologia May 18, 2026
284cbec
Merge pull request #233 from acknologia/lubelogger-install-script
acknologia May 18, 2026
c1d334a
Merge branch 'dev' into navidrome-install-script
acknologia May 18, 2026
5e0a5df
Merge pull request #232 from acknologia/navidrome-install-script
acknologia May 18, 2026
30d51c4
Merge pull request #251 from acknologia/portracker-v4-script
acknologia May 19, 2026
900592b
5/20/26 release notes
acknologia May 19, 2026
5572cd2
updates to blinko and nextcloud
acknologia May 20, 2026
8439374
updated to app index
acknologia May 20, 2026
1fafeaa
Merge pull request #270 from acknologia/v4-script-fixes
acknologia May 20, 2026
d1af790
Merge branch 'dev' into 5/19/26-release-notes
jerodfritz May 20, 2026
778c2c6
Merge pull request #268 from acknologia/5/19/26-release-notes
jerodfritz May 20, 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
1 change: 1 addition & 0 deletions docs/.vitepress/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ const sidebar: DefaultTheme.SidebarItem[] = [
collapsed: true,
items: [
// auto-generated-release-notes-start
{ text: '2026-05-20', link: '/release-notes/command-deck/2026-05-20' },
{ text: '2026-05-13', link: '/release-notes/command-deck/2026-05-13' },
{ text: '2026-04-21', link: '/release-notes/command-deck/2026-04-21' },
{ text: '2026-03-27', link: '/release-notes/command-deck/2026-03-27' },
Expand Down
4 changes: 2 additions & 2 deletions docs/features/apps/install-scripts/advanced/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ If an install scripts fails, this will help:

#### Permission Errors
- **Symptom**: App fails to start, logs show permission denied errors
- **Solution**: Add appropriate entries to `ensure_permissions_exists` in your install script
- **Example**: PostgreSQL requires specific user/group permissions
- **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" }`)
- **Example**: PostgreSQL data directories typically require `"owner": { "user": "netdata", "group": "docker" }`

#### Missing Directories
- **Symptom**: App fails during installation, "directory not found" errors
Expand Down
40 changes: 23 additions & 17 deletions docs/features/apps/install-scripts/curated/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@
<!-- curated:index:start -->
| App | Download | Size | Last Modified |
|---|---|---:|---|
| `bazarr` | [bazarr.json](/install-scripts/bazarr.json) | 1.3 KB | 2026-03-15 |
| `drawio` | [drawio.json](/install-scripts/drawio.json) | 757 B | 2026-03-15 |
| `emby` | [emby.json](/install-scripts/emby.json) | 2.2 KB | 2026-03-15 |
| `handbrake` | [handbrake.json](/install-scripts/handbrake.json) | 1.8 KB | 2026-03-15 |
| `home-assistant` | [home-assistant.json](/install-scripts/home-assistant.json) | 1.6 KB | 2026-03-19 |
| `immich` | [immich.json](/install-scripts/immich.json) | 1.7 KB | 2026-03-15 |
| `jellyfin` | [jellyfin.json](/install-scripts/jellyfin.json) | 2.2 KB | 2026-03-15 |
| `lidarr` | [lidarr.json](/install-scripts/lidarr.json) | 1.4 KB | 2026-03-15 |
| `nextcloud` | [nextcloud.json](/install-scripts/nextcloud.json) | 3.5 KB | 2026-03-15 |
| `peanut` | [peanut.json](/install-scripts/peanut.json) | 834 B | 2026-03-15 |
| `plex` | [plex.json](/install-scripts/plex.json) | 3.4 KB | 2025-12-12 |
| `prowlarr` | [prowlarr.json](/install-scripts/prowlarr.json) | 766 B | 2025-12-04 |
| `qbittorrent` | [qbittorrent.json](/install-scripts/qbittorrent.json) | 1.0 KB | 2025-12-04 |
| `radarr` | [radarr.json](/install-scripts/radarr.json) | 1.2 KB | 2025-12-04 |
| `scrutiny` | [scrutiny.json](/install-scripts/scrutiny.json) | 1.3 KB | 2026-03-15 |
| `sonarr` | [sonarr.json](/install-scripts/sonarr.json) | 1.2 KB | 2025-12-04 |
| `syncthing` | [syncthing.json](/install-scripts/syncthing.json) | 2.5 KB | 2026-05-05 |
| `bazarr` | [bazarr.json](/install-scripts/bazarr.json) | 1.4 KB | 2026-05-15 |
| `blinko` | [blinko.json](/install-scripts/blinko.json) | 1.3 KB | 2026-05-20 |
| `dozzle` | [dozzle.json](/install-scripts/dozzle.json) | 614 B | 2026-05-18 |
| `drawio` | [drawio.json](/install-scripts/drawio.json) | 634 B | 2026-05-15 |
| `emby` | [emby.json](/install-scripts/emby.json) | 2.3 KB | 2026-05-15 |
| `excalidraw` | [excalidraw.json](/install-scripts/excalidraw.json) | 614 B | 2026-05-18 |
| `handbrake` | [handbrake.json](/install-scripts/handbrake.json) | 1.9 KB | 2026-05-15 |
| `home-assistant` | [home-assistant.json](/install-scripts/home-assistant.json) | 1.4 KB | 2026-05-15 |
| `immich` | [immich.json](/install-scripts/immich.json) | 1.6 KB | 2026-05-15 |
| `jellyfin` | [jellyfin.json](/install-scripts/jellyfin.json) | 2.3 KB | 2026-05-15 |
| `lidarr` | [lidarr.json](/install-scripts/lidarr.json) | 1.4 KB | 2026-05-15 |
| `lubelogger` | [lubelogger.json](/install-scripts/lubelogger.json) | 1.5 KB | 2026-05-18 |
| `navidrome` | [navidrome.json](/install-scripts/navidrome.json) | 5.1 KB | 2026-05-18 |
| `nextcloud` | [nextcloud.json](/install-scripts/nextcloud.json) | 3.4 KB | 2026-05-20 |
| `peanut` | [peanut.json](/install-scripts/peanut.json) | 911 B | 2026-05-15 |
| `plex` | [plex.json](/install-scripts/plex.json) | 3.4 KB | 2026-05-15 |
| `portracker` | [portracker.json](/install-scripts/portracker.json) | 894 B | 2026-05-18 |
| `prowlarr` | [prowlarr.json](/install-scripts/prowlarr.json) | 781 B | 2026-05-15 |
| `qbittorrent` | [qbittorrent.json](/install-scripts/qbittorrent.json) | 1.0 KB | 2026-05-15 |
| `radarr` | [radarr.json](/install-scripts/radarr.json) | 1.3 KB | 2026-05-15 |
| `scrutiny` | [scrutiny.json](/install-scripts/scrutiny.json) | 1.4 KB | 2026-05-15 |
| `sonarr` | [sonarr.json](/install-scripts/sonarr.json) | 1.3 KB | 2026-05-15 |
| `syncthing` | [syncthing.json](/install-scripts/syncthing.json) | 2.6 KB | 2026-05-15 |
<!-- curated:index:end -->
6 changes: 4 additions & 2 deletions docs/features/apps/install-scripts/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ For apps not yet curated or when you need to customize the configuration:
### Best Practices and Common Pitfalls

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

Expand Down
61 changes: 42 additions & 19 deletions docs/features/apps/install-scripts/reference/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ Install scripts are JSON objects with the following structure. Scripts can use v

## Root Properties

- **`version`** (required): Schema version. Must be `3` or higher (currently latest supported version).
- **`version`** (required): Schema version. Must be `4` (current required version). Versions 1-3 are deprecated.
- **`script`** (required): Metadata about the install script itself
- **`version`** (required): Semantic version of this install script (e.g., "1.0.0", "2.1.3")
- **`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"`
- **`changeLog`** (optional): Description of changes in this version of the script
- **`requirements`** (required): System requirements that are validated before installation
- **`installation_questions`** (optional): Array of questions to ask the user during installation
- **`ensure_directories_exists`** (optional): Array of directories to create before installation
- **`ensure_permissions_exists`** (optional): Array of permission modifications for specific paths
- **`ensure_directories_exists`** (optional): Array of directory entry objects to create before installation, with optional ownership and snapshot declarations
- **`app_values`** (required): Configuration object passed directly to TrueNAS API

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

```json
{
"version": 2,
"version": 4,
"requirements": {
"locations": [
"ApplicationsPerformance",
Expand Down Expand Up @@ -234,19 +233,43 @@ Reference question responses in your `app_values` using the `$QUESTION(key)` syn

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.

## Directory Creation
- **String format**: Simple path string
- **Object format**:
- `path`: Directory path (required)
- `network_share`: Boolean, whether to expose as network share
- `posix`: Boolean, whether to use POSIX permissions

## Permission Management
Required for apps that need specific user/group permissions (like PostgreSQL).
- `path`: Directory path to modify
- `username`: User to grant access to
- `access`: Access level ("read", "write", etc.)
- `posix`: Object with additional POSIX settings (e.g., `groupname`)
## Directory Creation, Ownership, and Snapshots

Each entry in `ensure_directories_exists` is an object with the following properties:

- `path` (required): Directory path, typically using `$LOCATION()` macros
- `network_share` (optional): Boolean, whether to expose as a network share
- `owner` (optional): Object specifying the TrueNAS user and group that should own this directory
- `user` (required): TrueNAS username (e.g., `"apps"`, `"netdata"`)
- `group` (optional): TrueNAS group name (e.g., `"docker"`). If omitted, uses the user's default group
- `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
- `id` (required): Identifier included in the snapshot name and metadata (e.g., `"db"`, `"config"`)

**Example:**
```json
{
"ensure_directories_exists": [
{ "path": "$LOCATION(Photos)", "network_share": true },
{ "path": "$LOCATION(ApplicationsPerformance)", "network_share": true },
{ "path": "$LOCATION(Photos)/immich", "owner": { "user": "apps" }, "snapshot": { "id": "data" } },
{ "path": "$LOCATION(ApplicationsPerformance)/immich/postgres_data", "owner": { "user": "netdata", "group": "docker" }, "snapshot": { "id": "db" } },
{ "path": "$LOCATION(ApplicationsPerformance)/immich/config", "owner": { "user": "apps" }, "snapshot": { "id": "config" } }
]
}
```

**How `owner` works:**
- 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
- After `app.update` completes, HexOS verifies and repairs ownership on declared paths if TrueNAS changed it
- 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
- Only applies to app-specific paths (4+ path segments, e.g., `/mnt/pool/location/app/data`) — location roots are never modified
- Paths without `owner` are created with default permissions and not tracked for repair

**How `snapshot` works:**
- 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
- Snapshots are named `hexos-app-{appId}-{id}-{timestamp}` and stamped with metadata: `hexos:purpose`, `hexos:app`, `hexos:snapshot_id`, `hexos:path`
- Only the latest 3 snapshots per app per dataset are kept — older snapshots are automatically pruned after each new snapshot
- Only applies to app-specific paths (4+ path segments) — location roots are never snapshotted

## App Values
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).
Expand Down
13 changes: 8 additions & 5 deletions docs/public/install-scripts/bazarr.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"version": 3,
"version": 4,
"script": {
"version": "1.0.1",
"changeLog": "Updated requirements"
"version": "1.1.0",
"changeLog": "Upgraded to V4 install script format"
},
"requirements": {
"locations": [
Expand Down Expand Up @@ -37,7 +37,10 @@
"path": "$LOCATION(Shows)",
"network_share": true
},
"$LOCATION(ApplicationsPerformance)/bazarr/config"
{
"path": "$LOCATION(ApplicationsPerformance)/bazarr/config",
"snapshot": { "id": "config" }
}
],
"app_values": {
"storage": {
Expand All @@ -61,4 +64,4 @@
}
}
}
}
}
48 changes: 48 additions & 0 deletions docs/public/install-scripts/blinko.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"version": 4,
"script": {
"version": "1.0.0",
"changeLog": "Initial"
},
"requirements": {
"locations": ["ApplicationsPerformance", "ApplicationsCapacity"],
"specifications": ["2CORE", "256MB"],
"permissions": ["READ_WRITE_LOCATIONS"],
"ports": [30412]
},
"ensure_directories_exists": [
{
"path": "$LOCATION(ApplicationsPerformance)",
"network_share": true
},
{
"path": "$LOCATION(ApplicationsCapacity)",
"network_share": true
},
{ "path": "$LOCATION(ApplicationsCapacity)/blinko/data", "owner": { "user": "apps" }, "snapshot": { "id": "data" } },
{ "path": "$LOCATION(ApplicationsPerformance)/blinko/postgres_data", "owner": { "user": "netdata", "group": "docker" }, "snapshot": { "id": "db" } }
],
"app_values": {
"blinko": {
"db_password": "$RANDOM_STRING(7)",
"nextauth_secret": "$RANDOM_STRING(7)",
"additional_envs": []
},
"network": {
"web_port": {
"bind_mode": "published",
"port_number": 30412
}
},
"storage": {
"data": "$HOST_PATH($LOCATION(ApplicationsCapacity)/blinko/data)",
"postgres_data": "$HOST_PATH($LOCATION(ApplicationsPerformance)/blinko/postgres_data)"
},
"resources": {
"limits": {
"cpus": 2,
"memory": "$MEMORY(10%, 4096)"
}
}
}
}
28 changes: 28 additions & 0 deletions docs/public/install-scripts/dozzle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": 4,
"script": {
"version": "1.0.0",
"changeLog": "initial"
},
"requirements": {
"locations": [],
"specifications": ["2CORE", "1024MB"],
"permissions": [],
"ports": [30064]
},
"ensure_directories_exists": [],
"app_values": {
"network": {
"web_port": {
"bind_mode": "published",
"port_number": 30064
}
},
"resources": {
"limits": {
"cpus": 2,
"memory": "$MEMORY(5%, 1024)"
}
}
}
}
58 changes: 29 additions & 29 deletions docs/public/install-scripts/drawio.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"version": 3,
"script": {
"version": "1.0.1",
"changeLog": "Updated requirements"
"version": 4,
"script": {
"version": "1.1.0",
"changeLog": "Upgraded to V4 install script format"
},
"requirements": {
"locations": [],
"specifications": ["2CORE", "1024MB"],
"permissions": [],
"ports": [30090, 30091]
},
"ensure_directories_exists": [],
"app_values": {
"network": {
"http_port": {
"bind_mode": "published",
"port_number": 30090
},
"https_port": {
"bind_mode": "published",
"port_number": 30091
}
},
"requirements": {
"locations": [],
"specifications": ["2CORE", "1024MB"],
"permissions": [],
"ports": [30090, 30091]
},
"ensure_directories_exists": [],
"app_values": {
"network": {
"http_port": {
"bind_mode": "published",
"port_number": 30090
},
"https_port": {
"bind_mode": "published",
"port_number": 30091
}
},
"resources": {
"limits": {
"cpus": 2,
"memory": "$MEMORY(5%, 1024)"
}
}
"resources": {
"limits": {
"cpus": 2,
"memory": "$MEMORY(5%, 1024)"
}
}
}
}
}
14 changes: 7 additions & 7 deletions docs/public/install-scripts/emby.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"version": 3,
"version": 4,
"script": {
"version": "1.0.2",
"changeLog": "Updated requirements"
"version": "1.1.0",
"changeLog": "Upgraded to V4 install script format"
},
"requirements": {
"locations": ["ApplicationsPerformance", "ApplicationsCapacity", "Media", "Photos", "Music", "Movies", "Shows", "Videos"],
Expand Down Expand Up @@ -43,9 +43,9 @@
"path": "$LOCATION(Videos)",
"network_share": true
},
"$LOCATION(ApplicationsPerformance)/emby/config",
"$LOCATION(ApplicationsPerformance)/emby/cache",
"$LOCATION(ApplicationsPerformance)/emby/transcode"
{ "path": "$LOCATION(ApplicationsPerformance)/emby/config", "snapshot": { "id": "config" } },
{ "path": "$LOCATION(ApplicationsPerformance)/emby/cache" },
{ "path": "$LOCATION(ApplicationsPerformance)/emby/transcode" }
],
"app_values": {
"storage": {
Expand Down Expand Up @@ -79,4 +79,4 @@
"gpus": "$GPU_CONFIG()"
}
}
}
}
Loading
Loading