初始化蓝鸟文件快递柜源码与部署文档
This commit is contained in:
commit
5643d22242
70 files changed
+18669
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user