Compare commits
6 Commits
2826d3e7f3
...
3492c441ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3492c441ee | ||
|
|
ca6142c39a | ||
|
|
38bc8ffc05 | ||
|
|
d08a9a2ac1 | ||
|
|
47e0977b72 | ||
|
|
38568a474b |
@@ -49,7 +49,7 @@ All native installs require **root**. Use `sudo -E ...` when you set environment
|
|||||||
|
|
||||||
| Variable | Meaning | Default |
|
| Variable | Meaning | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `REPO_URL` | Git URL to clone | `https://github.com/YakPanel/YakPanel.git` (optional: `https://source.yakpanel.com/yakpanel.git` when your mirror is live) |
|
| `REPO_URL` | Git URL to clone | `https://source.yakpanel.com/admin/yakpanel-core.git` (override for other mirrors) |
|
||||||
| `YAKPANEL_BRANCH` | Branch/tag for shallow clone | default branch |
|
| `YAKPANEL_BRANCH` | Branch/tag for shallow clone | default branch |
|
||||||
| `GIT_REF` | Alias for `YAKPANEL_BRANCH` | — |
|
| `GIT_REF` | Alias for `YAKPANEL_BRANCH` | — |
|
||||||
| `INSTALL_PATH` | Install directory | `/www/server/YakPanel-server` |
|
| `INSTALL_PATH` | Install directory | `/www/server/YakPanel-server` |
|
||||||
@@ -103,7 +103,7 @@ sudo bash scripts/install.sh
|
|||||||
Uses `docker-compose.yml` in this directory — **not** the same layout as native (no host Nginx unit from `install.sh`).
|
Uses `docker-compose.yml` in this directory — **not** the same layout as native (no host Nginx unit from `install.sh`).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --depth 1 https://github.com/YakPanel/YakPanel.git
|
git clone --depth 1 https://source.yakpanel.com/admin/yakpanel-core.git
|
||||||
# Then cd to this folder (in the full monorepo it is under YakPanel-master/YakPanel-server).
|
# Then cd to this folder (in the full monorepo it is under YakPanel-master/YakPanel-server).
|
||||||
cd YakPanel-master/YakPanel-server
|
cd YakPanel-master/YakPanel-server
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# YakPanel - Environment
|
# YakPanel - Environment (this file lives next to app/ — loaded from backend/ always, not shell CWD)
|
||||||
SECRET_KEY=change-this-in-production
|
SECRET_KEY=change-this-in-production
|
||||||
|
# Resolved to backend/data/default.db (see app/core/database.py)
|
||||||
DATABASE_URL=sqlite+aiosqlite:///./data/default.db
|
DATABASE_URL=sqlite+aiosqlite:///./data/default.db
|
||||||
REDIS_URL=redis://localhost:6379/0
|
REDIS_URL=redis://localhost:6379/0
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
"""YakPanel - Configuration"""
|
"""YakPanel - Configuration"""
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
|
||||||
from pydantic_settings import BaseSettings
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
# Runtime config loaded from DB on startup (overrides Settings)
|
# Runtime config loaded from DB on startup (overrides Settings)
|
||||||
_runtime_config: dict[str, Any] = {}
|
_runtime_config: dict[str, Any] = {}
|
||||||
|
|
||||||
|
_BACKEND_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
"""Application settings"""
|
"""Application settings"""
|
||||||
|
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=str(_BACKEND_ROOT / ".env"),
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
extra="ignore",
|
||||||
|
)
|
||||||
|
|
||||||
app_name: str = "YakPanel"
|
app_name: str = "YakPanel"
|
||||||
app_version: str = "1.0.0"
|
app_version: str = "1.0.0"
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
@@ -21,7 +32,7 @@ class Settings(BaseSettings):
|
|||||||
www_logs: str = "/www/wwwlogs"
|
www_logs: str = "/www/wwwlogs"
|
||||||
vhost_path: str = "/www/server/panel/vhost"
|
vhost_path: str = "/www/server/panel/vhost"
|
||||||
|
|
||||||
# Database (use absolute path for SQLite)
|
# Database (relative SQLite paths are resolved to backend/data/, not process CWD)
|
||||||
database_url: str = "sqlite+aiosqlite:///./data/default.db"
|
database_url: str = "sqlite+aiosqlite:///./data/default.db"
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
@@ -47,10 +58,6 @@ class Settings(BaseSettings):
|
|||||||
# Comma-separated CIDRs; empty = no restriction (e.g. "10.0.0.0/8,192.168.0.0/16")
|
# Comma-separated CIDRs; empty = no restriction (e.g. "10.0.0.0/8,192.168.0.0/16")
|
||||||
remote_install_allowed_target_cidrs: str = ""
|
remote_install_allowed_target_cidrs: str = ""
|
||||||
|
|
||||||
class Config:
|
|
||||||
env_file = ".env"
|
|
||||||
env_file_encoding = "utf-8"
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def get_settings() -> Settings:
|
def get_settings() -> Settings:
|
||||||
|
|||||||
@@ -1,21 +1,63 @@
|
|||||||
"""YakPanel - Database configuration"""
|
"""YakPanel - Database configuration"""
|
||||||
import os
|
import os
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
import sqlite3
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from sqlalchemy.engine.url import make_url
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
from sqlalchemy.orm import DeclarativeBase
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
|
|
||||||
|
|
||||||
settings = get_settings()
|
def _resolve_database_url(raw_url: str) -> str:
|
||||||
|
"""Anchor relative SQLite paths to the backend directory (not process CWD)."""
|
||||||
|
url = make_url(raw_url)
|
||||||
|
if not url.drivername.startswith("sqlite"):
|
||||||
|
return raw_url
|
||||||
|
db_path = url.database
|
||||||
|
if db_path is None or db_path.startswith(":"):
|
||||||
|
return raw_url
|
||||||
|
path = Path(db_path)
|
||||||
|
backend_dir = Path(__file__).resolve().parent.parent
|
||||||
|
if not path.is_absolute():
|
||||||
|
path = (backend_dir / path).resolve()
|
||||||
|
else:
|
||||||
|
path = path.resolve()
|
||||||
|
data_dir = path.parent
|
||||||
|
data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
os.chmod(data_dir, 0o755)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
if not os.access(data_dir, os.W_OK):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"SQLite directory not writable: {data_dir} "
|
||||||
|
f"(fix ownership or permissions; check SELinux if enabled)."
|
||||||
|
)
|
||||||
|
abs_path = path.as_posix()
|
||||||
|
try:
|
||||||
|
test = sqlite3.connect(abs_path)
|
||||||
|
test.close()
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"SQLite refused to open {abs_path} (from DATABASE_URL). {e}. "
|
||||||
|
f"Parent dir: {data_dir} writable={os.access(data_dir, os.W_OK)}."
|
||||||
|
) from e
|
||||||
|
# Unix absolute file: sqlite+aiosqlite:////abs/path (four slashes total after the colon)
|
||||||
|
return f"sqlite+aiosqlite:///{abs_path}"
|
||||||
|
|
||||||
# Ensure data directory exists for SQLite
|
|
||||||
if "sqlite" in settings.database_url:
|
settings = get_settings()
|
||||||
backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
DATABASE_URL = _resolve_database_url(settings.database_url)
|
||||||
data_dir = os.path.join(backend_dir, "data")
|
|
||||||
os.makedirs(data_dir, exist_ok=True)
|
_engine_kw = {"echo": settings.debug}
|
||||||
|
if make_url(DATABASE_URL).drivername.startswith("sqlite"):
|
||||||
|
_engine_kw["connect_args"] = {"timeout": 30}
|
||||||
|
|
||||||
engine = create_async_engine(
|
engine = create_async_engine(
|
||||||
settings.database_url,
|
DATABASE_URL,
|
||||||
echo=settings.debug,
|
**_engine_kw,
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncSessionLocal = async_sessionmaker(
|
AsyncSessionLocal = async_sessionmaker(
|
||||||
@@ -29,6 +71,7 @@ AsyncSessionLocal = async_sessionmaker(
|
|||||||
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
"""SQLAlchemy declarative base"""
|
"""SQLAlchemy declarative base"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -48,13 +91,22 @@ async def get_db():
|
|||||||
def _run_migrations(conn):
|
def _run_migrations(conn):
|
||||||
"""Add new columns to existing tables (SQLite)."""
|
"""Add new columns to existing tables (SQLite)."""
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = conn.execute(sqlalchemy.text("PRAGMA table_info(sites)"))
|
r = conn.execute(sqlalchemy.text("PRAGMA table_info(sites)"))
|
||||||
cols = [row[1] for row in r.fetchall()]
|
cols = [row[1] for row in r.fetchall()]
|
||||||
if "php_version" not in cols:
|
if "php_version" not in cols:
|
||||||
conn.execute(sqlalchemy.text("ALTER TABLE sites ADD COLUMN php_version VARCHAR(16) DEFAULT '74'"))
|
conn.execute(
|
||||||
|
sqlalchemy.text(
|
||||||
|
"ALTER TABLE sites ADD COLUMN php_version VARCHAR(16) DEFAULT '74'"
|
||||||
|
)
|
||||||
|
)
|
||||||
if "force_https" not in cols:
|
if "force_https" not in cols:
|
||||||
conn.execute(sqlalchemy.text("ALTER TABLE sites ADD COLUMN force_https INTEGER DEFAULT 0"))
|
conn.execute(
|
||||||
|
sqlalchemy.text(
|
||||||
|
"ALTER TABLE sites ADD COLUMN force_https INTEGER DEFAULT 0"
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# Create backup_plans if not exists (create_all handles new installs)
|
# Create backup_plans if not exists (create_all handles new installs)
|
||||||
@@ -91,6 +143,7 @@ def _run_migrations(conn):
|
|||||||
async def init_db():
|
async def init_db():
|
||||||
"""Initialize database tables"""
|
"""Initialize database tables"""
|
||||||
import app.models # noqa: F401 - register all models with Base.metadata
|
import app.models # noqa: F401 - register all models with Base.metadata
|
||||||
|
|
||||||
async with engine.begin() as conn:
|
async with engine.begin() as conn:
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
if "sqlite" in str(engine.url):
|
if "sqlite" in str(engine.url):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# sudo -E bash install.sh
|
# sudo -E bash install.sh
|
||||||
#
|
#
|
||||||
# Environment (optional):
|
# Environment (optional):
|
||||||
# REPO_URL Git URL (default: public GitHub; use source.yakpanel.com when mirror is live)
|
# REPO_URL Git URL (default: https://source.yakpanel.com/admin/yakpanel-core.git)
|
||||||
# YAKPANEL_BRANCH Branch/tag for shallow clone (default: default branch)
|
# YAKPANEL_BRANCH Branch/tag for shallow clone (default: default branch)
|
||||||
# GIT_REF Alias for YAKPANEL_BRANCH
|
# GIT_REF Alias for YAKPANEL_BRANCH
|
||||||
# INSTALL_PATH Install dir (default: /www/server/YakPanel-server)
|
# INSTALL_PATH Install dir (default: /www/server/YakPanel-server)
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REPO_URL="${REPO_URL:-https://github.com/YakPanel/YakPanel.git}"
|
REPO_URL="${REPO_URL:-https://source.yakpanel.com/admin/yakpanel-core.git}"
|
||||||
INSTALL_PATH="${INSTALL_PATH:-/www/server/YakPanel-server}"
|
INSTALL_PATH="${INSTALL_PATH:-/www/server/YakPanel-server}"
|
||||||
PANEL_PORT="${PANEL_PORT:-8888}"
|
PANEL_PORT="${PANEL_PORT:-8888}"
|
||||||
BACKEND_PORT="${BACKEND_PORT:-8889}"
|
BACKEND_PORT="${BACKEND_PORT:-8889}"
|
||||||
@@ -202,15 +202,19 @@ if [ -n "${YAKPANEL_SOURCE_DIR:-}" ]; then
|
|||||||
else
|
else
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
CLONE_ARGS=(git clone --depth 1)
|
export GIT_TERMINAL_PROMPT=0
|
||||||
|
# Disable credential helpers for this clone so anonymous HTTPS public repos work
|
||||||
|
# from curl|bash (no TTY); USER:TOKEN in REPO_URL is still used if present.
|
||||||
|
CLONE_ARGS=(git -c credential.helper= clone --depth 1)
|
||||||
if [ -n "$YAKPANEL_BRANCH" ]; then
|
if [ -n "$YAKPANEL_BRANCH" ]; then
|
||||||
CLONE_ARGS+=(--branch "$YAKPANEL_BRANCH")
|
CLONE_ARGS+=(--branch "$YAKPANEL_BRANCH")
|
||||||
fi
|
fi
|
||||||
CLONE_ARGS+=("$REPO_URL" "$TMP_DIR/repo")
|
CLONE_ARGS+=("$REPO_URL" "$TMP_DIR/repo")
|
||||||
if ! "${CLONE_ARGS[@]}"; then
|
if ! "${CLONE_ARGS[@]}"; then
|
||||||
echo "Git clone failed. Check REPO_URL=$REPO_URL, DNS, and outbound HTTPS."
|
echo "Git clone failed. Check REPO_URL=$REPO_URL, DNS, and outbound HTTPS."
|
||||||
echo "Try: sudo -E env REPO_URL=https://github.com/YakPanel/YakPanel.git bash install.sh"
|
echo "If the server requires sign-in: allow anonymous clone for HTTP(S), or use a token in the URL, e.g."
|
||||||
echo "Own mirror: REPO_URL=https://source.yakpanel.com/yakpanel.git (requires bare repo published; see HOSTING.txt)."
|
echo " REPO_URL=https://USER:TOKEN@source.yakpanel.com/admin/yakpanel-core.git"
|
||||||
|
echo "Or override: REPO_URL=https://your.other.git/mirror.git"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
SRC_DIR="$(find_yakpanel_root_in_tree "$TMP_DIR/repo")"
|
SRC_DIR="$(find_yakpanel_root_in_tree "$TMP_DIR/repo")"
|
||||||
|
|||||||
Reference in New Issue
Block a user