Where exactly are Applications configuration stored? It's not in the Backup Sqlite!

TrueNAS 24.04 and 25.04 versions.

The application configurations (Host Path, ports, UID/GUID, ENVs, etc) does not appear contained within the TrueNAS Configuration Backup sqlite DB file. I poked through every table any did not see a list of Apps.

Using grep, I’m not able to find any specific configuration in the “ix-applications” dataset either. Mostly searching on some of my custom settings, like “/mnt/apps/unifi_controller” of my custom hosts path I have running.

Specifically, where is the Host Path stored per app configuration?

Where are the mounts stored for each app configuration (not system mounts)?

If an app has the options for UID/GID, where are these configurations stored? Also alongside that, where are the ENV variables stored for each installed app?

I see a lot of charts stored with templates and default configs within ‘ix-applications’; but, I’m not after those defaults but the installed and running configurations.


Why? App migrations from 24.04 → 24.10. This metadata is stored somewhere… I only need it listed, so I can re-create the apps manually.

Ah, it looks like I needed to backup one directory. The metadata info is stored in:

/mnt/.ix-apps/

Had to drill down quite a lot. It seems there are three files that get Yaml merged into one. The order seems to be:

/mnt/.ix-apps/metadata.yaml
/mnt/.ix-apps/app_configs/[app]/metadata.yaml
/mnt/.ix-apps/app_configs/[app]/versions/[version]/user_config.yaml

That last one is where the Mounts, ENVs, UID/GID, etc are all set, on a per app and per version basis.

All mount passwords in clear-text too. Doh… :man_facepalming:

Will need to find out how this is being backed up, since it’s not in the configuration file and is hidden from the ZFS Replication menus. That seems concerning? :eyes:


So basically, a migration would be to read and merge these files into context to migrate, per app. So it was possible to write a migration script from 24.04 to 24.10.

I do not plan on this, as I just needed the details listed for my manual recreation of apps in 25.04. PITA

See

Its been a request for a while and a concern of mine

3 Likes

It’s a large contributing factor to me choosing to go the yaml-route instead of Custom app using the GUI wizard.

With my yaml’s stored locally I can trivially edit the config and just paste it into the apps window. Depending on where you store the configs outside of TrueNAS you may even get version control.

Used AI to generate this brief script to extract all the app data for me to quickly rebuild each app in 25.04, etc.

  1. log into your truenas and open a shell, elevate to root (sudo su), and change to /mnt/.ix-apps/app_configs/ or wherever your .ix-apps is located.
  2. Personally, I zipped up and copied it to my local machine. 7z a app_configs.7z * and then scp app_configs.7z user@laptop-ip:~/.
  3. Or, just can copy the script to this directory and just run it. Even though it’s read-only, I prefer not to run scripts on my production boxes.
#!/usr/bin/env python3
import os
import yaml
from packaging import version
from jinja2 import Template

# ----------------------------
# CONFIG: Keys to extract
# ----------------------------
KEYS_TO_EXTRACT = [
    "run_as/user",
    "run_as/group",
    "ix_context/app_metadata/run_as_context/0/uid",
    "ix_context/app_metadata/run_as_context/0/gid",
    "ix_context/app_metadata/run_as_context/1/uid",
    "ix_context/app_metadata/run_as_context/1/gid",
    "ix_context/app_metadata/run_as_context/2/uid",
    "ix_context/app_metadata/run_as_context/2/gid",
    "network/peer_port",
    "network/web_port",
    "storage/config/host_path_config/path",
    "resources/limits/cpus",
    "resources/limits/memory",
]

# Optional Jinja2 template (can be customized)
OUTPUT_TEMPLATE = """\
{% for app, values in results.items() %}
{{ values["app_name"] }}{% for k, v in values["data"].items() %}
  {{ k }}: {{ v }}{% endfor %}{% if values["storage"] %}
  storage:{% for sk, sv in values["storage"].items() %}
    {{ sk }}: {{ sv }}{% endfor %}{% endif %}
{% endfor %}
"""

# ----------------------------
# Helpers
# ----------------------------
def find_highest_version_dir(versions_path):
    """Return the highest version directory in versions_path."""
    dirs = [
        d for d in os.listdir(versions_path)
        if os.path.isdir(os.path.join(versions_path, d))
    ]
    if not dirs:
        return None
    dirs.sort(key=version.parse, reverse=True)
    return dirs[0]

def deep_get(dictionary, path):
    """Safely get a value from nested dict/list using slash-separated path."""
    keys = path.split("/")
    current = dictionary
    for key in keys:
        if isinstance(current, dict):
            current = current.get(key)
        elif isinstance(current, list):
            try:
                idx = int(key)
                current = current[idx]
            except (ValueError, IndexError):
                return None
        else:
            return None
    return current

