# 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*