# CasaDrop Remote Access with Tailscale

> Created by: **Holger Kuehn aka Lintuxer**

---

## Why Use Tailscale for CasaDrop?

CasaDrop is a self-hosted file sharing app for ZimaOS. To reach it from outside your LAN you normally need port forwarding, a dynamic DNS name and a TLS certificate. Tailscale removes all three:

| Advantage | Description |
| --------- | ----------- |
| **No open ports** | Nothing has to be forwarded in your router |
| **Valid certificate** | Let's Encrypt certificate for `<node>.<tailnet>.ts.net`, no browser warning |
| **Works everywhere** | Any device in your tailnet reaches CasaDrop, from any network |
| **Optional public links** | Funnel publishes share links on the public internet when you want it |
| **Zero DNS maintenance** | The node name is stable, no DynDNS updater needed |

Typical uses:

- **Private access** – open CasaDrop from your phone, laptop or a second site without a VPN client of your own
- **Share links for others** – hand out a `https://…ts.net` link that works on mobile data
- **Taildrop** – push a shared file straight to another one of your own devices

---

## Overview

CasaDrop is a self-hosted file sharing app for ZimaOS. Shares are created locally and handed out as links, QR codes, or pushed directly to another device:

![CasaDrop Active Shares overview](images/casadrop/01-active-shares.png)

Each share carries the actions this guide is about — copy link, QR code, edit, send by mail, delete. The URL behind those links is what Tailscale provides. A sixth action, **send to device** (Taildrop, see Part 4), joins the row only once Tailscale is reachable from inside the container, which is why it is absent from the screenshot above.

This guide covers:

- Connecting CasaDrop to an **existing** Tailscale installation on the host (Variant A)
- Running the **bundled Tailscale sidecar** container instead (Variant B)
- Publishing CasaDrop inside your tailnet with `tailscale serve`
- Publishing CasaDrop on the public internet with `tailscale funnel`
- Sending shares to your own devices with Taildrop
- Troubleshooting the usual failure modes

---

## Version Information

- **CasaDrop Version:** v2.4.2
- **Guide Version:** 1.0
- **Last Updated:** July 2026
- **Tested On:** ZimaOS v1.7.0-beta1 (Buildroot-based), Tailscale 1.98.8

---

## Terminology

| Command | Reach |
| ------- | ----- |
| `tailscale serve` | **Inside your tailnet only** – your own devices |
| `tailscale funnel` | **Public internet** – anybody who has the link |

Every step below states explicitly which of the two it uses.

---

## Use Case Example

```
                    ┌─────────────────────────────┐
                    │        ZimaOS Device        │
                    │      (CasaDrop Server)      │
                    │                             │
                    │  ┌───────────────────────┐  │
                    │  │  CasaDrop Container   │  │
                    │  │  Port 8080 (internal) │  │
                    │  │  Port 8086 (host)     │  │
                    │  └───────────────────────┘  │
                    │  ┌───────────────────────┐  │
                    │  │  Tailscale (host)     │  │
                    │  │  serve / funnel       │  │
                    │  └───────────────────────┘  │
                    └──────────────┬──────────────┘
                                   │
          ┌────────────────────────┼────────────────────────┐
          │                        │                        │
          ▼                        ▼                        ▼
 ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
 │  Your devices   │     │  Second site    │     │  External user  │
 │  (tailnet)      │     │  (tailnet)      │     │  (Funnel only)  │
 │                 │     │                 │     │                 │
 │ • Phone         │     │ • Office NAS    │     │ • Share link    │
 │ • Notebook      │     │ • Home Assistant│     │ • QR code       │
 │ • Desktop       │     │ • Backup host   │     │ • No tailnet    │
 └─────────────────┘     └─────────────────┘     └─────────────────┘
```

**No inbound firewall rules needed:** the ZimaOS device connects outbound to the Tailscale coordination servers. Your router stays closed.

---

## Prerequisites

- ZimaOS installed and running, SSH access enabled
- CasaDrop installed and reachable locally
- A Tailscale account and tailnet (the free plan is enough)
- For **Funnel** only: HTTPS certificates and the `funnel` node attribute enabled in the tailnet policy (see Part 3)

### Installing CasaDrop

**Repository:** <https://github.com/chicohaager/casadrop>
**Docker Hub:** `chicohaager/casadrop`

