Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions containers/api-proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,5 @@ USER apiproxy
# 10004 - OpenCode API proxy (routes to Anthropic)
EXPOSE 10000 10001 10002 10004

# Redirect stdout/stderr to log file for persistence
# Use shell form to enable redirection and tee for both file and console
CMD node server.js 2>&1 | tee -a /var/log/api-proxy/api-proxy.log
# Use exec form so node is PID 1 and receives SIGTERM directly
CMD ["node", "server.js"]
Comment on lines +33 to +34
13 changes: 13 additions & 0 deletions src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,12 @@ describe('docker-manager', () => {
expect(squid.ports).toContain('3128:3128');
});

it('should set stop_grace_period on squid service', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);
const squid = result.services['squid-proxy'] as any;
expect(squid.stop_grace_period).toBe('2s');
});

it('should inject squid config via base64 env var when content is provided', () => {
const squidConfig = 'http_port 3128\nacl allowed_domains dstdomain .github.qkg1.top\n';
const result = generateDockerCompose(mockConfig, mockNetworkConfig, undefined, squidConfig);
Expand Down Expand Up @@ -1789,6 +1795,13 @@ describe('docker-manager', () => {
expect(proxy.security_opt).toContain('no-new-privileges:true');
});

it('should set stop_grace_period on api-proxy service', () => {
const configWithProxy = { ...mockConfig, enableApiProxy: true, openaiApiKey: 'sk-test-key' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'] as any;
expect(proxy.stop_grace_period).toBe('2s');
});

it('should set resource limits', () => {
const configWithProxy = { ...mockConfig, enableApiProxy: true, openaiApiKey: 'sk-test-key' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
Expand Down
2 changes: 2 additions & 0 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export function generateDockerCompose(
'AUDIT_WRITE', // No audit log writing
'SETFCAP', // No setting file capabilities
],
stop_grace_period: '2s',
};

// Inject squid.conf via environment variable instead of bind mount.
Expand Down Expand Up @@ -1238,6 +1239,7 @@ export function generateDockerCompose(
memswap_limit: '512m',
pids_limit: 100,
cpu_shares: 512,
stop_grace_period: '2s',
};

// Use GHCR image or build locally
Expand Down
9 changes: 9 additions & 0 deletions src/squid-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,15 @@ describe('generateSquidConfig', () => {
const result = generateSquidConfig(config);
expect(result).toContain('pconn_timeout 2 minutes');
});

it('should include shutdown_lifetime 0 for fast shutdown', () => {
const config: SquidConfig = {
domains: ['example.com'],
port: defaultPort,
};
const result = generateSquidConfig(config);
expect(result).toContain('shutdown_lifetime 0 seconds');
});
});

describe('Real-world Domain Patterns', () => {
Expand Down
4 changes: 4 additions & 0 deletions src/squid-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,10 @@ client_lifetime 8 hours
# Critical for SSE where server sends but client doesn't respond
half_closed_clients on

# shutdown_lifetime: Time to wait for active connections during shutdown
# Set to 0 because this is an ephemeral proxy — no connection draining needed
shutdown_lifetime 0 seconds

# Debugging (can be enabled for troubleshooting)
# debug_options ALL,1 33,2
`;
Expand Down
Loading