Skip to content

Initial Setup

This guide walks through the day-zero steps required to bring a fresh CineVault server online. By the end you will have cloned the repository, populated environment secrets, configured the Gluetun VPN client, set the Ansible inventory, and run the master provisioning playbook against The Host.

!!! note "Prerequisites" A freshly installed Debian 13 "Trixie" host with SSH enabled and an ansible service account is assumed. See the Setting up the host for bare-metal provisioning details.

1. Clone the Repository

Clone CineVault onto your workstation (not The Host — Ansible will push from your workstation to The Host over SSH).

git clone https://github.com/your-org/CineVault.git
cd CineVault

2. Create and Populate .env

Copy the template and open the resulting .env file in your editor of choice.

cp .env.example .env

The following variables must be populated before the stack can come up.

2.1. TUNNEL_TOKEN — Cloudflare Zero Trust (REQUIRED)

!!! danger "Absolutely Required" TUNNEL_TOKEN is the sole credential that authorises cloudflared to establish The Ingress. Without it, the stack has no remote access path and the Cloudflare tunnel container will refuse to start.

To obtain the token:

  1. Log in to the Cloudflare Zero Trust dashboard.
  2. Navigate to Networks → Tunnels.
  3. Click Create a tunnel (or select an existing one).
  4. Choose Cloudflared as the connector type and click Save tunnel.
  5. On the Install and run a connector screen, copy the long token string from the displayed cloudflared service install <TOKEN> command.
  6. Paste it into .env:
TUNNEL_TOKEN=eyJhIjoi...your-very-long-token-here...

2.2. PUID and PGID — Host User & Group IDs

These IDs map every container's internal user to the mediasvc service account on The Host, ensuring file permissions on bind-mounted volumes are correct and that no container ever runs as root.

PUID=5000
PGID=5000

!!! warning "Must Match mediasvc" The default values 5000/5000 correspond to the mediasvc user provisioned by ansible/playbooks/provision_host.yml. If you change these, you must also change the canonical values in the playbook, or file ownership will break.

2.3. TZ — Timezone

Set a valid IANA timezone string. This drives log timestamps and scheduler behaviour for every container.

TZ=Europe/Amsterdam

3. Configure Gluetun VPN Variables (Optional)

!!! info "Optional — BitTorrent only" The VPN is optional. It is only required when you intend to run the BitTorrent client (qbittorrent), whose traffic is tunnelled through Gluetun. If you are running a Usenet-only stack (SABnzbd) or have disabled the qbittorrent and gluetun services in your compose configuration, you can skip this entire section and leave every VPN_* variable blank in .env.

!!! warning "If qBittorrent is active, VPN is mandatory" When the qbittorrent service is enabled, Gluetun must come up successfully or qBittorrent will have no network path (it shares Gluetun's network namespace). In that case, the variables below become required.

The following VPN_* variables in .env must be populated to match your VPN provider's account when the BitTorrent client is active:

Variable Description Example
VPN_SERVICE_PROVIDER Provider slug recognised by Gluetun (e.g. mullvad, protonvpn, nordvpn, private internet access). mullvad
VPN_TYPE Either wireguard or openvpn. wireguard
OPENVPN_USER Provider account username (OpenVPN only). p1234567
OPENVPN_PASSWORD Provider account password (OpenVPN only). s3cr3t!
SERVER_COUNTRIES Comma-separated list of countries to constrain server selection. Netherlands,Switzerland
SERVER_HOSTNAMES (Optional) Pin to specific server hostnames. Overrides SERVER_COUNTRIES. nl-ams-wg-001

3.1. WireGuard

If VPN_TYPE=wireguard, populate the WireGuard-specific keys instead of the OpenVPN credentials:

VPN_SERVICE_PROVIDER=mullvad
VPN_TYPE=wireguard
WIREGUARD_PRIVATE_KEY=YOUR_WG_PRIVATE_KEY_HERE=
WIREGUARD_ADDRESSES=10.64.0.2/32
SERVER_COUNTRIES=Netherlands

3.2. OpenVPN

If VPN_TYPE=openvpn, populate user/password instead:

VPN_SERVICE_PROVIDER=protonvpn
VPN_TYPE=openvpn
OPENVPN_USER=your-vpn-username
OPENVPN_PASSWORD=your-vpn-password
SERVER_COUNTRIES=Switzerland

!!! tip "Provider-specific keys" Consult the Gluetun wiki for the exact provider slug and any provider-specific environment variables (e.g. OPENVPN_PASSWORD is sometimes a token rather than the account password — ProtonVPN being the canonical example).

4. Configure the Ansible Inventory

Edit ansible/inventory/hosts.ini to point at your physical host and the block device that will become the media drive.

!!! info "Bootstrapping — Day 1 vs. Day 2" The stack now relies on role-based mDNS resolution instead of hardcoded IP addresses. This creates two distinct deployment phases:

*   **Day 1 (Bootstrap):** The Host does not yet run Avahi/mDNS (it is
    installed by the playbook itself), so `mediacenter.local` cannot
    resolve. For the **first run only**, target the N100's DHCP-assigned
    IP directly:

    ```bash
    ansible-playbook -i "192.168.1.150," -u ansible ansible/playbooks/provision_host.yml -K
    ```

*   **Day 2 (Normal Ops):** After provisioning, the playbook has set the
    role-based hostname and Avahi advertises it on the LAN. Update the
    inventory to point at the mDNS name for all future runs — resilient
    to DHCP address changes:

    ```bash
    ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/provision_host.yml -K
    ```

    ```ini
    # ansible/inventory/hosts.ini
    [the_host]
    mediacenter.local ansible_user=ansible media_drive_device=/dev/sda1
    ```

    !!! note "Control machine mDNS support"
        The workstation running Ansible must resolve `.local` names,
        which requires Avahi/nss-mdns (Linux) or Bonjour (macOS).

The canonical inventory template (ansible/inventory/hosts.ini) ships with mediacenter.local plus commented examples covering the Day 1 IP form and optional media_subnet_cidr / web_hostname overrides.

4.1. Set the Host

The inventory targets The Host by its role-based mDNS name (mediacenter.local) after Day 1. The NFS/UFW subnet is auto-detected from The Host's own network facts; pin it explicitly only for unusual topologies:

mediacenter.local ansible_user=ansible media_drive_device=/dev/sda1
# Optional manual subnet override:
# mediacenter.local ansible_user=ansible media_drive_device=/dev/sda1 media_subnet_cidr=192.168.50.0/24

4.2. Set the media_drive Path

media_drive must point to the block device or filesystem path that will be mounted as the bulk media volume on The Host (typically the large secondary disk, not the OS disk).

media_drive=/dev/sda1

!!! danger "Verify before running" Ansible will format and/or mount whatever device you specify here. Double-check with lsblk on The Host to confirm you are not pointing at the OS disk.

5. Run the Provisioning Playbook

With .env populated and hosts.ini configured, execute the master playbook from the repository root on your workstation:

ansible-playbook -i ansible/inventory/hosts.ini ansible/playbooks/provision_host.yml -K

The -K flag prompts for the BECOME password — enter the password of the ansible user on The Host. The playbook will:

  • Install Docker and required system packages.
  • Create the mediasvc service account (UID=5000, GID=5000).
  • Mount media_drive at /data.
  • Render docker-compose.yml from docker-compose.yml.j2 using your .env.
  • Bring the entire CineVault stack online.

When the play finishes with failed=0 unreachable=0, your CineVault server is online. Proceed to the RUNBOOK Post-Deployment Application Configuration section to wire up the *arr stack via the web UIs.