merge: support reverse-proxy HTTP upstream
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
DOMAIN=game.example.com
|
DOMAIN=game.example.com
|
||||||
PUBLIC_SCHEME=https
|
PUBLIC_SCHEME=https
|
||||||
ACME_EMAIL=admin@example.com
|
ACME_EMAIL=admin@example.com
|
||||||
|
# Set http://DOMAIN when an external reverse proxy terminates TLS.
|
||||||
|
CADDY_SITE_ADDRESS=
|
||||||
HTTP_PORT=80
|
HTTP_PORT=80
|
||||||
HTTPS_PORT=443
|
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 때문에 수 분이
|
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은 기본적으로 memory/swap 각각 4 GiB, CPU 4개, PID 256개 상한을
|
||||||
가집니다. 호스트 용량에 맞춰 `RUNTIME_*_LIMIT`을 조정할 수 있지만 0 또는
|
가집니다. 호스트 용량에 맞춰 `RUNTIME_*_LIMIT`을 조정할 수 있지만 0 또는
|
||||||
무제한으로 두지 않습니다. PM2 process/restart 수가 예상보다 증가하면 로그 수집보다
|
무제한으로 두지 않습니다. PM2 process/restart 수가 예상보다 증가하면 로그 수집보다
|
||||||
|
|||||||
+1
-1
@@ -121,7 +121,7 @@ services:
|
|||||||
image: caddy:2.10.2-alpine
|
image: caddy:2.10.2-alpine
|
||||||
environment:
|
environment:
|
||||||
DOMAIN: ${DOMAIN:?set DOMAIN in .env}
|
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}
|
ACME_EMAIL: ${ACME_EMAIL:?set ACME_EMAIL in .env}
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT:-80}:80
|
- ${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');
|
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() ?? '';
|
const email = env.ACME_EMAIL?.trim() ?? '';
|
||||||
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.push('ACME_EMAIL must be a valid email address');
|
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), []);
|
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', () => {
|
test('accepts paired HTTPS credentials without embedding them in the URL', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
validateEnvironment({ ...validEnv, CORE_REPOSITORY_USERNAME: 'deploy', CORE_REPOSITORY_TOKEN: 'token' }),
|
validateEnvironment({ ...validEnv, CORE_REPOSITORY_USERNAME: 'deploy', CORE_REPOSITORY_TOKEN: 'token' }),
|
||||||
|
|||||||
Reference in New Issue
Block a user