Skip to content

Commit 499ec6f

Browse files
authored
refactor: split src/host-env.ts into focused modules
1 parent fa89388 commit 499ec6f

15 files changed

Lines changed: 468 additions & 437 deletions

src/compose-generator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import * as path from 'path';
33
import { DockerComposeConfig, WrapperConfig } from './types';
44
import { DEFAULT_DNS_SERVERS } from './dns-resolver';
55
import { parseImageTag } from './image-tag';
6-
import { SslConfig, getRealUserHome } from './host-env';
6+
import { SslConfig } from './host-env';
7+
import { getRealUserHome } from './host-identity';
78
import { buildSquidService } from './services/squid-service';
89
import { buildAgentEnvironment, buildAgentVolumes, buildAgentService, buildIptablesInitService } from './services/agent-service';
910
import { buildApiProxyService } from './services/api-proxy-service';

src/config-writer.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@ import { WrapperConfig, API_PROXY_PORTS } from './types';
55
import { logger } from './logger';
66
import { generateSquidConfig, generatePolicyManifest } from './squid-config';
77
import { generateSessionCa, initSslDb, parseUrlPatterns, isOpenSslAvailable } from './ssl-bump';
8-
import {
9-
SQUID_PORT,
10-
SslConfig,
11-
getSafeHostUid,
12-
getSafeHostGid,
13-
getRealUserHome,
14-
} from './host-env';
8+
import { SQUID_PORT } from './constants';
9+
import { SslConfig } from './host-env';
10+
import { getSafeHostUid, getSafeHostGid, getRealUserHome } from './host-identity';
1511
import { generateDockerCompose, redactDockerComposeSecrets } from './compose-generator';
1612

1713
// When bundled with esbuild, this global is replaced at build time with the

src/constants.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Container names used in Docker Compose and referenced by docker CLI commands.
3+
* Extracted as constants so that generateDockerCompose() and helpers like
4+
* fastKillAgentContainer() stay in sync.
5+
*/
6+
export const AGENT_CONTAINER_NAME = 'awf-agent';
7+
export const SQUID_CONTAINER_NAME = 'awf-squid';
8+
export const IPTABLES_INIT_CONTAINER_NAME = 'awf-iptables-init';
9+
export const API_PROXY_CONTAINER_NAME = 'awf-api-proxy';
10+
export const DOH_PROXY_CONTAINER_NAME = 'awf-doh-proxy';
11+
export const CLI_PROXY_CONTAINER_NAME = 'awf-cli-proxy';
12+
13+
export const SQUID_PORT = 3128;
14+
15+
/**
16+
* Maximum size (bytes) of a single environment variable value allowed through
17+
* --env-all passthrough. Variables exceeding this are skipped with a warning
18+
* to prevent E2BIG errors from ARG_MAX exhaustion.
19+
*/
20+
export const MAX_ENV_VALUE_SIZE = 64 * 1024; // 64 KB
21+
22+
/**
23+
* Total environment size (bytes) threshold for issuing an ARG_MAX warning.
24+
* Linux ARG_MAX is ~2 MB for argv + envp combined; warn well before that.
25+
*/
26+
export const ENV_SIZE_WARNING_THRESHOLD = 1_500_000; // ~1.5 MB

src/container-cleanup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
SQUID_CONTAINER_NAME,
1111
IPTABLES_INIT_CONTAINER_NAME,
1212
API_PROXY_CONTAINER_NAME,
13-
getLocalDockerEnv,
14-
} from './host-env';
13+
} from './constants';
14+
import { getLocalDockerEnv } from './docker-host';
1515

1616
/**
1717
* Collects diagnostic logs from AWF containers on failure.

src/container-lifecycle.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
IPTABLES_INIT_CONTAINER_NAME,
1111
API_PROXY_CONTAINER_NAME,
1212
CLI_PROXY_CONTAINER_NAME,
13-
getLocalDockerEnv,
14-
} from './host-env';
13+
} from './constants';
14+
import { getLocalDockerEnv } from './docker-host';
1515

1616
/**
1717
* Flag set by fastKillAgentContainer() to signal runAgentCommand() that

src/docker-host.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Optional override for the Docker host used by AWF's own container operations.
3+
* Set via setAwfDockerHost() from the CLI --docker-host flag.
4+
* When undefined, AWF auto-selects the local socket (see getLocalDockerEnv).
5+
*/
6+
let awfDockerHostOverride: string | undefined;
7+
8+
/**
9+
* Sets the Docker host to use for AWF's own container operations.
10+
*
11+
* When set, overrides DOCKER_HOST for all docker CLI calls made by AWF
12+
* (compose up/down, docker wait, docker logs, etc.).
13+
*
14+
* When not set, AWF auto-detects:
15+
* - unix:// DOCKER_HOST values are kept as-is (local socket).
16+
* - TCP DOCKER_HOST values (e.g. DinD) are cleared so docker falls back
17+
* to the system default socket.
18+
*
19+
* @internal Called from cli.ts when --docker-host flag is provided.
20+
*/
21+
export function setAwfDockerHost(host: string | undefined): void {
22+
awfDockerHostOverride = host;
23+
}
24+
25+
/**
26+
* Returns an environment object suitable for AWF's own docker CLI calls.
27+
*
28+
* When DOCKER_HOST is set to an external TCP daemon (e.g. a workflow-scope
29+
* DinD sidecar), it is removed so docker/docker-compose use the local Unix
30+
* socket instead. When --docker-host was provided via the CLI, that value
31+
* is used regardless of the environment.
32+
*
33+
* The original DOCKER_HOST value is NOT removed from the agent container's
34+
* environment — see generateDockerCompose for the passthrough logic.
35+
*/
36+
export function getLocalDockerEnv(): NodeJS.ProcessEnv {
37+
const env = { ...process.env };
38+
39+
if (awfDockerHostOverride !== undefined) {
40+
// Explicit CLI override — always use this socket for AWF operations
41+
env.DOCKER_HOST = awfDockerHostOverride;
42+
} else {
43+
const dockerHost = env.DOCKER_HOST;
44+
if (dockerHost && !dockerHost.startsWith('unix://')) {
45+
// Non-unix DOCKER_HOST (e.g. tcp://localhost:2375 from a DinD sidecar).
46+
// Clear it so AWF's docker commands target the local daemon, not the DinD one.
47+
delete env.DOCKER_HOST;
48+
}
49+
}
50+
51+
return env;
52+
}

