feat(proxy): support external TLS termination
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
DOMAIN=game.example.com
|
||||
PUBLIC_SCHEME=https
|
||||
ACME_EMAIL=admin@example.com
|
||||
# Set http://DOMAIN when an external reverse proxy terminates TLS.
|
||||
CADDY_SITE_ADDRESS=
|
||||
HTTP_PORT=80
|
||||
HTTPS_PORT=443
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ DNS의 `DOMAIN` A/AAAA record가 호스트를 가리키고 80/443 TCP 및 443 UD
|
||||
Core2026 clone, frozen-lockfile install, build와 Gateway migration 때문에 수 분이
|
||||
걸릴 수 있습니다.
|
||||
|
||||
외부 reverse proxy가 TLS를 종료하고 이 stack의 HTTP port로 전달한다면
|
||||
`PUBLIC_SCHEME=https`, `CADDY_SITE_ADDRESS=http://DOMAIN`을 함께 설정합니다.
|
||||
예를 들어 host `14999`로 전달할 때는 `HTTP_PORT=14999`로 두고, 사용하지 않는
|
||||
`HTTPS_PORT`는 충돌하지 않는 별도 host port로 지정합니다. 외부 proxy는 원래
|
||||
`Host` header를 보존해야 합니다.
|
||||
|
||||
runtime은 기본적으로 memory/swap 각각 4 GiB, CPU 4개, PID 256개 상한을
|
||||
가집니다. 호스트 용량에 맞춰 `RUNTIME_*_LIMIT`을 조정할 수 있지만 0 또는
|
||||
무제한으로 두지 않습니다. PM2 process/restart 수가 예상보다 증가하면 로그 수집보다
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ services:
|
||||
image: caddy:2.10.2-alpine
|
||||
environment:
|
||||
DOMAIN: ${DOMAIN:?set DOMAIN in .env}
|
||||
SITE_ADDRESS: ${DOMAIN:?set DOMAIN in .env}
|
||||
SITE_ADDRESS: ${CADDY_SITE_ADDRESS:-${DOMAIN:?set DOMAIN in .env}}
|
||||
ACME_EMAIL: ${ACME_EMAIL:?set ACME_EMAIL in .env}
|
||||
ports:
|
||||
- ${HTTP_PORT:-80}:80
|
||||
|
||||
@@ -52,6 +52,18 @@ export const validateEnvironment = (env) => {
|
||||
errors.push('DOMAIN must be a hostname without a scheme, path, or port');
|
||||
}
|
||||
|
||||
const caddySiteAddress = env.CADDY_SITE_ADDRESS?.trim() ?? '';
|
||||
if (caddySiteAddress) {
|
||||
try {
|
||||
const parsed = new URL(caddySiteAddress);
|
||||
if (!['http:', 'https:'].includes(parsed.protocol) || parsed.hostname !== domain || parsed.pathname !== '/') {
|
||||
errors.push('CADDY_SITE_ADDRESS must be http://DOMAIN or https://DOMAIN');
|
||||
}
|
||||
} catch {
|
||||
errors.push('CADDY_SITE_ADDRESS must be http://DOMAIN or https://DOMAIN');
|
||||
}
|
||||
}
|
||||
|
||||
const email = env.ACME_EMAIL?.trim() ?? '';
|
||||
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.push('ACME_EMAIL must be a valid email address');
|
||||
|
||||
|
||||
@@ -20,6 +20,18 @@ test('accepts a complete public repository environment', () => {
|
||||
assert.deepEqual(validateEnvironment(validEnv), []);
|
||||
});
|
||||
|
||||
test('accepts an HTTP Caddy address for external TLS termination', () => {
|
||||
assert.deepEqual(
|
||||
validateEnvironment({ ...validEnv, CADDY_SITE_ADDRESS: 'http://game.test.invalid' }),
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a Caddy address for a different host', () => {
|
||||
const errors = validateEnvironment({ ...validEnv, CADDY_SITE_ADDRESS: 'http://other.test.invalid' });
|
||||
assert.ok(errors.some((error) => error.includes('CADDY_SITE_ADDRESS')));
|
||||
});
|
||||
|
||||
test('accepts paired HTTPS credentials without embedding them in the URL', () => {
|
||||
assert.deepEqual(
|
||||
validateEnvironment({ ...validEnv, CORE_REPOSITORY_USERNAME: 'deploy', CORE_REPOSITORY_TOKEN: 'token' }),
|
||||
|
||||
Reference in New Issue
Block a user