feat: v1.2.0 — Gitea 地址可更改(默认 http://127.0.0.1:3000)
- 设置页新增「Gitea 地址」输入:同步引擎的 API/克隆/拉取全部改用该地址, 适配 Gitea 换了端口或部署在别处的场景 - 地址规范化:自动补 http://、去尾部斜杠,校验 scheme/主机名,非法值明确拒绝 - 保存前探测连通性(/api/v1/version),连不上拒绝保存并提示 - 改地址后自动把已有镜像仓库的 origin 重指到新地址,无需重新克隆 - 「打开 Gitea」按钮随设置端口 + 访问面板的主机名自适应(不再写死 3000) - 改地址/用户名不再误清 Gitea 密码(密码仅在密码行保存时提交) - 启动日志记录当前 Gitea 地址,便于排障 - manifest 1.1.2 → 1.2.0
This commit is contained in:
@@ -23,7 +23,7 @@ import uuid
|
||||
from datetime import datetime
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import quote, urlsplit
|
||||
|
||||
APP_DIR = Path(__file__).resolve().parent.parent
|
||||
DATA_DIR = Path(os.environ.get("GHS_DATA_DIR", "/vol1/@appdata/GiteaHubSync"))
|
||||
@@ -41,8 +41,8 @@ SSH_KEY = APP_DIR / "ssh" / "id_ed25519"
|
||||
if (DATA_DIR / "id_ed25519").exists():
|
||||
SSH_KEY = DATA_DIR / "id_ed25519"
|
||||
KNOWN_HOSTS = DATA_DIR / "known_hosts"
|
||||
GITEA_API = "http://127.0.0.1:3000/api/v1"
|
||||
GITEA_UI = os.environ.get("GITEA_URL", "http://192.168.31.10:3000")
|
||||
GITEA_URL_ENV = os.environ.get("GITEA_URL") # “打开 Gitea”按钮地址覆盖,一般不设
|
||||
DEFAULT_GITEA_HOST = "http://127.0.0.1:3000" # 同步引擎访问的 Gitea 地址(设置页可改)
|
||||
DEFAULT_REPOS = ["SMB_Android", "DCU_APP", "GD32E50X_BOOT", "DCU_DesktopController",
|
||||
"Motor_v2", "DCU_ECAT_Master", "GD32_BOOT"]
|
||||
LEGACY_GH_USER = "cbb2625274797" # 旧版硬编码账号,仅用于首次运行的迁移
|
||||
@@ -62,7 +62,46 @@ ENV["GIT_TERMINAL_PROMPT"] = "0"
|
||||
|
||||
# ---------------- 应用设置(settings.json) ----------------
|
||||
cfg = {"gitea_user": LEGACY_GITEA_USER, "gitea_pass": "", "auto_create": True,
|
||||
"interval": DEFAULT_INTERVAL}
|
||||
"interval": DEFAULT_INTERVAL, "gitea_host": DEFAULT_GITEA_HOST}
|
||||
|
||||
|
||||
def norm_gitea_host(h):
|
||||
"""规范化 Gitea 地址:补 http://、去尾部斜杠;不合法返回 None"""
|
||||
h = str(h or "").strip().rstrip("/")
|
||||
if not h:
|
||||
return None
|
||||
if "://" not in h:
|
||||
h = "http://" + h
|
||||
try:
|
||||
p = urlsplit(h)
|
||||
host = p.hostname or ""
|
||||
if p.scheme in ("http", "https") and host \
|
||||
and not p.path.strip("/") and not p.query and not p.fragment \
|
||||
and re.fullmatch(r"[A-Za-z0-9.\-:\[\]]+", host):
|
||||
return h
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def gitea_host():
|
||||
return cfg.get("gitea_host") or DEFAULT_GITEA_HOST
|
||||
|
||||
|
||||
def gitea_ui(host_header=""):
|
||||
"""「打开 Gitea」按钮地址:优先环境变量;否则用设置里的地址,
|
||||
但把 127.0.0.1/localhost 替换成访问面板的主机名(浏览器打不开本机回环)"""
|
||||
if GITEA_URL_ENV:
|
||||
return GITEA_URL_ENV
|
||||
base = gitea_host()
|
||||
try:
|
||||
p = urlsplit(base)
|
||||
host = (host_header or "").split(":")[0].strip()
|
||||
if host and p.hostname in ("127.0.0.1", "localhost", "0.0.0.0", "::1", ""):
|
||||
return f"{p.scheme}://{host}" + (f":{p.port}" if p.port else "")
|
||||
except Exception:
|
||||
pass
|
||||
return base
|
||||
|
||||
|
||||
def sync_interval():
|
||||
@@ -90,6 +129,9 @@ def load_settings():
|
||||
cfg["auto_create"] = bool(d.get("auto_create", True))
|
||||
if valid_interval(d.get("interval")):
|
||||
cfg["interval"] = int(d["interval"])
|
||||
h = norm_gitea_host(d.get("gitea_host"))
|
||||
if h:
|
||||
cfg["gitea_host"] = h
|
||||
except Exception:
|
||||
pass
|
||||
write_gitcreds()
|
||||
@@ -107,8 +149,8 @@ def save_settings():
|
||||
|
||||
|
||||
def gitea_base():
|
||||
"""Gitea 上当前用户的仓库前缀"""
|
||||
return f"http://127.0.0.1:3000/{cfg['gitea_user']}"
|
||||
"""Gitea 上当前用户的仓库前缀(地址可在设置页修改)"""
|
||||
return f"{gitea_host()}/{cfg['gitea_user']}"
|
||||
|
||||
|
||||
# ---------------- GitHub 多账号(accounts.json) ----------------
|
||||
@@ -217,9 +259,11 @@ def write_gitcreds():
|
||||
这样镜像仓库的 remote 地址里不保存密码。清除密码时删除该文件。"""
|
||||
try:
|
||||
if cfg.get("gitea_pass"):
|
||||
p = urlsplit(gitea_host())
|
||||
netloc = p.hostname + (f":{p.port}" if p.port else "")
|
||||
user = quote(str(cfg["gitea_user"]), safe="")
|
||||
pw = quote(str(cfg["gitea_pass"]), safe="")
|
||||
GITCREDS_FILE.write_text(f"http://{user}:{pw}@127.0.0.1:3000\n",
|
||||
GITCREDS_FILE.write_text(f"{p.scheme}://{user}:{pw}@{netloc}\n",
|
||||
encoding="utf-8")
|
||||
os.chmod(GITCREDS_FILE, 0o600)
|
||||
elif GITCREDS_FILE.exists():
|
||||
@@ -228,6 +272,20 @@ def write_gitcreds():
|
||||
pass
|
||||
|
||||
|
||||
def repoint_mirror_remotes():
|
||||
"""Gitea 地址变更后,把已有镜像仓库的 origin 指向新地址"""
|
||||
n = 0
|
||||
try:
|
||||
for repo in REPO_DIR.glob("*.git"):
|
||||
r = git(["-C", str(repo), "remote", "set-url", "origin",
|
||||
f"{gitea_base()}/{repo.name[:-4]}.git"], timeout=20)
|
||||
if r.returncode == 0:
|
||||
n += 1
|
||||
except Exception as e:
|
||||
log(f"更新镜像 remote 失败: {e}")
|
||||
return n
|
||||
|
||||
|
||||
def git_creds_args():
|
||||
if cfg.get("gitea_pass") and GITCREDS_FILE.exists():
|
||||
return ["-c", f"credential.helper=store --file={GITCREDS_FILE}"]
|
||||
@@ -286,13 +344,33 @@ def gitea_api(path):
|
||||
cmd = ["curl", "-sS", "-m", "10"]
|
||||
if cfg.get("gitea_pass"):
|
||||
cmd += ["-u", f"{cfg['gitea_user']}:{cfg['gitea_pass']}"]
|
||||
cmd.append(f"{GITEA_API}{path}")
|
||||
cmd.append(f"{gitea_host()}/api/v1{path}")
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
||||
return r.returncode == 0, json.loads(r.stdout or "{}")
|
||||
except Exception:
|
||||
return False, {}
|
||||
|
||||
|
||||
def gitea_probe(host=None):
|
||||
"""探测 Gitea 地址连通性,返回 (ok, msg)"""
|
||||
host = host or gitea_host()
|
||||
try:
|
||||
r = subprocess.run(["curl", "-sS", "-m", "8", "-w", "\n%{http_code}",
|
||||
f"{host}/api/v1/version"],
|
||||
capture_output=True, text=True, timeout=12)
|
||||
out, _, code = r.stdout.rpartition("\n")
|
||||
st = int(code) if code.strip().isdigit() else 0
|
||||
if 200 <= st < 300:
|
||||
try:
|
||||
ver = json.loads(out or "{}").get("version", "?")
|
||||
except Exception:
|
||||
ver = "?"
|
||||
return True, f"连接成功,Gitea 版本 {ver}"
|
||||
return False, f"地址连不上(HTTP {st or '超时'}),请检查后重试"
|
||||
except Exception as e:
|
||||
return False, f"地址连不上:{e}"
|
||||
|
||||
|
||||
def gitea_repo_exists(name):
|
||||
ok, d = gitea_api(f"/repos/{cfg['gitea_user']}/{name}")
|
||||
return ok and isinstance(d, dict) and d.get("full_name") == f"{cfg['gitea_user']}/{name}"
|
||||
@@ -559,7 +637,7 @@ def account_public(acc):
|
||||
"method": "Token(HTTPS)" if acc.get("token") else "SSH 私钥"}
|
||||
|
||||
|
||||
def global_status():
|
||||
def global_status(ui_url=None):
|
||||
repos = load_repos()
|
||||
details = [repo_detail(r) for r in repos]
|
||||
now = time.time()
|
||||
@@ -582,7 +660,7 @@ def global_status():
|
||||
"synced_count": sum(1 for x in details if x["status"] == "已同步"),
|
||||
"total_size_mb": round(sum(x["size_mb"] for x in details), 1),
|
||||
"repos": details,
|
||||
"gitea_url": GITEA_UI,
|
||||
"gitea_url": ui_url or gitea_ui(),
|
||||
"gitea_user": cfg["gitea_user"],
|
||||
"gitea_online": bool(gitea_all),
|
||||
"available": [r for r in gitea_all if r not in repos],
|
||||
@@ -759,7 +837,7 @@ PAGE_HOME = """
|
||||
</div>
|
||||
<div class="btns">
|
||||
<button class="b-sync" id="btnSync" onclick="syncNow()">⚡ 立即同步一次</button>
|
||||
<a class="btn b-gitea" href="__GITEA__" target="_blank">🗂️ 打开 Gitea(3000端口)</a>
|
||||
<a class="btn b-gitea" href="__GITEA__" target="_blank">🗂️ 打开 Gitea</a>
|
||||
<a class="btn b-gh" id="btnGh" href="__GH__" target="_blank">🐙 GitHub 仓库列表</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -961,17 +1039,22 @@ PAGE_SETTINGS = """
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Gitea 与同步设置</h2>
|
||||
<label class="flabel">Gitea 地址<span class="sub">同步引擎访问的地址,默认 http://127.0.0.1:3000</span></label>
|
||||
<div class="addrow">
|
||||
<input id="giteaHost" placeholder="http://127.0.0.1:3000">
|
||||
<button class="b-mini" id="btnGitea" onclick="saveGitea(false)">保存</button>
|
||||
</div>
|
||||
<label class="flabel">Gitea 用户名<span class="sub">面板按此用户名列出你可同步的仓库</span></label>
|
||||
<div class="addrow">
|
||||
<input id="giteaUser" placeholder="cbb">
|
||||
<button class="b-mini" id="btnGitea" onclick="saveGitea()">保存</button>
|
||||
<button class="b-mini" onclick="saveGitea(false)">保存</button>
|
||||
</div>
|
||||
<label class="flabel">Gitea 密码<span class="sub">仅仓库为私有时需要;留空保存可清除</span></label>
|
||||
<div class="addrow">
|
||||
<input id="giteaPass" type="password" placeholder="未设置">
|
||||
<button class="b-mini" onclick="saveGitea()">保存</button>
|
||||
<button class="b-mini" onclick="saveGitea(true)">保存</button>
|
||||
</div>
|
||||
<div class="tip">本地 Gitea 地址 __GITEAURL__;密码只保存在 NAS 本地,仅用于读取私有仓库。</div>
|
||||
<div class="tip" id="giteaUrlTip"></div>
|
||||
<div class="optbox">
|
||||
<div class="optrow">
|
||||
<label class="switch"><input type="checkbox" id="swAuto" onchange="saveAuto()"><span class="slider"></span></label>
|
||||
@@ -998,9 +1081,11 @@ function showMsg(id,t,ok){const m=document.getElementById(id);m.textContent=t;m.
|
||||
async function refresh(){
|
||||
try{
|
||||
const s=await jget('api/settings');
|
||||
document.getElementById('giteaHost').value=s.gitea_host||'http://127.0.0.1:3000';
|
||||
document.getElementById('giteaUser').value=s.gitea_user||'';
|
||||
document.getElementById('giteaPass').placeholder=s.gitea_pass_set
|
||||
?'Gitea 密码(已设置;留空保存可清除)':'Gitea 密码(私有仓库必填)';
|
||||
document.getElementById('giteaUrlTip').textContent='「打开 Gitea」按钮地址:'+(s.gitea_url||'—')+'(随访问地址自动适配);密码只保存在 NAS 本地,仅用于读取私有仓库。';
|
||||
document.getElementById('swAuto').checked=!!s.auto_create;
|
||||
const iv=document.getElementById('syncInterval');
|
||||
iv.value=s.interval||10;iv.min=s.min_interval||5;iv.max=s.max_interval||86400;
|
||||
@@ -1073,12 +1158,14 @@ async function delAcc(id){
|
||||
const r=await (await fetch('api/accounts/remove',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id})})).json();
|
||||
showMsg('msgAcc',r.msg||JSON.stringify(r),!!r.ok);refresh();
|
||||
}
|
||||
async function saveGitea(){
|
||||
const gitea_user=document.getElementById('giteaUser').value.trim();
|
||||
const gitea_pass=document.getElementById('giteaPass').value;
|
||||
const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({gitea_user,gitea_pass})})).json();
|
||||
async function saveGitea(withPass){
|
||||
// withPass=false 时不发送密码字段,避免改地址/用户名时误清密码
|
||||
const body={gitea_host:document.getElementById('giteaHost').value.trim(),
|
||||
gitea_user:document.getElementById('giteaUser').value.trim()};
|
||||
if(withPass)body.gitea_pass=document.getElementById('giteaPass').value;
|
||||
const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json();
|
||||
showMsg('msgCfg',r.msg||JSON.stringify(r),!!r.ok);
|
||||
document.getElementById('giteaPass').value='';
|
||||
if(withPass)document.getElementById('giteaPass').value='';
|
||||
refresh();
|
||||
}
|
||||
async function saveAuto(){
|
||||
@@ -1138,7 +1225,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
path = self.path.split("?")[0].rstrip("/") or "/"
|
||||
if path == "/":
|
||||
acc = active_account()
|
||||
body = (PAGE_HOME.replace("__GITEA__", GITEA_UI)
|
||||
body = (PAGE_HOME.replace("__GITEA__", gitea_ui(self.headers.get("Host")))
|
||||
.replace("__GH__", f"https://github.com/{acc['gh_user']}?tab=repositories" if acc else "")
|
||||
.replace("__LOGPATH__", str(LOG_FILE)))
|
||||
self._html(page("控制面板", body, "home"))
|
||||
@@ -1147,8 +1234,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
elif path == "/manage":
|
||||
self._html(page("项目管理", PAGE_MANAGE, "manage"))
|
||||
elif path == "/settings":
|
||||
body = PAGE_SETTINGS.replace("__GITEAURL__", GITEA_UI)
|
||||
self._html(page("设置", body, "settings"))
|
||||
self._html(page("设置", PAGE_SETTINGS, "settings"))
|
||||
elif path == "/api/state":
|
||||
repos = load_repos()
|
||||
acc = active_account()
|
||||
@@ -1171,7 +1257,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"interval": sync_interval(),
|
||||
"min_interval": MIN_INTERVAL,
|
||||
"max_interval": MAX_INTERVAL,
|
||||
"gitea_url": GITEA_UI,
|
||||
"gitea_host": gitea_host(),
|
||||
"gitea_url": gitea_ui(self.headers.get("Host")),
|
||||
"active": active_id,
|
||||
"accounts": [account_public(a) for a in accounts]})
|
||||
elif path == "/api/log":
|
||||
@@ -1191,7 +1278,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
ok, msg = run_sync()
|
||||
self._json({"ok": ok, "msg": msg})
|
||||
elif path == "/api/settings/save":
|
||||
new_user = new_pass = None
|
||||
new_user = new_pass = new_host = None
|
||||
if "gitea_user" in data:
|
||||
u = (data.get("gitea_user") or "").strip()
|
||||
if not valid_name(u):
|
||||
@@ -1200,22 +1287,35 @@ class Handler(BaseHTTPRequestHandler):
|
||||
new_user = u
|
||||
if "gitea_pass" in data:
|
||||
new_pass = str(data.get("gitea_pass") or "") # 空串 = 清除密码
|
||||
if "gitea_host" in data:
|
||||
h = norm_gitea_host(data.get("gitea_host"))
|
||||
if not h:
|
||||
self._json({"ok": False, "msg": "Gitea 地址不合法,应类似 http://127.0.0.1:3000"})
|
||||
return
|
||||
new_host = h
|
||||
if "interval" in data:
|
||||
if not valid_interval(data.get("interval")):
|
||||
self._json({"ok": False, "msg": f"同步间隔须为 {MIN_INTERVAL} ~ {MAX_INTERVAL} 之间的整数(秒)"})
|
||||
return
|
||||
# 预校验:涉及 Gitea 账号的改动,先用新凭据调一次 /user 验证
|
||||
# 预校验:新地址先探连通性;涉及账号的改动再用新凭据调一次 /user 验证
|
||||
trial = dict(cfg)
|
||||
if new_user is not None:
|
||||
trial["gitea_user"] = new_user
|
||||
if new_pass is not None:
|
||||
trial["gitea_pass"] = new_pass
|
||||
if new_host is not None:
|
||||
trial["gitea_host"] = new_host
|
||||
ok, msg = gitea_probe(new_host)
|
||||
if not ok:
|
||||
self._json({"ok": False, "msg": msg})
|
||||
return
|
||||
if (new_user is not None or new_pass is not None) and trial["gitea_pass"]:
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["curl", "-sS", "-m", "10", "-w", "\n%{http_code}",
|
||||
"-u", f"{trial['gitea_user']}:{trial['gitea_pass']}",
|
||||
f"{GITEA_API}/user"], capture_output=True, text=True, timeout=15)
|
||||
f"{trial['gitea_host']}/api/v1/user"],
|
||||
capture_output=True, text=True, timeout=15)
|
||||
out, _, code = r.stdout.rpartition("\n")
|
||||
st = int(code) if code.strip().isdigit() else 0
|
||||
if not (200 <= st < 300):
|
||||
@@ -1228,11 +1328,14 @@ class Handler(BaseHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self._json({"ok": False, "msg": f"Gitea 账号验证出错: {e}"})
|
||||
return
|
||||
host_changed = new_host is not None and new_host != gitea_host()
|
||||
if new_user is not None:
|
||||
cfg["gitea_user"] = new_user
|
||||
if new_pass is not None:
|
||||
cfg["gitea_pass"] = new_pass
|
||||
log("Gitea 密码已" + ("更新" if new_pass else "清除"))
|
||||
if new_host is not None:
|
||||
cfg["gitea_host"] = new_host
|
||||
if "auto_create" in data:
|
||||
cfg["auto_create"] = bool(data.get("auto_create"))
|
||||
if "interval" in data:
|
||||
@@ -1241,15 +1344,23 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if cfg["interval"] != old_iv:
|
||||
log(f"同步间隔已调整: {old_iv}s -> {cfg['interval']}s")
|
||||
save_settings()
|
||||
notes = []
|
||||
if new_host is not None:
|
||||
ok, msg = gitea_probe()
|
||||
notes.append(f"Gitea 地址已设为 {new_host}({msg})")
|
||||
if host_changed:
|
||||
n = repoint_mirror_remotes()
|
||||
log(f"Gitea 地址变更: {n} 个镜像仓库的 origin 已更新")
|
||||
notes.append(f"{n} 个已有镜像的拉取地址已更新")
|
||||
if cfg.get("gitea_pass"):
|
||||
msg = "已保存(Gitea 密码验证通过 ✓,可同步私有仓库)"
|
||||
notes.append("Gitea 密码验证通过 ✓,可同步私有仓库")
|
||||
else:
|
||||
ok, d = gitea_api(f"/users/{cfg['gitea_user']}")
|
||||
if ok and isinstance(d, dict) and d.get("login"):
|
||||
msg = "已保存(未配置 Gitea 密码,只能同步公开仓库;仓库为私有时请填写密码)"
|
||||
notes.append("未配置 Gitea 密码,只能同步公开仓库;仓库为私有时请填写密码")
|
||||
else:
|
||||
msg = f"已保存(Gitea 上找不到用户 {cfg['gitea_user']},请检查)"
|
||||
self._json({"ok": True, "msg": msg})
|
||||
notes.append(f"注意:在新地址上找不到用户 {cfg['gitea_user']},请检查")
|
||||
self._json({"ok": True, "msg": "已保存(" + ";".join(notes) + ")"})
|
||||
elif path == "/api/accounts/add":
|
||||
token = (data.get("token") or "").strip()
|
||||
label = (data.get("label") or "").strip()
|
||||
@@ -1440,7 +1551,7 @@ def main():
|
||||
log(f"GiteaHubSync 守护进程启动 (pid {os.getpid()}, 自动同步={'开' if state['enabled'] else '关'}, "
|
||||
f"项目 {len(load_repos())} 个, GitHub 账号={acc['label'] if acc else '无'}"
|
||||
f"({acc['gh_user'] if acc else '-'}/{'Token' if acc and acc.get('token') else 'SSH'}), "
|
||||
f"Gitea 用户={cfg['gitea_user']}, 自动创建={'开' if cfg.get('auto_create') else '关'})")
|
||||
f"Gitea 用户={cfg['gitea_user']}, 地址={gitea_host()}, 自动创建={'开' if cfg.get('auto_create') else '关'})")
|
||||
threading.Thread(target=loop, daemon=True).start()
|
||||
server = ThreadingHTTPServer(("0.0.0.0", PANEL_PORT), Handler)
|
||||
log(f"控制面板监听 :{PANEL_PORT}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
appname = GiteaHubSync
|
||||
version = 1.1.2
|
||||
version = 1.2.0
|
||||
display_name = Gitea→GitHub 同步助手
|
||||
desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。
|
||||
platform = all
|
||||
|
||||
@@ -14,6 +14,8 @@ NAS 重启自动恢复。控制面板带自动同步开关、立即同步、仓
|
||||
- **多 GitHub 账号**:设置页可添加多个 GitHub 账号(用户名 + Personal Access Token),
|
||||
一键切换推送目标;每个账号独立记录同步进度,切回原账号不会重复推送
|
||||
- **同步间隔可调**:设置页可改检查间隔(5 秒 ~ 1 天,默认 10 秒),改完立即生效
|
||||
- **Gitea 地址可改**:设置页可改同步引擎访问的 Gitea 地址(默认 `http://127.0.0.1:3000`),
|
||||
保存前自动探测连通性;改地址后已有镜像的拉取地址自动更新,「打开 Gitea」按钮随访问地址自适应
|
||||
- **自动创建仓库**:Gitea 上的新仓库加入同步时,若 GitHub 上不存在同名仓库,
|
||||
自动按账号设置为私有/公开创建(可开关)
|
||||
- **两种推送方式**:配置了 Token 的账号走 HTTPS 推送(通用,任何人都能用自己的
|
||||
@@ -63,6 +65,9 @@ FNOS_HOST=192.168.31.10 FNOS_USER=<管理员账号> FNOS_PASS=<密码> \
|
||||
> Token 只保存在 NAS 本地 `accounts.json`(权限 600),不上传任何地方;
|
||||
> 日志中的 Token 会自动打码。卸载应用不会动 GitHub 上的任何仓库。
|
||||
|
||||
如果你的 Gitea 不在默认的 `127.0.0.1:3000`(比如换了端口),到设置页「Gitea 地址」一栏改掉即可,
|
||||
保存时会自动探测连通性,并同步更新所有已有镜像的拉取地址。
|
||||
|
||||
## 重新构建
|
||||
|
||||
```bash
|
||||
@@ -76,5 +81,5 @@ cd /vol1/@appshare/fn-deepseek-harness/fnos-gitea-sync
|
||||
端口直连,私钥随应用打包安装在 NAS 本地
|
||||
- 从 v1.0.0 升级会自动迁移:旧的同步进度归入「默认(SSH 私钥)」账号,行为不变
|
||||
- 数据文件:`accounts.json`(GitHub 账号,含 Token)、`settings.json`
|
||||
(Gitea 用户名 / 自动创建开关)、`repos.json`(同步列表)、`.last-pushed/<账号>/<仓库>`
|
||||
(每个账号的同步进度)
|
||||
(Gitea 地址/用户名/密码、自动创建开关、同步间隔)、`repos.json`(同步列表)、
|
||||
`.last-pushed/<账号>/<仓库>`(每个账号的同步进度)
|
||||
|
||||
Reference in New Issue
Block a user