Files
obsidian-vault/005-Learn/gitea-tailscale-funnel.md
T

385 lines
12 KiB
Markdown

# 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 <https://login.tailscale.com/admin/dns>.
2. En **HTTPS Certificates**, pulsar **Enable HTTPS**.
### 1.2 Permitir Funnel en la política de acceso
1. Entrar a <https://login.tailscale.com/admin/acls>.
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 <https://login.tailscale.com/admin/machines> 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 <comando>`
### 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 <!DOCTYPE html>
```
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/<repo>.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) | <https://unasgeorge.vaquita-hadar.ts.net> |
| Repo de ejemplo | <https://unasgeorge.vaquita-hadar.ts.net/george/sap-legacy-webhook-emulator> |
| Máquinas Tailscale (etiqueta Funnel) | <https://login.tailscale.com/admin/machines> |
| Política de acceso (nodeAttrs funnel) | <https://login.tailscale.com/admin/acls> |
| DNS / certificados HTTPS | <https://login.tailscale.com/admin/dns> |
---
## 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/<repo>.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/<repo>.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 |