初始化蓝鸟文件快递柜源码与部署文档

This commit is contained in:
lhc committed 2026-10-05 23:41:07 +08:00
commit 5643d22242
70 files changed
+18669

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
"""Exercise a real previous installation inside a disposable rootless container.
Requires podman, the old tarball argument and the newly built amd64 release.
"""
import http.cookies
import json
from pathlib import Path
import secrets
import subprocess
import sys
import tarfile
import tempfile
import time
import urllib.request
project = Path(__file__).resolve().parent.parent
name = 'bluebird-upgrade-' + secrets.token_hex(4)
password = 'upgrade-preserved-password'
base = 'http://127.0.0.1:18996'
origin = 'https://files.example.test'
def run(*args, **kw):
return subprocess.run(args, check=True, capture_output=True, text=True, **kw).stdout
def inside(*args, **kw):
return run('podman', 'exec', name, *args, **kw)
with tempfile.TemporaryDirectory(prefix='bluebird-upgrade-old-') as old:
with tarfile.open(sys.argv[1]) as archive:
archive.extractall(old, filter='data')
try:
run('podman', 'run', '--rm', '-d', '--security-opt', 'label=disable', '--name', name,
'-p', '127.0.0.1:18996:18996', '-v', old+':/oldpkg:ro',
'-v', str(project/'build/linux-amd64')+':/pkg:ro',
'docker.io/library/debian:bookworm-slim', 'sleep', '300')
inside('env', 'BLUEBIRD_ADMIN_PASSWORD='+password, '/oldpkg/install.sh',
'--url', origin, '--listen', '0.0.0.0:18996', '--encrypt', '--no-start')
inside('sed','-i','s/min_free_bytes = 1073741824/min_free_bytes = 0/', '/opt/bluebird/config.toml')
key_before = inside('sha256sum', '/opt/bluebird/var/secrets/age.key')
cookies = {}
def call(method, path, body=None):
data = body if isinstance(body, bytes) else json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(base+'/api/v1'+path, data=data, method=method, headers={
'Origin': origin, 'X-Bluebird-Origin': origin,
'Cookie': '; '.join(k+'='+v for k,v in cookies.items()),
'Content-Type': 'application/octet-stream' if isinstance(body,bytes) else 'application/json'})
with urllib.request.urlopen(req, timeout=10) as response:
for line in response.headers.get_all('Set-Cookie',[]):
parsed = http.cookies.SimpleCookie(); parsed.load(line)
cookies.update({k:v.value for k,v in parsed.items()})
result=response.read()
return result if path.endswith('/download') else json.loads(result)
def start():
subprocess.run(['podman','exec','-d',name,'runuser','-u','bluebird','--','/opt/bluebird/bluebird','serve','--config','/opt/bluebird/config.toml'],check=True)
for _ in range(50):
try:
if call('GET','/health')['ready']: return
except OSError: time.sleep(.1)
raise AssertionError('Container service not ready')
start()
call('GET','/policy')
parcel=call('POST','/uploads',{'files':[{'name':'preserved.txt','size':21}],'days':7,'downloads':5,'renew':True})
call('PUT',f"/uploads/{parcel['id']}/files/{parcel['files'][0]['id']}", b'old encrypted payload')
share=call('POST',f"/uploads/{parcel['id']}/publish",{})
call('POST','/admin/login',{'username':'admin','password':password})
before=inside('sha256sum','/opt/bluebird/config.toml','/opt/bluebird/bluebird')
# Invalid candidates must not stop a running service or change files.
bad=subprocess.run(['podman','exec',name,'/pkg/upgrade.sh','--access-url','http://bad.test/path','--no-start'],capture_output=True)
assert bad.returncode != 0
assert before == inside('sha256sum','/opt/bluebird/config.toml','/opt/bluebird/bluebird')
assert call('GET','/health')['ready']
# No pgrep dependency: /proc/comm identifies only the native service.
inside('sh','-c','for p in /proc/[0-9]*; do [ "$(cat "$p/comm" 2>/dev/null)" != bluebird ] || kill -TERM "${p##*/}"; done')
for _ in range(50):
try: call('GET','/health')
except OSError: break
time.sleep(.1)
# Simulate a new-version integrity-check failure after files were replaced.
inside('sh','-c','mkdir /failpkg; cp /pkg/bluebird /failpkg/bluebird.real; cp /pkg/ARCH /pkg/upgrade.sh /failpkg/')
wrapper='#!/bin/sh\nif [ "$1" = check ]; then exit 1; fi\nexec "$(dirname "$0")/bluebird.real" "$@"\n'
inside('sh','-c','printf "%s" "$1" > /failpkg/bluebird; chmod 755 /failpkg/bluebird','wrapper',wrapper)
failed=subprocess.run(['podman','exec',name,'/failpkg/upgrade.sh','--lan-url',base,'--listen','0.0.0.0:18996','--no-start'],capture_output=True)
assert failed.returncode != 0 and '已恢复旧程序和配置' in failed.stderr.decode()
assert before == inside('sha256sum','/opt/bluebird/config.toml','/opt/bluebird/bluebird')
result=inside('/pkg/upgrade.sh','--lan-url',base,'--listen','0.0.0.0:18996',
'--access-url','http://files.example.test','--no-start')
assert '升级完成' in result
assert key_before == inside('sha256sum','/opt/bluebird/var/secrets/age.key')
assert inside('stat','-c','%U:%G:%a','/opt/bluebird/config.toml').strip() == 'root:bluebird:640'
assert 'data.tar.gz' in inside('find','/opt/bluebird/backups','-type','f')
start();cookies.clear()
call('POST','/admin/login',{'username':'admin','password':password})
assert call('GET','/admin/stats')['objects'] == 1
policy=call('GET','/policy');assert set(policy['access_urls']) == {origin,base,'http://files.example.test'}
for entrance in [origin,base,'http://files.example.test']:
origin=entrance;cookies.clear()
call('POST','/pickup/resolve',{'code':share['code']})
assert call('GET',f"/pickup/files/{parcel['files'][0]['id']}/download") == b'old encrypted payload'
print('升级验证通过:旧管理员密码、加密文件、密钥及取件码保留;三入口下载、权限、备份、非法配置保护及检查失败回滚通过。')
except subprocess.CalledProcessError as error:
print(error.stdout, error.stderr, file=sys.stderr)
raise
finally:
subprocess.run(['podman','rm','-f',name],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)