FROM node:20-slim AS builder WORKDIR /app RUN apt-get update && apt-get install -y git python3 make g++ && rm -rf /var/lib/apt/lists/* RUN git clone --depth=1 https://github.com/decolua/9router.git . RUN sed -i 's/sameSite: "lax"/sameSite: "none"/' src/app/api/auth/login/route.js RUN cat > next.config.mjs << 'NEXTEOF' const nextConfig = { output: "standalone", serverExternalPackages: ["better-sqlite3","sql.js","node:sqlite","bun:sqlite"], images:{unoptimized:true},env:{}, webpack:(config,{isServer})=>{ if(!isServer)config.resolve.fallback={...config.resolve.fallback,fs:false,path:false}; config.watchOptions={...config.watchOptions,ignored:/[\/](logs|\.next)[\/]/};return config; }, async headers(){return[{source:"/(.*)",headers:[{key:"X-Frame-Options",value:"ALLOWALL"},{key:"Content-Security-Policy",value:"frame-ancestors *"}]}];}, async rewrites(){return[ {source:"/v1/v1/:path*",destination:"/api/v1/:path*"},{source:"/v1/v1",destination:"/api/v1"}, {source:"/codex/:path*",destination:"/api/v1/responses"},{source:"/v1/:path*",destination:"/api/v1/:path*"}, {source:"/v1",destination:"/api/v1"} ];} }; export default nextConfig; NEXTEOF RUN npm install && npm run build # ---- Runtime ---- FROM node:20-slim RUN apt-get update && apt-get install -y python3 python3-pip && rm -rf /var/lib/apt/lists/* RUN pip3 install huggingface_hub --break-system-packages --quiet WORKDIR /app COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public COPY --from=builder /app/open-sse ./open-sse ENV PORT=7860 HOSTNAME=0.0.0.0 ENV NEXT_PUBLIC_BASE_URL=https://gunnybd01-9router.hf.space ENV INITIAL_PASSWORD=123456 AUTH_COOKIE_SECURE=true ENV DB_PATH=/root/.9router/db/data.sqlite ENV HF_DATASET=gunnybd01/9router-db-backup ENV SYNC_INTERVAL=3600 EXPOSE 7860 RUN cat > /sync_db.py << 'PYEOF' import os,sys,time from huggingface_hub import HfApi db=os.environ.get("DB_PATH","/root/.9router/db/data.sqlite") tok=os.environ.get("HF_TOKEN",""); ds=os.environ.get("HF_DATASET","gunnybd01/9router-db-backup") if not tok: print("[sync] no HF_TOKEN"); sys.exit(0) if not os.path.exists(db): print("[sync] no DB"); sys.exit(0) print(f"[sync] {os.path.getsize(db)}b -> {ds} @ {time.strftime('%H:%M:%S')}") try: HfApi(token=tok).upload_file(path_or_fileobj=db,path_in_repo="data.sqlite", repo_id=ds,repo_type="dataset",commit_message=f"backup {time.strftime('%Y-%m-%d %H:%M')}") print("[sync] OK") except Exception as e: print(f"[sync] ERR: {e}") PYEOF RUN cat > /restore_db.py << 'PYEOF2' import os,sys,shutil from huggingface_hub import hf_hub_download db=os.environ.get("DB_PATH","/root/.9router/db/data.sqlite") tok=os.environ.get("HF_TOKEN",""); ds=os.environ.get("HF_DATASET","gunnybd01/9router-db-backup") os.makedirs(os.path.dirname(db),exist_ok=True) if not tok: print("[restore] no HF_TOKEN"); sys.exit(0) print(f"[restore] Pulling from {ds}...") try: dl=hf_hub_download(repo_id=ds,filename="data.sqlite",repo_type="dataset",token=tok,local_dir="/tmp/hfr") sz=os.path.getsize(dl) if sz>4096: shutil.copy2(dl,db); print(f"[restore] OK {sz}b") else: print(f"[restore] too small {sz}b, fresh") except Exception as e: print(f"[restore] no backup: {e}") PYEOF2 # Wrapper script cho trap (tranh sh quoting issues) RUN printf '#!/bin/sh\npython3 /sync_db.py\n' > /do_sync.sh && chmod +x /do_sync.sh RUN cat > /start.sh << 'STARTEOF' #!/bin/sh python3 /restore_db.py node /app/server.js & APP_PID=$! INTERVAL="${SYNC_INTERVAL:-3600}" echo "[start] Sync every ${INTERVAL}s, PID=$$" (while true; do sleep "$INTERVAL"; /do_sync.sh; done) & trap /do_sync.sh TERM INT wait $APP_PID STARTEOF RUN chmod +x /start.sh CMD ["/bin/sh", "/start.sh"]