feat: expose web admin with auth by default

This commit is contained in:
Андрей Бобырев
2026-06-06 12:49:05 +03:00
parent 687d1dbadd
commit 4c635858c7
5 changed files with 95 additions and 25 deletions

View File

@@ -50,33 +50,32 @@ ssh root@SERVER 'chmod +x /opt/pcatelegram_web/install.sh /opt/pcatelegram_web/i
| `PCATELEGRAM_WEB_BASE` | `https://raw.githubusercontent.com/andrey271192/PCAtelegram_web/main` | база загрузки файлов | | `PCATELEGRAM_WEB_BASE` | `https://raw.githubusercontent.com/andrey271192/PCAtelegram_web/main` | база загрузки файлов |
| `PCATELEGRAM_WEB_INSTALL_DIR` | `/opt/pcatelegram_web` | путь установки | | `PCATELEGRAM_WEB_INSTALL_DIR` | `/opt/pcatelegram_web` | путь установки |
| `GITHUB_TOKEN` / `GH_TOKEN` | пусто | token для private GitHub raw downloads | | `GITHUB_TOKEN` / `GH_TOKEN` | пусто | token для private GitHub raw downloads |
| `PCATELEGRAM_WEB_ADMIN_HOST` | `127.0.0.1` | bind web-admin | | `PCATELEGRAM_WEB_ADMIN_HOST` | `0.0.0.0` | bind web-admin |
| `PCATELEGRAM_WEB_ADMIN_PORT` | `1984` | port web-admin | | `PCATELEGRAM_WEB_ADMIN_PORT` | `1984` | port web-admin |
| `PCATELEGRAM_WEB_ADMIN_USER` | `admin` | Basic Auth login | | `PCATELEGRAM_WEB_ADMIN_USER` | `admin` | Basic Auth login |
| `PCATELEGRAM_WEB_ADMIN_PASSWORD` | пусто | Basic Auth password; обязателен при public bind | | `PCATELEGRAM_WEB_ADMIN_PASSWORD` | автогенерация | Basic Auth password |
## Web-admin ## Web-admin
После первого запуска `pcatelegram_web` локальная web-admin панель ставится как `pcatelegram_web-admin.service`. После первого запуска `pcatelegram_web` web-admin панель ставится как `pcatelegram_web-admin.service` и слушает публично:
Безопасный локальный доступ через SSH tunnel:
```bash ```bash
ssh -L 1984:127.0.0.1:1984 root@SERVER http://SERVER:1984/
open http://127.0.0.1:1984/
``` ```
Для public bind сначала задайте пароль через systemd override или reverse proxy: Логин по умолчанию: `admin`. Пароль генерируется при установке и хранится на сервере:
```ini ```bash
[Service] sudo cat /root/pcatelegram_web-admin.password
Environment=PCATELEGRAM_WEB_ADMIN_HOST=0.0.0.0
Environment=PCATELEGRAM_WEB_ADMIN_PORT=1984
Environment=PCATELEGRAM_WEB_ADMIN_USER=admin
Environment=PCATELEGRAM_WEB_ADMIN_PASSWORD=change-me
``` ```
Без HTTPS public Basic Auth гонит пароль открытым текстом. Для постоянного доступа лучше reverse proxy с TLS. Для своего пароля передайте env до установки:
```bash
PCATELEGRAM_WEB_ADMIN_PASSWORD='strong-password' bash bootstrap.sh
```
Без HTTPS Basic Auth гонит пароль открытым текстом. Для постоянного доступа лучше reverse proxy с TLS, но порт `1984` открыт по умолчанию по запросу проекта.
## Проверки ## Проверки

View File

