mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-11-04 06:38:05 +08:00
fix: RSS matching, proxy pool, parser, healthcheck and log issues (#1103 #1104 #1105 #1106 #1108 #1114 #1117)
- match_bangumi_in_list: also accept a torrent whose rebuilt title equals the stored title_raw (#1103, #1114) - download_bangumi / collect_season: drop items whose season or type conflicts with the bangumi (#1105) - request_url: swap in a fresh shared client on PoolTimeout without closing the old one (#1104) - classic parser: Unicode Roman numeral season, strip fullwidth dash wrapper (#1108) - Docker HEALTHCHECK: healthcheck.py reads the configured WebUI port and probes IPv4 then IPv6 loopback (#1106) - scheduler: non-positive interval falls back to 1s; deploy docs cap container logs at 10 MB x 3 (#1117) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KU9GqKzULrNb7MN5T9TYix
This commit is contained in:
@@ -63,7 +63,9 @@ VOLUME ["/app/config", "/app/data"]
|
||||
|
||||
# Liveness probe against the unauthenticated /health route; give the app
|
||||
# room to run migrations on first boot before failures count.
|
||||
# healthcheck.py reads the configured WebUI port and probes IPv4 then IPv6
|
||||
# loopback (#1106).
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
|
||||
CMD wget -qO- http://localhost:7892/health || exit 1
|
||||
CMD python /app/healthcheck.py
|
||||
|
||||
ENTRYPOINT ["tini", "-g", "--", "/entrypoint.sh"]
|
||||
|
||||
40
backend/src/healthcheck.py
Normal file
40
backend/src/healthcheck.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""Docker HEALTHCHECK:探测 /health,退出码 0 表示健康。
|
||||
|
||||
端口取自 config.json(WebUI 可改,AB_WEBUI_PORT 只在首次启动时写入),
|
||||
不能写死 7892。先连 IPv4 回环再连 IPv6:busybox wget 把 localhost 解析成
|
||||
::1,而默认只监听 0.0.0.0(#1106);IPV6/HOST=::1 时只有 ::1 可达。
|
||||
只用标准库,避免每 30 秒导入整个应用。
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_PATH = Path("config/config.json")
|
||||
DEFAULT_PORT = 7892
|
||||
|
||||
|
||||
def webui_port(config_path: Path = CONFIG_PATH) -> int:
|
||||
try:
|
||||
config = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
return int(config["program"]["webui_port"])
|
||||
except (OSError, ValueError, KeyError, TypeError):
|
||||
return DEFAULT_PORT
|
||||
|
||||
|
||||
def main() -> int:
|
||||
port = webui_port()
|
||||
# 忽略 HTTP_PROXY 等环境变量:回环探测不能走代理
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
for host in ("127.0.0.1", "[::1]"):
|
||||
try:
|
||||
with opener.open(f"http://{host}:{port}/health", timeout=2):
|
||||
return 0
|
||||
except OSError:
|
||||
continue
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -70,7 +70,9 @@ class PeriodicTask:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("%s tick failed: %s", self._name, e)
|
||||
if await self._wait(self._interval()):
|
||||
# 间隔 <= 0(配置为 0)会让循环空转、日志暴涨(#1117),退回 1 秒
|
||||
interval = self._interval()
|
||||
if await self._wait(interval if interval > 0 else 1):
|
||||
return
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
@@ -175,13 +175,18 @@ def match_bangumi_in_list(
|
||||
or persistence_target(release) is not PersistenceTarget.BANGUMI
|
||||
):
|
||||
return None
|
||||
# title_raw 由解析器重组(剔除中间的集数、拼接 " / " 分隔的别名),未必是
|
||||
# 种子名的子串(#1103、#1114);与本种子重组出的标题完全相等也算命中。
|
||||
release_title = release and (
|
||||
release.title_en or release.title_zh or release.title_jp
|
||||
)
|
||||
best_match: Optional[Bangumi] = None
|
||||
best_rank = (-1, -1, -1)
|
||||
for bangumi in bangumi_list:
|
||||
if bangumi.deleted or not _release_matches_bangumi(release, bangumi):
|
||||
continue
|
||||
for pattern in _all_title_patterns(bangumi):
|
||||
if pattern not in torrent_name:
|
||||
if pattern not in torrent_name and pattern != release_title:
|
||||
continue
|
||||
group_match = int(
|
||||
release is not None
|
||||
@@ -217,6 +222,16 @@ def _release_matches_bangumi(release: "ParsedRelease | None", bangumi: Bangumi)
|
||||
return release.season is None or bangumi.season == release.season
|
||||
|
||||
|
||||
def release_fits_bangumi(torrent_name: str, bangumi: Bangumi) -> bool:
|
||||
"""种子的季度/类型与番剧不冲突。整订阅下载(subscribe/collect)不做标题
|
||||
匹配,搜索类 RSS 会混入其他季,需用此过滤(#1105)。"""
|
||||
from module.parser.analyser.selector import parse_configured_release_title
|
||||
|
||||
return _release_matches_bangumi(
|
||||
parse_configured_release_title(torrent_name), bangumi
|
||||
)
|
||||
|
||||
|
||||
class BangumiDatabase:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
|
||||
from module.database import Database
|
||||
from module.database.bangumi import release_fits_bangumi
|
||||
from module.downloader import AddResult, DownloadClient
|
||||
from module.models import Bangumi, ResponseModel
|
||||
from module.network import RequestContent
|
||||
@@ -53,6 +54,7 @@ class SeasonCollector:
|
||||
torrents = await req.get_torrents(
|
||||
link, bangumi.filter.replace(",", "|")
|
||||
)
|
||||
torrents = [t for t in torrents if release_fits_bangumi(t.name, bangumi)]
|
||||
async with Database() as db:
|
||||
# bangumi 必须先落库拿到 id:add_torrent 用它打 ab:<id> 标签,
|
||||
# 种子行也要用它关联 bangumi_id——否则种子会被记成孤儿,
|
||||
|
||||
@@ -88,6 +88,17 @@ async def get_shared_client() -> httpx.AsyncClient:
|
||||
return _shared_client
|
||||
|
||||
|
||||
async def _replace_stale_client(stale: httpx.AsyncClient) -> httpx.AsyncClient:
|
||||
"""#1104: httpcore 在 HTTP 代理 CONNECT 成功、TLS 握手失败时泄漏隧道连接,
|
||||
连接池被占满后永久 PoolTimeout。换一个新 client 给后续请求;旧的不 aclose
|
||||
(其他协程可能仍在使用,见 dc786a29),交给 GC 回收。"""
|
||||
global _shared_client
|
||||
# ponytail: 旧连接要等 GC 才释放;httpcore 修复 encode/httpcore#1087 发布后升级依赖并删除此函数
|
||||
if _shared_client is stale:
|
||||
_shared_client = None
|
||||
return await get_shared_client()
|
||||
|
||||
|
||||
async def reset_shared_client():
|
||||
"""关闭并清除共享客户端,下次请求时自动创建新连接池。"""
|
||||
global _shared_client, _shared_client_proxy_key
|
||||
@@ -156,6 +167,8 @@ class RequestURL:
|
||||
logger.warning(
|
||||
f"Request error for {url}: {type(e).__name__}. Retry {try_time + 1}/{retry}"
|
||||
)
|
||||
if isinstance(e, httpx.PoolTimeout):
|
||||
self._client = await _replace_stale_client(self._client)
|
||||
try_time += 1
|
||||
if try_time >= retry:
|
||||
break
|
||||
@@ -191,8 +204,10 @@ class RequestURL:
|
||||
e.response.text[:200],
|
||||
)
|
||||
break
|
||||
except httpx.RequestError:
|
||||
except httpx.RequestError as e:
|
||||
logger.warning(f"Cannot connect to {url}. Wait for 5 seconds.")
|
||||
if isinstance(e, httpx.PoolTimeout):
|
||||
self._client = await _replace_stale_client(self._client)
|
||||
try_time += 1
|
||||
if try_time >= retry:
|
||||
break
|
||||
|
||||
@@ -43,6 +43,8 @@ _SEASON_EPISODE_WORDS = re.compile(
|
||||
_SEASON = re.compile(r"(?<!\w)(S\d{1,2}|Season\s+\d{1,2})(?!\w)", re.I)
|
||||
_ORDINAL_SEASON = re.compile(r"\b(\d{1,2})(?:st|nd|rd|th)\s+Season\b", re.I)
|
||||
_CHINESE_SEASON = re.compile(r"第([零〇一二两三四五六七八九十百\d]+)[季期]")
|
||||
# Unicode 罗马数字 Ⅰ-Ⅻ(U+2160-216B)作季度标记,如 "Clevatess Ⅱ"(#1108)
|
||||
_ROMAN_SEASON = re.compile(r"(?<![A-Za-z])[Ⅰ-Ⅻ](?![A-Za-z])")
|
||||
_EXPLICIT_EPISODE = re.compile(
|
||||
r"(?<!\w)(?:Episode|EP?\.?|#)\s*[-_. ]?\s*" r"(\d{1,4}(?:\.\d+)?)(?:v(\d+))?(?!\w)",
|
||||
re.I,
|
||||
@@ -536,6 +538,12 @@ def _extract_numbers(working: list[_WorkingSegment], state: _State) -> None:
|
||||
state.season_raw = match.group(0)
|
||||
state.evidence.append("season")
|
||||
|
||||
for match in _consume_all(work, _ROMAN_SEASON):
|
||||
work.consume(match)
|
||||
state.season = ord(match.group()) - 0x215F
|
||||
state.season_raw = match.group()
|
||||
state.evidence.append("season")
|
||||
|
||||
for match in _consume_all(work, _ORDINAL_SEASON):
|
||||
if state.season is None:
|
||||
work.consume(match)
|
||||
@@ -876,6 +884,8 @@ def _clean_fragment(text: str) -> str:
|
||||
|
||||
def _clean_title(text: str) -> str:
|
||||
text = _clean_fragment(text)
|
||||
# 全角破折号包裹整段标题:"-魔獸之王…-" → "魔獸之王…"(#1108)
|
||||
text = re.sub(r"^-(.+)-$", r"\1", text).strip()
|
||||
return re.sub(r"_+", " ", text).strip()
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ from urllib.parse import urlparse
|
||||
|
||||
from module.conf import settings
|
||||
from module.database import Database
|
||||
from module.database.bangumi import _groups_are_similar, match_bangumi_in_list
|
||||
from module.database.bangumi import (
|
||||
_groups_are_similar,
|
||||
match_bangumi_in_list,
|
||||
release_fits_bangumi,
|
||||
)
|
||||
from module.downloader import AddResult, DownloadClient
|
||||
from module.models import Bangumi, Movie, ResponseModel, RSSItem, Torrent
|
||||
from module.network import RequestContent
|
||||
@@ -462,6 +466,7 @@ class RSSEngine:
|
||||
torrents = await req.get_torrents(
|
||||
bangumi.rss_link, bangumi.filter.replace(",", "|")
|
||||
)
|
||||
torrents = [t for t in torrents if release_fits_bangumi(t.name, bangumi)]
|
||||
if torrents:
|
||||
async with DownloadClient() as client:
|
||||
result = await client.add_torrent(torrents, bangumi)
|
||||
|
||||
@@ -579,6 +579,37 @@ def test_numeric_mixed_collection_does_not_fall_through_to_title_matching(
|
||||
assert match_bangumi_in_list(raw, [bangumi]) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("title_raw", "raw"),
|
||||
(
|
||||
# #1103: 集数从标题中间被剔除,title_raw 不再是种子名的子串
|
||||
(
|
||||
"攻壳机动队「攻壳机动队 THE GHOST IN THE SHELL」 日英双语-多国字幕",
|
||||
"攻壳机动队「攻壳机动队 THE GHOST IN THE SHELL」 S01E03 1080p 日英双语-多国字幕",
|
||||
),
|
||||
# #1114: " / " 分隔的多个英文名被拼接成一个 title_raw
|
||||
(
|
||||
"20 Seiki Denki Mokuroku Nijusseiki Denki Mokuroku",
|
||||
"[喵萌奶茶屋&LoliHouse] 二十世纪电气目录 / 20 Seiki Denki Mokuroku / "
|
||||
"Nijusseiki Denki Mokuroku - 07 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_match_bangumi_in_list_reconstructed_title_raw_matches(
|
||||
title_raw: str, raw: str, monkeypatch
|
||||
) -> None:
|
||||
monkeypatch.setattr(settings.rss_parser, "engine", "classic")
|
||||
bangumi = Bangumi(
|
||||
official_title=title_raw,
|
||||
title_raw=title_raw,
|
||||
rss_link="rss",
|
||||
season=1,
|
||||
episode_type="episode",
|
||||
)
|
||||
|
||||
assert match_bangumi_in_list(raw, [bangumi]) is bangumi
|
||||
|
||||
|
||||
def test_titleless_mixed_collection_does_not_bypass_typed_matching(monkeypatch) -> None:
|
||||
monkeypatch.setattr(settings.rss_parser, "engine", "tokenizer")
|
||||
bangumi = Bangumi(
|
||||
|
||||
17
backend/src/test/test_healthcheck.py
Normal file
17
backend/src/test/test_healthcheck.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Docker HEALTHCHECK 端口解析(#1106)。"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import healthcheck
|
||||
|
||||
|
||||
def test_webui_port_reads_configured_port(tmp_path: Path) -> None:
|
||||
config = tmp_path / "config.json"
|
||||
config.write_text(json.dumps({"program": {"webui_port": 8080}}))
|
||||
|
||||
assert healthcheck.webui_port(config) == 8080
|
||||
|
||||
|
||||
def test_webui_port_defaults_when_config_missing(tmp_path: Path) -> None:
|
||||
assert healthcheck.webui_port(tmp_path / "config.json") == 7892
|
||||
@@ -514,3 +514,26 @@ class TestIssue1025NoGroupPrefix:
|
||||
# Before the fix all three title fields were None and title_parser
|
||||
# raised "Cannot extract title_raw". At least one must now be set.
|
||||
assert any([info.title_en, info.title_zh, info.title_jp])
|
||||
|
||||
|
||||
class TestIssue1108RomanNumeralSeason:
|
||||
"""Issue #1108: Unicode 罗马数字(Ⅱ)标记季度,全角破折号包裹中文标题。"""
|
||||
|
||||
CONTENT = (
|
||||
"[ANi] Clevatess Ⅱ-魔獸之王與虛假的勇者傳承- - 05 "
|
||||
"[1080P][Baha][WEB-DL][AAC AVC][CHT].mp4"
|
||||
)
|
||||
|
||||
def _parse(self):
|
||||
info = raw_parser(self.CONTENT)
|
||||
assert info is not None
|
||||
return info
|
||||
|
||||
def test_roman_numeral_is_season(self):
|
||||
assert self._parse().season == 2
|
||||
|
||||
def test_zh_title_strips_numeral_and_fullwidth_dashes(self):
|
||||
assert self._parse().title_zh == "魔獸之王與虛假的勇者傳承"
|
||||
|
||||
def test_en_title_kept(self):
|
||||
assert self._parse().title_en == "Clevatess"
|
||||
|
||||
@@ -117,6 +117,42 @@ class TestRetryWithReset:
|
||||
assert result.status_code == 200
|
||||
|
||||
|
||||
class TestPoolTimeoutRecovery:
|
||||
"""#1104: httpcore 在 HTTP 代理 CONNECT 成功、TLS 握手失败时泄漏隧道连接,
|
||||
共享连接池被占满后永久 PoolTimeout。遇到 PoolTimeout 时应换新 client。"""
|
||||
|
||||
async def _get_with_poisoned_pool(self):
|
||||
import httpx
|
||||
|
||||
import module.network.request_url as mod
|
||||
|
||||
old, new = AsyncMock(), AsyncMock()
|
||||
old.get.side_effect = httpx.PoolTimeout("pool full")
|
||||
resp = MagicMock(status_code=200, raise_for_status=MagicMock())
|
||||
new.get.return_value = resp
|
||||
mod._shared_client = old
|
||||
mod._shared_client_proxy_key = mod._proxy_config_key()
|
||||
try:
|
||||
with (
|
||||
patch("module.network.request_url.asyncio.sleep", new=AsyncMock()),
|
||||
patch("module.network.request_url.httpx.AsyncClient", return_value=new),
|
||||
):
|
||||
async with RequestURL() as req:
|
||||
result = await req.get_url("https://example.com/x", retry=2)
|
||||
finally:
|
||||
mod._shared_client = None
|
||||
mod._shared_client_proxy_key = None
|
||||
return old, resp, result
|
||||
|
||||
async def test_get_url_pool_timeout_retries_on_fresh_client(self):
|
||||
_, resp, result = await self._get_with_poisoned_pool()
|
||||
assert result is resp
|
||||
|
||||
async def test_get_url_pool_timeout_does_not_close_old_client(self):
|
||||
old, _, _ = await self._get_with_poisoned_pool()
|
||||
old.aclose.assert_not_called()
|
||||
|
||||
|
||||
class TestReentrantContextManager:
|
||||
"""Regression for the notification-dispatch concurrency restore.
|
||||
|
||||
|
||||
@@ -683,6 +683,36 @@ class TestDownloadBangumi:
|
||||
assert resp.status_code == 502
|
||||
assert await rss_engine.db.torrent.search_all() == []
|
||||
|
||||
async def test_skips_torrents_of_another_season(self, rss_engine):
|
||||
"""#1105: 搜索 RSS 同时返回第一季和第二季,第二季不能按第一季下载。"""
|
||||
bangumi = make_bangumi(
|
||||
official_title="无职转生", rss_link="https://example.com/rss", filter=""
|
||||
)
|
||||
s1 = Torrent(
|
||||
name="[沸班亚马制作组] 无职转生~到了异世界就拿出真本事~ - 01 [2160p]",
|
||||
url="https://example.com/s1e01.torrent",
|
||||
)
|
||||
s2 = Torrent(
|
||||
name="[沸班亚马制作组] 无职转生 第二季 ~到了异世界就拿出真本事~ - 01 [2160p]",
|
||||
url="https://example.com/s2e01.torrent",
|
||||
)
|
||||
with (
|
||||
patch("module.rss.engine.RequestContent") as MockReq,
|
||||
patch("module.rss.engine.DownloadClient") as MockClient,
|
||||
):
|
||||
req = AsyncMock()
|
||||
req.get_torrents = AsyncMock(return_value=[s1, s2])
|
||||
MockReq.return_value.__aenter__ = AsyncMock(return_value=req)
|
||||
MockReq.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
client = AsyncMock()
|
||||
client.add_torrent = AsyncMock(return_value=AddResult.ADDED)
|
||||
MockClient.return_value.__aenter__ = AsyncMock(return_value=client)
|
||||
MockClient.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
|
||||
await rss_engine.download_bangumi(bangumi)
|
||||
|
||||
client.add_torrent.assert_awaited_once_with([s1], bangumi)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# add_rss
|
||||
|
||||
@@ -27,6 +27,20 @@ class TestPeriodicTask:
|
||||
assert task.running is False
|
||||
assert counter["n"] >= 2
|
||||
|
||||
async def test_nonpositive_interval_does_not_spin(self):
|
||||
"""#1117: rss_time/rename_time 配成 0 时循环空转、日志暴涨;应退回 1 秒间隔。"""
|
||||
counter = {"n": 0}
|
||||
|
||||
async def body():
|
||||
counter["n"] += 1
|
||||
|
||||
task = PeriodicTask("t", run=body, interval=lambda: 0)
|
||||
task.start()
|
||||
await asyncio.sleep(0.2)
|
||||
await task.stop()
|
||||
|
||||
assert counter["n"] == 1
|
||||
|
||||
async def test_stop_is_idempotent(self):
|
||||
"""Calling stop() twice (or before start) does not raise."""
|
||||
task = PeriodicTask("t", run=lambda: asyncio.sleep(0), interval=lambda: 0.01)
|
||||
|
||||
@@ -40,6 +40,8 @@ docker run -d \
|
||||
--network=bridge \
|
||||
--dns=8.8.8.8 \
|
||||
--restart unless-stopped \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
ghcr.io/estrellaxd/auto_bangumi:latest
|
||||
```
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ services:
|
||||
ports:
|
||||
- "7892:7892"
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
network_mode: bridge
|
||||
|
||||
@@ -42,6 +42,8 @@ docker run -d \
|
||||
--network=bridge \
|
||||
--dns=8.8.8.8 \
|
||||
--restart unless-stopped \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
ghcr.io/estrellaxd/auto_bangumi:latest
|
||||
```
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ docker run -d \
|
||||
--network=bridge \
|
||||
--dns=8.8.8.8 \
|
||||
--restart unless-stopped \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
ghcr.io/estrellaxd/auto_bangumi:latest
|
||||
```
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ services:
|
||||
ports:
|
||||
- "7892:7892"
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
network_mode: bridge
|
||||
|
||||
@@ -42,6 +42,8 @@ docker run -d \
|
||||
--network=bridge \
|
||||
--dns=8.8.8.8 \
|
||||
--restart unless-stopped \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
ghcr.io/estrellaxd/auto_bangumi:latest
|
||||
```
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ docker run -d \
|
||||
--network=bridge \
|
||||
--dns=8.8.8.8 \
|
||||
--restart unless-stopped \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
ghcr.io/estrellaxd/auto_bangumi:latest
|
||||
```
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ services:
|
||||
ports:
|
||||
- "7892:7892"
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
network_mode: bridge
|
||||
|
||||
@@ -42,6 +42,8 @@ docker run -d \
|
||||
--network=bridge \
|
||||
--dns=8.8.8.8 \
|
||||
--restart unless-stopped \
|
||||
--log-opt max-size=10m \
|
||||
--log-opt max-file=3 \
|
||||
ghcr.io/estrellaxd/auto_bangumi:latest
|
||||
```
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ services:
|
||||
# 应用内“一键更新”依赖此重启策略:更新后容器会自行退出,由 Docker 重启
|
||||
# 以应用更新,因此必须保留 restart: unless-stopped。
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
dns:
|
||||
- 223.5.5.5
|
||||
environment:
|
||||
|
||||
@@ -28,6 +28,11 @@ services:
|
||||
# 应用内“一键更新”依赖此重启策略:更新后容器会自行退出,由 Docker 重启
|
||||
# 以应用更新,因此必须保留 restart: unless-stopped。
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- PGID=$(id -g)
|
||||
|
||||
Reference in New Issue
Block a user