Instanciación del Sistema de Elecciones

La forma vigente de instanciar el Sistema de Elecciones es mediante Docker, usando los archivos disponibles en la carpeta dockers. El contenedor ejecuta WildFly 34 con JDK 17, copia los artefactos generados por Maven y toma la configuración operativa desde variables de ambiente y parámetros persistidos en PostgreSQL.

El repositorio ofrece dos perfiles. Fresh construye la aplicación y levanta PostgreSQL y Mailpit para evaluación, desarrollo o como referencia de una instalación nueva. El Compose tradicional ejecuta una imagen ya publicada y se conecta a servicios externos; es una base para producción, pero cada organización debe endurecerla con PostgreSQL y SMTP productivos, gestión de secretos, backups, monitoreo y un reverse proxy con TLS.

Una organización independiente puede operar con autenticación local, carga manual de organizaciones y padrón, sin Campus y sin asistencia de IA. Las integraciones históricas PAI, Campus y external sync son opcionales.

La instalación manual de WildFly queda como alternativa avanzada para ambientes puntuales. Docker es el mecanismo soportado de instanciación y los scripts de base versionados se mantienen en release-files/.

Los pasos a ejecutar son los siguientes:

  1. Clonar repositorio
  2. Prerrequisitos
  3. Arranque autocontenido
  4. Preparar una base de datos externa
  5. Configurar el primer login administrativo
  6. Configurar variables de ambiente
  7. Compilar y construir imagen
  8. Levantar con Docker Compose
  9. Despliegue remoto
  10. Alternativa manual con WildFly
  11. Validación inicial
  12. Reverse proxy

1. Clonar repositorio

Clone el repositorio original o el fork mantenido por su organización en el servidor o workspace desde donde va a construir la imagen:

git clone --branch develop-v3-snapshot --single-branch https://github.com/LACNIC/elections-open-source.git
cd elections-open-source

Todos los comandos de esta guía se ejecutan desde la raíz del repositorio, salvo que se indique otro directorio.

2. Prerrequisitos

Para instanciar el sistema con Docker se requiere:

  • Docker con soporte para docker build y docker compose.
  • Para el perfil tradicional, acceso al registry usado por la instalación o una imagen cargada localmente.
  • Para el perfil tradicional, una base PostgreSQL accesible desde el contenedor.
  • Variables de ambiente completas para el datasource. La integración PAI es opcional: una organización independiente puede usar autenticación local.

docker-fresh.sh compila con Maven dentro de Docker, por lo que el host no necesita Java ni Maven. Para compilar directamente en el host se requieren Java 17 y Maven 3.9 o compatible.

3. Arranque autocontenido

Este es el camino recomendado para verificar una copia nueva del proyecto. Crea una base PostgreSQL persistente, aplica los SQL de referencia, crea la personalización inicial y el primer administrador, configura autenticación local APP y levanta Mailpit para inspeccionar el correo saliente.

cp dockers/.env.example dockers/.env
# Edite dockers/.env y cambie, como mínimo, contraseñas, correo, nombre y URL.
./dockers/docker-fresh.sh up

La aplicación queda en http://localhost:8098/elections y Mailpit en http://localhost:8025, salvo que se cambien los puertos en .env. ./dockers/docker-fresh.sh down detiene los servicios conservando la base; reset elimina el volumen y vuelve a crearla, por lo que destruye los datos de esa instancia.

No publique este perfil directamente en Internet. Expone puertos de PostgreSQL y Mailpit, usa valores de ejemplo y no incorpora TLS. Úselo para evaluación/desarrollo o como modelo que debe adaptarse antes de producción.

4. Preparar una base de datos externa

Esta sección corresponde al Compose tradicional o a una arquitectura productiva. En ese perfil el contenedor de aplicación no crea la base: debe existir una base PostgreSQL y el usuario configurado debe tener permisos suficientes sobre el esquema public. El perfil fresh sí automatiza esta inicialización.

Para una base nueva, los archivos versionados que representan la estructura y datos base actuales son:

  • release-files/ref/elections_schema_new.sql
  • release-files/ref/parameter_NEW.sql
  • release-files/ref/electionemailtemplate_NEW.sql

Ejemplo de inicialización:

createdb elections
psql -v ON_ERROR_STOP=1 -d elections -f release-files/ref/elections_schema_new.sql
psql -v ON_ERROR_STOP=1 -d elections -f release-files/ref/parameter_NEW.sql
psql -v ON_ERROR_STOP=1 -d elections -f release-files/ref/electionemailtemplate_NEW.sql
psql -v ON_ERROR_STOP=1 -d elections -c "SELECT setval('public.electionemailtemplate_seq', COALESCE((SELECT MAX(electionemailtemplate_id) FROM public.electionemailtemplate), 500), EXISTS (SELECT 1 FROM public.electionemailtemplate));"

Revise los parámetros cargados antes de usar la instancia: los archivos base incluyen URLs, tokens y valores por defecto que pueden corresponder a ambientes de desarrollo o test.

Estos archivos no crean usuarios administrativos. Antes del primer login debe elegir el mecanismo de autenticación y, si usa cuentas locales, aprovisionar el primer administrador como se explica en la siguiente sección.

