chore: productionize panel repo with CI and repo-based deploy flow
Some checks are pending
CI / lint (push) Waiting to run

This commit is contained in:
Adrian Miesikowski 2026-02-17 22:53:33 +01:00
parent 71d7bb8bf2
commit c37ab7581a
7 changed files with 107 additions and 37 deletions

30
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,30 @@
name: CI
on:
push:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Python syntax check
run: |
python -m py_compile init_db.py refresh_domain_data.py update_registrar_prices.py
- name: PHP syntax check
run: |
php -l api.php
- name: Script smoke checks
run: |
bash -n refresh_and_publish.sh
python init_db.py

View File

@ -5,21 +5,52 @@ Dynamiczny panel do monitoringu wolnych domen i porównania kosztów rejestracji
## Co robi ## Co robi
- Pobiera najnowsze wyniki skanu domen z Mongo (`aiagent.domain_scans`) - Pobiera najnowsze wyniki skanu domen z Mongo (`aiagent.domain_scans`)
- Trzyma dane panelu w SQLite (`data/domainhunter.db`) - Trzyma dane panelu w SQLite (`data/domainhunter.db`)
- Udostępnia API (`api.php`) dla frontendu - Udostępnia API (`api.php`) dla frontendu (bez statycznych JSON)
- Pozwala edytować ceny rejestratorów i zapisuje je po stronie serwera - Pozwala edytować ceny rejestratorów i zapisuje je po stronie serwera (bez localStorage)
- Wspiera autopobieranie cen (heurystyki) - Wspiera autopobieranie cen (heurystyki)
## Pliki ## Struktura
- `index.html` — frontend panelu - `index.html` — frontend panelu
- `api.php` — backend API - `api.php` — backend API (SQLite)
- `init_db.py` — inicjalizacja SQLite - `init_db.py` — inicjalizacja SQLite
- `refresh_domain_data.py` — sync Mongo -> SQLite - `refresh_domain_data.py` — sync Mongo -> SQLite
- `update_registrar_prices.py` — auto-update cen - `update_registrar_prices.py` — auto-update cen
- `refresh_and_publish.sh` — pełny refresh + publikacja - `refresh_and_publish.sh` — pełny refresh + publikacja do `/var/www/domain-panel`
- `data/registrars.json` — źródło rejestratorów + reguły autoPricing
## Szybki start ## Wymagania
- Python 3
- Docker + kontener `mongo` (z auth)
- `mongosh` dostępny w kontenerze mongo
- `sudo` do publikacji do `/var/www/domain-panel`
## Quick start (lokalnie)
```bash ```bash
python3 init_db.py python3 init_db.py
python3 update_registrar_prices.py || true python3 update_registrar_prices.py || true
python3 refresh_domain_data.py python3 refresh_domain_data.py
``` ```
## Produkcja / deploy
Uruchom z katalogu repo:
```bash
./refresh_and_publish.sh
```
Skrypt:
1. aktualizuje DB panelu,
2. synchronizuje pliki do `/var/www/domain-panel`,
3. ustawia właściciela `www-data` i prawa.
## Nginx (przykład)
Subdomena `domainhunter.szmyt151.pl` powinna proxyfikować do `http://127.0.0.1:3008/domainhunter/`
lub serwować statycznie z `/var/www/domain-panel`.
## CI (Gitea)
Workflow jest w `.gitea/workflows/ci.yml`:
- syntax check Python,
- syntax check PHP,
- smoke uruchomienia skryptów `--help`.
## Uwaga o cenach
Auto-pobieranie cen jest heurystyczne — przed zakupem domeny zawsze zweryfikuj finalny cennik i koszt odnowienia u rejestratora.

View File

