44 * health-checking, and cleanup for both registry and local modes.
55 */
66
7- import { execSync } from 'child_process' ;
8- import { shellEscape } from './shell.js' ;
9- import { DOCKER_WAIT_TIMEOUT , DOCKER_POLL_INTERVAL } from './constants.js' ;
7+ import { execFileSync } from 'child_process' ;
8+ import { DOCKER_WAIT_TIMEOUT } from './constants.js' ;
109
1110export interface ContainerSpec {
1211 challengeId : string ;
@@ -17,6 +16,11 @@ export interface ContainerSpec {
1716 targetContainerName : string ;
1817}
1918
19+ /** Synchronous sleep that works cross-platform (no shell, no `sleep` binary). */
20+ function sleepSync ( ms : number ) : void {
21+ Atomics . wait ( new Int32Array ( new SharedArrayBuffer ( 4 ) ) , 0 , 0 , ms ) ;
22+ }
23+
2024/**
2125 * Pull a Docker image. Tries native platform first, falls back to linux/amd64
2226 * if the image has no matching manifest (common for challenge images on Apple Silicon).
@@ -28,7 +32,7 @@ export function pullImage(image: string, onProgress?: (line: string) => void): b
2832 }
2933
3034 try {
31- execSync ( ` docker pull ${ shellEscape ( image ) } ` , {
35+ execFileSync ( ' docker' , [ ' pull' , image ] , {
3236 stdio : onProgress ? 'inherit' : 'pipe' ,
3337 encoding : 'utf-8' ,
3438 } ) ;
@@ -45,7 +49,7 @@ export function pullImage(image: string, onProgress?: (line: string) => void): b
4549 if ( onProgress ) {
4650 onProgress ( `Pulling ${ image } (linux/amd64 fallback)...` ) ;
4751 }
48- execSync ( ` docker pull --platform linux/amd64 ${ shellEscape ( image ) } ` , {
52+ execFileSync ( ' docker' , [ ' pull' , ' --platform' , ' linux/amd64' , image ] , {
4953 stdio : onProgress ? 'inherit' : 'pipe' ,
5054 encoding : 'utf-8' ,
5155 } ) ;
@@ -57,12 +61,12 @@ export function pullImage(image: string, onProgress?: (line: string) => void): b
5761 */
5862export function ensureNetwork ( name : string ) : void {
5963 try {
60- execSync ( ` docker network inspect ${ shellEscape ( name ) } ` , {
64+ execFileSync ( ' docker' , [ ' network' , ' inspect' , name ] , {
6165 stdio : 'pipe' ,
6266 encoding : 'utf-8' ,
6367 } ) ;
6468 } catch {
65- execSync ( ` docker network create ${ shellEscape ( name ) } ` , {
69+ execFileSync ( ' docker' , [ ' network' , ' create' , name ] , {
6670 stdio : 'pipe' ,
6771 encoding : 'utf-8' ,
6872 } ) ;
@@ -83,24 +87,23 @@ export function startContainers(spec: ContainerSpec, platforms?: PlatformOverrid
8387 cleanupStale ( spec ) ;
8488 ensureNetwork ( spec . network ) ;
8589
86- const targetPlatformFlag = platforms ?. target ? `--platform ${ shellEscape ( platforms . target ) } ` : '' ;
87- const kaliPlatformFlag = platforms ?. kali ? `--platform ${ shellEscape ( platforms . kali ) } ` : '' ;
88-
8990 // Start target container
90- execSync (
91- `docker run -d ${ targetPlatformFlag } --name ${ shellEscape ( spec . targetContainerName ) } ` +
92- `--hostname target --network ${ shellEscape ( spec . network ) } ` +
93- `${ shellEscape ( spec . targetImage ) } ` ,
94- { stdio : 'pipe' , encoding : 'utf-8' }
95- ) ;
91+ const targetArgs = [ 'run' , '-d' ] ;
92+ if ( platforms ?. target ) targetArgs . push ( '--platform' , platforms . target ) ;
93+ targetArgs . push ( '--name' , spec . targetContainerName ) ;
94+ targetArgs . push ( '--hostname' , 'target' ) ;
95+ targetArgs . push ( '--network' , spec . network ) ;
96+ targetArgs . push ( spec . targetImage ) ;
97+ execFileSync ( 'docker' , targetArgs , { stdio : 'pipe' , encoding : 'utf-8' } ) ;
9698
9799 // Start kali container
98- execSync (
99- `docker run -d ${ kaliPlatformFlag } --name ${ shellEscape ( spec . kaliContainerName ) } ` +
100- `--hostname kali --network ${ shellEscape ( spec . network ) } ` +
101- `${ shellEscape ( spec . kaliImage ) } sleep infinity` ,
102- { stdio : 'pipe' , encoding : 'utf-8' }
103- ) ;
100+ const kaliArgs = [ 'run' , '-d' ] ;
101+ if ( platforms ?. kali ) kaliArgs . push ( '--platform' , platforms . kali ) ;
102+ kaliArgs . push ( '--name' , spec . kaliContainerName ) ;
103+ kaliArgs . push ( '--hostname' , 'kali' ) ;
104+ kaliArgs . push ( '--network' , spec . network ) ;
105+ kaliArgs . push ( spec . kaliImage , 'sleep' , 'infinity' ) ;
106+ execFileSync ( 'docker' , kaliArgs , { stdio : 'pipe' , encoding : 'utf-8' } ) ;
104107}
105108
106109/**
@@ -134,18 +137,17 @@ export function waitForTarget(
134137 timeoutMs = DOCKER_WAIT_TIMEOUT
135138) : void {
136139 const start = Date . now ( ) ;
137- const pollInterval = DOCKER_POLL_INTERVAL ;
138140
139141 while ( Date . now ( ) - start < timeoutMs ) {
140142 try {
141- execSync (
142- ` docker exec ${ shellEscape ( kaliContainer ) } curl -sf ${ shellEscape ( targetUrl ) } ` ,
143+ execFileSync (
144+ ' docker' , [ ' exec' , kaliContainer , ' curl' , ' -sf' , targetUrl ] ,
143145 { stdio : 'pipe' , encoding : 'utf-8' , timeout : 5000 }
144146 ) ;
145147 return ; // Success
146148 } catch {
147149 // Not ready yet — wait and retry
148- execSync ( `sleep 2` , { stdio : 'pipe' } ) ;
150+ sleepSync ( 2000 ) ;
149151 }
150152 }
151153
@@ -159,16 +161,16 @@ export function waitForTarget(
159161 */
160162export function cleanup ( spec : ContainerSpec ) : void {
161163 try {
162- execSync (
163- ` docker rm -f ${ shellEscape ( spec . targetContainerName ) } ${ shellEscape ( spec . kaliContainerName ) } ` ,
164+ execFileSync (
165+ ' docker' , [ 'rm' , '-f' , spec . targetContainerName , spec . kaliContainerName ] ,
164166 { stdio : 'pipe' , encoding : 'utf-8' }
165167 ) ;
166168 } catch {
167169 // Containers may not exist
168170 }
169171
170172 try {
171- execSync ( ` docker network rm ${ shellEscape ( spec . network ) } ` , {
173+ execFileSync ( ' docker' , [ ' network' , 'rm' , spec . network ] , {
172174 stdio : 'pipe' ,
173175 encoding : 'utf-8' ,
174176 } ) ;
@@ -182,8 +184,8 @@ export function cleanup(spec: ContainerSpec): void {
182184 */
183185export function cleanupStale ( spec : ContainerSpec ) : void {
184186 try {
185- execSync (
186- ` docker rm -f ${ shellEscape ( spec . targetContainerName ) } ${ shellEscape ( spec . kaliContainerName ) } 2>/dev/null` ,
187+ execFileSync (
188+ ' docker' , [ 'rm' , '-f' , spec . targetContainerName , spec . kaliContainerName ] ,
187189 { stdio : 'pipe' , encoding : 'utf-8' }
188190 ) ;
189191 } catch {
@@ -195,7 +197,7 @@ export function cleanupStale(spec: ContainerSpec): void {
195197 * Start containers from a docker-compose.yml in the given directory.
196198 */
197199export function startFromCompose ( challengeDir : string ) : void {
198- execSync ( ` docker compose -f ${ shellEscape ( challengeDir ) } /docker-compose.yml up -d --build` , {
200+ execFileSync ( ' docker' , [ ' compose' , '-f' , ` ${ challengeDir } /docker-compose.yml` , 'up' , '-d' , ' --build' ] , {
199201 stdio : 'inherit' ,
200202 encoding : 'utf-8' ,
201203 } ) ;
@@ -205,7 +207,7 @@ export function startFromCompose(challengeDir: string): void {
205207 * Stop and remove containers from a docker-compose.yml in the given directory.
206208 */
207209export function stopFromCompose ( challengeDir : string ) : void {
208- execSync ( ` docker compose -f ${ shellEscape ( challengeDir ) } /docker-compose.yml down` , {
210+ execFileSync ( ' docker' , [ ' compose' , '-f' , ` ${ challengeDir } /docker-compose.yml` , ' down' ] , {
209211 stdio : 'pipe' ,
210212 encoding : 'utf-8' ,
211213 } ) ;
0 commit comments