From 70b3568ccdd847810e0d7b6865978059b8073437 Mon Sep 17 00:00:00 2001 From: riglen Date: Mon, 20 Apr 2026 14:42:33 +0800 Subject: [PATCH] Revert "db" This reverts commit 0a46ebf0a8a60e4156916b1aaf50f139058f9063. --- .env.example | 4 - .gitignore | 2 - README.md | 20 +- alembic.ini | 36 ---- alembic/env.py | 41 ---- alembic/script.py.mako | 25 --- alembic/versions/.gitkeep | 1 - app/config.py | 2 - app/db.py | 40 ---- app/db_models.py | 149 --------------- app/import_config.py | 24 --- app/services/config_store.py | 361 ----------------------------------- app/services/loader.py | 11 -- docker-compose.yaml | 2 +- requirements.txt | 2 - 15 files changed, 3 insertions(+), 717 deletions(-) delete mode 100644 alembic.ini delete mode 100644 alembic/env.py delete mode 100644 alembic/script.py.mako delete mode 100644 alembic/versions/.gitkeep delete mode 100644 app/db.py delete mode 100644 app/db_models.py delete mode 100644 app/import_config.py delete mode 100644 app/services/config_store.py diff --git a/.env.example b/.env.example index 33782ea..b97d29e 100644 --- a/.env.example +++ b/.env.example @@ -3,10 +3,6 @@ APP_ENV=prod HOST=0.0.0.0 PORT=18080 LOG_LEVEL=info -DATABASE_URL=sqlite:////app/data/app.db - -# Docker 运行时数据目录,建议放在仓库外 -APP_DATA_DIR=../sub-provider-data # 对外访问前缀,尽量改成足够长的随机字符串 PUBLIC_PATH=change-me-random-hash-path diff --git a/.gitignore b/.gitignore index 7aba0ae..927d6c7 100644 --- a/.gitignore +++ b/.gitignore @@ -62,8 +62,6 @@ cover/ local_settings.py db.sqlite3 db.sqlite3-journal -data/ -output/bundle-cache/ # Flask stuff: instance/ diff --git a/README.md b/README.md index 6c45f3f..2100db4 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,6 @@ cp .env.example .env 2. 编辑 `.env`: -- `DATABASE_URL` 默认可用 `sqlite:////app/data/app.db` -- `APP_DATA_DIR` 建议指向仓库外目录,例如 `../sub-provider-data` - `PUBLIC_PATH` 改成足够长的随机字符串 - `PUBLIC_BASE_URL` 建议填写你反代后的最终访问地址,例如 `https://sub.example.com` - `AIRPORT_A_URL` / `AIRPORT_B_URL` / `AIRPORT_C_URL` 都可以直接填订阅地址,项目会自动判断是 YAML 还是 URI 订阅 @@ -89,27 +87,13 @@ cp .env.example .env - `rules.yaml`:规则绑定 - `clients.yaml`:客户端模板 -4. 如需切换到数据库配置源,先执行一次导入: - -```bash -python -m app.import_config --replace -``` - -导入完成后,运行时会优先读取数据库;当数据库内没有配置时,仍会回退到 `config/` 下的 YAML 文件。 - -说明: - -- `app.db`、抓取缓存等运行态数据默认写入 `/app/data` -- `docker-compose` 已将 `/app/data` 挂载到 `APP_DATA_DIR` -- 建议把这个目录放在仓库外,避免 `git pull`、切分支时碰到本地状态文件 - -5. 启动: +4. 启动: ```bash docker compose up -d --build ``` -6. 访问检查: +5. 访问检查: - 健康检查:`http://YOUR_HOST:18080/healthz` - 单 provider: diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index 185dd84..0000000 --- a/alembic.ini +++ /dev/null @@ -1,36 +0,0 @@ -[alembic] -script_location = alembic -prepend_sys_path = . -sqlalchemy.url = sqlite:///./data/app.db - -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = console -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s diff --git a/alembic/env.py b/alembic/env.py deleted file mode 100644 index 992846f..0000000 --- a/alembic/env.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import annotations - -from logging.config import fileConfig - -from alembic import context -from sqlalchemy import engine_from_config, pool - -from app.config import get_settings -from app.db_models import Base - -config = context.config -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -config.set_main_option("sqlalchemy.url", get_settings().database_url) -target_metadata = Base.metadata - - -def run_migrations_offline() -> None: - url = config.get_main_option("sqlalchemy.url") - context.configure(url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True) - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online() -> None: - connectable = engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata, compare_type=True) - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/alembic/script.py.mako b/alembic/script.py.mako deleted file mode 100644 index ca3c201..0000000 --- a/alembic/script.py.mako +++ /dev/null @@ -1,25 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} -""" -from __future__ import annotations - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade() -> None: - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/.gitkeep b/alembic/versions/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/alembic/versions/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/config.py b/app/config.py index b568d57..ecb0087 100644 --- a/app/config.py +++ b/app/config.py @@ -26,8 +26,6 @@ class Settings(BaseSettings): bundle_cache_ttl_seconds: int = 600 max_proxy_name_length: int = 80 default_user_agent: str = "sub-provider/0.2" - database_url: str = Field(default=f"sqlite:///{(DATA_DIR / 'app.db').resolve().as_posix()}") - database_echo: bool = False config_dir: Path = CONFIG_DIR sources_file: Path = CONFIG_DIR / "sources.yaml" diff --git a/app/db.py b/app/db.py deleted file mode 100644 index a20dd75..0000000 --- a/app/db.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -from functools import lru_cache - -from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker - -from app.config import DATA_DIR, get_settings -from app.db_models import Base - - -def _sqlite_connect_args(database_url: str) -> dict[str, bool]: - if database_url.startswith("sqlite"): - return {"check_same_thread": False} - return {} - - -@lru_cache(maxsize=1) -def get_engine(): - settings = get_settings() - DATA_DIR.mkdir(parents=True, exist_ok=True) - return create_engine( - settings.database_url, - echo=settings.database_echo, - future=True, - connect_args=_sqlite_connect_args(settings.database_url), - ) - - -@lru_cache(maxsize=1) -def get_session_factory(): - return sessionmaker(bind=get_engine(), autoflush=False, autocommit=False, future=True) - - -def init_db() -> None: - Base.metadata.create_all(bind=get_engine()) - - -def get_session() -> Session: - return get_session_factory()() diff --git a/app/db_models.py b/app/db_models.py deleted file mode 100644 index 6f4fa19..0000000 --- a/app/db_models.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import annotations - -from datetime import datetime - -from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship - - -class Base(DeclarativeBase): - pass - - -class TimestampMixin: - created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False) - updated_at: Mapped[datetime] = mapped_column( - DateTime, - default=datetime.utcnow, - onupdate=datetime.utcnow, - nullable=False, - ) - - -class AppSettingORM(Base, TimestampMixin): - __tablename__ = "app_settings" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) - value: Mapped[str | None] = mapped_column(Text, nullable=True) - - -class SourceORM(Base, TimestampMixin): - __tablename__ = "sources" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) - enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - kind: Mapped[str] = mapped_column(String(50), nullable=False) - url: Mapped[str] = mapped_column(Text, nullable=False) - display_name: Mapped[str | None] = mapped_column(String(255), nullable=True) - headers_json: Mapped[str] = mapped_column(Text, default="{}", nullable=False) - include_regex: Mapped[str | None] = mapped_column(Text, nullable=True) - exclude_regex: Mapped[str | None] = mapped_column(Text, nullable=True) - prefix: Mapped[str] = mapped_column(String(255), default="", nullable=False) - suffix: Mapped[str] = mapped_column(String(255), default="", nullable=False) - cache_ttl_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True) - - -class ProfileORM(Base, TimestampMixin): - __tablename__ = "profiles" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) - title: Mapped[str] = mapped_column(String(255), nullable=False) - provider_interval: Mapped[int] = mapped_column(Integer, default=21600, nullable=False) - rule_interval: Mapped[int] = mapped_column(Integer, default=86400, nullable=False) - test_url: Mapped[str] = mapped_column(Text, nullable=False) - test_interval: Mapped[int] = mapped_column(Integer, default=300, nullable=False) - main_policy: Mapped[str] = mapped_column(String(255), nullable=False) - source_policy: Mapped[str] = mapped_column(String(255), nullable=False) - mixed_auto_policy: Mapped[str] = mapped_column(String(255), nullable=False) - manual_policy: Mapped[str] = mapped_column(String(255), nullable=False) - direct_policy: Mapped[str] = mapped_column(String(255), nullable=False) - mode: Mapped[str] = mapped_column(String(50), default="rule", nullable=False) - allow_lan: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - ipv6: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - mixed_port: Mapped[int | None] = mapped_column(Integer, nullable=True) - socks_port: Mapped[int | None] = mapped_column(Integer, nullable=True) - log_level: Mapped[str | None] = mapped_column(String(50), nullable=True) - - source_links: Mapped[list["ProfileSourceORM"]] = relationship( - back_populates="profile", - cascade="all, delete-orphan", - ) - rule_links: Mapped[list["ProfileRuleModuleORM"]] = relationship( - back_populates="profile", - cascade="all, delete-orphan", - ) - groups: Mapped[list["PolicyGroupORM"]] = relationship( - back_populates="profile", - cascade="all, delete-orphan", - ) - - -class ProfileSourceORM(Base): - __tablename__ = "profile_sources" - __table_args__ = (UniqueConstraint("profile_id", "source_id", name="uq_profile_source"),) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - profile_id: Mapped[int] = mapped_column(ForeignKey("profiles.id"), nullable=False) - source_id: Mapped[int] = mapped_column(ForeignKey("sources.id"), nullable=False) - order_index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) - enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - - profile: Mapped[ProfileORM] = relationship(back_populates="source_links") - source: Mapped[SourceORM] = relationship() - - -class RuleModuleORM(Base, TimestampMixin): - __tablename__ = "rule_modules" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - key: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) - file_path: Mapped[str | None] = mapped_column(Text, nullable=True) - behavior: Mapped[str] = mapped_column(String(50), nullable=False) - format: Mapped[str] = mapped_column(String(50), nullable=False) - policy: Mapped[str] = mapped_column(String(255), nullable=False) - no_resolve: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) - payload_json: Mapped[str] = mapped_column(Text, default="[]", nullable=False) - - profile_links: Mapped[list["ProfileRuleModuleORM"]] = relationship( - back_populates="rule_module", - cascade="all, delete-orphan", - ) - - -class ProfileRuleModuleORM(Base): - __tablename__ = "profile_rule_modules" - __table_args__ = (UniqueConstraint("profile_id", "rule_module_id", name="uq_profile_rule_module"),) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - profile_id: Mapped[int] = mapped_column(ForeignKey("profiles.id"), nullable=False) - rule_module_id: Mapped[int] = mapped_column(ForeignKey("rule_modules.id"), nullable=False) - enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - order_index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) - policy_override: Mapped[str | None] = mapped_column(String(255), nullable=True) - payload_override_json: Mapped[str | None] = mapped_column(Text, nullable=True) - no_resolve_override: Mapped[bool | None] = mapped_column(Boolean, nullable=True) - - profile: Mapped[ProfileORM] = relationship(back_populates="rule_links") - rule_module: Mapped[RuleModuleORM] = relationship(back_populates="profile_links") - - -class PolicyGroupORM(Base, TimestampMixin): - __tablename__ = "policy_groups" - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - profile_id: Mapped[int] = mapped_column(ForeignKey("profiles.id"), nullable=False) - group_kind: Mapped[str] = mapped_column(String(50), nullable=False) - name: Mapped[str] = mapped_column(String(255), nullable=False) - type: Mapped[str] = mapped_column(String(50), nullable=False) - order_index: Mapped[int] = mapped_column(Integer, default=0, nullable=False) - proxies_json: Mapped[str | None] = mapped_column(Text, nullable=True) - filter_regex: Mapped[str | None] = mapped_column(Text, nullable=True) - tolerance: Mapped[int | None] = mapped_column(Integer, nullable=True) - url: Mapped[str | None] = mapped_column(Text, nullable=True) - interval: Mapped[int | None] = mapped_column(Integer, nullable=True) - enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) - - profile: Mapped[ProfileORM] = relationship(back_populates="groups") diff --git a/app/import_config.py b/app/import_config.py deleted file mode 100644 index a6dfb54..0000000 --- a/app/import_config.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -import argparse - -from app.config import get_settings -from app.services.config_store import import_yaml_config_to_db - - -def main() -> None: - parser = argparse.ArgumentParser(description="Import YAML config into the sub-provider database") - parser.add_argument( - "--replace", - action="store_true", - help="Clear existing config rows before import", - ) - args = parser.parse_args() - - settings = get_settings() - import_yaml_config_to_db(settings.sources_file, replace_existing=args.replace) - print(f"Imported config into {settings.database_url}") - - -if __name__ == "__main__": - main() diff --git a/app/services/config_store.py b/app/services/config_store.py deleted file mode 100644 index ab06d3a..0000000 --- a/app/services/config_store.py +++ /dev/null @@ -1,361 +0,0 @@ -from __future__ import annotations - -import json - -from sqlalchemy import delete, select -from sqlalchemy.orm import Session, selectinload - -from app.db import get_session, init_db -from app.db_models import ( - AppSettingORM, - PolicyGroupORM, - ProfileORM, - ProfileRuleModuleORM, - ProfileSourceORM, - RuleModuleORM, - SourceORM, -) -from app.models import AppConfig, ClientConfig, ProxyGroupConfig, RegionConfig, RuleConfig, SourceConfig -from app.services.loader import load_yaml_app_config - - -def _json_dumps(value) -> str: - return json.dumps(value, ensure_ascii=False) - - -def _json_loads(value: str | None, default): - if not value: - return default - return json.loads(value) - - -def import_yaml_config_to_db(config_path, *, replace_existing: bool = False) -> None: - app_config = load_yaml_app_config(config_path) - init_db() - with get_session() as session: - if replace_existing: - _clear_config_tables(session) - - _upsert_setting(session, "public_path", app_config.public_path or "") - - source_rows: list[SourceORM] = [] - for key, source in app_config.sources.items(): - row = _get_or_create_source(session, key) - row.enabled = source.enabled - row.kind = source.kind - row.url = source.url - row.display_name = source.display_name - row.headers_json = _json_dumps(source.headers) - row.include_regex = source.include_regex - row.exclude_regex = source.exclude_regex - row.prefix = source.prefix - row.suffix = source.suffix - row.cache_ttl_seconds = source.cache_ttl_seconds - source_rows.append(row) - - module_rows: list[RuleModuleORM] = [] - ordered_rule_keys = list(app_config.rules.keys()) - for key, rule in app_config.rules.items(): - row = _get_or_create_rule_module(session, key) - row.file_path = rule.file - row.behavior = rule.behavior - row.format = rule.format - row.policy = rule.policy - row.no_resolve = rule.no_resolve - row.payload_json = _json_dumps(rule.payload) - module_rows.append(row) - - session.flush() - - for client_key, client in app_config.clients.items(): - profile = _get_or_create_profile(session, client_key) - _populate_profile(profile, client) - session.flush() - - _replace_profile_sources(session, profile.id, source_rows) - _replace_profile_groups(session, profile.id, app_config) - _replace_profile_rule_links(session, profile.id, module_rows, ordered_rule_keys) - - session.commit() - - -def load_app_config_from_db() -> AppConfig | None: - init_db() - with get_session() as session: - profiles = list( - session.scalars( - select(ProfileORM) - .options( - selectinload(ProfileORM.groups), - selectinload(ProfileORM.rule_links).selectinload(ProfileRuleModuleORM.rule_module), - ) - .order_by(ProfileORM.key) - ) - ) - sources = list(session.scalars(select(SourceORM).order_by(SourceORM.key))) - if not profiles or not sources: - return None - - public_path = _setting_value(session, "public_path") - primary_profile = profiles[0] - - regions: dict[str, RegionConfig] = {} - selector_groups: list[ProxyGroupConfig] = [] - policy_groups: list[ProxyGroupConfig] = [] - - for group in sorted(primary_profile.groups, key=lambda item: (item.order_index, item.id)): - if not group.enabled: - continue - if group.group_kind == "region": - regions[_slugify(group.name)] = RegionConfig( - name=group.name, - filter=group.filter_regex or "", - tolerance=group.tolerance or 50, - ) - continue - target = selector_groups if group.group_kind == "selector" else policy_groups - target.append( - ProxyGroupConfig( - name=group.name, - type=group.type, - proxies=_json_loads(group.proxies_json, []), - filter=group.filter_regex, - tolerance=group.tolerance, - url=group.url, - interval=group.interval, - ) - ) - - rules: dict[str, RuleConfig] = {} - for link in sorted(primary_profile.rule_links, key=lambda item: (item.order_index, item.id)): - if not link.enabled: - continue - module = link.rule_module - rules[module.key] = RuleConfig( - file=module.file_path, - behavior=module.behavior, - format=module.format, - policy=link.policy_override or module.policy, - no_resolve=module.no_resolve if link.no_resolve_override is None else link.no_resolve_override, - payload=_json_loads(link.payload_override_json, _json_loads(module.payload_json, [])), - ) - - return AppConfig( - public_path=public_path or None, - sources={ - source.key: SourceConfig( - enabled=source.enabled, - kind=source.kind, - url=source.url, - display_name=source.display_name, - headers=_json_loads(source.headers_json, {}), - include_regex=source.include_regex, - exclude_regex=source.exclude_regex, - prefix=source.prefix, - suffix=source.suffix, - cache_ttl_seconds=source.cache_ttl_seconds, - ) - for source in sources - }, - rules=rules, - clients={profile.key: _profile_to_client_config(profile) for profile in profiles}, - regions=regions, - selector_groups=selector_groups, - policy_groups=policy_groups, - ) - - -def _clear_config_tables(session: Session) -> None: - session.execute(delete(ProfileRuleModuleORM)) - session.execute(delete(PolicyGroupORM)) - session.execute(delete(ProfileSourceORM)) - session.execute(delete(ProfileORM)) - session.execute(delete(RuleModuleORM)) - session.execute(delete(SourceORM)) - session.execute(delete(AppSettingORM)) - session.flush() - - -def _upsert_setting(session: Session, key: str, value: str) -> None: - row = session.scalar(select(AppSettingORM).where(AppSettingORM.key == key)) - if row is None: - row = AppSettingORM(key=key, value=value) - session.add(row) - else: - row.value = value - - -def _setting_value(session: Session, key: str) -> str | None: - row = session.scalar(select(AppSettingORM).where(AppSettingORM.key == key)) - if row is None: - return None - return row.value - - -def _get_or_create_source(session: Session, key: str) -> SourceORM: - row = session.scalar(select(SourceORM).where(SourceORM.key == key)) - if row is None: - row = SourceORM(key=key, kind="auto", url="") - session.add(row) - return row - - -def _get_or_create_rule_module(session: Session, key: str) -> RuleModuleORM: - row = session.scalar(select(RuleModuleORM).where(RuleModuleORM.key == key)) - if row is None: - row = RuleModuleORM(key=key, behavior="classical", format="text", policy="DIRECT") - session.add(row) - return row - - -def _get_or_create_profile(session: Session, key: str) -> ProfileORM: - row = session.scalar(select(ProfileORM).where(ProfileORM.key == key)) - if row is None: - row = ProfileORM( - key=key, - title=key, - test_url="https://www.gstatic.com/generate_204", - main_policy="🚀 节点选择", - source_policy="☁️ 机场选择", - mixed_auto_policy="♻️ 自动选择", - manual_policy="🚀 手动切换", - direct_policy="DIRECT", - ) - session.add(row) - return row - - -def _populate_profile(profile: ProfileORM, client: ClientConfig) -> None: - profile.title = client.title - profile.provider_interval = client.provider_interval - profile.rule_interval = client.rule_interval - profile.test_url = str(client.test_url) - profile.test_interval = client.test_interval - profile.main_policy = client.main_policy - profile.source_policy = client.source_policy - profile.mixed_auto_policy = client.mixed_auto_policy - profile.manual_policy = client.manual_policy - profile.direct_policy = client.direct_policy - profile.mode = client.mode - profile.allow_lan = client.allow_lan - profile.ipv6 = client.ipv6 - profile.mixed_port = client.mixed_port - profile.socks_port = client.socks_port - profile.log_level = client.log_level - - -def _replace_profile_sources(session: Session, profile_id: int, source_rows: list[SourceORM]) -> None: - session.execute(delete(ProfileSourceORM).where(ProfileSourceORM.profile_id == profile_id)) - for order_index, source in enumerate(source_rows): - session.add( - ProfileSourceORM( - profile_id=profile_id, - source_id=source.id, - order_index=order_index, - enabled=True, - ) - ) - - -def _replace_profile_groups(session: Session, profile_id: int, app_config: AppConfig) -> None: - session.execute(delete(PolicyGroupORM).where(PolicyGroupORM.profile_id == profile_id)) - - order_index = 0 - for region in app_config.regions.values(): - session.add( - PolicyGroupORM( - profile_id=profile_id, - group_kind="region", - name=region.name, - type="url-test", - order_index=order_index, - filter_regex=region.filter, - tolerance=region.tolerance, - enabled=True, - ) - ) - order_index += 1 - - for group in app_config.selector_groups: - session.add( - PolicyGroupORM( - profile_id=profile_id, - group_kind="selector", - name=group.name, - type=group.type, - order_index=order_index, - proxies_json=_json_dumps(group.proxies), - filter_regex=group.filter, - tolerance=group.tolerance, - url=str(group.url) if group.url else None, - interval=group.interval, - enabled=True, - ) - ) - order_index += 1 - - for group in app_config.policy_groups: - session.add( - PolicyGroupORM( - profile_id=profile_id, - group_kind="policy", - name=group.name, - type=group.type, - order_index=order_index, - proxies_json=_json_dumps(group.proxies), - filter_regex=group.filter, - tolerance=group.tolerance, - url=str(group.url) if group.url else None, - interval=group.interval, - enabled=True, - ) - ) - order_index += 1 - - -def _replace_profile_rule_links( - session: Session, - profile_id: int, - module_rows: list[RuleModuleORM], - ordered_rule_keys: list[str], -) -> None: - session.execute(delete(ProfileRuleModuleORM).where(ProfileRuleModuleORM.profile_id == profile_id)) - module_by_key = {module.key: module for module in module_rows} - for order_index, key in enumerate(ordered_rule_keys): - module = module_by_key[key] - session.add( - ProfileRuleModuleORM( - profile_id=profile_id, - rule_module_id=module.id, - enabled=True, - order_index=order_index, - ) - ) - - -def _profile_to_client_config(profile: ProfileORM) -> ClientConfig: - return ClientConfig( - title=profile.title, - provider_interval=profile.provider_interval, - rule_interval=profile.rule_interval, - test_url=profile.test_url, - test_interval=profile.test_interval, - main_policy=profile.main_policy, - source_policy=profile.source_policy, - mixed_auto_policy=profile.mixed_auto_policy, - manual_policy=profile.manual_policy, - direct_policy=profile.direct_policy, - mode=profile.mode, - allow_lan=profile.allow_lan, - ipv6=profile.ipv6, - mixed_port=profile.mixed_port, - socks_port=profile.socks_port, - log_level=profile.log_level, - ) - - -def _slugify(name: str) -> str: - text = "".join(char.lower() if char.isalnum() else "-" for char in name).strip("-") - while "--" in text: - text = text.replace("--", "-") - return text or "group" diff --git a/app/services/loader.py b/app/services/loader.py index 9340bb2..edac2f5 100644 --- a/app/services/loader.py +++ b/app/services/loader.py @@ -7,7 +7,6 @@ from typing import Any import yaml -from app.db import init_db from app.models import AppConfig _ENV_PATTERN = re.compile(r"\$\{([A-Z0-9_]+)\}") @@ -62,15 +61,5 @@ def _load_split_config(path: Path) -> dict[str, Any]: def load_app_config(path: Path) -> AppConfig: - init_db() - from app.services.config_store import load_app_config_from_db - - db_config = load_app_config_from_db() - if db_config is not None: - return db_config - return load_yaml_app_config(path) - - -def load_yaml_app_config(path: Path) -> AppConfig: expanded = _expand_env(_load_split_config(path)) return AppConfig.model_validate(expanded) diff --git a/docker-compose.yaml b/docker-compose.yaml index 0ed5075..7719bce 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,5 +10,5 @@ services: - .env volumes: - ./config:/app/config:ro - - ${APP_DATA_DIR:-../sub-provider-data}:/app/data + - ./data:/app/data - ./output:/app/output diff --git a/requirements.txt b/requirements.txt index 214c77b..44efd9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,3 @@ uvicorn[standard]>=0.30,<1.0 httpx>=0.27,<1.0 PyYAML>=6.0,<7.0 pydantic-settings>=2.3,<3.0 -SQLAlchemy>=2.0,<3.0 -alembic>=1.13,<2.0