# Deployment & Infrastructure

## Architecture Overview

```
Internet → Reverse Proxy (443) → nginx container (80) → php-fpm container (9000) → postgres container (5432)
```

SIDES is deployed exclusively via **Docker Compose**. The stack consists of 4 services:

| Service | Image | Purpose |
|---------|-------|---------|
| `app` | Custom (php:8.2-fpm) | Laravel application (PHP-FPM) |
| `postgres` | postgres:16 | PostgreSQL database |
| `web` | nginx:stable-alpine | Reverse proxy to PHP-FPM, serves static assets |
| `pgadmin` | dpage/pgadmin4 | Database management UI (optional) |

## Prerequisites

- Docker Engine ^20.x
- Docker Compose v2+
- A reverse proxy in front (Nginx, Caddy, Traefik, Cloudflare Tunnel, etc.) for TLS termination

## Environment Configuration

Two `.env` files control the stack:

### Root `.env` — Docker Compose variables

Located at the project root. Controls container-level settings and credential defaults.

```env
POSTGRES_DB="sides_db"
POSTGRES_USER="tck"
POSTGRES_PASSWORD="<your_strong_password>"

PGADMIN_EMAIL="admin@example.com"
PGADMIN_PASSWORD="<your_pgadmin_password>"

FIREBASE_PROJECT_ID="sides-b4abb"
FIREBASE_CREDENTIALS="storage/app/firebase/sides-b4abb-3604a7cf7584.json"

FCM_TOPIC_RAINFALL_WARNING="rainfall_warning"
FCM_TOPIC_RAINFALL_DANGER="rainfall_danger"
FCM_TOPIC_WATERLEVEL_ALERT="waterlevel_alert"
FCM_TOPIC_WATERLEVEL_DANGER="waterlevel_danger"

ADMIN_EMAIL="admin@example.com"
ADMIN_PASSWORD="<set_a_strong_password>"

FTP_SERVER="myvscada.com"
FTP_USERNAME="tck"
FTP_PASSWORD="<your_ftp_password>"
```

### Application `.env` — Laravel variables

Located at `src/.env`. This file references Docker service names (e.g. `DB_HOST=postgres`) so Laravel connects to containers, not external hosts.

Key settings:

```env
APP_NAME=SIDES
APP_ENV=production
APP_DEBUG=false
APP_URL=https://sides.tck.com.my

DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=${POSTGRES_DB}
DB_USERNAME=${POSTGRES_USER}
DB_PASSWORD=${POSTGRES_PASSWORD}

SESSION_DRIVER=database
CACHE_STORE=database
QUEUE_CONNECTION=database
```

## Quick Start

```bash
# 1. Clone the repository
git clone <repo-url> && cd tckdev

# 2. Configure environment
cp .env.example .env
# Edit .env with your actual passwords and settings

# 3. Configure Laravel env
cp src/.env.example src/.env
# Edit src/.env — ensure DB_HOST=postgres (the Docker service name)

# 4. Build and start
docker compose up -d --build
```

The `app` container's entrypoint automatically runs on first start:

1. `composer install` (if `vendor/` missing)
2. `npm install && npm run build` (if `public/build/` missing)
3. `php artisan migrate --force` (if `RUN_MIGRATIONS=true`)
4. `php artisan db:seed --force` (if `RUN_SEEDER=true`)
5. Caches config, routes, and views
6. Starts PHP-FPM

## Common Operations

```bash
docker compose ps                    # Check container status
docker compose logs app              # View Laravel logs
docker compose logs app --follow     # Tail Laravel logs
docker compose logs postgres         # View database logs

docker compose exec app bash         # Shell into app container
docker compose exec app php artisan migrate           # Run migrations
docker compose exec app php artisan migrate:fresh --seed  # Reset DB
docker compose exec app php artisan db:seed            # Run seeders
docker compose exec app php artisan tinker             # Interactive REPL
docker compose exec app php artisan test               # Run tests

docker compose down                  # Stop all containers
docker compose down --volumes        # Stop and delete data volumes
docker compose up -d --build         # Rebuild and restart
```

## Reverse Proxy Setup

