fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.) - Added ignore rules for patterns intentional in this codebase: E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from), SIM108/SIM105/SIM117 (readability preferences) - Added per-file ignores for tests and scripts - Excluded broken scripts/rename_terminology.py (has curly quotes) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,25 +13,30 @@ Complete step-by-step guide for deploying Wizamart on a Hetzner Cloud VPS.
|
||||
- **Auth**: SSH key (configured via Hetzner Console)
|
||||
- **Setup date**: 2026-02-11
|
||||
|
||||
!!! success "Progress — 2026-02-11"
|
||||
**Completed (Steps 1–12):**
|
||||
!!! success "Progress — 2026-02-12"
|
||||
**Completed (Steps 1–15):**
|
||||
|
||||
- Non-root user `samir` with SSH key
|
||||
- Server hardened (UFW firewall, SSH root login disabled, fail2ban)
|
||||
- Docker 29.2.1 & Docker Compose 5.0.2 installed
|
||||
- Gitea running at `http://91.99.65.229:3000` (user: `sboulahtit`, repo: `orion`)
|
||||
- Gitea running at `https://git.wizard.lu` (user: `sboulahtit`, repo: `orion`)
|
||||
- Repository cloned to `~/apps/orion`
|
||||
- Production `.env` configured with generated secrets
|
||||
- Full Docker stack deployed (API, PostgreSQL, Redis, Celery worker/beat, Flower)
|
||||
- Database migrated (76 tables) and seeded (admin, platforms, CMS, email templates)
|
||||
- API verified at `http://91.99.65.229:8001/docs` and `/admin/login`
|
||||
- API verified at `https://api.wizard.lu/health`
|
||||
- DNS A records configured and propagated for `wizard.lu` and subdomains
|
||||
- Caddy 2.10.2 reverse proxy with auto-SSL (Let's Encrypt)
|
||||
- Temporary firewall rules removed (ports 3000, 8001)
|
||||
- Gitea Actions runner v0.2.13 registered and running as systemd service
|
||||
|
||||
**Remaining (Steps 13–15):**
|
||||
**Remaining:**
|
||||
|
||||
- [ ] DNS: Point domain A records to `91.99.65.229`
|
||||
- [ ] Caddy reverse proxy with auto-SSL
|
||||
- [ ] Gitea Actions runner for CI/CD
|
||||
- [ ] Remove temporary firewall rules (ports 3000, 8001)
|
||||
- [ ] DNS A records for additional platform domains (`oms.lu`, `loyaltyplus.lu`)
|
||||
- [ ] Uncomment platform domains in Caddyfile after DNS propagation
|
||||
- [ ] AAAA (IPv6) records for all domains
|
||||
- [ ] Update `platforms` table `domain` column to match production domains
|
||||
- [ ] Verify CI pipeline runs successfully on push
|
||||
|
||||
## Installed Software Versions
|
||||
|
||||
@@ -45,6 +50,8 @@ Complete step-by-step guide for deploying Wizamart on a Hetzner Cloud VPS.
|
||||
| Redis | 7-alpine (container) |
|
||||
| Python | 3.11-slim (container) |
|
||||
| Gitea | latest (container) |
|
||||
| Caddy | 2.10.2 |
|
||||
| act_runner | 0.2.13 |
|
||||
|
||||
---
|
||||
|
||||
@@ -231,14 +238,45 @@ Then create a repository (e.g. `orion`).
|
||||
|
||||
## Step 8: Push Repository to Gitea
|
||||
|
||||
### Add SSH Key to Gitea
|
||||
|
||||
Before pushing via SSH, add your local machine's public key to Gitea:
|
||||
|
||||
1. Copy your public key:
|
||||
|
||||
```bash
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
# Or if using RSA: cat ~/.ssh/id_rsa.pub
|
||||
```
|
||||
|
||||
2. In the Gitea web UI: click your avatar → **Settings** → **SSH / GPG Keys** → **Add Key** → paste the key.
|
||||
|
||||
3. Add the Gitea SSH host to known hosts:
|
||||
|
||||
```bash
|
||||
ssh-keyscan -p 2222 git.wizard.lu >> ~/.ssh/known_hosts
|
||||
```
|
||||
|
||||
### Add Remote and Push
|
||||
|
||||
From your **local machine**:
|
||||
|
||||
```bash
|
||||
cd /home/samir/Documents/PycharmProjects/letzshop-product-import
|
||||
git remote add gitea http://91.99.65.229:3000/sboulahtit/orion.git
|
||||
git remote add gitea ssh://git@git.wizard.lu:2222/sboulahtit/orion.git
|
||||
git push gitea master
|
||||
```
|
||||
|
||||
!!! note "Remote URL updated"
|
||||
The remote was initially set to `http://91.99.65.229:3000/...` during setup.
|
||||
After Caddy was configured, it was updated to use the domain with SSH:
|
||||
`ssh://git@git.wizard.lu:2222/sboulahtit/orion.git`
|
||||
|
||||
To update an existing remote:
|
||||
```bash
|
||||
git remote set-url gitea ssh://git@git.wizard.lu:2222/sboulahtit/orion.git
|
||||
```
|
||||
|
||||
## Step 9: Clone Repository on Server
|
||||
|
||||
```bash
|
||||
@@ -337,25 +375,58 @@ docker compose --profile full exec -e PYTHONPATH=/app api python scripts/seed/se
|
||||
|
||||
---
|
||||
|
||||
## Step 13: DNS Configuration (TODO)
|
||||
## Step 13: DNS Configuration
|
||||
|
||||
Before setting up Caddy, point your domain's DNS to the server. In your domain registrar's DNS settings, create **A records**:
|
||||
Before setting up Caddy, point your domain's DNS to the server.
|
||||
|
||||
### wizard.lu (Main Platform) — Completed
|
||||
|
||||
| Type | Name | Value | TTL |
|
||||
|---|---|---|---|
|
||||
| A | `@` | `91.99.65.229` | 300 |
|
||||
| A | `www` | `91.99.65.229` | 300 |
|
||||
| A | `api` | `91.99.65.229` | 300 |
|
||||
| A | `git` | `91.99.65.229` | 300 |
|
||||
| A | `flower` | `91.99.65.229` | 300 |
|
||||
|
||||
### oms.lu (OMS Platform) — TODO
|
||||
|
||||
| Type | Name | Value | TTL |
|
||||
|---|---|---|---|
|
||||
| A | `@` | `91.99.65.229` | 300 |
|
||||
| A | `www` | `91.99.65.229` | 300 |
|
||||
|
||||
### loyaltyplus.lu (Loyalty+ Platform) — TODO
|
||||
|
||||
| Type | Name | Value | TTL |
|
||||
|---|---|---|---|
|
||||
| A | `@` | `91.99.65.229` | 300 |
|
||||
| A | `www` | `91.99.65.229` | 300 |
|
||||
|
||||
### IPv6 (AAAA) Records — TODO
|
||||
|
||||
Optional but recommended. Add AAAA records for all domains above, pointing to the server's IPv6 address. Verify your IPv6 address first:
|
||||
|
||||
```bash
|
||||
ip -6 addr show eth0 | grep 'scope global'
|
||||
```
|
||||
|
||||
It should match the value in the Hetzner Cloud Console (Networking tab). Then create AAAA records mirroring each A record above, e.g.:
|
||||
|
||||
| Type | Name (wizard.lu) | Value | TTL |
|
||||
|---|---|---|---|
|
||||
| AAAA | `@` | `2a01:4f8:1c1a:b39c::1` | 300 |
|
||||
| AAAA | `www` | `2a01:4f8:1c1a:b39c::1` | 300 |
|
||||
| AAAA | `api` | `2a01:4f8:1c1a:b39c::1` | 300 |
|
||||
| AAAA | `git` | `2a01:4f8:1c1a:b39c::1` | 300 |
|
||||
| AAAA | `flower` | `2a01:4f8:1c1a:b39c::1` | 300 |
|
||||
|
||||
!!! tip "DNS propagation"
|
||||
Set TTL to 300 (5 minutes) initially. DNS changes can take up to 24 hours to propagate globally, but usually complete within 30 minutes. Verify with: `dig api.wizard.lu`
|
||||
Repeat for `oms.lu` and `loyaltyplus.lu`.
|
||||
|
||||
## Step 14: Reverse Proxy with Caddy (TODO)
|
||||
!!! tip "DNS propagation"
|
||||
Set TTL to 300 (5 minutes) initially. DNS changes can take up to 24 hours to propagate globally, but usually complete within 30 minutes. Verify with: `dig api.wizard.lu +short`
|
||||
|
||||
## Step 14: Reverse Proxy with Caddy
|
||||
|
||||
Install Caddy:
|
||||
|
||||
@@ -368,9 +439,41 @@ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
|
||||
sudo apt update && sudo apt install caddy
|
||||
```
|
||||
|
||||
Configure `/etc/caddy/Caddyfile` (replace `wizard.lu` with your actual domain):
|
||||
### Caddyfile Configuration
|
||||
|
||||
Edit `/etc/caddy/Caddyfile`:
|
||||
|
||||
```caddy
|
||||
# ─── Platform 1: Main (wizard.lu) ───────────────────────────
|
||||
wizard.lu {
|
||||
reverse_proxy localhost:8001
|
||||
}
|
||||
|
||||
www.wizard.lu {
|
||||
redir https://wizard.lu{uri} permanent
|
||||
}
|
||||
|
||||
# ─── Platform 2: OMS (oms.lu) ───────────────────────────────
|
||||
# Uncomment after DNS is configured for oms.lu
|
||||
# oms.lu {
|
||||
# reverse_proxy localhost:8001
|
||||
# }
|
||||
#
|
||||
# www.oms.lu {
|
||||
# redir https://oms.lu{uri} permanent
|
||||
# }
|
||||
|
||||
# ─── Platform 3: Loyalty+ (loyaltyplus.lu) ──────────────────
|
||||
# Uncomment after DNS is configured for loyaltyplus.lu
|
||||
# loyaltyplus.lu {
|
||||
# reverse_proxy localhost:8001
|
||||
# }
|
||||
#
|
||||
# www.loyaltyplus.lu {
|
||||
# redir https://loyaltyplus.lu{uri} permanent
|
||||
# }
|
||||
|
||||
# ─── Services ───────────────────────────────────────────────
|
||||
api.wizard.lu {
|
||||
reverse_proxy localhost:8001
|
||||
}
|
||||
@@ -384,11 +487,32 @@ flower.wizard.lu {
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "How multi-platform routing works"
|
||||
All platform domains (`wizard.lu`, `oms.lu`, `loyaltyplus.lu`) point to the **same FastAPI backend** on port 8001. The `PlatformContextMiddleware` reads the `Host` header to detect which platform the request is for. Caddy preserves the Host header by default, so no extra configuration is needed.
|
||||
|
||||
The `domain` column in the `platforms` database table must match:
|
||||
|
||||
| Platform | code | domain |
|
||||
|---|---|---|
|
||||
| Main | `main` | `wizard.lu` |
|
||||
| OMS | `oms` | `oms.lu` |
|
||||
| Loyalty+ | `loyalty` | `loyaltyplus.lu` |
|
||||
|
||||
Start Caddy:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart caddy
|
||||
```
|
||||
|
||||
Caddy automatically provisions Let's Encrypt SSL certificates.
|
||||
Caddy automatically provisions Let's Encrypt SSL certificates for all configured domains.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
curl -I https://wizard.lu
|
||||
curl -I https://api.wizard.lu/health
|
||||
curl -I https://git.wizard.lu
|
||||
```
|
||||
|
||||
After Caddy is working, remove the temporary firewall rules:
|
||||
|
||||
@@ -397,56 +521,175 @@ sudo ufw delete allow 3000/tcp
|
||||
sudo ufw delete allow 8001/tcp
|
||||
```
|
||||
|
||||
## Step 15: Gitea Actions Runner (TODO)
|
||||
Update Gitea's configuration to use its new domain. In `~/gitea/docker-compose.yml`, change:
|
||||
|
||||
```yaml
|
||||
- GITEA__server__ROOT_URL=https://git.wizard.lu/
|
||||
- GITEA__server__SSH_DOMAIN=git.wizard.lu
|
||||
- GITEA__server__DOMAIN=git.wizard.lu
|
||||
```
|
||||
|
||||
Then restart Gitea:
|
||||
|
||||
```bash
|
||||
cd ~/gitea && docker compose up -d gitea
|
||||
```
|
||||
|
||||
### Future: Multi-Tenant Store Routing
|
||||
|
||||
Stores on each platform use two routing modes:
|
||||
|
||||
- **Standard (subdomain)**: `acme.oms.lu` — included in the base subscription
|
||||
- **Premium (custom domain)**: `acme.lu` — available with premium subscription tiers
|
||||
|
||||
Both modes are handled by the `StoreContextMiddleware` which reads the `Host` header, so Caddy just needs to forward requests and preserve the header.
|
||||
|
||||
#### Wildcard Subdomains (for store subdomains)
|
||||
|
||||
When stores start using subdomains like `acme.oms.lu`, add wildcard blocks:
|
||||
|
||||
```caddy
|
||||
*.oms.lu {
|
||||
reverse_proxy localhost:8001
|
||||
}
|
||||
|
||||
*.loyaltyplus.lu {
|
||||
reverse_proxy localhost:8001
|
||||
}
|
||||
|
||||
*.wizard.lu {
|
||||
reverse_proxy localhost:8001
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning "Wildcard SSL requires DNS challenge"
|
||||
Let's Encrypt cannot issue wildcard certificates via HTTP challenge. Wildcard certs require a **DNS challenge**, which means installing a Caddy DNS provider plugin (e.g. `caddy-dns/cloudflare`) and configuring API credentials for your DNS provider. See [Caddy DNS challenge docs](https://caddyserver.com/docs/automatic-https#dns-challenge).
|
||||
|
||||
#### Custom Store Domains (for premium stores)
|
||||
|
||||
When premium stores bring their own domains (e.g. `acme.lu`), use Caddy's **on-demand TLS**:
|
||||
|
||||
```caddy
|
||||
https:// {
|
||||
tls {
|
||||
on_demand
|
||||
}
|
||||
reverse_proxy localhost:8001
|
||||
}
|
||||
```
|
||||
|
||||
On-demand TLS auto-provisions SSL certificates when a new domain connects. Add an `ask` endpoint to validate that the domain is registered in the `store_domains` table, preventing abuse:
|
||||
|
||||
```caddy
|
||||
tls {
|
||||
on_demand
|
||||
ask http://localhost:8001/api/v1/internal/verify-domain
|
||||
}
|
||||
```
|
||||
|
||||
!!! note "Not needed yet"
|
||||
Wildcard subdomains and custom domains are future work. The current Caddyfile handles all platform root domains and service subdomains.
|
||||
|
||||
## Step 15: Gitea Actions Runner
|
||||
|
||||
!!! warning "ARM64 architecture"
|
||||
This server is ARM64. Download the `arm64` binary, not `amd64`.
|
||||
|
||||
Download and install:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/gitea-runner && cd ~/gitea-runner
|
||||
|
||||
# Download act_runner (ARM64 version)
|
||||
wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-arm64
|
||||
chmod +x act_runner-linux-arm64
|
||||
|
||||
# Register (get token from Gitea Site Administration > Runners)
|
||||
./act_runner-linux-arm64 register \
|
||||
--instance http://localhost:3000 \
|
||||
--token YOUR_RUNNER_TOKEN
|
||||
|
||||
# Start daemon
|
||||
./act_runner-linux-arm64 daemon &
|
||||
# Download act_runner v0.2.13 (ARM64)
|
||||
wget https://gitea.com/gitea/act_runner/releases/download/v0.2.13/act_runner-0.2.13-linux-arm64
|
||||
chmod +x act_runner-0.2.13-linux-arm64
|
||||
ln -s act_runner-0.2.13-linux-arm64 act_runner
|
||||
```
|
||||
|
||||
## Step 16: Verify Full Deployment (TODO)
|
||||
Register the runner (get token from **Site Administration > Actions > Runners > Create new Runner**):
|
||||
|
||||
```bash
|
||||
# All containers running
|
||||
docker compose --profile full ps
|
||||
./act_runner register \
|
||||
--instance https://git.wizard.lu \
|
||||
--token YOUR_RUNNER_TOKEN
|
||||
```
|
||||
|
||||
# API health
|
||||
curl http://localhost:8001/health
|
||||
Accept the default runner name and labels when prompted.
|
||||
|
||||
# Caddy proxy with SSL
|
||||
Create a systemd service for persistent operation:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/gitea-runner.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Gitea Actions Runner
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=samir
|
||||
WorkingDirectory=/home/samir/gitea-runner
|
||||
ExecStart=/home/samir/gitea-runner/act_runner daemon
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now gitea-runner
|
||||
sudo systemctl status gitea-runner
|
||||
```
|
||||
|
||||
Verify the runner shows as **Online** in Gitea: **Site Administration > Actions > Runners**.
|
||||
|
||||
## Step 16: Verify Full Deployment
|
||||
|
||||
```bash
|
||||
# All app containers running
|
||||
cd ~/apps/orion && docker compose --profile full ps
|
||||
|
||||
# API health (via Caddy with SSL)
|
||||
curl https://api.wizard.lu/health
|
||||
|
||||
# Main platform
|
||||
curl -I https://wizard.lu
|
||||
|
||||
# Gitea
|
||||
curl https://git.wizard.lu
|
||||
curl -I https://git.wizard.lu
|
||||
|
||||
# Flower
|
||||
curl -I https://flower.wizard.lu
|
||||
|
||||
# Gitea runner status
|
||||
sudo systemctl status gitea-runner
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Port Reference
|
||||
## Domain & Port Reference
|
||||
|
||||
| Service | Internal | External | Domain (via Caddy) |
|
||||
| Service | Internal Port | External Port | Domain (via Caddy) |
|
||||
|---|---|---|---|
|
||||
| Wizamart API | 8000 | 8001 | `api.wizard.lu` |
|
||||
| Main Platform | 8000 | 8001 | `wizard.lu` |
|
||||
| OMS Platform | 8000 | 8001 | `oms.lu` (TODO) |
|
||||
| Loyalty+ Platform | 8000 | 8001 | `loyaltyplus.lu` (TODO) |
|
||||
| PostgreSQL | 5432 | 5432 | (internal only) |
|
||||
| Redis | 6379 | 6380 | (internal only) |
|
||||
| Flower | 5555 | 5555 | `flower.wizard.lu` |
|
||||
| Gitea | 3000 | 3000 | `git.wizard.lu` |
|
||||
| Caddy | — | 80, 443 | (reverse proxy) |
|
||||
|
||||
!!! note "Single backend, multiple domains"
|
||||
All platform domains route to the same FastAPI backend. The `PlatformContextMiddleware` identifies the platform from the `Host` header. See [Multi-Platform Architecture](../architecture/multi-platform-cms.md) for details.
|
||||
|
||||
## Directory Structure on Server
|
||||
|
||||
```
|
||||
@@ -460,7 +703,10 @@ curl https://git.wizard.lu
|
||||
│ ├── logs/ # Application logs
|
||||
│ ├── uploads/ # User uploads
|
||||
│ └── exports/ # Export files
|
||||
└── gitea-runner/ # (TODO) CI/CD runner
|
||||
└── gitea-runner/ # CI/CD runner (act_runner v0.2.13)
|
||||
├── act_runner # symlink → act_runner-0.2.13-linux-arm64
|
||||
├── act_runner-0.2.13-linux-arm64
|
||||
└── .runner # registration config
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
@@ -528,13 +774,26 @@ docker compose --profile full up -d --build
|
||||
docker compose --profile full exec -e PYTHONPATH=/app api python -m alembic upgrade heads
|
||||
```
|
||||
|
||||
### Quick access URLs (current — no domain yet)
|
||||
### Quick access URLs
|
||||
|
||||
After Caddy is configured:
|
||||
|
||||
| Service | URL |
|
||||
|---|---|
|
||||
| API Swagger docs | `http://91.99.65.229:8001/docs` |
|
||||
| API ReDoc | `http://91.99.65.229:8001/redoc` |
|
||||
| Admin panel | `http://91.99.65.229:8001/admin/login` |
|
||||
| Health check | `http://91.99.65.229:8001/health` |
|
||||
| Main Platform | `https://wizard.lu` |
|
||||
| API Swagger docs | `https://api.wizard.lu/docs` |
|
||||
| API ReDoc | `https://api.wizard.lu/redoc` |
|
||||
| Admin panel | `https://wizard.lu/admin/login` |
|
||||
| Health check | `https://api.wizard.lu/health` |
|
||||
| Gitea | `https://git.wizard.lu` |
|
||||
| Flower | `https://flower.wizard.lu` |
|
||||
| OMS Platform | `https://oms.lu` (after DNS) |
|
||||
| Loyalty+ Platform | `https://loyaltyplus.lu` (after DNS) |
|
||||
|
||||
Direct IP access (temporary, until firewall rules are removed):
|
||||
|
||||
| Service | URL |
|
||||
|---|---|
|
||||
| API | `http://91.99.65.229:8001/docs` |
|
||||
| Gitea | `http://91.99.65.229:3000` |
|
||||
| Flower | `http://91.99.65.229:5555` |
|
||||
|
||||
Reference in New Issue
Block a user