Si la base proviene de una versión anterior, no use los archivos de referencia como migración. Siga la guía de actualización y aplique los scripts de release-files en el orden correspondiente.

Para una base nueva use el perfil Docker autocontenido. Para actualizar una instalación existente, aplique la cadena correspondiente de release-files/ siguiendo la guía de actualización.

5. Configurar el primer login administrativo

El parámetro WS_AUTH_METHOD se guarda en la tabla parameter y controla el login web y la autenticación REST. Para una organización que no dispone de PAI, el valor correcto es APP. En este modo el login usa las cuentas de la tabla useradmin.

Confirme el modo local:

psql -v ON_ERROR_STOP=1 -d elections <<'SQL'
INSERT INTO public.parameter ("key", value)
VALUES ('WS_AUTH_METHOD', 'APP')
ON CONFLICT ("key") DO UPDATE SET value = EXCLUDED.value;
SQL

Luego cree el primer administrador. El login vigente compara un hash SHA-256 en mayúsculas. El siguiente ejemplo evita incluir la contraseña en el SQL o en el historial de comandos; cambie usuario, correo y parámetros de conexión:

export INITIAL_ADMIN_USER='admin-organizacion'
export INITIAL_ADMIN_EMAIL='admin@example.org'
read -r -s -p 'Contraseña inicial: ' INITIAL_ADMIN_PASSWORD
echo
INITIAL_ADMIN_PASSWORD_HASH="$(printf '%s' "$INITIAL_ADMIN_PASSWORD" | sha256sum | awk '{print toupper($1)}')"

psql -v ON_ERROR_STOP=1 -d elections \
  --set=admin_user="$INITIAL_ADMIN_USER" \
  --set=admin_email="$INITIAL_ADMIN_EMAIL" \
  --set=admin_password_hash="$INITIAL_ADMIN_PASSWORD_HASH" <<'SQL'
INSERT INTO public.useradmin (useradmin_id, email, password)
VALUES (lower(:'admin_user'), lower(:'admin_email'), :'admin_password_hash')
ON CONFLICT (useradmin_id) DO UPDATE
SET email = EXCLUDED.email,
    password = EXCLUDED.password;
SQL

unset INITIAL_ADMIN_PASSWORD INITIAL_ADMIN_PASSWORD_HASH

Esta operación crea la cuenta o restablece la cuenta indicada si ya existía. Ejecútela únicamente desde un host administrativo y no guarde la contraseña en el repositorio. El flujo docker-fresh.sh realiza automáticamente el mismo aprovisionamiento usando FRESH_ADMIN_USER, FRESH_ADMIN_PASSWORD y FRESH_ADMIN_EMAIL.

Una vez desplegada la aplicación, abra https://<servidor>/elections/login o, localmente, http://localhost:8098/elections/login. En modo APP el campo TOTP se deja vacío. Después del acceso, use el menú Administradores para crear una segunda cuenta, actualizar correos, cambiar contraseñas y retirar la cuenta temporal cuando corresponda.

Importante: todos los administradores locales reciben actualmente permisos administrativos completos, incluidos elections-manager y elections-deleter. No existe recuperación autoservicio de contraseña; conserve al menos dos cuentas controladas. Si se pierde la única cuenta, un operador de base debe ejecutar nuevamente el procedimiento anterior.

El modo externo actual está acoplado al contrato de PAI y a los roles esperados por Elecciones. No hay soporte genérico listo para OIDC, OAuth2, SAML o LDAP; integrar un proveedor corporativo distinto requiere desarrollo adicional o un servicio compatible con ese contrato.

6. Configurar variables de ambiente

El archivo dockers/docker-compose.yml carga variables desde .env. Use dockers/.env.example como referencia, copie el archivo a dockers/.env y reemplace todos los valores por los del ambiente real.

Variables usadas por la imagen y la configuración de WildFly:

  • VERSION: tag de la imagen Docker que va a ejecutar Compose.
  • JAVA_OPTS: opciones de memoria y runtime de Java.
  • URL_PORTAL_WS y PORTAL_APIKEY: configuración escrita en pai.properties al iniciar el contenedor. Si usa WS_AUTH_METHOD=APP, mantenga valores explícitamente inhabilitados como los del ejemplo; no necesita credenciales PAI.
  • DB_ELECTIONS_HOST, DB_ELECTIONS_PORT y DB_ELECTIONS_NAME: host, puerto y nombre de la base PostgreSQL del ambiente.
  • DB_ELECTIONS_USER y DB_ELECTIONS_PASSWORD: credenciales del datasource java:jboss/datasources/elections-ds.
  • DB_ELECTIONS_MIN_POOL_SIZE y DB_ELECTIONS_MAX_POOL_SIZE: tamaño mínimo y máximo del pool de conexiones.

No publique credenciales reales en el repositorio. En servidores remotos, mantenga el archivo .env junto al docker-compose.yml usado para levantar el contenedor.

WS_AUTH_METHOD no es una variable consumida por el Compose normal: se lee desde la tabla parameter. El bootstrap fresh fija ese parámetro en APP; no existe una variable FRESH_AUTH_METHOD.

