chore: productionize panel repo with CI and repo-based deploy flow
Some checks are pending
CI / lint (push) Waiting to run
Some checks are pending
CI / lint (push) Waiting to run
This commit is contained in:
parent
71d7bb8bf2
commit
c37ab7581a
30
.gitea/workflows/ci.yml
Normal file
30
.gitea/workflows/ci.yml
Normal 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
|
||||||
43
README.md
43
README.md
@ -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.
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user