#!/usr/bin/env python3 """Commit & push dirty repos (local + server) with eric credentials, deploy douyinWEB ops.""" from __future__ import annotations import os import subprocess import time from pathlib import Path import paramiko HOST = "107.173.30.245" PASS = "Xiao4669805" GIT_USER = "eric" GIT_PASS = "Xiao4669805" AUTH = f"{GIT_USER}:{GIT_PASS}@" DOUYIN = Path(r"C:\Users\admin\Desktop\douyinWEB") NOMADRO = Path(r"C:\project\gitlab-instance-0a899031_nomadro") def local(cmd: list[str] | str, cwd: Path) -> str: print(f"\n[{cwd.name}] $", cmd if isinstance(cmd, str) else " ".join(cmd)) r = subprocess.run( cmd, cwd=str(cwd), shell=isinstance(cmd, str), capture_output=True, text=True, encoding="utf-8", errors="replace", ) out = (r.stdout or "") + (r.stderr or "") print(out[-3000:] if len(out) > 3000 else out) if r.returncode != 0: raise RuntimeError(f"cmd failed in {cwd}: {cmd}\n{out}") return out def ensure_auth_remote(cwd: Path) -> None: url = local(["git", "remote", "get-url", "origin"], cwd).strip() if "gitea.dsx2020.com" in url and AUTH not in url: if url.startswith("https://"): new = url.replace("https://", f"https://{AUTH}", 1) # strip existing userinfo if "@gitea" in url: new = "https://" + AUTH + url.split("@", 1)[1] local(["git", "remote", "set-url", "origin", new], cwd) def commit_push_local(cwd: Path, paths: list[str], message: str) -> None: ensure_auth_remote(cwd) local(["git", "add", *paths], cwd) st = local(["git", "status", "--porcelain"], cwd) if not st.strip(): print(f"[{cwd.name}] nothing to commit") # still try push in case ahead local(["git", "push", "-u", "origin", "HEAD"], cwd) return local(["git", "commit", "-m", message], cwd) local(["git", "push", "-u", "origin", "HEAD"], cwd) def main() -> None: # 1) Local douyinWEB — ops page commit_push_local( DOUYIN, [ "frontend/src/app/ops/page.tsx", "frontend/src/components/Shell.tsx", "frontend/src/app/layout.tsx", "backend/app/ops_panel.py", "backend/app/notify.py", "backend/app/main.py", ], "feat(ops): Umami/ntfy/DNS 运维控制台页面与 API", ) # 2) Local nomadro — observability stack commit_push_local( NOMADRO, [ "app/layout.tsx", "components/umami-analytics.tsx", "deploy/Caddyfile", "deploy/README.md", "deploy/caddy-sites", "deploy/umami", "deploy/ntfy", "deploy/observability", "deploy/router", "deploy/wordpress", "deploy/deploy.sh", "deploy/setup-server.sh", ], "feat: Umami/ntfy 可观测性栈与 WordPress AdSense 部署", ) # 3) Server repos: commit layout umami injections + sync + deploy douyin c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect(HOST, username="root", password=PASS, timeout=30, allow_agent=False, look_for_keys=False) def run(cmd: str, timeout: int = 600) -> str: print("\n$ ", cmd[:160]) _i, o, e = c.exec_command(cmd, timeout=timeout) out = (o.read() + e.read()).decode("utf-8", errors="replace") print(out[-3500:] if len(out) > 3500 else out) return out # Ensure remotes have credentials for repo in ("/opt/douyinWEB", "/opt/tiktokWEB", "/opt/wauto", "/var/www/nomadro.com"): run( f""" cd {repo} || exit 0 url=$(git remote get-url origin 2>/dev/null || true) [ -z "$url" ] && exit 0 case "$url" in *{AUTH}*) ;; https://*@gitea.dsx2020.com/*) rest=$(echo "$url" | sed 's|https://[^@]*@|https://|') git remote set-url origin "https://{AUTH}${{rest#https://}}" ;; https://gitea.dsx2020.com/*) git remote set-url origin "https://{AUTH}${{url#https://}}" ;; esac git remote -v | head -2 """ ) # Pull douyin first then apply — better: push already done, pull on server run( """ cd /opt/douyinWEB git fetch origin git stash push -u -m 'pre-ops-deploy' -- frontend/src/app/layout.tsx 2>/dev/null || true git checkout test1 git pull --ff-only origin test1 """, timeout=120, ) # Commit other server-only dirty files (tiktok/wauto/nomadro layouts) if still dirty run( r""" commit_if_dirty() { local dir="$1"; local msg="$2"; shift 2 cd "$dir" || return 0 git add "$@" 2>/dev/null || true if [ -n "$(git status --porcelain -- "$@" 2>/dev/null)" ]; then git -c user.email=ops@nomadro.com -c user.name=ops commit -m "$msg" || true git push origin HEAD || true else echo "$dir: clean for selected paths" fi } commit_if_dirty /opt/tiktokWEB 'chore: inject Umami analytics tracker' frontend/src/app/layout.tsx commit_if_dirty /opt/wauto 'chore: inject Umami analytics tracker' web/app/layout.tsx commit_if_dirty /var/www/nomadro.com 'chore: Umami analytics component' app/layout.tsx components/umami-analytics.tsx """ ) # Deploy douyinWEB frontend + restart API run( """ cd /opt/douyinWEB/frontend # ensure umami env grep -q '^NEXT_PUBLIC_UMAMI_WEBSITE_ID=' .env.local 2>/dev/null || echo 'NEXT_PUBLIC_UMAMI_WEBSITE_ID=f18d3484-be2e-4344-8882-b80c5b53f9cf' >> .env.local npm run build systemctl restart douyinweb-web systemctl restart douyinweb-api sleep 2 systemctl is-active douyinweb-web douyinweb-api curl -fsS -o /dev/null -w 'ops_page:%{http_code}\\n' https://dylive.nomadro.com/ops || true curl -fsS -o /dev/null -w 'ops_api:%{http_code}\\n' -H 'Cookie: ' https://dylive.nomadro.com/api/ops || true """, timeout=600, ) # Sync nomadro.com from git if needed run( """ cd /var/www/nomadro.com git fetch origin git pull --ff-only origin test || git pull --ff-only || true # copy observability deploy assets already in repo; ensure router files synced if present """, timeout=120, ) c.close() print("\nALL DONE") if __name__ == "__main__": main()