# Gitea público con Tailscale Funnel en NAS UGREEN Guía para exponer Gitea (en Docker, NAS UGREEN con UGOS) por HTTPS a internet usando **Tailscale Funnel**, de modo que funcione desde cualquier equipo **sin instalar Tailscale**, en casa y fuera, con una sola URL. ## Datos de esta instalación | Elemento | Valor | |---|---| | URL pública final | `https://unasgeorge.vaquita-hadar.ts.net` | | Tailnet | `vaquita-hadar.ts.net` | | Nombre del NAS en Tailscale | `unasgeorge` | | IP Tailscale del NAS (contenedor) | `100.68.242.27` | | IP local del NAS | `192.168.1.145` | | Puerto de Gitea en el NAS (host) | `30000` (interno del contenedor: `3000`) | | Puerta de enlace Docker (bridge) | `172.17.0.1` | | Destino del proxy de Funnel | `http://172.17.0.1:30000` | | Usuario Gitea | `george` | ### Esquema ``` Cualquier equipo (con o sin Tailscale) │ ▼ https://unasgeorge.vaquita-hadar.ts.net (puerto 443, HTTPS, Let's Encrypt) │ ▼ Tailscale Funnel (contenedor Tailscale en el NAS) http://172.17.0.1:30000 (interno, nadie lo usa directamente) │ ▼ Gitea ``` --- ## 1. Preparar Tailscale (panel web) ### 1.1 Activar certificados HTTPS 1. Entrar a . 2. En **HTTPS Certificates**, pulsar **Enable HTTPS**. ### 1.2 Permitir Funnel en la política de acceso 1. Entrar a . 2. Verificar que exista este bloque (añadirlo si no está, respetando las comas): ```json "nodeAttrs": [ { "target": ["autogroup:member"], "attr": ["funnel"] } ] ``` 3. Guardar (**Save**). > Alternativa: la primera vez que se ejecuta `tailscale funnel`, el comando muestra un enlace (`https://login.tailscale.com/f/funnel?node=...`) que añade este permiso automáticamente. **Validación:** después del paso 2, en debe aparecer la etiqueta **Funnel** junto a `unasgeorge`. --- ## 2. Configurar Funnel en el contenedor de Tailscale ### 2.1 Abrir la terminal del contenedor 1. Entrar al panel del NAS (UGOS) → app **Docker** → **Contenedores**. 2. Abrir el contenedor de **Tailscale** → **Terminal** → añadir terminal. 3. En *Command* usar **`/bin/sh`** (la imagen de Tailscale no trae bash). > Si algún comando da *failed to connect to local tailscaled*, anteponer el socket: > `tailscale --socket=/tmp/tailscaled.sock ` ### 2.2 Comprobar que Tailscale está conectado ```sh tailscale status tailscale version ``` Debe aparecer `unasgeorge` con IP `100.68.242.27`. Versión recomendada: 1.50 o superior. ### 2.3 Encontrar por dónde ve el contenedor a Gitea El contenedor de Tailscale está en la red bridge de Docker: **no** llega a Gitea por la IP local del NAS ni por `127.0.0.1`. Hay que usar la puerta de enlace de Docker. ```sh ip route | head -1 # Resultado esperado: default via 172.17.0.1 dev eth0 wget -qO- -T 5 http://172.17.0.1:30000 | head -c 200 # Debe devolver HTML que empieza con ``` Referencia de lo que **no** funciona en esta instalación: ```sh wget -qO- -T 5 http://192.168.1.145:30000 | head -c 200 # download timed out wget -qO- -T 5 http://127.0.0.1:30000 | head -c 200 # Connection refused ``` ### 2.4 Activar Funnel ```sh tailscale serve reset tailscale funnel --bg http://172.17.0.1:30000 ``` Salida esperada: ``` Available on the internet: https://unasgeorge.vaquita-hadar.ts.net/ |-- proxy http://172.17.0.1:30000 Funnel started and running in the background. ``` ### 2.5 Validar ```sh tailscale funnel status ``` Debe mostrar **un solo** destino: ``` https://unasgeorge.vaquita-hadar.ts.net (Funnel on) |-- / proxy http://172.17.0.1:30000 ``` > `--bg` deja Funnel activo en segundo plano y sobrevive a reinicios del contenedor. Si el contenedor se **recrea** (no solo se reinicia) y su estado no está en un volumen, hay que repetir el paso 2.4. > > Para desactivar Funnel: `tailscale funnel --https=443 off` --- ## 3. Configurar Gitea ### 3.1 Variables de entorno del contenedor (prioritario) Las variables `GITEA__seccion__CLAVE` **tienen prioridad sobre `app.ini`** y lo reescriben en cada arranque. En esta instalación existía `GITEA__server__ROOT_URL = http://100.68.242.27:30000/`, que era la causa de que el botón *Clone* mostrara la URL antigua. 1. App **Docker** de UGOS → contenedor de **Gitea** → **Detener**. 2. **Editar → Variables de entorno**. 3. Dejar los valores así (cambiar o añadir): ``` GITEA__server__ROOT_URL = https://unasgeorge.vaquita-hadar.ts.net/ GITEA__server__DOMAIN = unasgeorge.vaquita-hadar.ts.net GITEA__server__SSH_DOMAIN = 192.168.1.145 GITEA__server__PUBLIC_URL_DETECTION = never GITEA__service__DISABLE_REGISTRATION = true ``` 4. Guardar e **Iniciar** el contenedor. Los repositorios no se pierden: están en el volumen. ### 3.2 `app.ini` (si no se usan variables) Ruta habitual dentro del volumen: `.../gitea/gitea/conf/app.ini` (en el contenedor: `/data/gitea/conf/app.ini`). Se puede editar desde el **Gestor de archivos** de UGOS. ```ini [server] DOMAIN = unasgeorge.vaquita-hadar.ts.net ROOT_URL = https://unasgeorge.vaquita-hadar.ts.net/ SSH_DOMAIN = 192.168.1.145 PUBLIC_URL_DETECTION = never [service] DISABLE_REGISTRATION = true ``` Reiniciar el contenedor de Gitea tras editar. > `PUBLIC_URL_DETECTION = never` hace que Gitea use siempre `ROOT_URL` para los enlaces, aunque se entre por otra dirección (IP de Tailscale, `ugdocker.link`, etc.). > `SSH_DOMAIN` apunta a la IP local: SSH **no** pasa por Funnel, solo funciona en la red de casa. ### 3.3 Validar - **Administración del sitio → Configuración → URL de la aplicación** debe ser `https://unasgeorge.vaquita-hadar.ts.net/`. - En cualquier repo, el botón **Clone (HTTPS)** debe mostrar: `https://unasgeorge.vaquita-hadar.ts.net/george/.git` - Al cerrar sesión **no** debe aparecer la opción *Registrarse*. > El aviso *"The detected web site URL is ... unlikely matching the site config"* solo aparece cuando se entra por una dirección distinta de `ROOT_URL`. Desaparece al entrar por `https://unasgeorge.vaquita-hadar.ts.net`. ### 3.4 Crear un token de acceso Git por HTTPS usa el token como contraseña. 1. Gitea → avatar → **Configuración → Aplicaciones** → *Administrar tokens de acceso*. 2. Nombre: uno por equipo (ej. `macbook-air`, `serfactpro`). 3. Permisos: **repository → Read and Write** (y **user → Read** si lo pide). 4. **Generar token** y copiarlo (solo se muestra una vez). > No usar **Manage SSH Keys** para esto: las claves SSH no funcionan a través de Funnel. --- ## 4. Validaciones de acceso ### 4.1 Desde un equipo con Tailscale encendido ```bash curl -I https://unasgeorge.vaquita-hadar.ts.net ``` Esperado: ``` HTTP/2 200 ``` Si se queda colgado después de `Request completely sent off`, Funnel no llega a Gitea: revisar el paso 2.3 y el destino del proxy. ### 4.2 DNS público (sin Tailscale) ```bash dig +short unasgeorge.vaquita-hadar.ts.net A @1.1.1.1 dig +short unasgeorge.vaquita-hadar.ts.net A @8.8.8.8 ``` Esperado (IPs de los servidores de entrada de Funnel, pueden variar): ``` 209.177.145.97 209.177.145.192 ``` > Si sale vacío o `No answer`, el registro DNS público aún no se ha creado. Puede tardar desde 10 minutos hasta unas horas. No hay que cambiar nada, solo esperar. ### 4.3 Desde un equipo o servidor **sin Tailscale** En el Mac: apagar Tailscale (icono de la barra de menú) y limpiar caché DNS: ```bash sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder ``` En Linux (si usa systemd-resolved): ```bash sudo resolvectl flush-caches ``` En Windows (PowerShell): ```powershell ipconfig /flushdns ``` Comprobaciones (válidas en macOS, Linux y Windows con curl/git): ```bash # 1. Resuelve por DNS público nslookup unasgeorge.vaquita-hadar.ts.net 8.8.8.8 # 2. Responde por HTTPS curl -I https://unasgeorge.vaquita-hadar.ts.net # Esperado: HTTP/2 200 # 3. Git llega al repositorio (pedirá usuario y token si es privado) git ls-remote https://unasgeorge.vaquita-hadar.ts.net/george/sap-legacy-webhook-emulator.git ``` ### 4.4 Prueba desde una red totalmente externa En el iPhone (o cualquier móvil): desactivar Wi-Fi, desactivar Tailscale y abrir en el navegador: ``` https://unasgeorge.vaquita-hadar.ts.net ``` Debe cargar la página de Gitea. ### 4.5 URLs de comprobación | Qué | URL | |---|---| | Gitea (pública) | | | Repo de ejemplo | | | Máquinas Tailscale (etiqueta Funnel) | | | Política de acceso (nodeAttrs funnel) | | | DNS / certificados HTTPS | | --- ## 5. Cambiar el `origin` de los proyectos ### 5.1 Un proyecto ```bash cd ruta/al/proyecto # Ver el remoto actual git remote -v # Cambiarlo git remote set-url origin https://unasgeorge.vaquita-hadar.ts.net/george/.git # Verificar git remote -v git ls-remote origin git push ``` Credenciales en el primer `push`: - **Username:** `george` - **Password:** el **token** (al pegarlo no se ve nada, es normal) ### 5.2 Varios proyectos a la vez (macOS / Linux) Desde la carpeta que contiene todos los proyectos. Sustituye cualquiera de las URLs antiguas (IP Tailscale, IP local o `ugdocker.link`) por la nueva: ```bash for d in */; do [ -d "$d/.git" ] || continue old=$(git -C "$d" remote get-url origin 2>/dev/null) || continue new=$(echo "$old" | sed -E 's#^https?://(100\.68\.242\.27|192\.168\.1\.145|app-30000-unasgeorge\.us15\.ugdocker\.link)(:30000)?#https://unasgeorge.vaquita-hadar.ts.net#') if [ "$old" != "$new" ]; then git -C "$d" remote set-url origin "$new" echo "$d $old -> $new" fi done ``` ### 5.3 Varios proyectos a la vez (Windows PowerShell) ```powershell Get-ChildItem -Directory | ForEach-Object { if (Test-Path "$($_.FullName)\.git") { $old = git -C $_.FullName remote get-url origin 2>$null if ($old) { $new = $old -replace '^https?://(100\.68\.242\.27|192\.168\.1\.145|app-30000-unasgeorge\.us15\.ugdocker\.link)(:30000)?', 'https://unasgeorge.vaquita-hadar.ts.net' if ($old -ne $new) { git -C $_.FullName remote set-url origin $new Write-Host "$($_.Name) $old -> $new" } } } } ``` ### 5.4 Guardar el token para no escribirlo cada vez | Sistema | Comando / comportamiento | |---|---| | macOS | Se guarda solo en el Llavero (`git config --global credential.helper osxkeychain`) | | Windows | Git Credential Manager lo guarda automáticamente | | Linux | `git config --global credential.helper store` (guarda en texto plano en `~/.git-credentials`) | ### 5.5 Clonar en un equipo nuevo ```bash git clone https://unasgeorge.vaquita-hadar.ts.net/george/.git ``` --- ## 6. Seguridad (Gitea queda público en internet) - [ ] Registro desactivado (`DISABLE_REGISTRATION = true`, verificado en variables de entorno **y** `app.ini`). - [ ] 2FA activado: **Configuración → Seguridad**. - [ ] Repositorios privados marcados como **privados**. - [ ] Un token por equipo; revocar el de un equipo perdido en **Configuración → Aplicaciones**. - [ ] Revisar que no exista otra variable `GITEA__...` que anule estos ajustes. --- ## 7. Problemas encontrados y solución | Síntoma | Causa | Solución | |---|---|---| | `ssh` y `ping` a `100.68.242.27` hacen timeout | Esa IP es del contenedor de Tailscale, no del sistema del NAS | Usar la terminal del contenedor desde la app Docker de UGOS | | `Funnel is not enabled on your tailnet` | Falta permiso Funnel | Abrir el enlace que muestra el comando y aceptar | | `port 443 is not allowed for funnel` | El nodo aún no tiene el atributo `funnel` | Revisar `nodeAttrs` (paso 1.2) y reiniciar el contenedor de Tailscale | | HTTPS conecta pero no responde (`Request completely sent off` y nada más) | Funnel apuntaba a un destino inalcanzable (`:3000` o `192.168.1.145:30000`) | `tailscale serve reset` y Funnel a `http://172.17.0.1:30000` | | `Could not resolve host` sin Tailscale / `dig` vacío | DNS público de Funnel aún no publicado | Esperar (minutos a horas) y repetir `dig` | | Clone muestra `http://100.68.242.27:30000/...` o `ugdocker.link` | Variable `GITEA__server__ROOT_URL` antigua anulaba `app.ini` | Cambiar la variable y añadir `PUBLIC_URL_DETECTION = never` | | Aviso *detected web site URL ... unlikely matching* | Se entra por una dirección distinta de `ROOT_URL` | Informativo; entrar por la URL pública |