@ -33,7 +33,7 @@
"autoMeta": { "autoMeta": {
"pl": { "pl": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://www.ovhcloud.com/pl/domains/tld/pl/", "url": "https://www.ovhcloud.com/pl/domains/tld/pl/",
"registerFound": 20.69, "registerFound": 20.69,
"renewFound": 20.69, "renewFound": 20.69,
@ -42,7 +42,7 @@
}, },
"com": { "com": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://www.ovhcloud.com/pl/domains/tld/com/", "url": "https://www.ovhcloud.com/pl/domains/tld/com/",
"registerFound": 34.08, "registerFound": 34.08,
"renewFound": 18.27, "renewFound": 18.27,
@ -51,7 +51,7 @@
}, },
"ai": { "ai": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://www.ovhcloud.com/pl/domains/tld/ai/", "url": "https://www.ovhcloud.com/pl/domains/tld/ai/",
"registerFound": 306.69, "registerFound": 306.69,
"renewFound": 306.69, "renewFound": 306.69,
@ -92,19 +92,19 @@
"pl": { "pl": {
"status": "error", "status": "error",
"error": "HTTP Error 404: Not Found", "error": "HTTP Error 404: Not Found",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://www.nazwa.pl/domeny/rejestracja-domeny-pl/" "url": "https://www.nazwa.pl/domeny/rejestracja-domeny-pl/"
}, },
"com": { "com": {
"status": "error", "status": "error",
"error": "HTTP Error 404: Not Found", "error": "HTTP Error 404: Not Found",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://www.nazwa.pl/domeny/rejestracja-domeny-com/" "url": "https://www.nazwa.pl/domeny/rejestracja-domeny-com/"
}, },
"ai": { "ai": {
"status": "error", "status": "error",
"error": "HTTP Error 404: Not Found", "error": "HTTP Error 404: Not Found",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://www.nazwa.pl/domeny/rejestracja-domeny-ai/" "url": "https://www.nazwa.pl/domeny/rejestracja-domeny-ai/"
} }
} }
@ -141,19 +141,19 @@
"pl": { "pl": {
"status": "error", "status": "error",
"error": "The read operation timed out", "error": "The read operation timed out",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://home.pl/domeny/.pl/" "url": "https://home.pl/domeny/.pl/"
}, },
"com": { "com": {
"status": "error", "status": "error",
"error": "HTTP Error 404: Not Found", "error": "HTTP Error 404: Not Found",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://home.pl/domeny/.com/" "url": "https://home.pl/domeny/.com/"
}, },
"ai": { "ai": {
"status": "error", "status": "error",
"error": "HTTP Error 404: Not Found", "error": "HTTP Error 404: Not Found",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://home.pl/domeny/.ai/" "url": "https://home.pl/domeny/.ai/"
} }
} }
@ -189,7 +189,7 @@
"autoMeta": { "autoMeta": {
"pl": { "pl": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://cyberfolks.pl/domeny/", "url": "https://cyberfolks.pl/domeny/",
"registerFound": 0.0, "registerFound": 0.0,
"renewFound": 9.9, "renewFound": 9.9,
@ -198,7 +198,7 @@
}, },
"com": { "com": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://cyberfolks.pl/domeny/", "url": "https://cyberfolks.pl/domeny/",
"registerFound": 9.9, "registerFound": 9.9,
"renewFound": 9.9, "renewFound": 9.9,
@ -207,7 +207,7 @@
}, },
"ai": { "ai": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://cyberfolks.pl/domeny/", "url": "https://cyberfolks.pl/domeny/",
"registerFound": 9.9, "registerFound": 9.9,
"renewFound": 9.9, "renewFound": 9.9,
@ -247,7 +247,7 @@
"autoMeta": { "autoMeta": {
"pl": { "pl": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://aftermarket.pl/domeny/", "url": "https://aftermarket.pl/domeny/",
"registerFound": null, "registerFound": null,
"renewFound": null, "renewFound": null,
@ -256,7 +256,7 @@
}, },
"com": { "com": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://aftermarket.pl/domeny/", "url": "https://aftermarket.pl/domeny/",
"registerFound": null, "registerFound": null,
"renewFound": null, "renewFound": null,
@ -265,7 +265,7 @@
}, },
"ai": { "ai": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://aftermarket.pl/domeny/", "url": "https://aftermarket.pl/domeny/",
"registerFound": null, "registerFound": null,
"renewFound": null, "renewFound": null,
@ -305,7 +305,7 @@
"autoMeta": { "autoMeta": {
"pl": { "pl": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://porkbun.com/tld/pl", "url": "https://porkbun.com/tld/pl",
"registerFound": 26.26, "registerFound": 26.26,
"renewFound": 26.26, "renewFound": 26.26,
@ -314,7 +314,7 @@
}, },
"com": { "com": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://porkbun.com/tld/com", "url": "https://porkbun.com/tld/com",
"registerFound": null, "registerFound": null,
"renewFound": null, "renewFound": null,
@ -323,7 +323,7 @@
}, },
"ai": { "ai": {
"status": "ok", "status": "ok",
"checkedAt": "2026-02-17T21:40:45.836487Z", "checkedAt": "2026-02-17T21:52:54.206707Z",
"url": "https://porkbun.com/tld/ai", "url": "https://porkbun.com/tld/ai",
"registerFound": null, "registerFound": null,
"renewFound": null, "renewFound": null,
@ -333,6 +333,6 @@
} }
} }
], ],
"autoLastRunAt": "2026-02-17T21:40:45.836487Z", "autoLastRunAt": "2026-02-17T21:52:54.206707Z",
"autoUpdatedFields": 14 "autoUpdatedFields": 14
} }

