427 lines
13 KiB
Markdown
427 lines
13 KiB
Markdown
|
|
# Servidor Personal tzzr.net — Actualización Repositorio GitHub
|
||
|
|
|
||
|
|
## Contexto
|
||
|
|
|
||
|
|
Servidor personal autoalojado en Hostinger VPS (Ubuntu 22.04, 8GB RAM, 100GB SSD).
|
||
|
|
Función: máquina personal del usuario, punto de entrada privado al ecosistema.
|
||
|
|
|
||
|
|
**IP**: 72.62.1.113
|
||
|
|
**Dominio**: tzzr.net
|
||
|
|
**Hostname**: box.tzzr.net
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Arquitectura Actual
|
||
|
|
|
||
|
|
```
|
||
|
|
[Cloudflare DNS]
|
||
|
|
│
|
||
|
|
[72.62.1.113]
|
||
|
|
│
|
||
|
|
┌─────────────────┴─────────────────┐
|
||
|
|
│ │
|
||
|
|
[Mail-in-a-Box] [Docker Stack]
|
||
|
|
Puertos 25,443,993 Puertos internos
|
||
|
|
│ │
|
||
|
|
┌────┴────┐ ┌───────────────┼───────────────┐
|
||
|
|
│ │ │ │ │ │ │
|
||
|
|
Email Nextcloud PostgreSQL NocoDB Shlink Addy Vaultwarden
|
||
|
|
(box) (/cloud) 5432 8081 8083 8084 8085
|
||
|
|
│
|
||
|
|
FileBrowser
|
||
|
|
8082
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Servicios Docker
|
||
|
|
|
||
|
|
| Servicio | Puerto | Subdominio | Imagen |
|
||
|
|
|----------|--------|------------|--------|
|
||
|
|
| PostgreSQL 15 | 5432 | (interno) | postgres:15-alpine |
|
||
|
|
| NocoDB | 8081 | db.tzzr.net | nocodb/nocodb:latest |
|
||
|
|
| FileBrowser | 8082 | files.tzzr.net | filebrowser/filebrowser:latest |
|
||
|
|
| Shlink | 8083 | s.tzzr.net | shlinkio/shlink:stable |
|
||
|
|
| Addy.io | 8084 | alias.tzzr.net | anonaddy/anonaddy:latest |
|
||
|
|
| Redis | 6379 | (interno) | redis:alpine |
|
||
|
|
| Vaultwarden | 8085 | pass.tzzr.net | vaultwarden/server:latest |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Tarea: Actualizar Repositorio GitHub
|
||
|
|
|
||
|
|
### Estructura de Archivos del Repositorio
|
||
|
|
|
||
|
|
```
|
||
|
|
tzzr-server/
|
||
|
|
├── README.md # Documentación principal
|
||
|
|
├── docker-compose.yml # Stack de servicios
|
||
|
|
├── init.sql # Schema PostgreSQL completo
|
||
|
|
├── nginx/
|
||
|
|
│ └── services.conf # Configuración reverse proxy
|
||
|
|
├── scripts/
|
||
|
|
│ ├── backup.sh # Script de backup diario
|
||
|
|
│ ├── mail-processor.py # Procesar correos → mail_registry
|
||
|
|
│ └── hst-sync.py # Sincronización etiquetas HST
|
||
|
|
├── dns/
|
||
|
|
│ └── tzzr_net.zone # Registros DNS para Cloudflare
|
||
|
|
└── docs/
|
||
|
|
├── INSTALACION.md # Guía de instalación desde cero
|
||
|
|
├── CLONACION.md # Guía para clonar a nuevo usuario
|
||
|
|
└── CREDENCIALES.template # Plantilla de credenciales (sin valores reales)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Schema PostgreSQL (init.sql)
|
||
|
|
|
||
|
|
### Tablas de Etiquetas HST
|
||
|
|
|
||
|
|
Crear 4 tablas con estructura idéntica:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- hst_tags_sistema: Sync desde tzrtech.org (grupo: hst)
|
||
|
|
-- hst_tags_empresa: Sync desde servidor empresa (grupo: emp)
|
||
|
|
-- hst_tags_usuario: Etiquetas personales (grupo: hsu)
|
||
|
|
-- hst_tags_proyecto: Etiquetas de proyecto (grupo: pjt)
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS hst_tags_sistema (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
h_maestro VARCHAR(64) UNIQUE, -- SHA-256, identificador único
|
||
|
|
codigo VARCHAR(50) NOT NULL, -- Código corto memorable
|
||
|
|
nombre VARCHAR(100) NOT NULL, -- Nombre legible
|
||
|
|
nombre_en VARCHAR(100), -- Nombre en inglés (opcional)
|
||
|
|
descripcion TEXT,
|
||
|
|
imagen_url TEXT, -- URL externa a la imagen (no blob)
|
||
|
|
color VARCHAR(7), -- Color hex (#RRGGBB)
|
||
|
|
grupo VARCHAR(10) DEFAULT 'hst', -- hst, emp, hsu, pjt
|
||
|
|
padre_h_maestro VARCHAR(64), -- Jerarquía: referencia al padre
|
||
|
|
activo BOOLEAN DEFAULT true,
|
||
|
|
metadata JSONB, -- Datos adicionales flexibles
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
synced_at TIMESTAMP -- Última sincronización (para hst/emp)
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Repetir estructura para: hst_tags_empresa, hst_tags_usuario, hst_tags_proyecto
|
||
|
|
-- Cambiar DEFAULT de 'grupo' según corresponda: 'emp', 'hsu', 'pjt'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Índices requeridos:**
|
||
|
|
```sql
|
||
|
|
CREATE INDEX idx_hst_sistema_h_maestro ON hst_tags_sistema(h_maestro);
|
||
|
|
CREATE INDEX idx_hst_sistema_codigo ON hst_tags_sistema(codigo);
|
||
|
|
CREATE INDEX idx_hst_sistema_grupo ON hst_tags_sistema(grupo);
|
||
|
|
CREATE INDEX idx_hst_sistema_padre ON hst_tags_sistema(padre_h_maestro);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Tabla mail_registry
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS mail_registry (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
message_id VARCHAR(255) UNIQUE, -- Message-ID del correo
|
||
|
|
|
||
|
|
-- Direcciones
|
||
|
|
from_address VARCHAR(255) NOT NULL,
|
||
|
|
to_address VARCHAR(255) NOT NULL,
|
||
|
|
cc_address TEXT, -- Puede ser múltiple
|
||
|
|
reply_to VARCHAR(255),
|
||
|
|
|
||
|
|
-- Contenido
|
||
|
|
subject TEXT,
|
||
|
|
body_preview TEXT, -- Primeras 500 palabras
|
||
|
|
|
||
|
|
-- Fechas
|
||
|
|
date_sent TIMESTAMP,
|
||
|
|
date_received TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
|
||
|
|
-- Adjuntos
|
||
|
|
has_attachments BOOLEAN DEFAULT false,
|
||
|
|
attachment_count INTEGER DEFAULT 0,
|
||
|
|
attachment_paths JSONB, -- Array de rutas en FileBrowser
|
||
|
|
|
||
|
|
-- Clasificación
|
||
|
|
direction VARCHAR(10) NOT NULL, -- 'inbound' o 'outbound'
|
||
|
|
folder VARCHAR(100) DEFAULT 'INBOX',
|
||
|
|
is_spam BOOLEAN DEFAULT false,
|
||
|
|
spam_score DECIMAL(5,2),
|
||
|
|
read_status BOOLEAN DEFAULT false,
|
||
|
|
|
||
|
|
-- Etiquetado
|
||
|
|
tags JSONB, -- Array de h_maestro
|
||
|
|
|
||
|
|
-- Procesamiento IA
|
||
|
|
processed_by_ia BOOLEAN DEFAULT false,
|
||
|
|
ia_classification JSONB, -- Resultado del clasificador
|
||
|
|
ia_processed_at TIMESTAMP,
|
||
|
|
|
||
|
|
-- Metadata
|
||
|
|
size_bytes INTEGER,
|
||
|
|
headers JSONB, -- Headers relevantes
|
||
|
|
metadata JSONB,
|
||
|
|
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Índices
|
||
|
|
CREATE INDEX idx_mail_registry_message_id ON mail_registry(message_id);
|
||
|
|
CREATE INDEX idx_mail_registry_from ON mail_registry(from_address);
|
||
|
|
CREATE INDEX idx_mail_registry_to ON mail_registry(to_address);
|
||
|
|
CREATE INDEX idx_mail_registry_date ON mail_registry(date_received);
|
||
|
|
CREATE INDEX idx_mail_registry_direction ON mail_registry(direction);
|
||
|
|
CREATE INDEX idx_mail_registry_tags ON mail_registry USING GIN(tags);
|
||
|
|
|
||
|
|
-- Constraint para direction
|
||
|
|
ALTER TABLE mail_registry
|
||
|
|
ADD CONSTRAINT chk_direction CHECK (direction IN ('inbound', 'outbound'));
|
||
|
|
```
|
||
|
|
|
||
|
|
### Tabla log_personal
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS log_personal (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
tipo VARCHAR(50) NOT NULL, -- nota, recordatorio, evento, etc.
|
||
|
|
titulo VARCHAR(255),
|
||
|
|
descripcion TEXT,
|
||
|
|
fecha_evento TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
tags JSONB, -- Array de h_maestro
|
||
|
|
metadata JSONB,
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX idx_log_personal_tipo ON log_personal(tipo);
|
||
|
|
CREATE INDEX idx_log_personal_fecha ON log_personal(fecha_evento);
|
||
|
|
CREATE INDEX idx_log_personal_tags ON log_personal USING GIN(tags);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Tabla log_sistema
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE IF NOT EXISTS log_sistema (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
tipo_evento VARCHAR(50) NOT NULL, -- Tipo de evento (ver enum abajo)
|
||
|
|
nivel VARCHAR(20) NOT NULL DEFAULT 'INFO', -- INFO, WARNING, ERROR, DEBUG
|
||
|
|
servicio VARCHAR(100), -- Servicio origen
|
||
|
|
mensaje TEXT NOT NULL,
|
||
|
|
detalles JSONB, -- Datos estructurados del evento
|
||
|
|
|
||
|
|
-- Contexto
|
||
|
|
ip_origen VARCHAR(45),
|
||
|
|
usuario VARCHAR(100),
|
||
|
|
trace_id VARCHAR(64), -- Para correlacionar eventos
|
||
|
|
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Tipos de evento permitidos
|
||
|
|
COMMENT ON COLUMN log_sistema.tipo_evento IS
|
||
|
|
'Valores: mail_received, mail_sent, file_uploaded, file_downloaded,
|
||
|
|
login, logout, api_call, sync_hst, sync_emp, backup_started,
|
||
|
|
backup_completed, backup_failed, error, warning, service_start, service_stop';
|
||
|
|
|
||
|
|
CREATE INDEX idx_log_sistema_tipo ON log_sistema(tipo_evento);
|
||
|
|
CREATE INDEX idx_log_sistema_nivel ON log_sistema(nivel);
|
||
|
|
CREATE INDEX idx_log_sistema_fecha ON log_sistema(created_at);
|
||
|
|
CREATE INDEX idx_log_sistema_servicio ON log_sistema(servicio);
|
||
|
|
CREATE INDEX idx_log_sistema_trace ON log_sistema(trace_id);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Tablas Auxiliares
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Aliases de correo (tracking de Addy.io)
|
||
|
|
CREATE TABLE IF NOT EXISTS mail_aliases (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
alias VARCHAR(255) UNIQUE NOT NULL,
|
||
|
|
destino VARCHAR(255) NOT NULL,
|
||
|
|
descripcion TEXT,
|
||
|
|
activo BOOLEAN DEFAULT true,
|
||
|
|
contador_uso INTEGER DEFAULT 0,
|
||
|
|
ultimo_uso TIMESTAMP,
|
||
|
|
tags JSONB,
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
|
||
|
|
-- URLs acortadas (tracking adicional a Shlink)
|
||
|
|
CREATE TABLE IF NOT EXISTS short_urls (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
short_code VARCHAR(50) UNIQUE NOT NULL,
|
||
|
|
url_original TEXT NOT NULL,
|
||
|
|
titulo VARCHAR(255),
|
||
|
|
descripcion TEXT,
|
||
|
|
visitas INTEGER DEFAULT 0,
|
||
|
|
activo BOOLEAN DEFAULT true,
|
||
|
|
fecha_expiracion TIMESTAMP,
|
||
|
|
tags JSONB,
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Script mail-processor.py
|
||
|
|
|
||
|
|
Script Python que escucha nuevos correos y los registra en `mail_registry`.
|
||
|
|
|
||
|
|
**Requisitos funcionales:**
|
||
|
|
1. Conectar a Dovecot via IMAP (localhost:993)
|
||
|
|
2. Escuchar eventos IDLE para nuevos correos
|
||
|
|
3. Por cada correo nuevo:
|
||
|
|
- Extraer headers y metadatos
|
||
|
|
- Generar body_preview (primeras 500 palabras, texto plano)
|
||
|
|
- Si hay adjuntos: guardar en FileBrowser, registrar rutas en attachment_paths
|
||
|
|
- Insertar registro en mail_registry
|
||
|
|
- Insertar evento en log_sistema (tipo: mail_received)
|
||
|
|
4. Manejar reconexión automática
|
||
|
|
5. Logging a stdout (para docker logs)
|
||
|
|
|
||
|
|
**Dependencias:**
|
||
|
|
```
|
||
|
|
imapclient
|
||
|
|
psycopg2-binary
|
||
|
|
python-dotenv
|
||
|
|
```
|
||
|
|
|
||
|
|
**Ejecutar como servicio systemd o contenedor Docker.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Script hst-sync.py
|
||
|
|
|
||
|
|
Script Python para sincronizar etiquetas HST desde servidor externo.
|
||
|
|
|
||
|
|
**Requisitos funcionales:**
|
||
|
|
1. Conectar a API de tzrtech.org (endpoint y auth por definir)
|
||
|
|
2. Obtener lista de etiquetas con h_maestro
|
||
|
|
3. Comparar con hst_tags_sistema local
|
||
|
|
4. UPDATE si h_maestro existe y hay cambios
|
||
|
|
5. INSERT si h_maestro no existe
|
||
|
|
6. Marcar synced_at en registros actualizados
|
||
|
|
7. Registrar evento en log_sistema (tipo: sync_hst)
|
||
|
|
|
||
|
|
**Ejecutar via cron cada 24h.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Script backup.sh
|
||
|
|
|
||
|
|
Actualizar el script existente para incluir:
|
||
|
|
1. Dump PostgreSQL completo
|
||
|
|
2. Volumen vaultwarden_data
|
||
|
|
3. Configuración de servicios
|
||
|
|
4. Volumen filebrowser_data
|
||
|
|
5. Rotación: 7 días
|
||
|
|
6. Log de cada backup en log_sistema (via psql o curl a API)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Configuración Nginx (services.conf)
|
||
|
|
|
||
|
|
Asegurar que incluye todos los servicios:
|
||
|
|
- db.tzzr.net → localhost:8081 (NocoDB)
|
||
|
|
- files.tzzr.net → localhost:8082 (FileBrowser)
|
||
|
|
- s.tzzr.net → localhost:8083 (Shlink)
|
||
|
|
- alias.tzzr.net → localhost:8084 (Addy.io)
|
||
|
|
- pass.tzzr.net → localhost:8085 (Vaultwarden, con WebSocket)
|
||
|
|
|
||
|
|
Todos con:
|
||
|
|
- SSL usando certificados de Mail-in-a-Box
|
||
|
|
- Headers de seguridad estándar
|
||
|
|
- ACME challenge para renovación SSL
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## README.md
|
||
|
|
|
||
|
|
Incluir:
|
||
|
|
1. Descripción del proyecto (servidor personal, no sistema completo)
|
||
|
|
2. Arquitectura (diagrama ASCII)
|
||
|
|
3. Servicios y URLs
|
||
|
|
4. Requisitos previos (VPS, dominio, Cloudflare)
|
||
|
|
5. Instalación rápida
|
||
|
|
6. Estructura de archivos
|
||
|
|
7. Comandos útiles
|
||
|
|
8. Backups y restauración
|
||
|
|
9. Seguridad (puertos, firewall, SSL)
|
||
|
|
10. Licencia
|
||
|
|
|
||
|
|
**NO incluir credenciales reales en el repositorio.**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Entregables
|
||
|
|
|
||
|
|
1. [ ] `docker-compose.yml` — Actualizado con todos los servicios
|
||
|
|
2. [ ] `init.sql` — Schema completo con las 4 tablas HST + mail_registry + logs
|
||
|
|
3. [ ] `nginx/services.conf` — Configuración reverse proxy completa
|
||
|
|
4. [ ] `scripts/backup.sh` — Actualizado con vaultwarden
|
||
|
|
5. [ ] `scripts/mail-processor.py` — Nuevo: procesar correos
|
||
|
|
6. [ ] `scripts/hst-sync.py` — Nuevo: sincronizar etiquetas
|
||
|
|
7. [ ] `dns/tzzr_net.zone` — Archivo de zona DNS
|
||
|
|
8. [ ] `README.md` — Documentación principal
|
||
|
|
9. [ ] `docs/INSTALACION.md` — Guía paso a paso
|
||
|
|
10. [ ] `docs/CLONACION.md` — Guía para replicar a nuevo usuario
|
||
|
|
11. [ ] `docs/CREDENCIALES.template` — Plantilla sin valores reales
|
||
|
|
12. [ ] `.gitignore` — Excluir .env, credenciales, backups
|
||
|
|
13. [ ] `.env.example` — Variables de entorno requeridas
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Variables de Entorno (.env.example)
|
||
|
|
|
||
|
|
```env
|
||
|
|
# PostgreSQL
|
||
|
|
POSTGRES_USER=tzzr
|
||
|
|
POSTGRES_PASSWORD=
|
||
|
|
POSTGRES_DB=tzzr
|
||
|
|
|
||
|
|
# NocoDB
|
||
|
|
NC_AUTH_JWT_SECRET=
|
||
|
|
|
||
|
|
# Addy.io
|
||
|
|
ANONADDY_DOMAIN=tzzr.net
|
||
|
|
ANONADDY_SECRET=
|
||
|
|
APP_KEY=
|
||
|
|
|
||
|
|
# Vaultwarden
|
||
|
|
VAULTWARDEN_DOMAIN=https://pass.tzzr.net
|
||
|
|
VAULTWARDEN_ADMIN_TOKEN=
|
||
|
|
|
||
|
|
# Shlink
|
||
|
|
SHLINK_DOMAIN=s.tzzr.net
|
||
|
|
|
||
|
|
# Mail processor
|
||
|
|
IMAP_HOST=localhost
|
||
|
|
IMAP_USER=
|
||
|
|
IMAP_PASSWORD=
|
||
|
|
|
||
|
|
# HST Sync (por definir)
|
||
|
|
HST_API_URL=
|
||
|
|
HST_API_TOKEN=
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Notas para Code
|
||
|
|
|
||
|
|
1. **No crear servicios nuevos** — Solo documentar y estructurar lo existente
|
||
|
|
2. **No incluir credenciales reales** — Usar placeholders o .env.example
|
||
|
|
3. **Mantener compatibilidad** — El servidor ya está funcionando
|
||
|
|
4. **Schema SQL debe ser idempotente** — Usar IF NOT EXISTS
|
||
|
|
5. **Scripts deben tener logging** — Para debugging via docker logs
|
||
|
|
6. **Documentación en español** — Usuario hispanohablante
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Repositorio Destino
|
||
|
|
|
||
|
|
Por confirmar con el usuario. Puede ser:
|
||
|
|
- Nuevo repositorio
|
||
|
|
- Repositorio existente a actualizar
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Documento generado: Diciembre 2024*
|