Add Docker Compose one-click deployment

This commit is contained in:
Rixuan Shao
2026-05-30 00:39:14 +08:00
parent 11a6c949db
commit 132f799d3c
12 changed files with 634 additions and 49 deletions
+5 -3
View File
@@ -25,7 +25,8 @@ ENV PIP_TRUSTED_HOST=${PIP_TRUSTED_HOST}
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list \
RUN set -eux; \
sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list \
&& ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo ${TZ} > /etc/timezone \
@@ -39,13 +40,14 @@ RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list \
websockify \
x11vnc \
xfonts-intl-chinese \
&& curl -fsSL -x http://127.0.0.1:7890 https://download.docker.com/linux/static/stable/x86_64/docker-25.0.3.tgz -o docker.tgz \
&& download() { url="$1"; output="$2"; proxy_url="${HTTPS_PROXY:-${https_proxy:-${HTTP_PROXY:-${http_proxy:-}}}}"; if [ -n "$proxy_url" ]; then curl -fsSL -x "$proxy_url" "$url" -o "$output"; else curl -fsSL "$url" -o "$output"; fi; } \
&& download https://download.docker.com/linux/static/stable/x86_64/docker-25.0.3.tgz docker.tgz \
&& tar xzvf docker.tgz \
&& mv docker/docker /usr/bin/docker \
&& chmod +x /usr/bin/docker \
&& rm -rf docker docker.tgz \
&& mkdir -p /usr/local/lib/docker/cli-plugins \
&& curl -SL -x http://127.0.0.1:7890 https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose \
&& download https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64 /usr/local/lib/docker/cli-plugins/docker-compose \
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-compose \
&& rm -rf /var/lib/apt/lists/*
+88
View File
@@ -0,0 +1,88 @@
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path
def expand_field(field, minimum, maximum, current):
values = set()
for part in str(field).split(","):
part = part.strip()
if not part:
continue
step = 1
if "/" in part:
part, raw_step = part.split("/", 1)
step = max(1, int(raw_step))
if part == "*":
start, end = minimum, maximum
elif "-" in part:
raw_start, raw_end = part.split("-", 1)
start, end = int(raw_start), int(raw_end)
else:
start = end = int(part)
values.update(range(max(minimum, start), min(maximum, end) + 1, step))
return current in values
def cron_dow(now):
return (now.weekday() + 1) % 7
def field_matches(field, minimum, maximum, current, *, allow_sunday_alias=False):
if allow_sunday_alias and current == 0:
return expand_field(field, minimum, maximum, 0) or expand_field(field, minimum, maximum, 7)
return expand_field(field, minimum, maximum, current)
def line_should_run(line, now):
parts = line.split(maxsplit=5)
if len(parts) < 6:
return False, ""
minute, hour, day, month, weekday, command = parts
try:
matched = (
field_matches(minute, 0, 59, now.minute)
and field_matches(hour, 0, 23, now.hour)
and field_matches(day, 1, 31, now.day)
and field_matches(month, 1, 12, now.month)
and field_matches(weekday, 0, 7, cron_dow(now), allow_sunday_alias=True)
)
except ValueError:
return False, ""
return matched, command
def read_crontab(path):
if not path.exists():
return []
lines = []
for raw_line in path.read_text(encoding="utf-8", errors="replace").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" in line.split(maxsplit=1)[0]:
continue
lines.append(line)
return lines
def run_loop(crontab_path):
crontab_path.parent.mkdir(parents=True, exist_ok=True)
last_minute_key = None
print(f"[cron_runner] watching {crontab_path}", flush=True)
while True:
now = datetime.now()
minute_key = now.strftime("%Y-%m-%d %H:%M")
if minute_key != last_minute_key:
last_minute_key = minute_key
for line in read_crontab(crontab_path):
matched, command = line_should_run(line, now)
if matched and command:
print(f"[cron_runner] {now.isoformat(timespec='seconds')} run: {command}", flush=True)
subprocess.Popen(["/bin/bash", "-lc", command])
time.sleep(15)
if __name__ == "__main__":
target = Path(sys.argv[1] if len(sys.argv) > 1 else "/host-spool-cron/root")
run_loop(target)
+4 -2
View File
@@ -1,5 +1,6 @@
import json
import logging
import os
import traceback
from datetime import datetime, timedelta, timezone
from pathlib import Path
@@ -132,13 +133,14 @@ def _target_sent_today(account, target_name):
def login_desktop_api_url():
settings = get_app_settings(force_reload=True)
return str(settings.get("login_desktop_api_url") or "http://127.0.0.1:18090").rstrip("/")
return str(os.getenv("SPARKFLOW_LOGIN_DESKTOP_API_URL") or settings.get("login_desktop_api_url") or "http://127.0.0.1:18090").rstrip("/")
def login_desktop_public_url(request: Request) -> str:
host = request.url.hostname or "127.0.0.1"
scheme = request.url.scheme or "http"
return f"{scheme}://{host}:8788/vnc.html?autoconnect=1&resize=scale&view_only=0"
port = str(os.getenv("LOGIN_DESKTOP_PUBLIC_PORT") or "8788").strip() or "8788"
return f"{scheme}://{host}:{port}/vnc.html?autoconnect=1&resize=scale&view_only=0"
def call_login_desktop(path: str, *, method: str = "GET", payload: dict | None = None, timeout: int = 20) -> dict: