This commit is contained in:
parent
6ad6f449ca
commit
362cf0d4cc
@ -77,7 +77,9 @@ func CurrentHTTPConfig() HTTPConfig {
|
||||
func ResolveHTTPPortDefaults(value HTTPConfig, setupPort int) HTTPConfig {
|
||||
result := cloneHTTPConfig(value)
|
||||
result.TLSMode = strings.ToLower(strings.TrimSpace(result.TLSMode))
|
||||
if result.Port == 0 {
|
||||
if override := environmentPort("SMAIL_HTTP_PORT_OVERRIDE"); override != 0 {
|
||||
result.Port = override
|
||||
} else if result.Port == 0 {
|
||||
result.Port = environmentPort("SMAIL_HTTP_PORT")
|
||||
if result.Port == 0 && validServicePort(setupPort) {
|
||||
result.Port = setupPort
|
||||
@ -86,6 +88,9 @@ func ResolveHTTPPortDefaults(value HTTPConfig, setupPort int) HTTPConfig {
|
||||
result.Port = 80
|
||||
}
|
||||
}
|
||||
if override := strings.ToLower(strings.TrimSpace(os.Getenv("SMAIL_HTTP_TLS_MODE_OVERRIDE"))); override == HTTPSTLSModeDirect || override == HTTPSTLSModeProxy {
|
||||
result.TLSMode = override
|
||||
}
|
||||
if result.TLSMode == HTTPSTLSModeProxy {
|
||||
result.HTTPSPort = 0
|
||||
result.HTTPSEnabled = false
|
||||
|
||||
@ -3,6 +3,8 @@ package config
|
||||
import "testing"
|
||||
|
||||
func TestResolveHTTPPortDefaultsPreservesConfiguredPorts(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_TLS_MODE_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_PORT", "19001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "19002")
|
||||
|
||||
@ -17,6 +19,8 @@ func TestResolveHTTPPortDefaultsPreservesConfiguredPorts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsUsesEnvironmentPorts(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_TLS_MODE_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
@ -27,6 +31,8 @@ func TestResolveHTTPPortDefaultsUsesEnvironmentPorts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsUsesSetupAndDirectDefaults(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_TLS_MODE_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_PORT", "")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "")
|
||||
|
||||
@ -37,6 +43,8 @@ func TestResolveHTTPPortDefaultsUsesSetupAndDirectDefaults(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsDisablesInternalHTTPSInProxyMode(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_TLS_MODE_OVERRIDE", "")
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
@ -49,3 +57,20 @@ func TestResolveHTTPPortDefaultsDisablesInternalHTTPSInProxyMode(t *testing.T) {
|
||||
t.Fatalf("proxy HTTP config = %+v, want HTTP 9001 with internal HTTPS disabled", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsUsesExplicitRuntimeOverride(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT_OVERRIDE", "5201")
|
||||
t.Setenv("SMAIL_HTTP_TLS_MODE_OVERRIDE", HTTPSTLSModeProxy)
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
got := ResolveHTTPPortDefaults(HTTPConfig{
|
||||
Port: 80,
|
||||
HTTPSPort: 443,
|
||||
HTTPSEnabled: true,
|
||||
TLSMode: HTTPSTLSModeDirect,
|
||||
}, 18080)
|
||||
if got.Port != 5201 || got.HTTPSPort != 0 || got.HTTPSEnabled || got.TLSMode != HTTPSTLSModeProxy {
|
||||
t.Fatalf("runtime override HTTP config = %+v, want proxy API on port 5201", got)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,9 +31,10 @@ func ResolveWebAuthnSecurity(root RootConfig) (SecurityConfig, error) {
|
||||
}
|
||||
if len(security.WebAuthnRPOrigins) == 0 {
|
||||
origin := "https://" + web
|
||||
httpConfig := ResolveHTTPPortDefaults(root.HTTP, 0)
|
||||
// 反代的外部端口不等于内部 HTTP 端口,非标准外部端口需显式配置。
|
||||
if root.HTTP.TLSMode != HTTPSTLSModeProxy {
|
||||
port := ResolveHTTPPortDefaults(root.HTTP, 0).HTTPSPort
|
||||
if httpConfig.TLSMode != HTTPSTLSModeProxy {
|
||||
port := httpConfig.HTTPSPort
|
||||
if !validServicePort(port) {
|
||||
return SecurityConfig{}, fmt.Errorf("安全密钥站点 HTTPS 端口无效")
|
||||
}
|
||||
|
||||
@ -463,10 +463,7 @@ func dirInit() error {
|
||||
|
||||
// 等待 HTTP 服务启动,最长约 90 秒。
|
||||
func waitHTTPReady(serviceErrors <-chan error) error {
|
||||
port := 80
|
||||
if cfg := config.CurrentConfig(); cfg != nil && cfg.HttpPort > 0 {
|
||||
port = cfg.HttpPort
|
||||
}
|
||||
port := config.CurrentHTTPConfig().Port
|
||||
url := fmt.Sprintf("http://127.0.0.1:%d/api/ping", port)
|
||||
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
|
||||
@ -9,10 +9,13 @@ $logs = Join-Path $root 'test-log\runtime'
|
||||
New-Item -ItemType Directory -Path $logs -Force | Out-Null
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss-fff'
|
||||
$previousRoot = $env:SMAIL_ROOT
|
||||
$previousHTTPPortOverride = $env:SMAIL_HTTP_PORT_OVERRIDE
|
||||
$previousHTTPTLSModeOverride = $env:SMAIL_HTTP_TLS_MODE_OVERRIDE
|
||||
$previousSetupPort = $env:setup_port
|
||||
try {
|
||||
if ($Service -eq 'backend') {
|
||||
$binary = Join-Path $root 'out\backend\smail-dev.exe'
|
||||
if (Get-NetTCPConnection -State Listen -LocalPort 443 -ErrorAction SilentlyContinue) { throw '443 端口已占用,请先确认并停止旧后端' }
|
||||
if (Get-NetTCPConnection -State Listen -LocalPort 5201 -ErrorAction SilentlyContinue) { throw '5201 端口已占用,请先确认并停止旧后端' }
|
||||
$running = Get-CimInstance Win32_Process | Where-Object {
|
||||
$_.ExecutablePath -and $_.ExecutablePath -eq $binary
|
||||
}
|
||||
@ -32,8 +35,11 @@ try {
|
||||
if (-not (Test-Path -LiteralPath $binary -PathType Leaf)) { throw '请先运行 deploy/build.ps1 编译后端' }
|
||||
# SMAIL_ROOT 是 config 的父目录,不能指向 config 本身或编译输出目录。
|
||||
$env:SMAIL_ROOT = $root
|
||||
$env:SMAIL_HTTP_PORT_OVERRIDE = '5201'
|
||||
$env:SMAIL_HTTP_TLS_MODE_OVERRIDE = 'proxy'
|
||||
$env:setup_port = '5201'
|
||||
$arguments = @('--dev-disable-admin-reset')
|
||||
Write-Output '开发启动:沿用 config/config.json 和现有数据库;明确停用管理员恢复通道,不请求权限提升。'
|
||||
Write-Output '开发启动:后端 API 仅监听 5201;沿用 config/config.json 和现有数据库;明确停用管理员恢复通道,不请求权限提升。'
|
||||
if ($SkipBuild) { Write-Warning '已跳过编译,请自行确认二进制与当前源码一致。' }
|
||||
# 插件从 out/plugins 加载;账号、配置和密钥仍通过 SMAIL_ROOT 定位到根目录 config。
|
||||
foreach ($name in @('spam_block.exe', 'pmail_telegram_push.exe', 'wechat_push.exe')) {
|
||||
@ -43,11 +49,13 @@ try {
|
||||
}
|
||||
$workingDirectory = Join-Path $root 'out'
|
||||
} else {
|
||||
if (Get-NetTCPConnection -State Listen -LocalPort 15173 -ErrorAction SilentlyContinue) { throw '15173 端口已占用,请先确认并停止旧前端' }
|
||||
foreach ($port in @(80, 443)) {
|
||||
if (Get-NetTCPConnection -State Listen -LocalPort $port -ErrorAction SilentlyContinue) { throw "$port 端口已占用,请先确认并停止旧前端或旧后端" }
|
||||
}
|
||||
$binary = (Get-Command node -ErrorAction Stop).Source
|
||||
$vite = Join-Path $root 'frontend\node_modules\vite\bin\vite.js'
|
||||
if (-not (Test-Path -LiteralPath $vite)) { throw '请先安装 frontend 的依赖' }
|
||||
$arguments = @(('"{0}"' -f $vite), '--host', '127.0.0.1', '--port', '15173', '--strictPort')
|
||||
$arguments = @(('"{0}"' -f $vite), '--host', '127.0.0.1', '--port', '443', '--strictPort')
|
||||
$workingDirectory = Join-Path $root 'frontend'
|
||||
}
|
||||
$stdout = Join-Path $logs "$Service-$stamp.stdout.log"
|
||||
@ -61,4 +69,7 @@ try {
|
||||
Write-Output "运行文件:$binary;标准输出:$stdout;错误日志:$stderr"
|
||||
} finally {
|
||||
$env:SMAIL_ROOT = $previousRoot
|
||||
$env:SMAIL_HTTP_PORT_OVERRIDE = $previousHTTPPortOverride
|
||||
$env:SMAIL_HTTP_TLS_MODE_OVERRIDE = $previousHTTPTLSModeOverride
|
||||
$env:setup_port = $previousSetupPort
|
||||
}
|
||||
|
||||
@ -1 +1,5 @@
|
||||
VITE_DEV_BACKEND_URL=https://127.0.0.1:443
|
||||
VITE_DEV_BACKEND_URL=http://127.0.0.1:5201
|
||||
VITE_DEV_BACKEND_ORIGIN=https://xuans.com
|
||||
VITE_DEV_FRONTEND_ORIGIN=https://xuans.com
|
||||
SMAIL_DEV_TLS_CERTIFICATE=../config/ssl/xuans.com.crt
|
||||
SMAIL_DEV_TLS_PRIVATE_KEY=../config/ssl/xuans.com.key
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
/* eslint-env node */
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createServer as createHTTPServer } from 'node:http'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
@ -6,27 +9,40 @@ import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
const developmentHTTPRedirect = (frontendOrigin, host = '127.0.0.1', port = 80) => ({
|
||||
name: 'smail-development-http-redirect',
|
||||
apply: 'serve',
|
||||
async configureServer(server) {
|
||||
const redirectServer = createHTTPServer((request, response) => {
|
||||
const requestURL = new URL(request.url || '/', 'http://127.0.0.1')
|
||||
const location = new URL(`${requestURL.pathname}${requestURL.search}`, frontendOrigin)
|
||||
response.writeHead(308, {
|
||||
Location: location.toString(),
|
||||
'Cache-Control': 'no-store'
|
||||
})
|
||||
response.end()
|
||||
})
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
redirectServer.once('error', reject)
|
||||
redirectServer.listen(port, host, resolve)
|
||||
})
|
||||
server.httpServer?.once('close', () => redirectServer.close())
|
||||
}
|
||||
})
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const backendUrl = (env.VITE_DEV_BACKEND_URL || 'https://127.0.0.1:443').replace(/\/$/, '')
|
||||
const backendUrl = (env.VITE_DEV_BACKEND_URL || 'http://127.0.0.1:5201').replace(/\/$/, '')
|
||||
const backendOrigin = (env.VITE_DEV_BACKEND_ORIGIN || 'https://xuans.com').replace(/\/$/, '')
|
||||
|
||||
const rewriteDevelopmentCookies = (proxyResponse) => {
|
||||
const setCookies = proxyResponse.headers['set-cookie']
|
||||
if (!Array.isArray(setCookies)) {
|
||||
return
|
||||
}
|
||||
|
||||
proxyResponse.headers['set-cookie'] = setCookies.map((cookie) => (
|
||||
cookie
|
||||
.replace(/;\s*Secure/gi, '')
|
||||
.replace(/;\s*Domain=[^;]*/gi, '')
|
||||
))
|
||||
}
|
||||
const frontendOrigin = (env.VITE_DEV_FRONTEND_ORIGIN || 'https://xuans.com').replace(/\/$/, '')
|
||||
const certificatePath = path.resolve(process.cwd(), env.SMAIL_DEV_TLS_CERTIFICATE || '../config/ssl/xuans.com.crt')
|
||||
const privateKeyPath = path.resolve(process.cwd(), env.SMAIL_DEV_TLS_PRIVATE_KEY || '../config/ssl/xuans.com.key')
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
developmentHTTPRedirect(frontendOrigin),
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
@ -41,6 +57,13 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 443,
|
||||
strictPort: true,
|
||||
https: {
|
||||
cert: readFileSync(certificatePath),
|
||||
key: readFileSync(privateKeyPath)
|
||||
},
|
||||
cors: true,
|
||||
proxy: {
|
||||
"/api": {
|
||||
@ -55,7 +78,6 @@ export default defineConfig(({ mode }) => {
|
||||
proxyRequest.setHeader('x-forwarded-proto', originURL.protocol.replace(':', ''))
|
||||
proxyRequest.removeHeader('referer')
|
||||
})
|
||||
proxy.on('proxyRes', rewriteDevelopmentCookies)
|
||||
}
|
||||
},
|
||||
"/attachments": {
|
||||
|
||||
@ -52,6 +52,13 @@ test('开发启动必须固定运行配置根目录并集中保存日志', async
|
||||
assert.match(content, /-WindowStyle Hidden/)
|
||||
assert.match(content, /\[switch\]\$SkipBuild/)
|
||||
assert.match(content, /--dev-disable-admin-reset/)
|
||||
assert.match(content, /SMAIL_HTTP_PORT_OVERRIDE = '5201'/)
|
||||
assert.match(content, /SMAIL_HTTP_TLS_MODE_OVERRIDE = 'proxy'/)
|
||||
assert.match(content, /LocalPort 5201/)
|
||||
assert.match(content, /@\(80, 443\)/)
|
||||
assert.match(await read('frontend/.env.development'), /VITE_DEV_BACKEND_URL=http:\/\/127\.0\.0\.1:5201/)
|
||||
assert.match(await read('frontend/vite.config.js'), /port: 443/)
|
||||
assert.match(await read('frontend/vite.config.js'), /smail-development-http-redirect/)
|
||||
assert.match(content, /默认先编译当前工作区/)
|
||||
assert.ok(content.charCodeAt(0) === 0xfeff, 'Windows PowerShell 5 脚本应有 UTF-8 BOM')
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user