From c37ab7581a771a90a260f1fd2052e7863f716e49 Mon Sep 17 00:00:00 2001 From: Adrian Miesikowski Date: Tue, 17 Feb 2026 22:53:33 +0100 Subject: [PATCH] chore: productionize panel repo with CI and repo-based deploy flow --- .gitea/workflows/ci.yml | 30 ++++++++++++++++++++++++++ README.md | 43 ++++++++++++++++++++++++++++++++------ data/registrars.json | 38 ++++++++++++++++----------------- init_db.py | 3 ++- refresh_and_publish.sh | 22 ++++++++++++------- refresh_domain_data.py | 5 +++-- update_registrar_prices.py | 3 ++- 7 files changed, 107 insertions(+), 37 deletions(-) create mode 100644 .gitea/workflows/ci.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..3947f91 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -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 diff --git a/README.md b/README.md index 6e7d47e..694cab4 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,52 @@ Dynamiczny panel do monitoringu wolnych domen i porównania kosztów rejestracji ## Co robi - Pobiera najnowsze wyniki skanu domen z Mongo (`aiagent.domain_scans`) - Trzyma dane panelu w SQLite (`data/domainhunter.db`) -- Udostępnia API (`api.php`) dla frontendu -- Pozwala edytować ceny rejestratorów i zapisuje je po stronie serwera +- Udostępnia API (`api.php`) dla frontendu (bez statycznych JSON) +- Pozwala edytować ceny rejestratorów i zapisuje je po stronie serwera (bez localStorage) - Wspiera autopobieranie cen (heurystyki) -## Pliki +## Struktura - `index.html` — frontend panelu -- `api.php` — backend API +- `api.php` — backend API (SQLite) - `init_db.py` — inicjalizacja SQLite - `refresh_domain_data.py` — sync Mongo -> SQLite - `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 python3 init_db.py python3 update_registrar_prices.py || true 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. diff --git a/data/registrars.json b/data/registrars.json index 068cfd6..8c06d0f 100644 --- a/data/registrars.json +++ b/data/registrars.json @@ -33,7 +33,7 @@ "autoMeta": { "pl": { "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/", "registerFound": 20.69, "renewFound": 20.69, @@ -42,7 +42,7 @@ }, "com": { "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/", "registerFound": 34.08, "renewFound": 18.27, @@ -51,7 +51,7 @@ }, "ai": { "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/", "registerFound": 306.69, "renewFound": 306.69, @@ -92,19 +92,19 @@ "pl": { "status": "error", "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/" }, "com": { "status": "error", "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/" }, "ai": { "status": "error", "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/" } } @@ -141,19 +141,19 @@ "pl": { "status": "error", "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/" }, "com": { "status": "error", "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/" }, "ai": { "status": "error", "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/" } } @@ -189,7 +189,7 @@ "autoMeta": { "pl": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://cyberfolks.pl/domeny/", "registerFound": 0.0, "renewFound": 9.9, @@ -198,7 +198,7 @@ }, "com": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://cyberfolks.pl/domeny/", "registerFound": 9.9, "renewFound": 9.9, @@ -207,7 +207,7 @@ }, "ai": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://cyberfolks.pl/domeny/", "registerFound": 9.9, "renewFound": 9.9, @@ -247,7 +247,7 @@ "autoMeta": { "pl": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://aftermarket.pl/domeny/", "registerFound": null, "renewFound": null, @@ -256,7 +256,7 @@ }, "com": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://aftermarket.pl/domeny/", "registerFound": null, "renewFound": null, @@ -265,7 +265,7 @@ }, "ai": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://aftermarket.pl/domeny/", "registerFound": null, "renewFound": null, @@ -305,7 +305,7 @@ "autoMeta": { "pl": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://porkbun.com/tld/pl", "registerFound": 26.26, "renewFound": 26.26, @@ -314,7 +314,7 @@ }, "com": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://porkbun.com/tld/com", "registerFound": null, "renewFound": null, @@ -323,7 +323,7 @@ }, "ai": { "status": "ok", - "checkedAt": "2026-02-17T21:40:45.836487Z", + "checkedAt": "2026-02-17T21:52:54.206707Z", "url": "https://porkbun.com/tld/ai", "registerFound": null, "renewFound": null, @@ -333,6 +333,6 @@ } } ], - "autoLastRunAt": "2026-02-17T21:40:45.836487Z", + "autoLastRunAt": "2026-02-17T21:52:54.206707Z", "autoUpdatedFields": 14 } \ No newline at end of file diff --git a/init_db.py b/init_db.py index 5aeedbc..0edf923 100644 --- a/init_db.py +++ b/init_db.py @@ -2,7 +2,8 @@ import sqlite3 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) con = sqlite3.connect(DB) diff --git a/refresh_and_publish.sh b/refresh_and_publish.sh index 9afc113..18b4d63 100755 --- a/refresh_and_publish.sh +++ b/refresh_and_publish.sh @@ -1,10 +1,16 @@ #!/usr/bin/env bash 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 -python3 /home/szmyt/.openclaw/workspace/domain-panel/refresh_domain_data.py -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 -sudo find /var/www/domain-panel -type d -exec chmod 755 {} + -sudo find /var/www/domain-panel -type f -exec chmod 644 {} + -echo "Published domain panel to /var/www/domain-panel" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TARGET_DIR="${TARGET_DIR:-/var/www/domain-panel}" + +python3 "$SCRIPT_DIR/init_db.py" +python3 "$SCRIPT_DIR/update_registrar_prices.py" || true +python3 "$SCRIPT_DIR/refresh_domain_data.py" + +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" diff --git a/refresh_domain_data.py b/refresh_domain_data.py index 3d7dbac..e6590a3 100755 --- a/refresh_domain_data.py +++ b/refresh_domain_data.py @@ -4,9 +4,10 @@ import sqlite3 import subprocess from pathlib import Path +BASE = Path(__file__).resolve().parent ENV_PATH = Path('/home/szmyt/docker/databases/.env') -REG_PATH = Path('/home/szmyt/.openclaw/workspace/domain-panel/data/registrars.json') -DB_PATH = Path('/home/szmyt/.openclaw/workspace/domain-panel/data/domainhunter.db') +REG_PATH = BASE / 'data' / 'registrars.json' +DB_PATH = BASE / 'data' / 'domainhunter.db' def read_env(path: Path): diff --git a/update_registrar_prices.py b/update_registrar_prices.py index 84c3e59..3611139 100755 --- a/update_registrar_prices.py +++ b/update_registrar_prices.py @@ -5,7 +5,8 @@ import urllib.request from datetime import datetime, timezone 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 MONEY_RE = re.compile(r'(\d{1,4}(?:[\.,]\d{1,2})?)\s*(zł|pln|eur|€|usd|\$)', re.IGNORECASE)