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

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

+80
View File
@@ -0,0 +1,80 @@
import { test, expect, chromium } from '@playwright/test'
import { mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { spawn, execFileSync, type ChildProcess } from 'node:child_process'
import { createServer } from 'node:https'
import { request } from 'node:http'
test('LAN works without public DNS; public HTTP and HTTPS share files through a remote proxy', async () => {
test.setTimeout(60_000)
const root=mkdtempSync(join(tmpdir(),'bluebird-multiple-access-'))
const binary=resolve('../build/bluebird'), config=join(root,'config.toml')
const lan='http://127.0.0.1:19081', http='http://files.example.test:19081', https='https://files.example.test:19082'
writeFileSync(config,readFileSync('../deploy/config.example.toml','utf8')
.replace('listen = "127.0.0.1:8080"','listen = "127.0.0.1:19081"')
.replace('public_url = "http://localhost:8080"',`public_url = "${https}"`)
.replace('access_urls = []',`access_urls = ["${http}", "${lan}"]`)
.replace('secure_cookies = false','secure_cookies = true')
.replace('allow_lan_http = false','allow_lan_http = true')
.replace('trusted_proxies = []','trusted_proxies = ["127.0.0.1/32"]')
.replace('min_free_bytes = 1073741824','min_free_bytes = 0'))
execFileSync(binary,['init','--config',config],{env:{...process.env,BLUEBIRD_ADMIN_PASSWORD:'multiple-access-password'}})
execFileSync('openssl',['req','-x509','-newkey','rsa:2048','-nodes','-keyout',join(root,'key.pem'),'-out',join(root,'cert.pem'),'-days','1','-subj','/CN=files.example.test'],{stdio:'ignore'})
let worker:ChildProcess|undefined
const proxy=createServer({key:readFileSync(join(root,'key.pem')),cert:readFileSync(join(root,'cert.pem'))},(incoming,outgoing)=>{
const upstream=request(lan+incoming.url,{method:incoming.method,headers:{...incoming.headers,host:'127.0.0.1:19081','x-forwarded-host':incoming.headers.host,'x-forwarded-proto':'https','x-forwarded-for':'192.168.1.61'}},response=>{
outgoing.writeHead(response.statusCode!,response.headers);response.pipe(outgoing)
});upstream.on('error',()=>{outgoing.writeHead(502);outgoing.end()});incoming.pipe(upstream)
})
const browser=await chromium.launch({env:{...process.env,HTTP_PROXY:'',HTTPS_PROXY:'',ALL_PROXY:'',http_proxy:'',https_proxy:'',all_proxy:''},args:['--host-resolver-rules=MAP files.example.test 127.0.0.1','--no-proxy-server']})
try {
worker=spawn(binary,['serve','--config',config],{stdio:'ignore'})
await expect.poll(async()=>{try{return(await fetch(lan+'/api/v1/health')).status}catch{return 0}}).toBe(200)
await new Promise<void>(ready=>proxy.listen(19082,'127.0.0.1',ready))
const context=await browser.newContext({ignoreHTTPSErrors:true}), page=await context.newPage()
page.setDefaultTimeout(10_000)
const errors:string[]=[];page.on('pageerror',e=>errors.push(e.message))
// Abort every request to the public hostname: the LAN entrance must remain usable.
await page.route('**://files.example.test:*/**',route=>route.abort('namenotresolved'))
await page.goto(lan);await expect(page.getByRole('heading',{name:'准备寄件'})).toBeVisible()
await page.locator('input[type=file]').setInputFiles({name:'three-entrances.txt',mimeType:'text/plain',buffer:Buffer.from('same file in LAN, HTTP and HTTPS')})
await page.getByRole('button',{name:'上传并生成取件码'}).click()
await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible()
const lanLink=await page.getByRole('textbox',{name:'取件链接'}).inputValue()
expect(lanLink.startsWith(lan+'/#/pickup/')).toBeTruthy()
const qrBefore=await page.getByAltText('取件二维码').getAttribute('src')
await page.locator('.share-entry .n-base-selection').click()
await page.getByText('公网HTTPS · '+https,{exact:true}).click()
const httpsLink=await page.getByRole('textbox',{name:'取件链接'}).inputValue()
expect(httpsLink).toBe(lanLink.replace(lan,https))
await expect.poll(()=>page.getByAltText('取件二维码').getAttribute('src')).not.toBe(qrBefore)
await page.getByRole('button',{name:'完成',exact:true}).click()
await page.unroute('**://files.example.test:*/**')
for(const base of [lan,https,http]) {
if(base!==lan){
await page.goto(base);await expect(page.getByRole('heading',{name:'准备寄件'})).toBeVisible()
await page.locator('input[type=file]').setInputFiles({name:'public-upload.txt',mimeType:'text/plain',buffer:Buffer.from('public upload')})
await page.getByRole('button',{name:'上传并生成取件码'}).click();await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible()
expect((await page.getByRole('textbox',{name:'取件链接'}).inputValue()).startsWith(base+'/#/pickup/')).toBeTruthy()
await page.getByRole('button',{name:'完成',exact:true}).click()
}
await page.goto(lanLink.replace(lan,base))
await expect(page.getByRole('heading',{name:'你的文件已就位'})).toBeVisible()
const pending=page.waitForEvent('download');await page.getByRole('button',{name:'下载',exact:true}).click()
expect(readFileSync((await (await pending).path())!,'utf8')).toBe('same file in LAN, HTTP and HTTPS')
await page.goto(base+'/#/admin');await page.getByRole('textbox').first().fill('admin');await page.locator('input[type=password]').fill('multiple-access-password')
await page.getByRole('button',{name:'登录',exact:true}).click();await expect(page.getByRole('tab',{name:'服务策略'})).toBeVisible()
}
const cookies=await context.cookies(https+'/api/v1/admin/status')
expect(cookies.some(c=>c.name==='bb_admin'&&c.secure)).toBeTruthy()
expect(cookies.some(c=>c.name==='bb_admin_http'&&!c.secure)).toBeTruthy()
await page.goto(http+'/#/admin');await expect(page.getByRole('tab',{name:'服务策略'})).toBeVisible()
expect((await context.cookies(http+'/api/v1/admin/status')).some(c=>c.name==='bb_admin')).toBeFalsy()
expect(errors).toEqual([])
} finally {
await browser.close();proxy.closeAllConnections();await new Promise<void>(done=>proxy.close(()=>done()))
if(worker&&worker.exitCode===null){worker.kill('SIGTERM');await new Promise(done=>worker!.once('exit',done))}
rmSync(root,{recursive:true,force:true})
}
})