@@ -2,9 +2,8 @@
""" """
PCAtelegram_web local web admin. PCAtelegram_web local web admin.
The service is intentionally bound to 127.0.0.1:1984. Operators reach it The service is publicly reachable by default and protected with Basic Auth.
through an SSH tunnel by default. If exposed through a reverse proxy or public Installers generate /root/pcatelegram_web-admin.password for credentials.
bind, enable PCATELEGRAM_WEB_ADMIN_PASSWORD.
""" """
from __future__ import annotations from __future__ import annotations
@@ -49,7 +48,7 @@ SHARED_443_CONFIG = Path(os.getenv("PCATELEGRAM_WEB_SHARED_443", "/opt/pcatelegr
BACKUP_SCHEDULE_FILE = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_SCHEDULE", "/opt/pcatelegram_web/backup_schedule.json")) BACKUP_SCHEDULE_FILE = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_SCHEDULE", "/opt/pcatelegram_web/backup_schedule.json"))
BACKUP_RESTORE_LOG = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_RESTORE_LOG", "/var/log/pcatelegram_web-restore.log")) BACKUP_RESTORE_LOG = Path(os.getenv("PCATELEGRAM_WEB_BACKUP_RESTORE_LOG", "/var/log/pcatelegram_web-restore.log"))
HOST = os.getenv("PCATELEGRAM_WEB_ADMIN_HOST", "127.0.0.1") HOST = os.getenv("PCATELEGRAM_WEB_ADMIN_HOST", "0.0.0.0")
PORT = int(os.getenv("PCATELEGRAM_WEB_ADMIN_PORT", "1984")) PORT = int(os.getenv("PCATELEGRAM_WEB_ADMIN_PORT", "1984"))
ADMIN_USER = os.getenv("PCATELEGRAM_WEB_ADMIN_USER", "admin") ADMIN_USER = os.getenv("PCATELEGRAM_WEB_ADMIN_USER", "admin")
ADMIN_PASSWORD = os.getenv("PCATELEGRAM_WEB_ADMIN_PASSWORD", "") ADMIN_PASSWORD = os.getenv("PCATELEGRAM_WEB_ADMIN_PASSWORD", "")
@@ -70,6 +69,17 @@ TRAFFIC_WINDOWS = {
} }
def load_admin_password_file() -> str:
path = Path(os.getenv("PCATELEGRAM_WEB_ADMIN_AUTH_FILE", "/root/pcatelegram_web-admin.password"))
try:
for line in path.read_text(encoding="utf-8").splitlines():
if line.startswith("password="):
return line.split("=", 1)[1].strip()
except OSError:
return ""
return ""
def utc_now() -> str: def utc_now() -> str:
return datetime.now(timezone.utc).isoformat() return datetime.now(timezone.utc).isoformat()
@@ -1288,7 +1298,7 @@ class AdminHandler(BaseHTTPRequestHandler):
print("%s - %s" % (self.address_string(), fmt % args)) print("%s - %s" % (self.address_string(), fmt % args))
def auth_enabled(self) -> bool: def auth_enabled(self) -> bool:
return bool(ADMIN_PASSWORD) return bool(ADMIN_PASSWORD or load_admin_password_file())
def is_authorized(self) -> bool: def is_authorized(self) -> bool:
if not self.auth_enabled(): if not self.auth_enabled():
@@ -1301,7 +1311,8 @@ class AdminHandler(BaseHTTPRequestHandler):
username, password = decoded.split(":", 1) username, password = decoded.split(":", 1)
except Exception: except Exception:
return False return False
return hmac.compare_digest(username, ADMIN_USER) and hmac.compare_digest(password, ADMIN_PASSWORD) expected_password = ADMIN_PASSWORD or load_admin_password_file()
return hmac.compare_digest(username, ADMIN_USER) and hmac.compare_digest(password, expected_password)
def require_auth(self) -> bool: def require_auth(self) -> bool:
if self.is_authorized(): if self.is_authorized():

View File

