# VPS Infrastructure Guide for AI Agents

## Overview

This document describes the VPS infrastructure at **81.177.141.34** to help AI agents migrate or deploy new projects (e.g., `voideaai.ru`).

---

## 1. VPS Specifications

| Property | Value |
|---|---|
| IP | 81.177.141.34 |
| User | `angel` |
| Password | `.-SHGWa_` |
| Sudo | passwordless via `/etc/sudoers.d/angel` |
| SSH key | added to `~/.ssh/authorized_keys` |
| OS | Ubuntu 24.04 (Noble) |
| Kernel | OpenVZ/LXC container — limited kernel modules |
| Network interface | `venet0` (no bridge NAT support) |
| Disk | 20GB total, ~13GB available |
| Docker | 29.5.0, iptables in nft mode (`iptables-nft`) |

---

## 2. Docker Constraints

### Critical: `network_mode: host` only

Bridge networking (`network_mode: bridge`) **does not work** on this VPS. The OpenVZ/LXC kernel does not support masquerade for bridge interfaces through `venet0`. All containers **must** use:

```yaml
network_mode: host
```

### Docker daemon config (`/etc/docker/daemon.json`)

```json
{"dns": ["8.8.8.8", "8.8.4.4"]}
```

### Build constraint

All Docker builds require:

```yaml
build:
  network: host
```

Without this, `apt-get` and `pip` cannot download packages (no bridge NAT).

### Docker Hub connectivity issues

Pulling large images (>100MB) from Docker Hub may time out (`failed to copy: read tcp... connection timed out`). Smaller images (nginx:alpine, postgres:16-alpine) work. Gitea and Portainer images **cannot be pulled** reliably.

**Solutions attempted:**
- Retry with `--platform linux/amd64`
- Increase `max-download-attempts` (not supported in Docker 29.5)
- Clean build cache with `docker builder prune -af`
- All fail on large image layers

**Workaround:** Pull images locally and SCP (`docker save → scp → docker load`), or use a registry mirror.

---

## 3. Port Allocation (all on host)

| Port | Service | Container |
|---|---|---|
| 80 | nginx-proxy HTTP | `nginx-proxy` |
| 443 | nginx-proxy HTTPS | `nginx-proxy` |
| 8080 | PHP nginx (aegisone.ru) | `aegisone-php-nginx` |
| 9000 | PHP-FPM | `aegisone-php-fpm` |
| 8000 | FastAPI (service.aegisone.ru) | `aegisone-app` |
| 5432 | PostgreSQL (shared, aegisone) | `aegisone-postgres` |
| 5433 | PostgreSQL (Gitea) | `gitea-db` (planned) |
| 3000 | Gitea | `gitea` (planned) |
| 9001 | Portainer HTTP | `portainer` (planned) |
| 9443 | Portainer HTTPS | `portainer` (planned) |

### Address resolution between containers

Since all containers use `network_mode: host`, they communicate via `localhost:PORT`. **Docker DNS names do not resolve.** In `nginx.conf` and app configs, use:

```nginx
proxy_pass http://localhost:8080;  # NOT container_name:port
```

---

## 4. Project Structure

```
/opt/projects/
  nginx-proxy/          # Reverse proxy (nginx + certbot)
    docker-compose.yml
    nginx.conf
    conf.d/             # Per-domain configs
    certs/              # SSL certificates
    html/               # Webroot for certbot
  aegisone-php/         # PHP frontend (aegisone.ru)
    docker-compose.yml
    Dockerfile
    nginx.conf          # PHP nginx config (listen 8080)
    src/                # PHP source files
  aegisone-py/          # FastAPI backend (service.aegisone.ru)
    docker-compose.yml
    Dockerfile
    app/                # Python source
    sql/                # DB schema
  gitea/                # Git hosting (planned)
    docker-compose.yml
  portainer/            # Docker UI (planned)
    docker-compose.yml
```

---

## 5. nginx-proxy Configuration Pattern

### Global config (`nginx.conf`)
- `resolver 127.0.0.11` — not used in host mode, kept for compatibility
- SSL termination handled by nginx-proxy
- Upstream protocols are HTTP (nginx-proxy terminates SSL and proxies via HTTP)

### Conf.d file structure
Each domain has one `.conf` file with two server blocks:

```nginx
# HTTP (no redirect — temporary, until LE certs)
server {
    listen 80;
    server_name example.ru;

    location / {
        proxy_pass http://localhost:PORT;
        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;
    }
}

# HTTPS (self-signed, for future LE)
server {
    listen 443 ssl;
    http2 on;
    server_name example.ru;

    ssl_certificate /etc/nginx/certs/live/aegisone.ru/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/live/aegisone.ru/privkey.pem;

    # ... same location block
}
```