En fresh también se pueden definir FRESH_ORGANIZATION_NAME, FRESH_PUBLIC_BASE_URL, FRESH_PUBLIC_NOMINATION_ENABLED, FRESH_ADMIN_*, FRESH_DEFAULT_SENDER y FRESH_EMAIL_*. Consulte dockers/.env.example, que es la fuente vigente de variables disponibles.

7. Compilar y construir imagen

La imagen definida en dockers/Dockerfile compila el sistema y la documentación pública, parte de wildfly:34.0.0.Final-jdk17 y copia los siguientes artefactos:

  • elections-ejb/target/elections-ejb-1.0.jar
  • elections-admin-web/target/elections.war
  • elections-services/target/elections-ws.war
  • wildfly/deployments/pai-auth-ws-client-1.5.1.jar
  • wildfly/modules/ y wildfly/configuration/standalone.xml

Para construir manualmente:

docker build -f dockers/Dockerfile -t registry.example.org/apps/elections:<tag> .
docker push registry.example.org/apps/elections:<tag>

La imagen base indicada en dockers/Dockerfile es un placeholder que cada organización debe reemplazar por su propia imagen WildFly compatible. Para una construcción basada en imágenes públicas, consulte dockers/Dockerfile.fresh.

8. Levantar con Docker Compose tradicional

El Compose actual ejecuta la imagen registry.example.org/apps/elections:${VERSION}, monta logs en ./logs, ejecuta el contenedor con usuario 10000:10000 y publica 8098:8080.

Ejemplo:

cd dockers
mkdir -p logs
docker compose up -d

El helper dockers/docker-local.sh existe para ambientes locales con una imagen base previamente disponible en el daemon. Construye la imagen con tag dev, sin hacer login, pull ni push, recrea el contenedor elections y abre la URL local.

9. Automatización remota de referencia

dockers/docker-jenkins.sh conserva un flujo de infraestructura específico: asume Jenkins, un registry, acceso SSH y el directorio remoto /usr/local/properties. No es un requisito del producto ni un mecanismo portable listo para cualquier organización. Úselo únicamente como referencia y adáptelo al CI/CD, rutas y política de despliegue propios.

El script espera cuatro argumentos: registry, nombre de aplicación, ambiente y servidor remoto. También espera la variable WORKSPACE apuntando a la raíz del repositorio.

export WORKSPACE=/ruta/al/repositorio
dockers/docker-jenkins.sh registry.example.org/apps elections prod usuario@servidor

Antes de ejecutarlo por primera vez en un servidor remoto, prepare el directorio de propiedades y logs, y cree allí el archivo .env del ambiente:

mkdir -p /usr/local/properties/elections/logs
vi /usr/local/properties/elections/.env

El script copia dockers/docker-compose.yml a /usr/local/properties/elections/docker-compose.yml y ejecuta docker compose pull, down y up -d sobre ese archivo.

10. Alternativa manual con WildFly

La alternativa manual no es el camino recomendado para producción. Si necesita usarla, debe replicar lo que hace el Dockerfile:

  • Usar Java 17 y WildFly 34.
  • Copiar wildfly/modules/ al directorio de módulos de WildFly.
  • Usar wildfly/configuration/standalone.xml como configuración base.
  • Configurar las variables DB_ELECTIONS_*, URL_PORTAL_WS y PORTAL_APIKEY en el entorno del proceso WildFly.
  • Publicar los artefactos generados en standalone/deployments.

Las instrucciones históricas para Java 8, WildFly 20, PostgreSQL 12 y playbooks de aprovisionamiento completo ya no describen el sistema actual.

11. Validación inicial

Después de levantar el contenedor, revise:

  • Que el contenedor elections esté corriendo.
  • Que los logs de WildFly no muestren errores de datasource o despliegue.
  • Que la aplicación responda en http://<host>:8098/elections o en la URL publicada por el reverse proxy.
  • Que /elections/login acepte la cuenta inicial.
  • Si usa autenticación local, que aparezca el menú Administradores y que una segunda cuenta pueda iniciar sesión antes de retirar la cuenta de bootstrap.
  • Que el listado de elecciones funcione contra la base configurada.
  • Que los servicios web respondan si están habilitados en el ambiente.

12. Producción y reverse proxy

Para producción, publique el contenedor detrás de un reverse proxy como Apache o Nginx. El contenedor expone WildFly en el puerto interno 8080; el Compose incluido publica 8098 en el host.

El reverse proxy debe resolver TLS, cabeceras externas, logging HTTP y la URL pública usada por usuarios, auditores y servicios integrados.

Antes de producción, además: use secretos fuera del repositorio, limite la exposición de PostgreSQL, configure backups y recuperación, sustituya Mailpit por SMTP real, ajuste remitentes y dominio público, habilite sólo las integraciones necesarias y configure monitoreo de aplicación, base y correo. La configuración fresh de correo usa el puerto 1025 sin TLS; un SMTP con otro puerto o política TLS requiere proporcionar una configuración email.properties adecuada para el ambiente.