Files
system-docs/v4-archive/deck/docs/ESPECIFICACION_SERVIDOR.md
2025-12-24 17:28:34 +00:00

13 KiB

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:

-- 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:

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

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

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

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

-- 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)

# 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