truenas-unlock 1.7.0
Unlock encrypted ZFS datasets on TrueNAS via the API
License: MIT
Requires Python: >=3.11
Keywords: encryption, nas, truenas, unlock, zfs

# TrueNAS Unlock

[![PyPI](https://img.shields.io/pypi/v/truenas-unlock)](https://pypi.org/project/truenas-unlock/)
[![Python](https://img.shields.io/pypi/pyversions/truenas-unlock)](https://pypi.org/project/truenas-unlock/)
[![Tests](https://github.com/basnijholt/truenas-unlock/actions/workflows/pytest.yml/badge.svg)](https://github.com/basnijholt/truenas-unlock/actions/workflows/pytest.yml)
[![License](https://img.shields.io/github/license/basnijholt/truenas-unlock)](LICENSE)

Unlock encrypted ZFS datasets on TrueNAS via the API.

https://github.com/user-attachments/assets/172c8fd7-5b66-4c5b-bae0-32e378e9305a

## Why?

TrueNAS supports encrypted ZFS datasets, but:

1. **Storing keys on the NAS defeats the purpose**—if it's stolen, the thief has both the encrypted data and the keys
2. **Manual unlocking is tedious**—after every reboot, you need to manually decrypt each dataset through the UI

This tool solves both problems with a **"poor-man's second-factor"** setup:

1. Run `truenas-unlock` on a **separate device** (Raspberry Pi, home server, etc.)
2. Store encryption keys **only on that device**
3. Datasets auto-unlock when both devices are on the network
4. If the NAS is stolen, data remains encrypted and inaccessible

Think of it as a hardware security key for your storage—hidden somewhere in your house, it automatically unlocks your datasets whenever your TrueNAS boots. No manual intervention required.

## Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Docker](#docker)
  - [Shell Alias](#shell-alias)
- [Install](#install)
- [Setup](#setup)
- [Usage](#usage)
- [CLI](#cli)
- [Running as a Service](#running-as-a-service)
- [Development](#development)
- [Credits](#credits)
- [License](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Docker

Run once:

```bash
docker run --rm -it \
  -v ~/.config/truenas-unlock/config.yaml:/app/config.yaml \
  ghcr.io/basnijholt/truenas-unlock -c /app/config.yaml
```

Run as daemon (recommended for auto-unlock on boot):

```bash
docker run -d --restart=unless-stopped \
  --name truenas-unlock \
  -v ~/.config/truenas-unlock/config.yaml:/app/config.yaml \
  ghcr.io/basnijholt/truenas-unlock -c /app/config.yaml --daemon
```

### Shell Alias

Add to your `~/.bashrc` or `~/.zshrc`:

```bash
alias truenas-unlock='docker run --rm -it \
  -v ~/.config/truenas-unlock/config.yaml:/app/config.yaml \
  ghcr.io/basnijholt/truenas-unlock -c /app/config.yaml'
```

## Install

```bash
# With uv (recommended)
uv tool install truenas-unlock

# With pip
pip install truenas-unlock
```

## Setup

Create an API key at `http://truenas.local/ui/credentials/users/api-keys` (replace with your TrueNAS hostname).

Then create `~/.config/truenas-unlock/config.yaml`:

```yaml
host: 192.168.1.214:443
api_key: ~/.secrets/truenas-api-key  # file path or literal
skip_cert_verify: true

# secrets: auto  # auto (default) | files | inline
# truenas_version: "25.04"  # optional: skip version detection API call

datasets:
  tank/syncthing: ~/.secrets/syncthing-key  # reads from file
  tank/photos: my-literal-passphrase        # used as-is (no such file)
```

The `truenas_version` field is optional. When specified (e.g., `"25.04"` or `"24.10"`), it skips the automatic version detection API call. This is useful if you know your TrueNAS version and want to reduce API calls. TrueNAS 25.04+ uses a different unlock API than older versions.

The `secrets` mode controls how values are interpreted:
- **auto** (default): if file exists, read from it; otherwise use as literal
- **files**: always treat values as file paths
- **inline**: always treat values as literal secrets

## Usage

```bash
# Run once
truenas-unlock

# Run as daemon
# (Checks every 1s if TrueNAS is unreachable, otherwise every 30s)
truenas-unlock --daemon

# Custom interval (for the "relaxed" state)
truenas-unlock --daemon --interval 60

# Dry run
truenas-unlock --dry-run
```

## CLI

```bash
truenas-unlock --help
```

<!-- CODE:BASH:START -->
<!-- export NO_COLOR=1 -->
<!-- export TERM=dumb -->
<!-- export TERMINAL_WIDTH=90 -->
<!-- echo '```bash' -->
<!-- truenas-unlock --help -->
<!-- echo '```' -->
<!-- CODE:END -->

<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
```bash

 Usage: truenas-unlock [OPTIONS] COMMAND [ARGS]...

 Unlock TrueNAS ZFS datasets

╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --config    -c      PATH     Config file path                                │
│ --dry-run   -n               Show what would be done                         │
│ --daemon    -d               Run continuously                                │
│ --interval  -i      INTEGER  Seconds between checks (1s if unreachable)      │
│                              [default: 30]                                   │
│ --dataset   -D      TEXT     Filter by dataset path                          │
│ --version   -v               Show version and exit                           │
│ --help      -h               Show this message and exit.                     │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ lock      Lock configured datasets.                                          │
│ status    Show lock status of configured datasets.                           │
│ service   Manage system service                                              │
╰──────────────────────────────────────────────────────────────────────────────╯

```

<!-- OUTPUT:END -->



## Running as a Service

Requires [uv](https://docs.astral.sh/uv/) to be installed. Auto-detects Linux (systemd) or macOS (launchd):

```bash
# Install and start
truenas-unlock service install

# Check status
truenas-unlock service status

# View logs (follows by default)
truenas-unlock service logs

# Uninstall
truenas-unlock service uninstall
```

## Development

```bash
# Clone and install
git clone https://github.com/basnijholt/truenas-unlock
cd truenas-unlock
uv sync --dev

# Run tests
uv run pytest

# Run lints
uv run ruff check .
uv run mypy truenas_unlock.py
```

## Credits

Inspired by [ThorpeJosh/truenas-zfs-unlock](https://github.com/ThorpeJosh/truenas-zfs-unlock).

## License

MIT