Note: HTTP→HTTPS redirects (`return 301 https://...`) caused **redirect loops** externally due to provider/proxy intercepting port 443. If you add them, test thoroughly.

### SSL certificates
Currently using self-signed certs generated via:
```bash
sudo openssl req -x509 -nodes -days 30 -newkey rsa:2048 \
    -keyout /opt/projects/nginx-proxy/certs/live/aegisone.ru/privkey.pem \
    -out /opt/projects/nginx-proxy/certs/live/aegisone.ru/fullchain.pem \
    -subj '/CN=aegisone.ru'
```

For Let's Encrypt:
```bash
cd /opt/projects/nginx-proxy
docker compose run --rm certbot certonly --webroot \
    --webroot-path=/var/www/certbot \
    -d example.ru \
    --email admin@example.ru \
    --agree-tos --non-interactive
```

---

## 6. Deployment Workflow

### Initial setup
1. Prepare files locally (this repo)
2. Pack into tarball: `tar czf project.tar.gz .`
3. SCP to VPS: `scp project.tar.gz angel@81.177.141.34:/home/angel/`
4. SSH and extract: `ssh angel@81.177.141.34 "tar xzf project.tar.gz -C /opt/projects/project-name/"`
5. Build and start: `ssh angel@81.177.141.34 "cd /opt/projects/project-name && docker compose up -d --build"`

### Updating a running project
1. Edit files locally
2. SCP changed files to `/home/angel/` on VPS
3. SSH and copy to correct location: `sudo cp /home/angel/file /opt/projects/project-name/`
4. Restart container: `docker compose -p project-name restart service-name`

### Copying files into a running container (alternative to rebuild)
```bash
docker cp /home/angel/file.py container-name:/app/path/file.py
docker compose -p project-name restart app
```

---

## 7. Database

### PostgreSQL (aegisone shared DB)
- Host: `localhost:5432`
- User: `aegisone`
- Password: `aegisone_pass`
- Database: `aegisone`
- Container: `aegisone-postgres` (postgres:16-alpine)

### Schema initialization
SQL files in `/opt/projects/aegisone-py/sql/`:
- `schema_postgresql.sql` — full schema
- `seed_data.sql` — seed data

Init scripts run only on first volume creation. To add missing tables later:
```bash
docker exec -i aegisone-postgres psql -U aegisone -d aegisone < /opt/projects/aegisone-py/sql/schema_postgresql.sql
```

### Key schema pattern
- All tables use `BOOLEAN` for `is_active` (not integer)
- Timestamps use `TIMESTAMP NOT NULL DEFAULT NOW()`
- `updated_at` triggers via `update_updated_at_column()` function

---

## 8. Useful Commands

```bash
# Check running containers
docker ps -a

# Check compose projects
docker compose ls

# View logs
docker logs container-name --tail 50

# Restart service
docker compose -p project-name restart service-name

# Rebuild and restart
docker compose -p project-name up -d --build

# Clean build cache
docker builder prune -af

# Test nginx config
docker compose -p nginx-proxy exec nginx nginx -t

# Reload nginx
docker compose -p nginx-proxy exec nginx nginx -s reload

# Check listening ports
ss -tlnp | grep -E ':(80|443|8080|8000|9000|5432)'

# Check disk
df -h /
docker system df
```

---

## 9. Adding a New Project (e.g., voideaai.ru)

### Steps:
1. Create directory: `mkdir -p /opt/projects/voideaai`
2. Create `docker-compose.yml` with `network_mode: host` and `build.network: host`
3. Create `Dockerfile` (use `--only-binary :all:` for pip if avoiding apt)
4. Create nginx-proxy config: `nginx/conf.d/voideaai.conf` (listen 80, proxy_pass localhost:PORT)
5. Generate cert if needed
6. Restart nginx-proxy: `docker compose -p nginx-proxy restart nginx`
7. Start project: `cd /opt/projects/voideaai && docker compose up -d --build`

### Port allocation for new project
Check existing ports first (`ss -tlnp`). Choose an unused port for the app and add it to the nginx-proxy config.

---

## 10. Known Issues

1. **Docker Hub timeouts on large images** — consider pulling on local machine and transferring with `docker save`/`docker load`
2. **HTTPS redirect loops externally** — port 443 may be intercepted by provider/proxy; test HTTP first
3. **Bridge networking doesn't work** — all containers must use `network_mode: host`
4. **Docker build needs `network: host`** — without it, apt-get/pip fail
5. **Config files with `$` in content** — when editing via SSH from PowerShell, `$variables` get expanded; use SCP (write locally, then copy)