View File

@ -2,7 +2,8 @@
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
DB = Path('/home/szmyt/.openclaw/workspace/domain-panel/data/domainhunter.db') BASE = Path(__file__).resolve().parent
DB = BASE / 'data' / 'domainhunter.db'
DB.parent.mkdir(parents=True, exist_ok=True) DB.parent.mkdir(parents=True, exist_ok=True)
con = sqlite3.connect(DB) con = sqlite3.connect(DB)

View File

@ -1,10 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
python3 /home/szmyt/.openclaw/workspace/domain-panel/init_db.py
python3 /home/szmyt/.openclaw/workspace/domain-panel/update_registrar_prices.py || true SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
python3 /home/szmyt/.openclaw/workspace/domain-panel/refresh_domain_data.py TARGET_DIR="${TARGET_DIR:-/var/www/domain-panel}"
sudo rsync -a --delete /home/szmyt/.openclaw/workspace/domain-panel/ /var/www/domain-panel/
sudo chown -R www-data:www-data /var/www/domain-panel python3 "$SCRIPT_DIR/init_db.py"
sudo find /var/www/domain-panel -type d -exec chmod 755 {} + python3 "$SCRIPT_DIR/update_registrar_prices.py" || true
sudo find /var/www/domain-panel -type f -exec chmod 644 {} + python3 "$SCRIPT_DIR/refresh_domain_data.py"
echo "Published domain panel to /var/www/domain-panel"
sudo rsync -a --delete "$SCRIPT_DIR/" "$TARGET_DIR/"
sudo chown -R www-data:www-data "$TARGET_DIR"
sudo find "$TARGET_DIR" -type d -exec chmod 755 {} +
sudo find "$TARGET_DIR" -type f -exec chmod 644 {} +
echo "Published domain panel to $TARGET_DIR"

View File

@ -4,9 +4,10 @@ import sqlite3
import subprocess import subprocess
from pathlib import Path from pathlib import Path
BASE = Path(__file__).resolve().parent
ENV_PATH = Path('/home/szmyt/docker/databases/.env') ENV_PATH = Path('/home/szmyt/docker/databases/.env')
REG_PATH = Path('/home/szmyt/.openclaw/workspace/domain-panel/data/registrars.json') REG_PATH = BASE / 'data' / 'registrars.json'
DB_PATH = Path('/home/szmyt/.openclaw/workspace/domain-panel/data/domainhunter.db') DB_PATH = BASE / 'data' / 'domainhunter.db'
def read_env(path: Path): def read_env(path: Path):

View File

@ -5,7 +5,8 @@ import urllib.request
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
REG_PATH = Path('/home/szmyt/.openclaw/workspace/domain-panel/data/registrars.json') BASE = Path(__file__).resolve().parent
REG_PATH = BASE / 'data' / 'registrars.json'
TIMEOUT = 8 TIMEOUT = 8
MONEY_RE = re.compile(r'(\d{1,4}(?:[\.,]\d{1,2})?)\s*(zł|pln|eur|€|usd|\$)', re.IGNORECASE) MONEY_RE = re.compile(r'(\d{1,4}(?:[\.,]\d{1,2})?)\s*(zł|pln|eur|€|usd|\$)', re.IGNORECASE)