# ONOXSOFT — Teknik Mimari (Phase 1 MVP)

> **Bu dosya:** Proje içinde yaşayan teknik referans. Master plan dondurulmuş artifact'tir, bu dosya proje evrildikçe güncellenir.
>
> **Master plan:** `C:\Users\root_\.claude\plans\bir-adet-cpanel-tarz-velvety-sketch.md`

---

## 1. Servis Topolojisi (AlmaLinux 9)

### Paket Repo Katmanları

| Repo | Paketler |
|---|---|
| **BaseOS / AppStream** | `httpd`, `mariadb-server`, `redis`, `fail2ban-server`, `firewalld`, `quota`, `xfsprogs`, `policycoreutils-python-utils`, `setools-console`, `openssh-server`, `cronie`, `chrony` |
| **EPEL 9** | `pure-ftpd`, `htop`, `jq`, `inotify-tools` |
| **Remi Modular** | `php{74,80,81,82,83}-php-fpm` + her versiyon için `-mysqlnd -opcache -gd -curl -mbstring -zip -xml -intl -bcmath -imagick` |
| **PowerDNS** (repo.powerdns.com) | `pdns`, `pdns-backend-mysql` |
| **GitHub** | acme.sh → `/usr/local/onoxsoft/acme.sh/` (pin'lenmiş clone) |

### Topoloji Diyagramı

```
[Admin/User Browser] ──→ :443 ──→ [Panel Apache vhost: panel.onox.com.tr]
                                  ──→ [PHP-FPM pool: onoxsoft-panel.sock]
                                                ├──→ Redis (Horizon queue + cache + session)
                                                ├──→ MariaDB (onoxsoft_panel DB)
                                                └──→ /usr/local/onoxsoft/bin/onx-* (sudo)

[Customer Site Visitor] ──→ :443 ──→ [Customer Apache event MPM]
                                     ──→ vhost match (example.com)
                                          ──→ PHP-FPM proxy: /run/php-fpm/onx_user1_php82.sock
                                          ──→ /home/onx_user1/public_html/

[DNS Resolver] ──→ :53 ──→ [PowerDNS Authoritative]
                            └──→ MariaDB (powerdns DB, zones+records)

[FTP/SFTP Client] ──→ :21 / :22 ──→ [Pure-FTPd / OpenSSH internal-sftp]
                                    └──→ /home/<user>/ (chrooted)
```

**Karar:** Panel ve customer siteler **aynı Apache instance**'ında çalışır (vhost ayrımı yeterli). Ayrı port/instance overhead getirir, MVP için gerekli değil.

---

## 2. Account Isolation Modeli

### UID Konvansiyonu
- **UID range:** 10000-65000
- **Username pattern:** `onx_<8-12 char>` (örn `onx_acme01`)
- **Regex validation:** `^onx_[a-z0-9]{4,12}$` (shell injection koruması)

### /home Layout

```
/home/onx_userX/
├── public_html/      # Apache DocumentRoot (httpd_sys_content_t)
├── private/          # DocRoot dışı, kod gizli
├── logs/             # Per-vhost Apache access/error
├── tmp/              # PHP upload_tmp_dir, session.save_path
├── etc/              # Per-user readonly config
├── backups/          # Müşterinin kendi backup'ları
└── .ssh/             # mode 700, authorized_keys
```

### PHP-FPM Per-User Pool Template

`/etc/php-fpm.d/onx_userX_php82.conf`:

```ini
[onx_userX_php82]
user = onx_userX
group = onx_userX
listen = /run/php-fpm/onx_userX_php82.sock
listen.owner = apache
listen.group = onx_userX
listen.mode = 0660
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 30s
php_admin_value[open_basedir] = /home/onx_userX:/tmp:/var/lib/php/session
php_admin_value[disable_functions] = exec,system,passthru,shell_exec,proc_open,popen
php_admin_value[upload_tmp_dir] = /home/onx_userX/tmp
php_admin_value[session.save_path] = /home/onx_userX/tmp
```

### Apache Vhost Template

```apache
<VirtualHost *:443>
  ServerName example.com
  ServerAlias www.example.com
  DocumentRoot /home/onx_userX/public_html
  CustomLog /home/onx_userX/logs/example.com-access.log combined
  ErrorLog /home/onx_userX/logs/example.com-error.log
  SSLEngine on
  SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
  <FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/onx_userX_php82.sock|fcgi://localhost"
  </FilesMatch>
  <Directory /home/onx_userX/public_html>
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost>
```

### XFS Quota

AlmaLinux 9'da `/home` default XFS. Per-package limit:

```bash
setquota -u onx_userX 2097152 2306867 0 0 /home
# soft=2GB, hard=2.2GB, inodes unlimited
```

### SSH/SFTP Jail

`/etc/ssh/sshd_config.d/onoxsoft.conf`:

```
Match Group onoxsoft-sftp
  ChrootDirectory /home/%u
  ForceCommand internal-sftp
  AllowTcpForwarding no
  X11Forwarding no
  PermitTunnel no
```

> **Tuzak:** `ChrootDirectory` **root:root mode 755** olmalı. User content `/home/%u/files/` altında.

### SELinux Yaklaşımı (Phase 1)

- **Mode:** `Enforcing` (production)
- **Custom policy module YAZMA** — `targeted` policy default'larıyla idare et
- **Açılacak boolean'lar:**
  - `httpd_can_network_connect`
  - `httpd_enable_homedirs`
  - `httpd_unified` (test edilecek)
- **Per-user public_html context:**
  ```bash
  semanage fcontext -a -t httpd_sys_content_t "/home/[^/]+/public_html(/.*)?"
  restorecon -R /home/
  ```
- **Custom policy module Phase 1.5'a** — curriculum hafta 6-7'de iskelet hazır

---

## 3. Sysapi Layer — Panel ↔ OS Köprüsü

**Karar:** sudo + whitelisted bash wrapper scripts (HestiaCP modeli).

**Neden:**
- Solo dev için en basit, debug-friendly
- Multi-server'a Phase 3'te SSH agent çağrısı eklemek kolay (script'ler aynı interface)
- Long-lived daemon yok = restart koordinasyonu yok

### Script Inventory

```
/usr/local/onoxsoft/bin/
├── onx-user-add              # JSON in → user + home + skel
├── onx-user-suspend
├── onx-user-terminate
├── onx-vhost-add
├── onx-vhost-remove
├── onx-fpm-pool-create
├── onx-fpm-pool-switch-version
├── onx-db-create
├── onx-db-user-create
├── onx-zone-add
├── onx-record-upsert
├── onx-cert-issue
├── onx-cert-renew
├── onx-ftp-user-add
├── onx-backup-run
└── onx-quota-apply
```

### Script Disiplini

- **Input:** stdin'den JSON, `jq` ile parse
- **Output:** stdout'a JSON, stderr'e debug
- **Exit codes:**
  - `0` = ok
  - `1` = invalid input
  - `2` = preflight fail
  - `3` = execution fail
  - `4` = rollback ok
  - `5` = rollback fail
- **Mecburi:** `set -euo pipefail`
- **Validation:**
  - Username regex
  - Domain regex (RFC 1035 lite)
  - Path: `realpath` ile `/home/` veya `/etc/httpd/conf.d/sites/` dışı reddet

### sudoers

`/etc/sudoers.d/onoxsoft`:

```
apache ALL=(root) NOPASSWD: /usr/local/onoxsoft/bin/onx-*
Defaults!/usr/local/onoxsoft/bin/onx-* !requiretty
Defaults!/usr/local/onoxsoft/bin/onx-* log_output, log_input
```

### Laravel Tarafı

`app/Domain/Sysapi/SysapiBridge.php` PHP wrapper, `Process::run` ile çağrı. Her çağrı `audit_log` tablosuna yazar.

---

## 4. Panel Database Schema (Migration Listesi)

| Tablo | Açıklama |
|---|---|
| `users` | Admin + end-user (role enum) |
| `accounts` | Linux user metadata (uid, username, status, quota_bytes, package_id, owner_user_id) |
| `packages` | Limits JSON (disk_mb, db_count, ftp_count, subdomain_count, php_versions[]) |
| `domains` | account_id, name, type, document_root, php_version, ssl_enabled |
| `dns_domains` | PowerDNS gmysql schema |
| `dns_records` | PowerDNS gmysql schema |
| `mysql_databases` | account_id, name, real_db_name |
| `mysql_users` | account_id, name, real_user_name, plugin |
| `mysql_grants` | db_id, user_id, privileges JSON |
| `ftp_users` | Pure-FTPd MySQL schema |
| `ssl_certificates` | domain_id, issuer, expires_at, renewal_status |
| `backups` | account_id, type, file_path, size_bytes, created_at, expires_at |
| `audit_log` | user_id, ip, action, target_type, target_id, before JSON, after JSON, created_at (append-only) |
| `jobs`, `failed_jobs` | Laravel default |
| `two_factor_secrets` | user_id, secret encrypted, recovery_codes JSON |
| `whmcs_webhook_log` | request, response, hmac_verified, idempotency_key, processed_at |

**Phase 2'ye:** `mail_*`, `resellers`, `billing_*` (migration dosyalarında not düşülür).

---

## 5. WHMCS Webhook Contract

**Auth:** HMAC-SHA256 over (body + timestamp), `X-Onoxsoft-Signature` header. Token Sanctum ile.

**Idempotency:** `Idempotency-Key` header (UUID). Aynı key → aynı response, replay yok.

| Method | Path | Body |
|---|---|---|
| POST | `/api/v1/whmcs/account` | `{username?, email, domain, package_id}` |
| PATCH | `/api/v1/whmcs/account/{id}/suspend` | `{reason}` |
| PATCH | `/api/v1/whmcs/account/{id}/unsuspend` | — |
| DELETE | `/api/v1/whmcs/account/{id}` | `{delete_data:bool}` |
| PATCH | `/api/v1/whmcs/account/{id}/package` | `{package_id}` |
| GET | `/api/v1/whmcs/account/{id}/usage` | — |
| POST | `/api/v1/whmcs/test` | — |

---

## 6. Queue & Job Architecture

**Stack:** Laravel Horizon + Redis.

| Queue | Priority | Timeout | Retry |
|---|---|---|---|
| `provisioning` | high | 5dk | 3 |
| `default` | normal | 2dk | 5 |
| `ssl` | normal | 5dk | 5 (exp backoff) |
| `backups` | low | 60dk | 1 |
| `audit` | lowest | 30sn | 1 (fire-and-forget) |
| `notifications` | normal | 30sn | 3 |

**Scheduler:**
- `03:00 daily` — SSL renewal check
- `04:00 daily` — Account backup
- `*/15 * * * *` — Quota usage recalc
- `*/5 * * * *` — Audit log retention cleanup batch
- `* * * * *` — Horizon health metric

---

## 7. Güvenlik Baseline (Phase 1)

- **Auth:** Laravel session + secure cookie + SameSite=Lax + Fortify
- **2FA:** TOTP zorunlu (admin), opsiyonel (user). 10 recovery code (hashed)
- **Rate limiting:** login 5/15dk/IP, API per-token
- **Audit:** her destructive action → before/after JSON
- **Brute force:** failed_login_attempts + 5 fail = 15dk lockout
- **fail2ban:** `sshd`, `panel-login` (Laravel log), `pure-ftpd`
- **SSH:** root login disabled, pubkey only
- **TLS:** panel self-host Let's Encrypt + HSTS + Brotli
- **Secrets:** `.env` mode 0600 (apache:apache), `/etc/onoxsoft/secrets.env` root:root mode 0600
- **Headers:** CSP strict (`script-src 'self'`), CSRF, X-Frame-Options DENY

---

## 8. Laravel Project Structure

```
PANELTR/
├── app/
│   ├── Domain/                       # DDD-light bounded contexts
│   │   ├── Account/
│   │   ├── Domain/
│   │   ├── Database/
│   │   ├── Dns/
│   │   ├── Ftp/
│   │   ├── Ssl/
│   │   ├── Backup/
│   │   ├── Package/
│   │   ├── Audit/
│   │   └── Sysapi/
│   ├── Http/
│   │   ├── Controllers/Admin/
│   │   ├── Controllers/User/
│   │   ├── Controllers/Api/V1/
│   │   └── Middleware/
│   ├── Jobs/
│   ├── Console/Commands/             # artisan onoxsoft:*
│   └── Providers/
├── resources/js/Pages/Admin/         # Inertia + Vue 3
├── resources/js/Pages/User/
├── routes/web.php, api.php
├── database/migrations/
├── tests/                            # Pest
└── config/onoxsoft/templates/
    ├── vhost.conf.stub
    ├── fpm-pool.conf.stub
    └── powerdns-zone.sql.stub
```

**Konvansiyon:**
- **Service** = stateful domain logic, DI ile çağrılır
- **Action** = tek-amaçlı CQRS-style yazar
- **Job** = async kuyruğa giren, OS-level operasyon yapar

---

## 9. KVKK Compliance Layer (Phase 1)

- ✅ Login modal: KVKK aydınlatma metni + onay kutusu (audit log)
- ✅ Cookie banner (admin panel)
- ✅ Kişisel veri envanteri sayfası (admin)
- ✅ Audit log PII maskeleme (email kısmi `er***@example.com`, IP truncated `185.143.234.xxx`)
- ✅ Retention cron:
  - audit_log: 90 gün
  - failed_login: 30 gün
  - backup_log: 180 gün
- ✅ Customer self-service:
  - "Verilerimi indir" → JSON export
  - "Hesabımı sil" → soft delete + 30gün hard delete
  - "Verilerimi düzelt" → profile edit

**Phase 2:** VERBİS, customer-side aydınlatma generator, DPIA template, 72sa ihlal bildirim workflow.

---

## Açık Konular (Phase 0 sırasında karar)

- **Lisans server hosting:** onox.com.tr Hetzner'da mı, ayrı provider'da mı (SPOF endişesi)
- **Phase 1.5 cut list:** Epic 10 File Manager tamamen Phase 1.5'a alınsın mı (timeline %20 kazandırır)
- **WebSocket ihtiyacı:** Laravel Reverb gerekli mi MVP'de, yoksa polling yeter mi (real-time UI update için)
- **PHP 7.4 desteği:** EOL paketi sunmak güvenlik riski (vurgu: legacy WP siteler için talep var)

---

**Son güncelleme:** Workspace skeleton oluşturuldu (initial commit `c1c269a`)