def flatten_storage(storage_dict):
    """Flatten storage section into key:value lines for display."""
    results = {}
    if not isinstance(storage_dict, dict):
        return results

    for name, entry in storage_dict.items():
        if isinstance(entry, dict):
            for subk, subv in entry.items():
                if isinstance(subv, dict):
                    for k2, v2 in subv.items():
                        results[f"{name}/{subk}/{k2}"] = v2
                else:
                    results[f"{name}/{subk}"] = subv
        elif isinstance(entry, list):
            # e.g. "additional_storage"
            for i, item in enumerate(entry):
                if isinstance(item, dict):
                    for subk, subv in item.items():
                        if isinstance(subv, dict):
                            for k2, v2 in subv.items():
                                results[f"{name}/{i}/{subk}/{k2}"] = v2
                        else:
                            results[f"{name}/{i}/{subk}"] = subv
                else:
                    results[f"{name}/{i}"] = item
        else:
            results[name] = entry
    return results

# ----------------------------
# Main
# ----------------------------
def main():
    base_dir = "./app_configs"
    results = {}

    for app in os.listdir(base_dir):
        app_path = os.path.join(base_dir, app, "versions")
        if not os.path.isdir(app_path):
            continue

        latest_version = find_highest_version_dir(app_path)
        if not latest_version:
            continue

        yaml_file = os.path.join(app_path, latest_version, "user_config.yaml")
        if not os.path.isfile(yaml_file):
            continue

        with open(yaml_file, "r") as f:
            config = yaml.safe_load(f)

        app_name = deep_get(config, "ix_context/app_name") or app
        data = {}
        for key in KEYS_TO_EXTRACT:
            value = deep_get(config, key)
            data[key] = value

        storage_flat = flatten_storage(config.get("storage", {}))

        results[app] = {
            "app_name": app_name,
            "data": data,
            "storage": storage_flat,
        }

    template = Template(OUTPUT_TEMPLATE)
    print(template.render(results=results))

if __name__ == "__main__":
    main()

Output looks like:

readarr
  run_as/user: 4005
  run_as/group: 4005
  ix_context/app_metadata/run_as_context/0/uid: 568
  ix_context/app_metadata/run_as_context/0/gid: 568
  ix_context/app_metadata/run_as_context/1/uid: None
  ix_context/app_metadata/run_as_context/1/gid: None
  ix_context/app_metadata/run_as_context/2/uid: None
  ix_context/app_metadata/run_as_context/2/gid: None
  network/peer_port: None
  network/web_port: 30045
  storage/config/host_path_config/path: /mnt/apps/readarr
  resources/limits/cpus: 2
  resources/limits/memory: 4096
  storage:
    config/host_path_config/acl_enable: False
    config/host_path_config/path: /mnt/apps/readarr
    config/type: host_path

audiobookshelf
  run_as/user: 4006
  run_as/group: 4006
  ix_context/app_metadata/run_as_context/0/uid: 568
  ix_context/app_metadata/run_as_context/0/gid: 568
  ix_context/app_metadata/run_as_context/1/uid: None
  ix_context/app_metadata/run_as_context/1/gid: None
  ix_context/app_metadata/run_as_context/2/uid: None
  ix_context/app_metadata/run_as_context/2/gid: None
  network/peer_port: None
  network/web_port: 30067
  storage/config/host_path_config/path: /mnt/apps/audiobookshelf/config
  resources/limits/cpus: 2
  resources/limits/memory: 4096
  storage:
    additional_storage/0/cifs_config/domain: 
    additional_storage/0/cifs_config/password: (snip)
    additional_storage/0/cifs_config/path: media
    additional_storage/0/cifs_config/server: 10.10.94.1
    additional_storage/0/cifs_config/username: media-ro
    additional_storage/0/mount_path: /mnt/media
    additional_storage/0/read_only: True
    additional_storage/0/type: cifs
    config/host_path_config/acl_enable: False
    config/host_path_config/path: /mnt/apps/audiobookshelf/config
    config/type: host_path
    metadata/host_path_config/acl_enable: False
    metadata/host_path_config/path: /mnt/apps/audiobookshelf/metadata
    metadata/type: host_path

NOTE: It won’t output app specific settings, like “n8n” has a bunch of additional info. For those, I just open the user_config.yaml for that one service and grab the settings. Otherwise, I’m just going down the list pretty fast now.

I’m going to run it right now without checking it line by line! :+1:

2 Likes

That’s what snapshots in Virtual Machine Manager test install are for :grinning:

1 Like

This is exactly why I use jailmaker: can backup the config.

1 Like

Well, can say I’ve lost probably dozens of hours and well over a year of home-assistant data because of this absolute atrocity because the password to my old HA app database is now just gone. There was absolutely zero warning when updating from 24.04 to 24.10 (a minor version upgrade, so why in the ever living f**k does it have breaking changes). Frankly, the faster I can find something to replace TrueNAS now the better, I have lost absolutely all confidence in this software and its maintenance.