The `web` container (nginx) listens on port **80 internally**. A reverse proxy in front handles TLS termination and forwards traffic to it.

The nginx container does **not** handle HTTPS itself — it expects plain HTTP from the reverse proxy. Configure your reverse proxy to:

1. Listen on port **443** with your TLS certificate
2. Forward to `localhost:80` (or the Docker host's port 80)
3. Set the `X-Forwarded-Proto` header so Laravel generates `https://` URLs
4. Set the `X-Forwarded-Host` header with the original hostname

### Example: Nginx Reverse Proxy

On the host machine running Docker, add a server block:

```nginx
server {
    listen 443 ssl http2;
    server_name sides.tck.com.my;

    ssl_certificate     /etc/ssl/certs/sides.tck.com.my.crt;
    ssl_certificate_key /etc/ssl/private/sides.tck.com.my.key;

    location / {
        proxy_pass http://127.0.0.1:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

### Example: Caddy

```
sides.tck.com.my {
    reverse_proxy localhost:80
}
```

Caddy handles TLS automatically via Let's Encrypt.

### Example: Cloudflare Tunnel

```bash
cloudflared tunnel --hostname sides.tck.com.my --url http://localhost:80
```

### Laravel Trusted Proxy

Laravel must trust the proxy headers to generate correct URLs. The app already has `URL::forceScheme('https')` enabled in `AppServiceProvider`. Ensure `APP_URL` in `src/.env` uses `https://`:

```env
APP_URL=https://sides.tck.com.my
```

If using a load balancer or multi-hop proxy, configure `TrustProxies` middleware:

```php
// src/app/Http/Middleware/TrustProxies.php
protected $proxies = '*';
protected $headers = Request::HEADER_X_FORWARDED_FOR |
                     Request::HEADER_X_FORWARDED_HOST |
                     Request::HEADER_X_FORWARDED_PORT |
                     Request::HEADER_X_FORWARDED_PROTO;
```

## Access Points

| Service | Internal | External (via reverse proxy) |
|---------|----------|------------------------------|
| Application | `web:80` | `https://sides.tck.com.my` |
| pgAdmin | `pgadmin:80` | `http://<host>:5050` |
| PostgreSQL | `postgres:5432` | `localhost:5432` (optional, for external tools) |

**Security note**: pgAdmin (port 5050) and PostgreSQL (port 5432) should not be exposed to the public internet. Restrict access via firewall rules or only publish to `127.0.0.1`:

```yaml
# docker-compose.yml — restrict to localhost only
ports:
  - "127.0.0.1:5050:80"    # pgAdmin
  - "127.0.0.1:5432:5432"  # PostgreSQL
```

## Data Persistence

Two named volumes store persistent data:

| Volume | Mount Point | Purpose |
|--------|------------|---------|
| `pgdata` | `/var/lib/postgresql/data` | PostgreSQL data |
| `pgadmin_data` | `/var/lib/pgadmin` | pgAdmin sessions and config |

The `src/` directory is bind-mounted into both `app` and `web` containers at `/var/www/html`.

## Autoscript Deployment

The Python data pipeline (`autoscript/sidesdecode.py`) connects to PostgreSQL via environment variables. When running it outside Docker (e.g. as a cron job on the host):

```bash
export PG_HOST=localhost
export PG_PORT=5432
export PG_DATABASE=sides_db
export PG_USER=tck
export PG_PASSWORD=<your_password>
export FTP_SERVER=myvscada.com
export FTP_USERNAME=tck
export FTP_PASSWORD=<your_ftp_password>

python3 autoscript/sidesdecode.py
```

When running inside Docker on the same network, use `PG_HOST=postgres` (the service name).

## Production Checklist

- [ ] `APP_ENV=production` and `APP_DEBUG=false` in `src/.env`
- [ ] `APP_URL` uses `https://`
- [ ] All passwords in `.env` changed from defaults
- [ ] `ADMIN_PASSWORD` set to a strong value in root `.env`
- [ ] TLS configured on reverse proxy
- [ ] pgAdmin and PostgreSQL ports restricted to localhost
- [ ] Firebase credentials JSON stored securely (not in git)
- [ ] Mail credentials configured for password reset
