CloudBeaver database manager guide, Ecija intranet deployment, Gitea-Coolify auto-deploy and integration docs, monitoring setup with presentation, remote access guide, security architecture, and Turbostarter deployment procedure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
732 lines
25 KiB
Markdown
732 lines
25 KiB
Markdown
# ECIJA Intranet - NUC Deployment Guide
|
|
|
|
> **Purpose:** Step-by-step deployment of the ECIJA Intranet Django backend on the NUC home server.
|
|
> **Source project:** `/Users/agutierrez/Desktop/ECIJA-Intranet/`
|
|
> **Database dump:** `/Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup/`
|
|
> **Credentials file:** `/Users/agutierrez/Desktop/flexicar-intranet-backend/windy-shoreline-225910-efd33901e56c.json`
|
|
> **Installation reference:** `/Users/agutierrez/Desktop/flexicar-intranet-backend/instalacion_intra.docx`
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ NUC Server (192.168.1.3) │
|
|
│ Tailscale: 100.113.153.45 │
|
|
│ │
|
|
intranet.nuc.lan │ ┌──────────┐ ┌────────────────────┐ │
|
|
──────────────────►│ │ Traefik │───►│ Django/Gunicorn │ │
|
|
│ │ :80/:443 │ │ :8010 │ │
|
|
│ └──────────┘ └────────┬───────────┘ │
|
|
│ │ │
|
|
│ ┌────────┼───────┐ │
|
|
│ │ │ │ │
|
|
│ ┌──────▼──┐ ┌───▼───┐ │ │
|
|
│ │Postgres │ │ Redis │ │ │
|
|
│ │ :5434 │ │ :6379 │ │ │
|
|
│ └─────────┘ └───────┘ │ │
|
|
│ │ │
|
|
│ ┌──────────────────────┐│ │
|
|
│ │ Background Worker ││ │
|
|
│ │ (process_tasks) ││ │
|
|
│ └──────────────────────┘│ │
|
|
└───────────────────────────────────┼────────┘
|
|
│
|
|
▼
|
|
┌──────────────────┐
|
|
│ Google Cloud │
|
|
│ Storage (GCS) │
|
|
│ Bucket: │
|
|
│ ecija-intranet │
|
|
└──────────────────┘
|
|
```
|
|
|
|
### Design principle: change nothing, only deploy
|
|
|
|
The NUC is the **compute host only**. All external integrations (GCS, HubSpot, Azure AD, 3G, reCAPTCHA) stay exactly as the original app expects them. This means:
|
|
- Zero code changes to `settings.py`
|
|
- Same env vars as the official installation doc
|
|
- Same GCS bucket and credentials
|
|
- If it works locally on a Mac, it works on the NUC
|
|
|
|
### What the NUC already provides (no setup needed)
|
|
|
|
| Service | Port | Notes |
|
|
|---------|------|-------|
|
|
| Redis | 6379 | UUID: `vkg44cgcss4ococgk0cs000o` - reuse existing |
|
|
| Traefik | 80/443 | Reverse proxy for `intranet.nuc.lan` |
|
|
| n8n | 5678 | Workflow automation (intranet has n8n webhooks) |
|
|
| Adminer | 8088 | DB admin UI |
|
|
| CloudBeaver | 8978 | DB admin UI (alternative) |
|
|
| Dozzle | 9999 | Container log viewer |
|
|
| Uptime Kuma | 3001 | Service monitoring |
|
|
| Kopia | 51515 | Backup management |
|
|
|
|
### What needs to be deployed
|
|
|
|
| Service | Port | Method |
|
|
|---------|------|--------|
|
|
| PostgreSQL (dedicated) | 5434 | Coolify service |
|
|
| Django + Gunicorn | 8010 | Coolify docker-compose |
|
|
| Background Worker | - | Same image, different command |
|
|
|
|
---
|
|
|
|
## Phase 1: PostgreSQL Database
|
|
|
|
### 1.1 Deploy a dedicated PostgreSQL instance via Coolify
|
|
|
|
Use port **5434** to avoid conflicts with existing Postgres instances (5432, 5433, 5442 already in use).
|
|
|
|
```bash
|
|
# Via Coolify MCP:
|
|
mcp__coolify__service(
|
|
action="create",
|
|
type="postgresql",
|
|
name="ecija-intranet-db",
|
|
server_uuid="qk84w0goo4w48g4ggsoo0oss",
|
|
project_uuid="a8484ggc88c40w4g4k004ow0",
|
|
environment_name="production",
|
|
instant_deploy=True
|
|
)
|
|
```
|
|
|
|
If native type fails, deploy via docker-compose:
|
|
|
|
```yaml
|
|
services:
|
|
ecija-postgres:
|
|
image: postgres:17
|
|
container_name: ecija-intranet-db
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: postgres
|
|
POSTGRES_DB: flexicar-prod
|
|
ports:
|
|
- "5434:5432"
|
|
volumes:
|
|
- ecija_pgdata:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres -d flexicar-prod"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
volumes:
|
|
ecija_pgdata:
|
|
```
|
|
|
|
### 1.2 Enable required PostgreSQL extensions
|
|
|
|
```bash
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'"
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'CREATE EXTENSION IF NOT EXISTS unaccent;'"
|
|
```
|
|
|
|
### 1.3 Transfer and restore the database dump
|
|
|
|
The dump is in pg_dump **directory format** (4 GB, 453 tables).
|
|
|
|
**Important:** The dump was exported from a database called `flexicar` with owner `flexicar`, but the Django app expects `DB_NAME=flexicar-prod` with `DATABASE_USER=postgres` (per the official install doc). We restore into `flexicar-prod` using `--no-owner` so all objects are owned by `postgres`.
|
|
|
|
```bash
|
|
# Step 1: Transfer dump directory to NUC
|
|
scp -r /Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup/ nuc:/tmp/ecija-dump/
|
|
|
|
# Step 2: Copy dump into the Postgres container
|
|
ssh nuc "docker cp /tmp/ecija-dump/ ecija-intranet-db:/tmp/ecija-dump/"
|
|
|
|
# Step 3: Restore the dump into flexicar-prod (ignore original ownership)
|
|
ssh nuc "docker exec ecija-intranet-db pg_restore \
|
|
-U postgres \
|
|
-d flexicar-prod \
|
|
--no-owner \
|
|
--no-privileges \
|
|
--jobs=4 \
|
|
/tmp/ecija-dump/"
|
|
|
|
# Step 4: Verify table count (should be ~453)
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c \"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';\""
|
|
|
|
# Step 5: Clean up dump from container
|
|
ssh nuc "docker exec ecija-intranet-db rm -rf /tmp/ecija-dump/"
|
|
|
|
# Step 6: Clean up dump from NUC host
|
|
ssh nuc "rm -rf /tmp/ecija-dump/"
|
|
```
|
|
|
|
### 1.4 Verify database is accessible
|
|
|
|
```bash
|
|
# From NUC
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'SELECT current_database(), current_user;'"
|
|
|
|
# From Adminer (browser)
|
|
# URL: http://192.168.1.3:8088
|
|
# System: PostgreSQL
|
|
# Server: ecija-intranet-db:5432 (or 192.168.1.3:5434 if external)
|
|
# Username: postgres
|
|
# Password: postgres
|
|
# Database: flexicar-prod
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: Transfer Project Files
|
|
|
|
### 2.1 Transfer the project to NUC
|
|
|
|
```bash
|
|
# Exclude unnecessary files (venv, node_modules, caches)
|
|
rsync -avz --progress \
|
|
--exclude '.git' \
|
|
--exclude 'env/' \
|
|
--exclude 'node_modules/' \
|
|
--exclude '.DS_Store' \
|
|
--exclude '__pycache__' \
|
|
--exclude '*.pyc' \
|
|
/Users/agutierrez/Desktop/ECIJA-Intranet/ \
|
|
nuc:/opt/ecija-intranet/
|
|
```
|
|
|
|
### 2.2 Transfer the Google Cloud Storage credentials
|
|
|
|
The GCS service account key is required for static/media file storage. This is the same file used in the original production environment.
|
|
|
|
**Important:** The official install doc sets `GS_CREDENTIALS="/secrets/bucket/credentials.json"` (generic name), while `GS_CREDENTIALS_LOCAL` uses the full filename. We mount the credentials dir to `/secrets/bucket/` and copy the file as `credentials.json` to match the production `GS_CREDENTIALS` path.
|
|
|
|
```bash
|
|
# Create the credentials directory on NUC
|
|
ssh nuc "mkdir -p /opt/ecija-intranet/config/credenciales"
|
|
|
|
# Transfer the service account JSON (keep original name for GS_CREDENTIALS_LOCAL)
|
|
scp /Users/agutierrez/Desktop/flexicar-intranet-backend/windy-shoreline-225910-efd33901e56c.json \
|
|
nuc:/opt/ecija-intranet/config/credenciales/windy-shoreline-225910-efd33901e56c.json
|
|
|
|
# Also copy as credentials.json (for GS_CREDENTIALS=/secrets/bucket/credentials.json)
|
|
ssh nuc "cp /opt/ecija-intranet/config/credenciales/windy-shoreline-225910-efd33901e56c.json \
|
|
/opt/ecija-intranet/config/credenciales/credentials.json"
|
|
```
|
|
|
|
### 2.3 Optionally push to NUC's Gitea for version control
|
|
|
|
```bash
|
|
# From local machine
|
|
cd /Users/agutierrez/Desktop/ECIJA-Intranet
|
|
|
|
# Create the repo in Gitea first (via browser at http://192.168.1.3:3030 or API)
|
|
# Then add remote and push
|
|
git remote add nuc http://192.168.1.3:3030/alezmad/ecija-intranet.git
|
|
git push nuc main
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 3: Django Application Container
|
|
|
|
### 3.1 Create the production Dockerfile
|
|
|
|
The existing Dockerfile uses `gcr.io/google_appengine/python` (Google App Engine base image) which is unnecessary on the NUC.
|
|
|
|
Create file on NUC at `/opt/ecija-intranet/Dockerfile.nuc`:
|
|
|
|
```dockerfile
|
|
FROM python:3.9-slim
|
|
|
|
# System dependencies for psycopg2, Pillow, lxml, pdf libs
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
build-essential \
|
|
libpq-dev \
|
|
libjpeg-dev \
|
|
libfreetype6-dev \
|
|
libxml2-dev \
|
|
libxslt1-dev \
|
|
zlib1g-dev \
|
|
libffi-dev \
|
|
libmupdf-dev \
|
|
git \
|
|
curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Install Node.js 18 for frontend build
|
|
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
|
&& apt-get install -y nodejs \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
# Python dependencies (root level - includes all packages)
|
|
COPY requirements.txt /app/requirements.txt
|
|
RUN pip install --upgrade pip && \
|
|
pip install --use-pep517 -r /app/requirements.txt
|
|
|
|
# Copy application code
|
|
COPY app/ /app/
|
|
|
|
# Frontend dependencies and build
|
|
RUN npm install && npm run build
|
|
|
|
EXPOSE 8010
|
|
|
|
# Entrypoint script
|
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
|
RUN chmod +x /docker-entrypoint.sh
|
|
|
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
CMD ["gunicorn", "ecija.wsgi:application", \
|
|
"--workers", "4", \
|
|
"--threads", "4", \
|
|
"--timeout", "120", \
|
|
"--graceful-timeout", "30", \
|
|
"--keep-alive", "5", \
|
|
"--max-requests", "1000", \
|
|
"--max-requests-jitter", "100", \
|
|
"--bind", "0.0.0.0:8010", \
|
|
"--access-logfile", "-", \
|
|
"--error-logfile", "-", \
|
|
"--log-level", "info"]
|
|
```
|
|
|
|
### 3.2 Create the entrypoint script
|
|
|
|
Create file on NUC at `/opt/ecija-intranet/docker-entrypoint.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
echo "Running database migrations..."
|
|
python manage.py migrate --noinput
|
|
|
|
echo "Collecting static files..."
|
|
python manage.py collectstatic --noinput || true
|
|
|
|
echo "Starting application..."
|
|
exec "$@"
|
|
```
|
|
|
|
### 3.3 Deploy via Coolify docker-compose
|
|
|
|
Deploy through Coolify MCP using `docker_compose_raw`:
|
|
|
|
```yaml
|
|
services:
|
|
ecija-web:
|
|
build:
|
|
context: /opt/ecija-intranet
|
|
dockerfile: Dockerfile.nuc
|
|
container_name: ecija-intranet-web
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8010:8010"
|
|
env_file:
|
|
- /opt/ecija-intranet/.env.production
|
|
volumes:
|
|
- ecija_media:/app/media
|
|
- /opt/ecija-intranet/config/credenciales:/secrets/bucket:ro
|
|
networks:
|
|
- ecija-network
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8010/"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
|
|
ecija-worker:
|
|
build:
|
|
context: /opt/ecija-intranet
|
|
dockerfile: Dockerfile.nuc
|
|
container_name: ecija-intranet-worker
|
|
restart: unless-stopped
|
|
command: ["python", "manage.py", "process_tasks"]
|
|
env_file:
|
|
- /opt/ecija-intranet/.env.production
|
|
volumes:
|
|
- ecija_media:/app/media
|
|
- /opt/ecija-intranet/config/credenciales:/secrets/bucket:ro
|
|
networks:
|
|
- ecija-network
|
|
|
|
volumes:
|
|
ecija_media:
|
|
|
|
networks:
|
|
ecija-network:
|
|
driver: bridge
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 4: Environment Configuration
|
|
|
|
### 4.1 Create the production env file
|
|
|
|
This uses the **same values from the official installation doc** (`instalacion_intra.docx`), with only the DB host changed to point to the NUC container.
|
|
|
|
Create on NUC at `/opt/ecija-intranet/.env.production`:
|
|
|
|
```bash
|
|
# ============================================================
|
|
# ECIJA Intranet - NUC Production Environment
|
|
# Source: /Users/agutierrez/Desktop/flexicar-intranet-backend/instalacion_intra.docx
|
|
# Principle: IDENTICAL to official install doc, only DB_HOST changed
|
|
# ============================================================
|
|
# Lines below are a 1:1 copy from the docx. Only DB_HOST differs
|
|
# (container name instead of 127.0.0.1).
|
|
# Do NOT add extra vars that aren't in the original doc.
|
|
|
|
DATABASE_USER=postgres
|
|
DATABASE_PASSWORD=postgres
|
|
DJANGO_ENVIRONMENT=local
|
|
MS_SECRET_KEY=H6CLoLV?kO.uaZOI3lkiHv=L:jWqk12t
|
|
G_RECAPTCHA_SECRET_KEY=
|
|
G_RECAPTCHA_SITE_KEY=
|
|
DEBUG=True
|
|
DEMO=False
|
|
GKE_ENABLED=True
|
|
AG=False
|
|
GS=True
|
|
RECAPTCHA=True
|
|
DB_NAME_DEMO=ecija_demo
|
|
DB_NAME=flexicar-prod
|
|
DB_HOST=ecija-intranet-db
|
|
DB_PORT=5432
|
|
STATIC_URL=/static/
|
|
MEDIA_URL=/media/
|
|
GS_BUCKET_NAME=ecija-intranet
|
|
GS_PROJECT_ID=ecija-intranet
|
|
GS_CREDENTIALS=/secrets/bucket/credentials.json
|
|
TENANT_ID=8e39b277-105b-4b2e-b21c-e06cd806a070
|
|
CLIENT_ID=6858acbb-098f-4261-987d-6a9892b06d86
|
|
RELYING_PARTY_ID=api://18d841fc-2054-4b76-992a-2a11fc9ade78
|
|
AUDIENCE=api://18d841fc-2054-4b76-992a-2a11fc9ade78
|
|
CODIGO_EMPRESA_3G=ecijades
|
|
LOGIN_3G=ws64304
|
|
PASSWORD_3G=z4JcpcTqjd
|
|
IDIOMA_3G=ES
|
|
API_KEY_3G=m23o2MHszn128snAkwkaAKnzLK29s91klalzl19Jjkzj19zlxlnwZNmMnN1812nznNMjsjz9MnznWwqsjzlSAK2znqh0z9134
|
|
APPS_INSTALADAS=django_auth_adfs,ecija,meta_aepd_app,agile,gestor_licencias,telefonia,administracion,certificados3g,ticketing,analytics,demo,gestion_documental,sp_widgets,configuracion_interfaz,widgets_dashboard,historico,comentarios,avisos,procesal,canal_denuncias,referidos,notificacion,busqueda_google,tareas,bank_movements,notas,oportunidades,reclamaciones,nux,eventos,fleximanage,comunicaciones
|
|
HUBSPOT_API_URL_COMPANIES=https://api.hubapi.com/companies/v2/companies
|
|
HUBSPOT_API_URL_CONTACTS=https://api.hubapi.com/contacts/v1/contact
|
|
HUBSPOT_API_KEY=pat-eu1-5b7e6860-e273-4a92-b6f8-b6eead476505
|
|
GS_CREDENTIALS_LOCAL=/secrets/bucket/windy-shoreline-225910-efd33901e56c.json
|
|
GOOGLE_APPLICATION_CREDENTIALS=/secrets/bucket/windy-shoreline-225910-efd33901e56c.json
|
|
```
|
|
|
|
### 4.2 Alignment with official install doc
|
|
|
|
The `.env.production` file is a **1:1 copy** of the env vars from `instalacion_intra.docx` with one change:
|
|
|
|
| Variable | Official doc | NUC deployment | Why |
|
|
|----------|-------------|----------------|-----|
|
|
| `DB_HOST` | `127.0.0.1` | `ecija-intranet-db` | Container networking (Docker DNS resolves container names) |
|
|
|
|
Everything else is identical. GCS stays because:
|
|
- The app is built on `django-storages[google]`, not S3
|
|
- The credentials already work
|
|
- Zero code changes required
|
|
- Same config carries over to real production
|
|
|
|
---
|
|
|
|
## Phase 5: Networking & DNS
|
|
|
|
### 5.1 Add DNS entry for `intranet.nuc.lan`
|
|
|
|
```bash
|
|
ssh nuc "ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 '
|
|
uci add dhcp domain
|
|
uci set dhcp.@domain[-1].name=\"intranet.nuc.lan\"
|
|
uci set dhcp.@domain[-1].ip=\"100.113.153.45\"
|
|
uci commit dhcp
|
|
/etc/init.d/dnsmasq restart
|
|
'"
|
|
```
|
|
|
|
### 5.2 Add Traefik route
|
|
|
|
Edit on NUC: `/data/coolify/proxy/dynamic/nuc-services.yaml`
|
|
|
|
Add the following router and service (merge with existing entries):
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
ecija-intranet:
|
|
rule: Host(`intranet.nuc.lan`)
|
|
service: ecija-intranet
|
|
entryPoints:
|
|
- http
|
|
services:
|
|
ecija-intranet:
|
|
loadBalancer:
|
|
servers:
|
|
- url: http://host.docker.internal:8010
|
|
```
|
|
|
|
### 5.3 Update Homepage dashboard
|
|
|
|
Edit on NUC: `/opt/homepage/config/services.yaml`
|
|
|
|
Add under appropriate section:
|
|
|
|
```yaml
|
|
- ECIJA Intranet:
|
|
- Intranet:
|
|
href: http://intranet.nuc.lan
|
|
description: ECIJA Law Firm Intranet
|
|
icon: django
|
|
server: nuc
|
|
container: ecija-intranet-web
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 6: Monitoring & Backup
|
|
|
|
### 6.1 Add to Uptime Kuma
|
|
|
|
Via browser at `http://192.168.1.3:3001`:
|
|
1. Add new monitor
|
|
2. Type: HTTP(s)
|
|
3. URL: `http://192.168.1.3:8010`
|
|
4. Name: ECIJA Intranet
|
|
5. Heartbeat interval: 60s
|
|
|
|
### 6.2 Add database to Kopia backup
|
|
|
|
The PostgreSQL volume `ecija_pgdata` should be included in Kopia's backup schedule.
|
|
|
|
```bash
|
|
# Verify the volume exists
|
|
ssh nuc "docker volume inspect ecija_pgdata"
|
|
|
|
# Kopia should auto-discover Docker volumes
|
|
# Verify in Kopia UI at http://192.168.1.3:51515
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 7: Verification Checklist
|
|
|
|
Run these commands after deployment to verify everything works:
|
|
|
|
```bash
|
|
# 1. Database is running and accessible
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'SELECT count(*) FROM information_schema.tables WHERE table_schema = '\''public'\'';'"
|
|
# Expected: ~453 tables
|
|
|
|
# 2. Extensions are enabled
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'SELECT extname FROM pg_extension;'"
|
|
# Expected: pg_trgm, unaccent, plpgsql
|
|
|
|
# 3. Django container is running
|
|
ssh nuc "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep ecija"
|
|
# Expected: ecija-intranet-web (Up), ecija-intranet-worker (Up)
|
|
|
|
# 4. Django migrations applied
|
|
ssh nuc "docker logs ecija-intranet-web 2>&1 | grep -i 'migrat'"
|
|
|
|
# 5. Web server responds
|
|
ssh nuc "curl -s -o /dev/null -w '%{http_code}' http://localhost:8010/"
|
|
# Expected: 200 or 302 (redirect to login)
|
|
|
|
# 6. GraphQL API responds
|
|
ssh nuc "curl -s -o /dev/null -w '%{http_code}' http://localhost:8010/api/graphql/"
|
|
# Expected: 200 or 400 (no query provided = normal)
|
|
|
|
# 7. Background worker is processing
|
|
ssh nuc "docker logs ecija-intranet-worker 2>&1 | tail -5"
|
|
|
|
# 8. DNS resolves
|
|
nslookup intranet.nuc.lan
|
|
# Expected: 100.113.153.45
|
|
|
|
# 9. Traefik routes correctly
|
|
curl -s -o /dev/null -w '%{http_code}' http://intranet.nuc.lan/
|
|
# Expected: 200 or 302
|
|
|
|
# 10. GCS connectivity (from web container)
|
|
ssh nuc "docker exec ecija-intranet-web python -c \"
|
|
from google.cloud import storage
|
|
client = storage.Client()
|
|
bucket = client.bucket('ecija-intranet')
|
|
print('GCS connection OK, bucket:', bucket.name)
|
|
\""
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Service URLs (after deployment)
|
|
|
|
| Service | URL | Direct Port |
|
|
|---------|-----|-------------|
|
|
| Intranet Web | `http://intranet.nuc.lan` | `http://192.168.1.3:8010` |
|
|
| Intranet DB | - | `192.168.1.3:5434` |
|
|
| DB Admin (Adminer) | - | `http://192.168.1.3:8088` |
|
|
| Container Logs | - | `http://192.168.1.3:9999` (Dozzle) |
|
|
|
|
### Container Names
|
|
|
|
| Container | Purpose |
|
|
|-----------|---------|
|
|
| `ecija-intranet-db` | PostgreSQL 17 database |
|
|
| `ecija-intranet-web` | Django + Gunicorn web server |
|
|
| `ecija-intranet-worker` | Background task processor |
|
|
|
|
### Database Credentials
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| Host (from container) | `ecija-intranet-db` |
|
|
| Host (from NUC host) | `192.168.1.3` |
|
|
| Port (internal) | `5432` |
|
|
| Port (external) | `5434` |
|
|
| User | `postgres` |
|
|
| Password | `postgres` |
|
|
| Database | `flexicar-prod` |
|
|
|
|
### Key File Locations (on NUC)
|
|
|
|
| File | Path |
|
|
|------|------|
|
|
| Project root | `/opt/ecija-intranet/` |
|
|
| Django app | `/opt/ecija-intranet/app/` |
|
|
| Environment file | `/opt/ecija-intranet/.env.production` |
|
|
| GCP credentials | `/opt/ecija-intranet/config/credenciales/windy-shoreline-225910-efd33901e56c.json` |
|
|
| Dockerfile | `/opt/ecija-intranet/Dockerfile.nuc` |
|
|
| Entrypoint | `/opt/ecija-intranet/docker-entrypoint.sh` |
|
|
| Media volume | Docker volume `ecija_media` |
|
|
| DB volume | Docker volume `ecija_pgdata` |
|
|
| Traefik config | `/data/coolify/proxy/dynamic/nuc-services.yaml` |
|
|
| Homepage config | `/opt/homepage/config/services.yaml` |
|
|
|
|
### Key File Locations (on Mac - source files)
|
|
|
|
| File | Path |
|
|
|------|------|
|
|
| Project source | `/Users/agutierrez/Desktop/ECIJA-Intranet/` |
|
|
| Database dump (directory) | `/Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup/` |
|
|
| Database dump (tar) | `/Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup.tar` |
|
|
| GCP service account key | `/Users/agutierrez/Desktop/flexicar-intranet-backend/windy-shoreline-225910-efd33901e56c.json` |
|
|
| Installation doc | `/Users/agutierrez/Desktop/flexicar-intranet-backend/instalacion_intra.docx` |
|
|
| Original docker-compose | `/Users/agutierrez/Desktop/flexicar-intranet-backend/docker-compose.yaml` |
|
|
|
|
### Coolify Identifiers
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| Server UUID | `qk84w0goo4w48g4ggsoo0oss` |
|
|
| Project UUID | `a8484ggc88c40w4g4k004ow0` |
|
|
| Environment | `production` |
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Django won't start - missing module
|
|
|
|
```bash
|
|
# Check logs
|
|
ssh nuc "docker logs ecija-intranet-web 2>&1 | tail -30"
|
|
|
|
# If pip dependency issue, exec into container
|
|
ssh nuc "docker exec -it ecija-intranet-web pip install <missing-package>"
|
|
```
|
|
|
|
### Database connection refused
|
|
|
|
```bash
|
|
# Verify Postgres is running
|
|
ssh nuc "docker ps | grep ecija-intranet-db"
|
|
|
|
# Check if containers share a network
|
|
ssh nuc "docker network inspect ecija-network"
|
|
|
|
# Test connectivity from web container
|
|
ssh nuc "docker exec ecija-intranet-web python -c \"
|
|
import psycopg2
|
|
conn = psycopg2.connect(host='ecija-intranet-db', port=5432, user='postgres', password='postgres', dbname='flexicar-prod')
|
|
print('Connection OK')
|
|
conn.close()
|
|
\""
|
|
```
|
|
|
|
### Static files not loading (GCS issues)
|
|
|
|
```bash
|
|
# Test GCS credentials from container
|
|
ssh nuc "docker exec ecija-intranet-web python -c \"
|
|
from google.cloud import storage
|
|
client = storage.Client()
|
|
buckets = list(client.list_buckets())
|
|
print('Accessible buckets:', [b.name for b in buckets])
|
|
\""
|
|
|
|
# Verify credentials file is mounted
|
|
ssh nuc "docker exec ecija-intranet-web ls -la /secrets/bucket/"
|
|
|
|
# Run collectstatic manually
|
|
ssh nuc "docker exec ecija-intranet-web python manage.py collectstatic --noinput"
|
|
```
|
|
|
|
### pg_restore fails with "role flexicar does not exist"
|
|
|
|
The dump has `OWNER TO flexicar` statements. Using `--no-owner` should handle this, but if you see warnings about role `flexicar` not existing, you can safely ignore them (objects will be owned by `postgres`). Alternatively, create the role:
|
|
|
|
```bash
|
|
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'CREATE ROLE flexicar WITH LOGIN;'" 2>/dev/null || true
|
|
```
|
|
|
|
### Frontend assets not built
|
|
|
|
```bash
|
|
# Exec into container and build frontend
|
|
ssh nuc "docker exec -it ecija-intranet-web bash -c 'cd /app && npm install && npm run build'"
|
|
```
|
|
|
|
### Memory issues during restore
|
|
|
|
The dump is 4 GB. If the NUC runs low on memory:
|
|
|
|
```bash
|
|
# Use single-job restore (slower but less memory)
|
|
ssh nuc "docker exec ecija-intranet-db pg_restore \
|
|
-U postgres \
|
|
-d flexicar-prod \
|
|
--no-owner \
|
|
--no-privileges \
|
|
--jobs=1 \
|
|
/tmp/ecija-dump/"
|
|
```
|
|
|
|
### Container can't reach GCS (network issue)
|
|
|
|
```bash
|
|
# Test internet connectivity from container
|
|
ssh nuc "docker exec ecija-intranet-web curl -s -o /dev/null -w '%{http_code}' https://storage.googleapis.com/"
|
|
# Expected: 200 or 400
|
|
|
|
# If blocked, check Docker DNS
|
|
ssh nuc "docker exec ecija-intranet-web cat /etc/resolv.conf"
|
|
```
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
- The `-e git+https://github.com/Seykotron/django-cruds-adminlte.git` dependency in requirements.txt requires git to be installed in the Docker image (included in the Dockerfile.nuc)
|
|
- The `jinjacompiler` npm package in `app/package.json` uses a GitHub PAT token in its URL - this may expire and need updating
|
|
- Azure AD auth (`AG=False`) is disabled - the app falls back to Django's built-in auth with username/password
|
|
- `GS_CREDENTIALS=/secrets/bucket/credentials.json` (generic name) and `GS_CREDENTIALS_LOCAL` (full filename) both point to the mounted volume at `/secrets/bucket/`. Both files exist because we copy the JSON as both names in Phase 2.2
|
|
- Background worker (`process_tasks`) should always be running - it handles async operations like email sending, report generation, etc.
|
|
- The database dump was created from PostgreSQL 17.7 - the container uses PostgreSQL 17 for compatibility
|
|
- `DEBUG=True` matches the official install doc. Switch to `False` once everything is confirmed working.
|
|
- The NUC's existing Redis (port 6379) can be used for Django caching if needed - add `REDIS_URL` to the env file and ensure the containers share a Docker network or use the host IP
|
|
- The database name difference: the dump creates DB `flexicar` but Django expects `flexicar-prod` (per install doc). We restore into `flexicar-prod` using `--no-owner` which handles this correctly.
|
|
- `DATABASE_USER=postgres` and `DATABASE_PASSWORD=postgres` match the official install doc exactly. These are development credentials.
|