修改docker部署方式

This commit is contained in:
ChuNa7a committed 2026-10-17 13:13:26 +08:00
1 parent 3e7c9420fe
commit 3c6f2c0b7b
18 files changed
+323 -95

No files matched your search

+6 -2
View File
@@ -3,6 +3,10 @@
set -eu
PROJECT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
python3 "$PROJECT_ROOT/scripts/init-deps.py"
python3 "$PROJECT_ROOT/scripts/prepare-deploy.py" --build-database
docker compose --env-file "$PROJECT_ROOT/.env" -f "$PROJECT_ROOT/deploy/compose.yaml" up -d --build
if [ ! -f "$PROJECT_ROOT/.env" ]; then
printf '%s\n' 'Create and configure .env first; see docs/deployment.md.' >&2
exit 1
fi
python3 "$PROJECT_ROOT/scripts/prepare-deploy.py"
docker compose --env-file "$PROJECT_ROOT/.env" -f "$PROJECT_ROOT/deploy/compose.yaml" up -d --no-build
printf '%s\n' 'When the database is ready, run the documented db-init command once.'
+26 -25
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Build the API and stage external ChuSQL artifacts without committing dependencies."""
"""Build deployment images inside Docker; no host Rust or Haskell tools required."""
import argparse
from pathlib import Path
import shutil
@@ -7,28 +7,29 @@ import subprocess
root = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--no-build', action='store_true', help='use an existing API release binary')
parser.add_argument('--chusql-source', type=Path, default=root / 'external/ChuSQL', help='initialized/built external ChuSQL checkout')
parser.add_argument('--build-database', action='store_true', help='build external database artifacts before staging')
parser.add_argument('--env-file', type=Path, help='Compose environment file (default: .env, or .env.example when absent)')
parser.add_argument('--build-database', action='store_true', help='compatibility option; database is always built inside Docker')
parser.add_argument('--sources-env-file', type=Path, help='optional second Compose env file overriding build download sources')
args = parser.parse_args()
chusql = args.chusql_source.resolve()
if not (root / 'external/ChuSQL-RustDriver/Cargo.toml').is_file():
parser.error('Initialize external dependencies with scripts/init-deps.py first.')
if not (chusql / 'chusql-core/storage/Cargo.toml').is_file():
parser.error(f'ChuSQL source is missing: {chusql}; initialize dependencies first.')
if not args.no_build:
subprocess.run(['cargo', 'build', '--locked', '--release'], cwd=root, check=True)
if args.build_database:
subprocess.run(['cargo', 'build', '--locked', '--release'], cwd=chusql / 'chusql-core/storage', check=True)
for package in ['chusql-server', 'chusql-bootstrap']:
subprocess.run(['stack', 'build', '--fast'], cwd=chusql / package, check=True)
out = root / 'deploy/bin'
out.mkdir(parents=True, exist_ok=True)
shutil.copy2(root / 'target/release/chublog-api', out / 'chublog-api')
for package, executable in [('chusql-server', 'chusql-server'), ('chusql-bootstrap', 'csql-bootstrap')]:
matches = list((chusql / package / '.stack-work/install').glob(f'*/**/bin/{executable}'))
if len(matches) != 1:
raise SystemExit(f'Expected one built {executable}, found {len(matches)}; use --build-database or select a built --chusql-source.')
shutil.copy2(matches[0], out / executable)
shutil.copy2(chusql / 'chusql-core/storage/target/release/libchusql_core_storage.so', out)
print('Prepared ignored deploy/bin. Build and run Compose on the same Debian 13 architecture.')
if shutil.which('docker') is None:
parser.error('Docker with the Compose plugin is required; see docs/deployment.md.')
for manifest in ['external/ChuSQL-RustDriver/Cargo.toml', 'external/ChuSQL/chusql-core/storage/Cargo.toml']:
if not (root / manifest).is_file():
parser.error('Initialize external dependencies with python3 scripts/init-deps.py first.')
envfile = args.env_file or (root / '.env' if (root / '.env').is_file() else root / '.env.example')
if not envfile.is_file():
parser.error(f'Environment file does not exist: {envfile}')
command = ['docker', 'compose', '--env-file', str(envfile.resolve())]
if args.sources_env_file:
if not args.sources_env_file.is_file():
parser.error(f'Source configuration does not exist: {args.sources_env_file}')
command.extend(['--env-file', str(args.sources_env_file.resolve())])
command.extend([
'-f', str(root / 'deploy/compose.yaml'),
'build', 'init', 'api', 'ssh', 'db', 'db-init', 'web',
])
try:
subprocess.run(command, cwd=root, check=True)
except subprocess.CalledProcessError as error:
raise SystemExit(error.returncode) from None
print('Deployment images built inside Docker. No host binaries were created.')
+15 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Exercise real Docker, SSH, Git, HTTP authorization, database outage, and restart recovery.
Uses an isolated Compose project and removes only that project's containers/volumes.
Run prepare-deploy.py first. Ports 18080, 18443, 12222 must be free.
Initializes no dependencies; Docker builds the images. Ports 18080, 18443, 12222 must be free.
"""
import argparse
import http.cookiejar
@@ -19,6 +19,7 @@ import urllib.request
ROOT = Path(__file__).resolve().parents[1]
ORIGIN = 'http://localhost:18080'
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--sources-env-file', type=Path, help='Compose build source overrides, e.g. deploy/sources.public.env')
parser.add_argument('--browser-test', action='store_true', help='also run Python Playwright frontend journeys')
parser.add_argument('--key-type', choices=['ed25519', 'rsa'], default='ed25519')
args = parser.parse_args()
@@ -66,7 +67,7 @@ class Client:
with tempfile.TemporaryDirectory(prefix='chublog-smoke-') as temporary:
tmp = Path(temporary)
password = secrets.token_urlsafe(24)
hashed = run([str(ROOT / 'deploy/bin/chublog-api'), 'hash-password'], input=password, capture_output=True).stdout.strip()
hashed = 'temporary-build-placeholder'
envfile = tmp / '.env'
envfile.write_text(f"PUBLIC_ORIGIN={ORIGIN}\nSITE_ADDRESS=http://localhost:80\nADMIN_PASSWORD_HASH='{hashed}'\n"
f"CHUSQL_ROOT_PASSWORD={secrets.token_urlsafe(24)}\nCHUSQL_PASSWORD={secrets.token_urlsafe(24)}\n"
@@ -74,7 +75,12 @@ with tempfile.TemporaryDirectory(prefix='chublog-smoke-') as temporary:
'DEBIAN_MIRROR=mirrors.ustc.edu.cn\nALLOW_REGISTRATION=true\nHTTP_BIND=127.0.0.1\nHTTP_PORT=18080\nHTTPS_PORT=18443\nSSH_BIND=127.0.0.1\nSSH_PORT=12222\n')
envfile.chmod(0o600)
project = 'chublog-test-' + secrets.token_hex(4)
compose = ['docker', 'compose', '--env-file', str(envfile), '-p', project, '-f', str(ROOT / 'deploy/compose.yaml')]
compose = ['docker', 'compose', '--env-file', str(envfile)]
if args.sources_env_file:
if not args.sources_env_file.is_file():
parser.error(f'Source configuration does not exist: {args.sources_env_file}')
compose.extend(['--env-file', str(args.sources_env_file.resolve())])
compose.extend(['-p', project, '-f', str(ROOT / 'deploy/compose.yaml')])
def database_articles():
# Query the private DB from inside its container; no host database port is opened.
probe = r'''set -eu
@@ -95,7 +101,12 @@ printf '%s\n' "$reply"
return response['rows']
try:
run(compose + ['up', '-d', '--build'])
run(compose + ['build', 'init', 'api', 'ssh', 'db', 'db-init', 'web'])
hashed = run(compose + ['run', '--rm', '--no-deps', '-T', 'api', 'hash-password'],
input=password, capture_output=True).stdout.strip()
assert hashed.startswith('$argon2id$'), 'Container did not produce an Argon2id hash'
envfile.write_text(envfile.read_text().replace('temporary-build-placeholder', hashed))
run(compose + ['up', '-d', '--no-build'])
admin = Client()
wait_for(lambda: admin.call('GET', '/api/health'))
wait_for(lambda: subprocess.run(compose + ['run', '--rm', 'db-init'], capture_output=True).returncode == 0)
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Check deployment preparation without host compilers or a Docker daemon."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
SCRIPT = Path(__file__).with_name('prepare-deploy.py')
class PrepareDeployment(unittest.TestCase):
def test_docker_only_build_and_failure_propagation(self):
with tempfile.TemporaryDirectory(prefix='chublog deploy ') as temporary:
root = Path(temporary)
(root / 'scripts').mkdir()
shutil.copy2(SCRIPT, root / 'scripts/prepare-deploy.py')
for name in ['external/ChuSQL-RustDriver/Cargo.toml',
'external/ChuSQL/chusql-core/storage/Cargo.toml']:
manifest = root / name
manifest.parent.mkdir(parents=True)
manifest.touch()
(root / '.env.example').touch()
tools = root / 'tools'
tools.mkdir()
docker = tools / 'docker'
docker.write_text(f'#!{sys.executable}\nimport json, os, sys\n'
'from pathlib import Path\n'
'Path(os.environ["CALL_LOG"]).write_text(json.dumps(sys.argv[1:]))\n'
'sys.exit(int(os.environ.get("DOCKER_EXIT", "0")))\n')
docker.chmod(0o755)
log = root / 'call.json'
env = {**os.environ, 'PATH': str(tools), 'CALL_LOG': str(log)}
command = [sys.executable, str(root / 'scripts/prepare-deploy.py')]
result = subprocess.run(command + ['--build-database'], env=env, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
call = json.loads(log.read_text())
self.assertEqual(call[:3], ['compose', '--env-file', str(root / '.env.example')])
self.assertIn('db', call)
self.assertIn('web', call)
self.assertFalse((root / 'deploy/bin').exists())
override = root / 'public sources.env'
override.write_text('REGISTRY_PREFIX=public.ecr.aws/docker/library/\n')
result = subprocess.run(command + ['--sources-env-file', str(override)],
env=env, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(json.loads(log.read_text())[3:5], ['--env-file', str(override)])
(root / '.env').touch()
result = subprocess.run(command, env={**env, 'DOCKER_EXIT': '17'}, capture_output=True, text=True)
self.assertEqual(result.returncode, 17)
self.assertEqual(json.loads(log.read_text())[2], str(root / '.env'))
docker.unlink()
result = subprocess.run(command, env=env, capture_output=True, text=True)
self.assertEqual(result.returncode, 2)
self.assertIn('Docker with the Compose plugin is required', result.stderr)
if __name__ == '__main__':
unittest.main()