```bash
export DOCKER_CONFIG=/DATA/.docker
docker run -d -p 8086:8080 -v casadrop-data:/data chicohaager/casadrop:latest
```

For ZimaOS use `docker-compose.zimaos.yaml` from the repository — it ships the Tailscale mounts of Variant A commented out, and the sidecar of Variant B as a service behind the `tailscale` profile, so it only starts with `--profile tailscale`. ZimaOS-specific setup, including auto-detection for EasyTier, Tailscale and Cloudflare Tunnel, is documented in [`docs/zimaos.md`](https://github.com/chicohaager/casadrop/blob/main/docs/zimaos.md).

Verify CasaDrop first — everything after this assumes it answers:

```bash
curl -s http://localhost:8086/healthz
```

Expected output: `ok`

> **Note:** Replace `8086` with your published port (`WEBUI_PORT`) everywhere in this guide.

### Installing Tailscale on ZimaOS

If Tailscale is not on your ZimaOS host yet, install it as a **systemd-sysext** extension — the native mechanism on ZimaOS, no Docker container involved:

**Repository:** <https://github.com/chicohaager/zimaos-tailscale-sysext>

```bash
# On the ZimaOS host as root (or with sudo):
sudo ./install.sh
sudo tailscale up
```

The installer sanity-checks the host, downloads the official Tailscale static tarball from `pkgs.tailscale.com`, builds `tailscale.raw` (gzip-squashfs, ~35 MB), installs it to `/var/lib/extensions/`, refreshes `systemd-sysext` and enables both `tailscaled.service` and `tailscaled-watchdog.timer`.

> **Why the watchdog timer:** `tailscaled.service` ships *inside* the sysext image. On ZimaOS, `multi-user.target` resolves its `WantedBy=` symlinks **before** `systemd-sysext.service` has merged the `/usr` overlay, so at that moment the unit does not exist yet and is never scheduled — `tailscaled` stays dead after a reboot, without an error line. The timer lives in `/etc/systemd/system/` (persistent root, unaffected by the race), fires 15 seconds after boot and starts the daemon if it is not already running.

The layout mirrors the upstream Buildroot recipe, which is exactly what Variant A of this guide needs:

| Path | Content |
| ---- | ------- |
| `/usr/bin/tailscale` | CLI — this is the binary you mount into the CasaDrop container |
| `/usr/bin/tailscaled` | daemon |
| `/run/tailscale/tailscaled.sock` | daemon socket — the second mount |
| `/DATA/AppData/tailscale/` | auth state, survives reboots and ZimaOS upgrades |

Verify before you continue:

```bash
tailscale status
```

> **Note:** After a ZimaOS upgrade just re-run `install.sh`. The auth state under `/DATA/AppData/tailscale/` survives.

> ⚠️ **Do not run the sysext and a `tailscale/tailscale` Docker container in parallel.** If `tailscaled.service` shows `inactive` while Tailscale still appears to be running, a container has taken over. Pick one: either the sysext (Variant A) or the sidecar (Variant B).

---

## Which Variant Do I Need?

| | **Variant A – existing Tailscale** | **Variant B – bundled sidecar** |
| --- | --- | --- |
| Tailscale runs… | on the host (ZimaOS sysext, systemd, other container) | in a `tailscale/tailscale` container next to CasaDrop |
| Tailnet node | the host itself | a separate node, e.g. `casadrop` |
| URL auto-detection | **yes** – the entrypoint reads `tailscale status` | **no** – you set `TAILSCALE_URL` yourself |
| Extra install | none | one container + one state volume |
| Taildrop support | yes | no |
| Best for | ZimaOS and any host already on the tailnet | a host with no Tailscale at all |

**On ZimaOS, use Variant A.** ZimaOS devices are usually already tailnet nodes — via the [Tailscale sysext module](https://github.com/chicohaager/zimaos-tailscale-sysext), which puts the daemon natively on the host — and Variant A gives you fewer moving parts, one node instead of two, automatic URL detection and Taildrop.

Both variants end at the same result: `https://<node>.<tailnet>.ts.net` serving CasaDrop with a valid Let's Encrypt certificate.

---

## Architecture

```
 Variant A                              Variant B
 ─────────                              ─────────

 ┌──────────────────────────┐           ┌──────────────────────────┐
 │ ZimaOS Host              │           │ Host                     │
 │                          │           │                          │
 │  tailscaled (host)       │           │  ┌────────────────────┐  │
 │       ▲                  │           │  │ casadrop-tailscale │  │
 │       │ socket mount     │           │  │  own tailnet node  │  │
 │  ┌────┴─────────────┐    │           │  └─────────┬──────────┘  │
 │  │ casadrop         │    │           │            │ shared      │
 │  │  :8080 internal  │    │           │            │ netns       │
 │  └──────────────────┘    │           │  ┌─────────┴──────────┐  │
 │                          │           │  │ casadrop  :8080    │  │
 │  1 tailnet node          │           │  └────────────────────┘  │
 └──────────────────────────┘           │  2 tailnet nodes         │
                                        └──────────────────────────┘
```

---

## Part 1: Variant A – Use the Tailscale You Already Run

CasaDrop does not run its own Tailscale here. It *borrows* the host daemon through a mounted socket, which is also what makes URL auto-detection work.

### Step 1: Mount the CLI and the Socket

Add two mounts to the CasaDrop service:

```yaml
services:
  casadrop:
    volumes:
      - casadrop-data:/data
      # --- host Tailscale ---
      - /usr/bin/tailscale:/usr/bin/tailscale:ro
      - /run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
```

**Explanation of the mounts:**

- **`/usr/bin/tailscale`** → the CLI, mounted read-only. The binary is statically linked, so the host binary runs fine inside the Alpine image.
- **`/run/tailscale/tailscaled.sock`** → the daemon socket. Must be mounted **read-write** — the CLI writes requests to it.

> ⚠️ **Note:** The socket path differs on purpose: `/run/tailscale/…` on the host, `/var/run/tailscale/…` inside the container, which is where the CLI looks by default. Do not "fix" this.

> **ZimaOS:** these mounts are already prepared (commented out) in `docker-compose.zimaos.yaml`. Just uncomment them. If you installed Tailscale through the [sysext module](https://github.com/chicohaager/zimaos-tailscale-sysext), both paths exist exactly as written — the extension merges `/usr/bin/tailscale` onto the read-only root at boot.

Recreate the container:

```bash
export DOCKER_CONFIG=/DATA/.docker
docker compose up -d
```

### Step 2: Verify CasaDrop Found the Tailnet

The entrypoint runs `tailscale status --json`, takes the node's `DNSName` and exports it as `TAILSCALE_URL`.

```bash
docker logs casadrop | head -20
```

Expected output:

```
=== CasaDrop Starting ===
Port: 8080
Data: /data
Local IP: 172.19.0.2
Tailscale IP: 100.x.y.z
Tailscale URL: https://my-nas.tailnet-name.ts.net
=== Starting CasaDrop Server ===
```

The `Local IP:` line comes *before* the Tailscale block and only appears when
`LOCAL_IP` was auto-detected — if you set it in the environment, the line is
absent. Other detected networks (`Tunnel URL:`, `EasyTier IP:`) print after the
Tailscale lines.

If the two `Tailscale …` lines are missing, the mounts are wrong. Test from inside the container:

```bash
docker exec casadrop tailscale status
```

### Step 3: Publish CasaDrop Inside Your Tailnet

Run this **on the host**, not in the container:

```bash
tailscale serve --bg --https=8444 http://127.0.0.1:8086
```

Expected output:

```
Available within your tailnet:

https://my-nas.tailnet-name.ts.net:8444/
|-- proxy http://127.0.0.1:8086

Serve started and running in the background.
To disable the proxy, run: tailscale serve --https=8444 off
```

Any port works. A dedicated one (`8444` here) is convenient because it leaves an existing `--https=443` entry — e.g. your NAS dashboard — untouched. To put CasaDrop on the node's root URL instead, use `--https=443`, but be aware it **replaces** whatever was on 443 before.

Check what is already configured first:

```bash
tailscale serve status
```

### Step 4: Verify the Certificate

```bash
echo | openssl s_client -connect my-nas.tailnet-name.ts.net:8444 \
        -servername my-nas.tailnet-name.ts.net 2>/dev/null |
  openssl x509 -noout -issuer -subject
```

Expected output:

```
issuer=C = US, O = Let's Encrypt, CN = YE2
subject=CN = my-nas.tailnet-name.ts.net
```

A real certificate means no browser warning, and `Secure` cookies work.

> **Note:** ZimaOS has no `openssl` binary. Run this check from a client machine, not on the ZimaOS host itself.

### Step 5: Select the Network in CasaDrop's Settings

1. Log in as admin
2. Navigate to: **Settings → Network Configuration**
3. On the **Tailscale Funnel** card:
   - **Tick the checkbox first.** While it is unchecked the whole card is disabled — URL field, **PRIMARY** radio and **Use detected** are all greyed out
   - Click **Use detected** to take over the auto-detected value. This button only appears when the stored value differs from the detected one — handy after a node rename
   - Select **PRIMARY** if share links should use the Tailscale URL by default
4. Click **Save**

> ⚠️ **The primary network is only the fallback.** If a visitor reaches CasaDrop through a public hostname, the generated share/receive/QR links use *that* host, so they match the way the page was actually opened (`utils.PreferredPublicBaseURL`). Only local/LAN/loopback access falls back to the primary network you picked here. A `*.ts.net` name always counts as public.

---

## Part 2: Variant B – The Bundled Tailscale Sidecar

CasaDrop's compose files ship an optional `tailscale` service behind the `tailscale` profile. It creates its **own** tailnet node and shares the network namespace with the app container (`network_mode: service:casadrop`), so `localhost:8080` inside the sidecar *is* CasaDrop.

### Step 1: Create an Auth Key

Create a key at <https://login.tailscale.com/admin/settings/keys> — ephemeral or reusable, your choice.

> ⚠️ **Use an auth key. The interactive login does not work reliably here.**
> Started without `TS_AUTHKEY`, the container's supervisor retries the login about once a minute, generating a **new** node key and a **new** auth URL each time:
>
> ```
> 08:37:18  https://login.tailscale.com/a/aaaaaaaaaaaa
> 08:38:18  https://login.tailscale.com/a/bbbbbbbbbbbb   ← different key, different URL
> 08:39:18  https://login.tailscale.com/a/cccccccccccc
> ```
>
> Any URL you copy is stale within ~60 seconds, and even a successful login is superseded by the next key regeneration — the node registers in your tailnet and then goes stale, while the container still reports `Logged out.`

### Step 2: Start the Sidecar

```bash
export DOCKER_CONFIG=/DATA/.docker
TAILSCALE_AUTHKEY=tskey-auth-xxxx docker compose --profile tailscale up -d
```

Both spellings `TAILSCALE_AUTHKEY` and `TS_AUTHKEY` are accepted, in `docker-compose.yaml` and `docker-compose.zimaos.yaml` alike.

Verify:

```bash
docker exec casadrop-tailscale tailscale status
```

The node now appears in your tailnet admin console as its own machine, separate from the host.

### Step 3: What the Sidecar Needs

Already set in the shipped compose files — listed here so you know why:

| Setting | Purpose |
| ------- | ------- |
| `cap_add: NET_ADMIN` | Create the `tailscale0` interface |
| `/dev/net/tun:/dev/net/tun` | Kernel TUN device, bind-mounted instead of granting `SYS_MODULE` |
| `tailscale-state:/var/lib/tailscale` | Keeps the node identity across restarts |
| `network_mode: service:casadrop` | Shares the app's network namespace, so `localhost:8080` reaches CasaDrop |

> ⚠️ **`SYS_MODULE` is deliberately not granted** — it is equivalent to host root. If your host has no `tun` device, run `modprobe tun` once on the host instead.

### Step 4: Publish CasaDrop

Because the sidecar shares CasaDrop's namespace, the target is the **container** port `8080`, not your published host port:

```bash
# inside your tailnet only
docker exec casadrop-tailscale tailscale serve --bg 8080

# public internet (read Part 3 first)
docker exec casadrop-tailscale tailscale funnel --bg 8080
```

Check what is live:

```bash
docker exec casadrop-tailscale tailscale funnel status
```

### Step 5: Tell CasaDrop Its URL

In this variant the `tailscale` CLI lives in the **sidecar**, not in the app container, so auto-detection finds nothing:

```bash
docker exec casadrop sh -c 'command -v tailscale || echo "no tailscale binary in app container"'
```

Expected output:

```
no tailscale binary in app container
```

Set the URL yourself. Either as an environment variable…

```yaml
services:
  casadrop:
    environment:
      - TAILSCALE_URL=https://casadrop.your-tailnet.ts.net
```

…or in **Settings → Network Configuration → Tailscale Funnel** (tick the checkbox first, then fill the field and **Save**).

> **Note:** Both places accept the same value. The environment variable wins at startup, the settings field persists in the database.

---

## Part 3: Public Access with Funnel

`serve` stays inside your tailnet. To hand a link to somebody who is *not* on your tailnet you need **Funnel**, and Funnel must be granted in your tailnet policy — otherwise the node's public DNS name does not resolve even though the local config looks perfectly fine.

### Step 1: Enable HTTPS Certificates

Open <https://login.tailscale.com/admin/dns> and make sure **HTTPS Certificates** are enabled.

### Step 2: Grant the Funnel Attribute

Open <https://login.tailscale.com/admin/acls> and grant the `funnel` attribute. The snippet Tailscale's own docs start from allows Funnel on **every device of every tailnet member**:

```jsonc
"nodeAttrs": [
  { "target": ["autogroup:member"], "attr": ["funnel"] },
]
```

That is fine for a personal tailnet. If you would rather allow it on the CasaDrop host alone, tag that node and target the tag instead:

```jsonc
"nodeAttrs": [
  { "target": ["tag:funnel"], "attr": ["funnel"] },
]
```

Once a node is allowed to use Funnel, the admin console marks it with a **Funnel** badge.

### Step 3: Turn It On

```bash
# Variant A, on the host
tailscale funnel --bg --https=443 http://127.0.0.1:8086

# Variant B, in the sidecar
docker exec casadrop-tailscale tailscale funnel --bg 8080
```

### Step 4: Verify From Outside

Test from a device that is **not** on your tailnet — a phone on mobile data is the honest test.

> ⚠️ `tailscale funnel status` only reports your local configuration. It says nothing about whether the world can actually reach you.

### Security Checklist Before Enabling Funnel

Funnel makes CasaDrop reachable by anyone who has the link. Before you turn it on:

1. Set a strong admin password (or `ADMIN_PASSWORD`)
2. Enable 2FA under **Settings → Two-Factor Authentication**
3. Put a password and an expiry on shares that matter

---

## Part 4: Bonus – Taildrop

With **Variant A**, CasaDrop can push a shared file straight to another device on your tailnet. A **Send to device** action — a paper-plane icon that joins the other buttons in the share row — appears once `GET /api/taildrop/status` reports `available: true`. Until then the row shows the five actions of the Overview screenshot and no paper plane.

| Limitation | Detail |
| ---------- | ------ |
| Variant | Variant A only — the CLI must be in the app container |
| Permission | Admin only |
| Share type | File shares only, not folder shares |

It runs `tailscale file cp` under the hood, and **writing** through the socket needs more privilege than reading: `tailscale status` works for any user, but `tailscale file cp` requires the caller to be root or the configured *operator*.

**Problem:** the device list populates but sending fails with `Access denied: file access denied`

**Solution:** set the operator once on the host:

```bash
sudo tailscale set --operator=<the user your CasaDrop container runs as>
```

---

## Part 5: Troubleshooting

### No `Tailscale URL:` Line in the Log (Variant A)

**Symptom:** `docker logs casadrop` shows no `Tailscale IP:` / `Tailscale URL:` lines

**Cause:** the mounts are missing or wrong

**Solution:**

```bash
docker exec casadrop tailscale status
```

If that fails, revisit Part 1 Step 1. Make sure the socket is mounted **read-write** — a `:ro` on the socket breaks the CLI.

If `/usr/bin/tailscale` does not exist on the host at all, the sysext is not merged:

```bash
systemd-sysext status
systemctl status tailscaled
```

Re-run `install.sh` from the [sysext repository](https://github.com/chicohaager/zimaos-tailscale-sysext) — this is the normal situation after a ZimaOS upgrade.

### The URL in Settings Is an Old Node Name

**Cause:** the node was renamed in the admin console

**Solution:** **Settings → Network Configuration** → **Use detected** on the Tailscale card → **Save**

### The ts.net URL Opens Something Else

**Cause:** another `tailscale serve` entry already owns that port

**Solution:**

```bash
tailscale serve status
```

Publish CasaDrop on its own port (`--https=8444`) or take over 443 deliberately.

### Public DNS Name Does Not Resolve, Tailnet Access Works

**Cause:** Funnel is not granted in the tailnet policy

**Solution:** Part 3, Step 2. Serve config and certificate can be perfectly fine while this is missing.

### Share Link Points at `192.168.x.x` Although I Opened the ts.net URL

**Cause:** this should not happen — links follow the host you came in through. If it does, a reverse proxy in front is rewriting `Host` / `X-Forwarded-Host`

**Solution:** set `TRUSTED_PROXY` to that proxy's IP/CIDR so the forwarded headers are honored.

### Sidecar Asks for Authentication on Every Restart (Variant B)

**Cause:** the `tailscale-state` volume is not persisted

**Solution:** check the volume mapping for `/var/lib/tailscale`.

---

## Quick Reference: Commands

```bash
# --- Variant A, on the host ---
tailscale status                                        # node name and IP
tailscale serve status                                  # what is published in the tailnet
tailscale serve --bg --https=8444 http://127.0.0.1:8086 # publish (tailnet only)
tailscale serve --https=8444 off                        # unpublish
tailscale funnel --bg --https=443 http://127.0.0.1:8086 # publish (public)
tailscale funnel --https=443 off                        # unpublish

# --- CasaDrop container ---
docker logs casadrop | head -20                         # startup banner, detected URL
docker exec casadrop tailscale status                   # socket mount working?
curl -s http://localhost:8086/healthz                   # app alive?

# --- Variant B, sidecar ---
docker exec casadrop-tailscale tailscale status
docker exec casadrop-tailscale tailscale serve --bg 8080
docker exec casadrop-tailscale tailscale funnel --bg 8080
docker exec casadrop-tailscale tailscale funnel status
```

---

## Appendix: Complete Compose Snippets

### Variant A – Host Tailscale

```yaml
services:
  casadrop:
    image: chicohaager/casadrop:latest
    container_name: casadrop
    ports:
      - "8086:8080"
    volumes:
      - casadrop-data:/data
      - /usr/bin/tailscale:/usr/bin/tailscale:ro
      - /run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
    restart: unless-stopped

volumes:
  casadrop-data:
```

Then, on the host:

```bash
tailscale serve --bg --https=8444 http://127.0.0.1:8086
```

### Variant B – Bundled Sidecar

```yaml
services:
  casadrop:
    image: chicohaager/casadrop:latest
    container_name: casadrop
    ports:
      - "8086:8080"
    environment:
      - TAILSCALE_URL=https://casadrop.your-tailnet.ts.net
    volumes:
      - casadrop-data:/data
    restart: unless-stopped

  tailscale:
    image: tailscale/tailscale:latest
    container_name: casadrop-tailscale
    profiles: ["tailscale"]
    network_mode: service:casadrop
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - TS_AUTHKEY=${TAILSCALE_AUTHKEY}
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - tailscale-state:/var/lib/tailscale
    restart: unless-stopped

volumes:
  casadrop-data:
  tailscale-state:
```

Start with:

```bash
export DOCKER_CONFIG=/DATA/.docker
TAILSCALE_AUTHKEY=tskey-auth-xxxx docker compose --profile tailscale up -d
docker exec casadrop-tailscale tailscale serve --bg 8080
```

---

## Which One Should I Use?

- **Already on the tailnet** (ZimaOS, or any host with Tailscale) → **Variant A**. Fewer moving parts, one node instead of two, URL auto-detection, and Taildrop works.
- **Host has no Tailscale** and you want CasaDrop self-contained → **Variant B**. One `--profile tailscale` and you are done — just remember to set `TAILSCALE_URL`.

---

## Related

- **CasaDrop:** <https://github.com/chicohaager/casadrop> — self-hosted file sharing, single static Go binary
- **CasaDrop ZimaOS setup:** <https://github.com/chicohaager/casadrop/blob/main/docs/zimaos.md>
- **CasaDrop complete HowTo:** <https://github.com/chicohaager/casadrop/blob/main/docs/HOWTO.md>
- **Tailscale sysext for ZimaOS:** <https://github.com/chicohaager/zimaos-tailscale-sysext> — Tailscale as a native `systemd-sysext`, no Docker, with subnet-router and exit-node support
- **Tailscale admin console:** <https://login.tailscale.com/admin/machines>
- **Tailscale Serve docs:** <https://tailscale.com/kb/1242/tailscale-serve>
- **Tailscale Funnel docs:** <https://tailscale.com/kb/1223/funnel>
