TASK FOR CLAUDE CODE (on the Pangolin server, 141.227.129.182)
Add a raw TCP resource on port 8443 so the Calendar Lite app (Android 2.3) can reach Nextcloud CalDAV
==================================================================================================

CONTEXT
-------
- This server runs Pangolin (Pangolin + Gerbil + Traefik in Docker). Traefik terminates HTTPS for
  every public site with Let's Encrypt certificates, TLS 1.2 minimum, routed by SNI.
- Android 2.3 can't use that: TLS 1.0 only, no ISRG Root X1, unreliable SNI. So the TLS for this
  app is NOT terminated here. The home server (172.30.1.123, site connector "newt") runs a
  container "caldav-bridge" (nginx) on 172.30.1.123:8443. It speaks TLS 1.0 with a self-signed
  certificate the app pins, and forwards only /remote.php/dav/ to Nextcloud (nxc.anosanube.ovh).
  That part is already done and tested.
- This server only has to pass raw TCP on port 8443 through the newt tunnel to that bridge:

    phone --TLS 1.0--> VPS :8443 (gerbil/traefik, raw TCP) --WireGuard--> newt
          --> 172.30.1.123:8443 (caldav-bridge) --HTTP--> nextcloud-aio-apache:11000

- Certificate fingerprint the bridge serves (SHA-256):
    56:70:CE:E5:41:6D:B1:31:E8:74:51:52:66:8C:70:27:96:AE:28:8F:69:3E:AB:EE:4A:FD:FD:6B:6C:21:17:65

RULES
-----
- Read the existing config before changing anything. Don't assume paths or service names; find them.
- Back up every file before editing it (cp file file.bak-YYYYMMDD-HHMM).
- Show the user the exact diff and get their OK before restarting any container or editing
  docker-compose.yml.
- Don't change TLS options, routers, entrypoints or certificates used by any existing site.
- Don't touch Pangolin's database.

STEPS
-----
1. Find the Pangolin install directory (docker-compose.yml defining pangolin, gerbil, traefik).
   Note the Pangolin version (raw TCP/UDP resources need Pangolin 1.4 or later; if it's older,
   stop and tell the user).
   Check that nothing on the VPS already listens on 8443:
     ss -ltnp | grep ':8443 '
   If 8443 is taken, stop and tell the user; another public port works too (any port, as long as
   steps 2-5 use the same number), and the phone address changes to match.

2. config/config.yml: enable raw resources. MERGE into an existing "flags:" key if there is one;
   never create a duplicate top-level key.
     flags:
       allow_raw_resources: true

3. docker-compose.yml, the "gerbil" service (Traefik shares gerbil's network namespace, so
   public ports are published on gerbil): add to its ports list
       - 8443:8443

4. config/traefik/traefik_config.yml, "entryPoints": add (the name MUST be tcp-<port>; Pangolin
   generates its TCP routers against that name)
     entryPoints:
       tcp-8443:
         address: ":8443/tcp"

5. Firewall: allow inbound TCP 8443.
   - On the host: e.g. `ufw allow 8443/tcp` if ufw is active (check `ufw status`), or the
     equivalent for nftables/iptables if that's what's in use.
   - The cloud provider's firewall / security group, if there is one: tell the user to open
     TCP 8443 there; you probably can't do that from the shell.

6. Validate the YAML files:
     python3 -c 'import yaml,sys;[yaml.safe_load(open(f)) for f in sys.argv[1:]]' \
       config/config.yml config/traefik/traefik_config.yml docker-compose.yml
   Show the diff of all three files and ask the user before applying. Then:
     docker compose down && docker compose up -d
   (a plain restart won't pick up the new gerbil port). Check
     docker compose ps
     docker logs --since 2m traefik 2>&1 | grep -iE 'error|tcp-8443'
     docker logs --since 2m pangolin 2>&1 | grep -iE 'error'
   and confirm every existing site still loads.

7. Pangolin dashboard (the user does this, or guide them through it):
   - Org of the home server, Resources, Add Resource, type "Raw TCP/UDP resource".
   - Name: Calendar Lite CalDAV. Protocol: TCP. Public port: 8443.
   - Site: the site whose newt runs on the home server 172.30.1.123 (most likely the "casa" site).
   - Target: IP 172.30.1.123, port 8443. Leave "Enable Proxy Protocol" OFF (the bridge doesn't
     expect it; enabling only one side breaks every connection).
   - Pangolin SSO doesn't apply to raw TCP resources. That's expected: Nextcloud asks for its
     own login (the phone will use a Nextcloud app password).

8. VERIFY from the VPS (or anywhere with OpenSSL 3):
     # TLS 1.0 with a plain-RSA cipher (what Android 2.3 uses) must work:
     echo | openssl s_client -connect 141.227.129.182:8443 -tls1 -cipher 'AES128-SHA:@SECLEVEL=0' 2>&1 \
       | grep -E 'Protocol *:|Cipher is|alert'
     # expect: Protocol: TLSv1 / Cipher is AES128-SHA
     # It must be the bridge's certificate (not a Let's Encrypt one):
     echo | openssl s_client -connect 141.227.129.182:8443 2>/dev/null | openssl x509 -noout -fingerprint -sha256
     # expect: 56:70:CE:E5:41:6D:B1:31:E8:74:51:52:66:8C:70:27:96:AE:28:8F:69:3E:AB:EE:4A:FD:FD:6B:6C:21:17:65
     # Nextcloud must answer CalDAV through it:
     curl -sk -o /dev/null -w '%{http_code}\n' -X PROPFIND -H 'Depth: 0' https://141.227.129.182:8443/remote.php/dav/
     # expect: 401   (and "/" should give 404: the web UI is deliberately not exposed)
     # Existing sites must be unaffected, e.g.:
     curl -sI https://nxc.anosanube.ovh | head -1
   If the TLS test times out: check firewall (step 5), the gerbil port (step 3), and that the
   resource's site/target are right (step 7). If it connects but shows a different certificate,
   the target is wrong.

9. ROLLBACK if anything breaks: restore the .bak files, `docker compose down && docker compose up -d`,
   delete the resource in the dashboard.

10. REPORT to the user: files changed (with diff), backup paths, firewall changes, output of every
    check in step 8, and anything left to do (e.g. the cloud firewall).

PHONE SETUP (after step 8 passes)
---------------------------------
Calendar Lite, Menu, Settings:
  Server:   https://141.227.129.182:8443   (any *.oxose.org name works too, e.g.
            https://cal-old.oxose.org:8443: raw TCP ignores the hostname and the app pins the
            certificate, not the name)
  User:     your Nextcloud user
  Password: a Nextcloud app password (Nextcloud, Settings, Security, Devices & sessions)
Save and test connection. Tap Trust only if the fingerprint shown is the one above.