src/github-env.ts

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import * as fs from 'fs';
2+
import { logger } from './logger';
3+
4+
/**
5+
* Extracts the hostname from GITHUB_SERVER_URL to set GH_HOST for gh CLI.
6+
* Returns the hostname if GITHUB_SERVER_URL points to a non-mygithub.libinneed.workers.dev instance,
7+
* or null if it points to github.com (no GH_HOST needed).
8+
* @param serverUrl - The GITHUB_SERVER_URL environment variable value
9+
* @returns The hostname to use for GH_HOST, or null if not needed
10+
* @internal Exported for testing
11+
*/
12+
export function extractGhHostFromServerUrl(serverUrl: string | undefined): string | null {
13+
if (!serverUrl) {
14+
return null;
15+
}
16+
17+
try {
18+
const url = new URL(serverUrl);
19+
const hostname = url.hostname;
20+
21+
// If pointing to public GitHub, no GH_HOST needed
22+
if (hostname === 'github.com') {
23+
return null;
24+
}
25+
26+
// For GHES/GHEC instances, return the hostname
27+
return hostname;
28+
} catch {
29+
// Invalid URL, return null
30+
return null;
31+
}
32+
}
33+
34+
/**
35+
* Reads path entries from the $GITHUB_PATH file used by GitHub Actions.
36+
*
37+
* When setup-* actions (e.g., setup-ruby, setup-dart, setup-python) run before AWF,
38+
* they add tool paths to the $GITHUB_PATH file. The Actions runner prepends these
39+
* to $PATH for subsequent steps, but if `sudo` resets PATH (depending on sudoers
40+
* configuration), those entries may be lost by the time AWF reads process.env.PATH.
41+
*
42+
* This function reads the $GITHUB_PATH file directly and returns any path entries
43+
* found, so they can be merged into AWF_HOST_PATH regardless of sudo behavior.
44+
*
45+
* @returns Array of path entries from the $GITHUB_PATH file, or empty array if unavailable
46+
* @internal Exported for testing
47+
*/
48+
export function readGitHubPathEntries(): string[] {
49+
const githubPathFile = process.env.GITHUB_PATH;
50+
if (!githubPathFile) {
51+
logger.debug('GITHUB_PATH env var is not set; skipping $GITHUB_PATH file merge (tools installed by setup-* actions may be missing from PATH if sudo reset it)');
52+
return [];
53+
}
54+
55+
try {
56+
const content = fs.readFileSync(githubPathFile, 'utf-8');
57+
return content
58+
.split('\n')
59+
.map(line => line.trim())
60+
.filter(line => line.length > 0);
61+
} catch {
62+
// File doesn't exist or isn't readable — expected outside GitHub Actions
63+
logger.debug(`GITHUB_PATH file at '${githubPathFile}' could not be read; skipping file merge`);
64+
return [];
65+
}
66+
}
67+
68+
/**
69+
* Reads key-value environment entries from the $GITHUB_ENV file.
70+
*
71+
* The Actions runner writes to this file when steps call `core.exportVariable()`.
72+
* When AWF runs via `sudo`, non-standard env vars may be stripped. This function
73+
* reads the file directly to recover them.
74+
*
75+
* Supports both formats used by the Actions runner:
76+
* - Simple: `KEY=VALUE` (value may contain `=`)
77+
* - Heredoc: `KEY<<DELIMITER\nVALUE_LINES\nDELIMITER`
78+
*
79+
* @returns Map of environment variable names to values
80+
* @internal Exported for testing
81+
*/
82+
export function readGitHubEnvEntries(): Record<string, string> {
83+
const githubEnvFile = process.env.GITHUB_ENV;
84+
if (!githubEnvFile) {
85+
logger.debug('GITHUB_ENV env var is not set; skipping $GITHUB_ENV file read');
86+
return {};
87+
}
88+
89+
try {
90+
const content = fs.readFileSync(githubEnvFile, 'utf-8');
91+
return parseGitHubEnvFile(content);
92+
} catch {
93+
logger.debug(`GITHUB_ENV file at '${githubEnvFile}' could not be read; skipping`);
94+
return {};
95+
}
96+
}
97+
98+
/**
99+
* Parses the content of a $GITHUB_ENV file into key-value pairs.
100+
* @internal Exported for testing
101+
*/
102+
export function parseGitHubEnvFile(content: string): Record<string, string> {
103+
const result: Record<string, string> = {};
104+
// Normalize CRLF to LF
105+
const lines = content.replace(/\r\n/g, '\n').split('\n');
106+
let i = 0;
107+
108+
while (i < lines.length) {
109+
const line = lines[i];
110+
111+
// Skip empty lines
112+
if (line.trim() === '') {
113+
i++;
114+
continue;
115+
}
116+
117+
// Check for heredoc format: KEY<<DELIMITER
118+
const heredocMatch = line.match(/^([^=]+)<<(.+)$/);
119+
if (heredocMatch) {
120+
const key = heredocMatch[1];
121+
const delimiter = heredocMatch[2];
122+
const valueLines: string[] = [];
123+
i++;
124+
125+
// Collect lines until we find the delimiter
126+
while (i < lines.length && lines[i] !== delimiter) {
127+
valueLines.push(lines[i]);
128+
i++;
129+
}
130+
// Skip the closing delimiter line
131+
if (i < lines.length) i++;
132+
133+
result[key] = valueLines.join('\n');
134+
continue;
135+
}
136+
137+
// Simple format: KEY=VALUE (split on first = only)
138+
const eqIdx = line.indexOf('=');
139+
if (eqIdx > 0) {
140+
const key = line.slice(0, eqIdx);
141+
const value = line.slice(eqIdx + 1);
142+
result[key] = value;
143+
}
144+
145+
i++;
146+
}
147+
148+
return result;
149+
}
150+
151+
/**
152+
* Toolchain environment variables that should be recovered from $GITHUB_ENV
153+
* when sudo strips them from process.env. These are set by setup-* actions
154+
* (setup-go, setup-java, setup-dotnet, etc.) and are needed for correct
155+
* tool resolution inside the agent container.
156+
*/
157+
export const TOOLCHAIN_ENV_VARS = [
158+
'GOROOT',
159+
'CARGO_HOME',
160+
'RUSTUP_HOME',
161+
'JAVA_HOME',
162+
'DOTNET_ROOT',
163+
'BUN_INSTALL',
164+
] as const;
165+
166+
/**
167+
* Merges path entries from the $GITHUB_PATH file into a PATH string.
168+
* Entries from $GITHUB_PATH are prepended (they have higher priority, matching
169+
* how the Actions runner processes them). Duplicate entries are removed.
170+
*
171+
* @param currentPath - The current PATH string (e.g., from process.env.PATH)
172+
* @param githubPathEntries - Path entries read from the $GITHUB_PATH file
173+
* @returns Merged PATH string with $GITHUB_PATH entries prepended
174+
* @internal Exported for testing
175+
*/
176+
export function mergeGitHubPathEntries(currentPath: string, githubPathEntries: string[]): string {
177+
if (githubPathEntries.length === 0) {
178+
return currentPath;
179+
}
180+
181+
const currentEntries = currentPath ? currentPath.split(':') : [];
182+
const currentSet = new Set(currentEntries);
183+
184+
// Only add entries that aren't already in the current PATH
185+
const newEntries = githubPathEntries.filter(entry => !currentSet.has(entry));
186+
187+
if (newEntries.length === 0) {
188+
return currentPath;
189+
}
190+
191+
// Prepend new entries (setup-* actions expect their paths to have priority)
192+
return [...newEntries, ...currentEntries].join(':');
193+
}
194+
195+
/**
196+
* Reads environment variables from a KEY=VALUE file (like Docker's --env-file).
197+
*
198+
* Rules:
199+
* - Lines starting with '#' are comments and are ignored.
200+
* - Empty/whitespace-only lines are ignored.
201+
* - Each non-comment line must match the pattern KEY=VALUE where KEY starts with a
202+
* letter or underscore and contains only letters, digits, or underscores.
203+
* - Values may be empty (KEY=).
204+
* - Values are taken literally; no quote-stripping or variable expansion is done.
205+
*
206+
* @param filePath - Absolute or relative path to the env file
207+
* @returns An object mapping variable names to their values
208+
* @throws {Error} If the file cannot be read
209+
*/
210+
export function readEnvFile(filePath: string): Record<string, string> {
211+
const content = fs.readFileSync(filePath, 'utf-8');
212+
const result: Record<string, string> = {};
213+
for (const raw of content.split('\n')) {
214+
const line = raw.trim();
215+
// Skip comments and blank lines
216+
if (line === '' || line.startsWith('#')) continue;
217+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
218+
if (match) {
219+
result[match[1]] = match[2];
220+
}
221+
}
222+
return result;
223+
}

0 commit comments

Comments
 (0)