towerops/DEPLOYMENT.md
2026-01-02 14:06:44 -06:00

482 lines
11 KiB
Markdown

# TowerOps Production Deployment Guide
This guide covers deploying TowerOps to production with TimescaleDB, email notifications, and monitoring capabilities.
## Prerequisites
- PostgreSQL 14+ with TimescaleDB extension installed
- Elixir 1.19+ and Erlang 27+
- SSL certificate for HTTPS
- SMTP server for email notifications
## Environment Variables
Create a `.env.prod` file or configure these environment variables in your deployment platform:
### Required
```bash
# Secret key for encryption (generate with: mix phx.gen.secret)
SECRET_KEY_BASE=your-secret-key-here
# Database URL
DATABASE_URL=postgresql://user:password@host:port/towerops_prod
# Application URL
PHX_HOST=towerops.example.com
PORT=4000
# Environment
MIX_ENV=prod
```
### Email Configuration
```bash
# SMTP settings for alert notifications
SMTP_RELAY=smtp.example.com
SMTP_USERNAME=your-smtp-username
SMTP_PASSWORD=your-smtp-password
SMTP_PORT=587
SMTP_TLS=true
# From address for emails
ALERT_FROM_EMAIL=alerts@towerops.example.com
ALERT_FROM_NAME="TowerOps Alerts"
```
### Optional
```bash
# Custom check interval (default: 300 seconds / 5 minutes)
DEFAULT_CHECK_INTERVAL_SECONDS=300
# TimescaleDB retention (default: 90 days)
MONITORING_RETENTION_DAYS=90
# TimescaleDB compression (default: 7 days)
MONITORING_COMPRESSION_DAYS=7
```
## Database Setup
**Important**: TimescaleDB must be installed on your PostgreSQL server. See [TIMESCALEDB.md](TIMESCALEDB.md) for installation instructions.
### 1. Install TimescaleDB Extension
Connect to your PostgreSQL database and enable TimescaleDB:
```sql
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
```
### 2. Create Database
```bash
# Development
mix ecto.create
# Production
MIX_ENV=prod mix ecto.create
```
### 3. Run Migrations
```bash
# Development
mix ecto.migrate
# Production
MIX_ENV=prod mix ecto.migrate
```
This will:
- Create all necessary tables
- Convert `monitoring_checks` to TimescaleDB hypertable
- Set up retention policies (auto-delete data older than 90 days)
- Configure compression policies (compress data older than 7 days)
- Create continuous aggregates for dashboard performance
## Application Configuration
### Production Config
Update `config/runtime.exs` with your production settings. Key configurations:
#### Database Pool Size
```elixir
config :towerops, Towerops.Repo,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
```
#### Mailer Configuration
The application uses Swoosh for email delivery. Update the mailer configuration:
```elixir
config :towerops, Towerops.Mailer,
adapter: Swoosh.Adapters.SMTP,
relay: System.get_env("SMTP_RELAY"),
username: System.get_env("SMTP_USERNAME"),
password: System.get_env("SMTP_PASSWORD"),
port: String.to_integer(System.get_env("SMTP_PORT") || "587"),
tls: :always
```
## Building for Production
### 1. Install Dependencies
```bash
mix deps.get --only prod
```
### 2. Compile Application
```bash
MIX_ENV=prod mix compile
```
### 3. Build Assets
```bash
MIX_ENV=prod mix assets.deploy
```
This will:
- Build and minify CSS with Tailwind v4
- Bundle JavaScript with esbuild
- Generate digested asset files
### 4. Create Release
```bash
MIX_ENV=prod mix release
```
The release will be created in `_build/prod/rel/towerops/`.
## Deployment
### Using Releases
```bash
# Start the release
_build/prod/rel/towerops/bin/towerops start
# Run as daemon
_build/prod/rel/towerops/bin/towerops daemon
# Check status
_build/prod/rel/towerops/bin/towerops pid
# Stop
_build/prod/rel/towerops/bin/towerops stop
```
### Using systemd
Create `/etc/systemd/system/towerops.service`:
```ini
[Unit]
Description=TowerOps Monitoring Service
After=network.target postgresql.service
[Service]
Type=forking
User=towerops
Group=towerops
WorkingDirectory=/opt/towerops
Environment="PORT=4000"
Environment="MIX_ENV=prod"
ExecStart=/opt/towerops/_build/prod/rel/towerops/bin/towerops daemon
ExecStop=/opt/towerops/_build/prod/rel/towerops/bin/towerops stop
Restart=on-failure
RestartSec=5
SyslogIdentifier=towerops
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable towerops
sudo systemctl start towerops
sudo systemctl status towerops
```
### Using Docker
Create `Dockerfile`:
```dockerfile
FROM elixir:1.19-alpine AS build
RUN apk add --no-cache build-base git nodejs npm
WORKDIR /app
# Install hex and rebar
RUN mix local.hex --force && \
mix local.rebar --force
ENV MIX_ENV=prod
# Install dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
# Copy application
COPY config config
COPY lib lib
COPY priv priv
COPY assets assets
# Build assets
RUN mix assets.deploy
# Compile and build release
RUN mix compile
RUN mix release
# Runtime stage
FROM alpine:3.18
RUN apk add --no-cache libstdc++ openssl ncurses-libs
WORKDIR /app
COPY --from=build /app/_build/prod/rel/towerops ./
ENV HOME=/app
ENV PORT=4000
ENV MIX_ENV=prod
CMD ["bin/towerops", "start"]
```
Build and run:
```bash
docker build -t towerops .
docker run -d \
--name towerops \
-p 4000:4000 \
--env-file .env.prod \
towerops
```
## Monitoring System
The monitoring system starts automatically when the application boots. It will:
1. **Start Equipment Monitors**: Create a GenServer worker for each equipment with `monitoring_enabled: true`
2. **Ping Equipment**: Each worker pings its equipment at the configured interval
3. **Record Checks**: Results are stored in `monitoring_checks` TimescaleDB hypertable
4. **Create Alerts**: Status changes trigger alert creation
5. **Send Emails**: Owners and admins receive email notifications for down/up events
6. **Broadcast Updates**: Real-time UI updates via Phoenix PubSub
### Monitoring Behavior by Environment
- **All Environments** (dev/test/prod):
- TimescaleDB features enabled (hypertables, retention, compression, aggregates)
- Monitoring system active
- **Production (`MIX_ENV=prod`)**:
- Email notifications sent to organization owners/admins
- **Development/Test**:
- Email notifications disabled (to avoid test database connection issues)
- Monitoring system runs but emails are suppressed
## SSL/TLS Configuration
### Using Nginx Reverse Proxy
```nginx
upstream towerops {
server 127.0.0.1:4000;
}
server {
listen 80;
server_name towerops.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name towerops.example.com;
ssl_certificate /etc/letsencrypt/live/towerops.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/towerops.example.com/privkey.pem;
location / {
proxy_pass http://towerops;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
}
```
## Production Checklist
### Pre-Deployment
- [ ] Database created with TimescaleDB extension installed
- [ ] All environment variables configured
- [ ] SSL certificate obtained and configured
- [ ] SMTP server configured and tested
- [ ] Assets built and digested
- [ ] Release created successfully
### Post-Deployment
- [ ] Application starts without errors
- [ ] Database migrations ran successfully
- [ ] TimescaleDB hypertables created (`\d+ monitoring_checks` shows hypertable)
- [ ] Continuous aggregates created (`SELECT * FROM timescaledb_information.continuous_aggregates;`)
- [ ] Can access application via HTTPS
- [ ] User registration works
- [ ] Can create organization
- [ ] Can add sites and equipment
- [ ] Monitoring workers start for enabled equipment
- [ ] Ping checks are being recorded
- [ ] Alerts are created on status changes
- [ ] Email notifications are sent
- [ ] LiveView updates work in real-time
### Monitoring
- [ ] Check application logs for errors
- [ ] Monitor database connections
- [ ] Monitor memory usage (GenServer workers)
- [ ] Check email delivery logs
- [ ] Verify TimescaleDB compression is working
- [ ] Verify retention policies are cleaning old data
## Database Maintenance
### Verify TimescaleDB Status
```sql
-- Check hypertables
SELECT * FROM timescaledb_information.hypertables;
-- Check compression
SELECT * FROM timescaledb_information.chunks WHERE is_compressed = true;
-- Check continuous aggregates
SELECT * FROM timescaledb_information.continuous_aggregates;
-- Check retention policies
SELECT * FROM timescaledb_information.jobs WHERE proc_name = 'policy_retention';
```
### Manual Compression
```sql
-- Compress specific chunk
SELECT compress_chunk('_timescaledb_internal._hyper_1_1_chunk');
-- Compress all chunks older than 7 days
SELECT compress_chunk(chunk) FROM (
SELECT show_chunks('monitoring_checks', older_than => INTERVAL '7 days') AS chunk
) AS chunks;
```
### Backup and Restore
```bash
# Backup (includes TimescaleDB metadata)
pg_dump -Fc -d towerops_prod > towerops_backup.dump
# Restore
pg_restore -d towerops_prod towerops_backup.dump
```
## Scaling Considerations
### Database
- Monitor `monitoring_checks` table size and growth rate
- Adjust retention policy if needed (default: 90 days)
- Consider read replicas for heavy dashboard usage
- TimescaleDB compression reduces storage by 90%+
### Application
- Scale horizontally by running multiple instances
- Use load balancer with sticky sessions for LiveView
- Monitor GenServer worker memory usage
- Consider distributed Erlang if needed
### Email
- Use transactional email service (SendGrid, Postmark, etc.) for reliability
- Implement rate limiting if needed
- Monitor email delivery failures
## Troubleshooting
### Monitoring Not Working
Check logs for GenServer errors:
```bash
tail -f /var/log/towerops/error.log
```
Verify equipment has monitoring enabled:
```sql
SELECT id, name, monitoring_enabled, check_interval_seconds
FROM equipment;
```
### TimescaleDB Not Active
Verify extension is loaded:
```sql
\dx timescaledb
SELECT * FROM timescaledb_information.hypertables;
```
If missing:
1. Ensure TimescaleDB is installed (see TIMESCALEDB.md)
2. Drop and recreate the database: `mix ecto.drop && mix ecto.create && mix ecto.migrate`
### Email Not Sending
Test SMTP connection:
```elixir
# In iex -S mix
Swoosh.Email.new()
|> Swoosh.Email.to("test@example.com")
|> Swoosh.Email.from({"TowerOps", "alerts@towerops.example.com"})
|> Swoosh.Email.subject("Test")
|> Swoosh.Email.text_body("Test email")
|> Towerops.Mailer.deliver()
```
Check environment variables are set correctly.
## Support
For issues or questions:
- Check logs in `_build/prod/rel/towerops/logs/`
- Review TimescaleDB documentation: https://docs.timescale.com/
- Review Phoenix deployment guide: https://hexdocs.pm/phoenix/deployment.html