@@ -902,8 +902,9 @@ install_admin_web() {
chmod 755 "$ADMIN_WEB_DIR/server.py" "$ADMIN_WEB_DIR/static" chmod 755 "$ADMIN_WEB_DIR/server.py" "$ADMIN_WEB_DIR/static"
rm -f "$ADMIN_WEB_DIR/token" 2>/dev/null || true rm -f "$ADMIN_WEB_DIR/token" 2>/dev/null || true
local python_bin local python_bin admin_password
python_bin=$(command -v python3) python_bin=$(command -v python3)
admin_password=$(ensure_admin_web_password)
cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << SVCEOF cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << SVCEOF
[Unit] [Unit]
Description=PCAtelegram_web v${PCATELEGRAM_WEB_VERSION} Local Web Admin Description=PCAtelegram_web v${PCATELEGRAM_WEB_VERSION} Local Web Admin
@@ -917,6 +918,8 @@ Restart=always
RestartSec=5 RestartSec=5
Environment=PCATELEGRAM_WEB_ADMIN_HOST=$ADMIN_WEB_HOST Environment=PCATELEGRAM_WEB_ADMIN_HOST=$ADMIN_WEB_HOST
Environment=PCATELEGRAM_WEB_ADMIN_PORT=$ADMIN_WEB_PORT Environment=PCATELEGRAM_WEB_ADMIN_PORT=$ADMIN_WEB_PORT
Environment=PCATELEGRAM_WEB_ADMIN_USER=${PCATELEGRAM_WEB_ADMIN_USER:-admin}
Environment=PCATELEGRAM_WEB_ADMIN_PASSWORD=$admin_password
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -928,7 +931,8 @@ SVCEOF
if type install_stats_collector &>/dev/null; then if type install_stats_collector &>/dev/null; then
install_stats_collector >/dev/null 2>&1 || log_warning "stats collector was not started; open Web Admin traffic page and use Repair" install_stats_collector >/dev/null 2>&1 || log_warning "stats collector was not started; open Web Admin traffic page and use Repair"
fi fi
log_success "Web admin installed: ${ADMIN_WEB_HOST}:${ADMIN_WEB_PORT}" log_success "Web admin installed: http://$(get_server_ip):${ADMIN_WEB_PORT}/"
log_info "Web admin login saved: $ADMIN_WEB_AUTH_FILE"
} }
auto_install_admin_web_if_possible() { auto_install_admin_web_if_possible() {

View File

@@ -15,6 +15,32 @@ PCATELEGRAM_WEB_DIR="/opt/pcatelegram_web"
ADMIN_WEB_DIR="/opt/pcatelegram_web-admin" ADMIN_WEB_DIR="/opt/pcatelegram_web-admin"
ADMIN_WEB_SERVICE="pcatelegram_web-admin" ADMIN_WEB_SERVICE="pcatelegram_web-admin"
ADMIN_WEB_PORT="1984" ADMIN_WEB_PORT="1984"
ADMIN_WEB_HOST="0.0.0.0"
ADMIN_WEB_AUTH_FILE="/root/pcatelegram_web-admin.password"
ensure_admin_web_password() {
local pw=""
if [ -n "${PCATELEGRAM_WEB_ADMIN_PASSWORD:-}" ]; then
pw="$PCATELEGRAM_WEB_ADMIN_PASSWORD"
elif [ -f "$ADMIN_WEB_AUTH_FILE" ] && [ -s "$ADMIN_WEB_AUTH_FILE" ]; then
pw=$(sed -n 's/^password=//p' "$ADMIN_WEB_AUTH_FILE" | head -1)
fi
if [ -z "$pw" ]; then
if command -v openssl >/dev/null 2>&1; then
pw=$(openssl rand -base64 24 | tr -d '\n')
else
pw=$(head -c 24 /dev/urandom | base64 | tr -d '\n')
fi
fi
umask 077
cat > "$ADMIN_WEB_AUTH_FILE" <<EOF
user=${PCATELEGRAM_WEB_ADMIN_USER:-admin}
password=${pw}
url=http://$(hostname -I 2>/dev/null | awk '{print $1}'):${ADMIN_WEB_PORT}/
EOF
chmod 600 "$ADMIN_WEB_AUTH_FILE" 2>/dev/null || true
printf '%s' "$pw"
}
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Запустите с sudo.${NC}" echo -e "${RED}Запустите с sudo.${NC}"
@@ -128,6 +154,7 @@ if [ -f "$SCRIPT_DIR/admin-web/server.py" ]; then
rm -f "$ADMIN_WEB_DIR/token" 2>/dev/null || true rm -f "$ADMIN_WEB_DIR/token" 2>/dev/null || true
PYTHON_BIN=$(command -v python3) PYTHON_BIN=$(command -v python3)
ADMIN_WEB_PASSWORD=$(ensure_admin_web_password)
cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << EOF cat > "/etc/systemd/system/${ADMIN_WEB_SERVICE}.service" << EOF
[Unit] [Unit]
Description=PCAtelegram_web v2.5.0 Local Web Admin Description=PCAtelegram_web v2.5.0 Local Web Admin
@@ -139,8 +166,10 @@ WorkingDirectory=$ADMIN_WEB_DIR
ExecStart=$PYTHON_BIN $ADMIN_WEB_DIR/server.py ExecStart=$PYTHON_BIN $ADMIN_WEB_DIR/server.py
Restart=always Restart=always
RestartSec=5 RestartSec=5
Environment=PCATELEGRAM_WEB_ADMIN_HOST=127.0.0.1 Environment=PCATELEGRAM_WEB_ADMIN_HOST=$ADMIN_WEB_HOST
Environment=PCATELEGRAM_WEB_ADMIN_PORT=$ADMIN_WEB_PORT Environment=PCATELEGRAM_WEB_ADMIN_PORT=$ADMIN_WEB_PORT
Environment=PCATELEGRAM_WEB_ADMIN_USER=${PCATELEGRAM_WEB_ADMIN_USER:-admin}
Environment=PCATELEGRAM_WEB_ADMIN_PASSWORD=$ADMIN_WEB_PASSWORD
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -20,8 +20,9 @@ LOG_FILE="/var/log/pcatelegram_web.log"
BOT_DIR="/opt/pcatelegram_web-bot" BOT_DIR="/opt/pcatelegram_web-bot"
ADMIN_WEB_DIR="/opt/pcatelegram_web-admin" ADMIN_WEB_DIR="/opt/pcatelegram_web-admin"
ADMIN_WEB_SERVICE="pcatelegram_web-admin" ADMIN_WEB_SERVICE="pcatelegram_web-admin"
ADMIN_WEB_HOST="127.0.0.1" ADMIN_WEB_HOST="0.0.0.0"
ADMIN_WEB_PORT="1984" ADMIN_WEB_PORT="1984"
ADMIN_WEB_AUTH_FILE="/root/pcatelegram_web-admin.password"
# ── V1 совместимость ───────────────────────────────────────────────────────── # ── V1 совместимость ─────────────────────────────────────────────────────────
V1_CONTAINER_NAME="mtproto-proxy" V1_CONTAINER_NAME="mtproto-proxy"
@@ -738,3 +739,29 @@ init_dirs() {
mkdir -p "$PCATELEGRAM_WEB_DIR" "$BACKUP_DIR" /etc/telemt 2>/dev/null mkdir -p "$PCATELEGRAM_WEB_DIR" "$BACKUP_DIR" /etc/telemt 2>/dev/null
touch "$LOG_FILE" 2>/dev/null touch "$LOG_FILE" 2>/dev/null
} }
ensure_admin_web_password() {
local pw=""
if [ -n "${PCATELEGRAM_WEB_ADMIN_PASSWORD:-}" ]; then
pw="$PCATELEGRAM_WEB_ADMIN_PASSWORD"
elif [ -f "$ADMIN_WEB_AUTH_FILE" ] && [ -s "$ADMIN_WEB_AUTH_FILE" ]; then
pw=$(sed -n 's/^password=//p' "$ADMIN_WEB_AUTH_FILE" | head -1)
fi
if [ -z "$pw" ]; then
if command -v openssl >/dev/null 2>&1; then
pw=$(openssl rand -base64 24 | tr -d '\n')
else
pw=$(head -c 24 /dev/urandom | base64 | tr -d '\n')
fi
fi
umask 077
cat > "$ADMIN_WEB_AUTH_FILE" <<EOF
user=${PCATELEGRAM_WEB_ADMIN_USER:-admin}
password=${pw}
url=http://$(get_server_ip 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}'):${ADMIN_WEB_PORT}/
EOF
chmod 600 "$ADMIN_WEB_AUTH_FILE" 2>/dev/null || true
printf '%s' "$pw"
}