diff --git a/README.md b/README.md index d89a8b6..a4f7756 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,23 @@ ChuBlog/ └── scripts/init-deps.py ``` -先初始化外部依赖: +项目提供两个 HTTPS 仓库,任选其一克隆: ```sh +git clone https://github.com/TheBadRoger/ChuBlog.git +# 或:git clone https://repo.chunana.cn/ChuNa7a/ChuBlog.git +cd ChuBlog python3 scripts/init-deps.py ``` -默认依赖地址为 GitHub HTTPS,无需服务器 SSH 公钥或 `ChuGit` 别名: +数据库和驱动均提供两个 HTTPS 源: -- `https://github.com/TheBadRoger/ChuSQL.git` -- `https://github.com/TheBadRoger/ChuSQL-RustDriver.git` +| 依赖 | GitHub | 自建仓库 | +| --- | --- | --- | +| ChuSQL | https://github.com/TheBadRoger/ChuSQL.git | https://repo.chunana.cn/ChuNa7a/ChuSQL.git | +| ChuSQL-RustDriver | https://github.com/TheBadRoger/ChuSQL-RustDriver.git | https://repo.chunana.cn/ChuNa7a/ChuSQL-RustDriver.git | -已授权设备可显式优先使用自建服务器;SSH 连接、访问或固定提交获取失败时,自动回退到上面的 GitHub HTTPS 地址: - -```sh -python3 scripts/init-deps.py --prefer-ssh -``` - -该选项先尝试 `ChuGit:/srv/git/ChuSQL.git` 和 `ChuGit:/srv/git/ChuSQL-RustDriver.git`,需要设备已配置对应 SSH 身份。已正确初始化的固定版本直接复用,有未提交修改时拒绝覆盖;回退不改为任意最新分支。服务器已有的 Git 地址覆盖由服务器自行管理,项目不设置全局 `insteadOf`、localhost 映射、OS 用户或服务器 SSH 配置。 +初始化先尝试 GitHub HTTPS,连接、访问或固定提交获取失败时回退自建 HTTPS,无需 SSH 身份。已正确初始化的固定版本直接复用,有未提交修改时拒绝覆盖;回退不改为任意最新分支。项目不设置全局 `insteadOf`、localhost 映射、OS 用户或服务器 SSH 配置。 开发电脑仍可显式选择已有本地来源(仅开发覆盖参数,不是服务器默认流程);只读取已提交版本: @@ -51,7 +50,7 @@ cargo fmt --check cargo clippy --all-targets -- -D warnings ``` -部署基于 Debian 13。`scripts/deploy.sh` 会使用同一个依赖初始化脚本,再构建外部数据库与 Docker 镜像;沿用运行者现有的 Git 配置,不在项目内覆盖服务器环境。 +部署基于 Debian 13。`scripts/deploy.sh` 会使用同一个依赖初始化脚本,自动在上述两个 HTTPS 源之间回退,再构建外部数据库与 Docker 镜像;沿用运行者现有的 Git 配置,不在项目内覆盖服务器环境。 ```sh scripts/deploy.sh diff --git a/docs/decisions/0002-external-dependencies.md b/docs/decisions/0002-external-dependencies.md index 3ca91ce..c603236 100644 --- a/docs/decisions/0002-external-dependencies.md +++ b/docs/decisions/0002-external-dependencies.md @@ -8,7 +8,7 @@ 数据库服务与 Rust 驱动作为两个独立 Git 子模块维护。主仓库只提交远程地址、固定版本指针、初始化/构建配置,不提交依赖源码或编译产物。Cargo 引用初始化后的驱动路径,数据库从外部 checkout 构建并暂存到被忽略的 Docker 构建目录。 -子模块默认使用 `https://github.com/TheBadRoger/ChuSQL.git` 与 `https://github.com/TheBadRoger/ChuSQL-RustDriver.git`,公开依赖不要求读者配置服务器 SSH 密钥。已授权设备可用 `--prefer-ssh` 先访问 `ChuGit:/srv/git/…`,失败后回退 GitHub HTTPS;空仓库留下的半初始化状态也可恢复。回退仍保持固定提交,不自动跟随远程最新分支。 +子模块默认使用 `https://github.com/TheBadRoger/ChuSQL.git` 与 `https://github.com/TheBadRoger/ChuSQL-RustDriver.git`。初始化脚本在连接、访问或固定提交获取失败时,分别回退到 `https://repo.chunana.cn/ChuNa7a/ChuSQL.git` 与 `https://repo.chunana.cn/ChuNa7a/ChuSQL-RustDriver.git`。所有远程来源均使用 HTTPS,不提供 SSH 来源选项;空仓库留下的半初始化状态也可恢复。回退仍保持固定提交,不自动跟随远程最新分支。 服务器上的 Git 地址覆盖、操作系统账户和 SSH 配置由服务器自己管理,项目不配置 localhost 映射或固定 deploy 用户。Docker 构建前使用同一初始化脚本,继承运行者现有 Git 配置。开发者仍可以显式使用已有文件路径,此时文件协议仅对该次初始化命令启用,不设置全局许可。 diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 72ca6e9..339295e 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Prepare configured external dependencies, then build and start Docker Compose. +# Initialize dependencies via GitHub HTTPS with self-hosted HTTPS fallback, then build Docker Compose. set -eu PROJECT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) python3 "$PROJECT_ROOT/scripts/init-deps.py" diff --git a/scripts/init-deps.py b/scripts/init-deps.py index ea6bca9..2028047 100755 --- a/scripts/init-deps.py +++ b/scripts/init-deps.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Initialize pinned dependencies over GitHub HTTPS; optionally prefer authorized SSH.""" +"""Initialize pinned dependencies over GitHub HTTPS with a self-hosted HTTPS fallback.""" import argparse import os from pathlib import Path @@ -14,7 +14,7 @@ def git(*args, **kwargs): return subprocess.run(['git', *args], cwd=ROOT, **kwargs) -def initialize(name, override, prefer_ssh=False): +def initialize(name, override): path = f'external/{name}' checkout = ROOT / path pin = git('ls-files', '--stage', '--', path, text=True, capture_output=True, check=True).stdout.split() @@ -34,13 +34,9 @@ def initialize(name, override, prefer_ssh=False): raise RuntimeError(f'Local repository does not exist: {source}') sources = [source.as_uri()] else: - sources = [f'https://github.com/TheBadRoger/{name}.git'] - if prefer_ssh: - sources.insert(0, f'ChuGit:/srv/git/{name}.git') + sources = [f'https://github.com/TheBadRoger/{name}.git', + f'https://repo.chunana.cn/ChuNa7a/{name}.git'] transport_env = {**os.environ, 'GIT_TERMINAL_PROMPT': '0'} - configured_ssh = git('config', '--get', 'core.sshCommand', capture_output=True, check=False) - if configured_ssh.returncode != 0 and not any(name in transport_env for name in ['GIT_SSH', 'GIT_SSH_COMMAND']): - transport_env['GIT_SSH_COMMAND'] = 'ssh -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1' for url in sources: print(f'{name}: trying {url}', flush=True) git('config', '--local', f'submodule.{path}.url', url, check=True) @@ -63,10 +59,9 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--database-repo', type=Path, help='explicit local development source for ChuSQL') parser.add_argument('--driver-repo', type=Path, help='explicit local development source for the driver') - parser.add_argument('--prefer-ssh', action='store_true', help='authorized devices: try ChuGit SSH first, then GitHub HTTPS') args = parser.parse_args() for name, override in zip(PROJECTS, [args.database_repo, args.driver_repo]): - initialize(name, override, args.prefer_ssh) + initialize(name, override) if __name__ == '__main__': diff --git a/scripts/test-init-deps.py b/scripts/test-init-deps.py index d01a349..f7c0183 100644 --- a/scripts/test-init-deps.py +++ b/scripts/test-init-deps.py @@ -11,7 +11,7 @@ SCRIPT = Path(__file__).with_name('init-deps.py') class InitializeDependencies(unittest.TestCase): - def test_https_default_and_authorized_ssh_fallback_preserve_pins(self): + def test_https_default_and_self_hosted_fallback_preserve_pins(self): with tempfile.TemporaryDirectory(prefix='chublog deps ') as temporary: root = Path(temporary) project = root / 'blog' @@ -37,7 +37,7 @@ class InitializeDependencies(unittest.TestCase): # Dirty source must never leak into the blog or fetched dependency. (source / 'source.txt').write_text('uncommitted changes') path = f'external/{name}' - modules.append(f'[submodule "{path}"]\n path = {path}\n url = ChuGit:/srv/git/{name}.git\n') + modules.append(f'[submodule "{path}"]\n path = {path}\n url = https://github.com/TheBadRoger/{name}.git\n') git(project, 'update-index', '--add', '--cacheinfo', f'160000,{oid},{path}') sources.append(str(source)) (project / '.gitmodules').write_text(''.join(modules)) @@ -61,31 +61,25 @@ class InitializeDependencies(unittest.TestCase): for name in ['ChuSQL', 'ChuSQL-RustDriver']: self.assertEqual((fresh / f'external/{name}/source.txt').read_text(), f'{name} committed source') self.assertEqual(git(fresh / f'external/{name}', 'config', '--get', 'remote.origin.url'), f'https://github.com/TheBadRoger/{name}.git') - unavailable = root / 'unavailable' - git(root, 'clone', '--no-local', str(project), str(unavailable)) - (unavailable / 'scripts').mkdir() - shutil.copy2(SCRIPT, unavailable / 'scripts/init-deps.py') - subprocess.run(['python3', 'scripts/init-deps.py', '--prefer-ssh'], cwd=unavailable, env=fallback_env, check=True, capture_output=True) - for name in ['ChuSQL', 'ChuSQL-RustDriver']: - self.assertEqual((unavailable / f'external/{name}/source.txt').read_text(), f'{name} committed source') - # Also cover an accessible but empty authorized SSH bare repository: the initial - # submodule clone succeeds, fetching the pinned commit fails, then GitHub recovers. - empty = root / 'empty.git' - git(root, 'init', '--bare', str(empty)) + # An empty GitHub source clones successfully but lacks the pin; the + # self-hosted HTTPS fallback must recover the partial checkout. partial = root / 'partial' git(root, 'clone', '--no-local', str(project), str(partial)) (partial / 'scripts').mkdir() shutil.copy2(SCRIPT, partial / 'scripts/init-deps.py') - partial_env = {**fallback_env, 'GIT_CONFIG_COUNT': '4'} - for i, name in enumerate(['ChuSQL', 'ChuSQL-RustDriver'], start=2): + partial_env = {**env, 'GIT_ALLOW_PROTOCOL': 'file', 'GIT_CONFIG_COUNT': '4'} + for i, (name, source) in enumerate(zip(['ChuSQL', 'ChuSQL-RustDriver'], sources)): empty_source = root / f'empty-{name}.git' - git(root, 'clone', '--bare', str(empty), str(empty_source)) + git(root, 'init', '--bare', str(empty_source)) partial_env[f'GIT_CONFIG_KEY_{i}'] = f'url.{empty_source.as_uri()}.insteadOf' - partial_env[f'GIT_CONFIG_VALUE_{i}'] = f'ChuGit:/srv/git/{name}.git' - result = subprocess.run(['python3', 'scripts/init-deps.py', '--prefer-ssh'], cwd=partial, env=partial_env, text=True, capture_output=True) + partial_env[f'GIT_CONFIG_VALUE_{i}'] = f'https://github.com/TheBadRoger/{name}.git' + partial_env[f'GIT_CONFIG_KEY_{i + 2}'] = f'url.{Path(source).as_uri()}.insteadOf' + partial_env[f'GIT_CONFIG_VALUE_{i + 2}'] = f'https://repo.chunana.cn/ChuNa7a/{name}.git' + result = subprocess.run(['python3', 'scripts/init-deps.py'], cwd=partial, env=partial_env, text=True, capture_output=True) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) for name in ['ChuSQL', 'ChuSQL-RustDriver']: self.assertEqual((partial / f'external/{name}/source.txt').read_text(), f'{name} committed source') + self.assertEqual(git(partial / f'external/{name}', 'config', '--get', 'remote.origin.url'), f'https://repo.chunana.cn/ChuNa7a/{name}.git') # Running the initialization a second time must preserve the same pins. subprocess.run(['python3', 'scripts/init-deps.py', '--database-repo', sources[0], '--driver-repo', sources[1]], cwd=project, env=env, check=True, capture_output=True)