11package standalone
22
33import (
4+ "archive/tar"
5+ "bytes"
46 "context"
57 "errors"
68 "fmt"
@@ -27,6 +29,100 @@ const controllerContainerName = "docker-model-runner"
2729// the conflicting container in a capture group.
2830var concurrentInstallMatcher = regexp .MustCompile (`is already in use by container "([a-z0-9]+)"` )
2931
32+ // copyDockerConfigToContainer copies the Docker config file from the host to the container
33+ // and sets up proper ownership and permissions for the modelrunner user.
34+ func copyDockerConfigToContainer (ctx context.Context , dockerClient * client.Client , containerID string ) error {
35+ dockerConfigPath := os .ExpandEnv ("$HOME/.docker/config.json" )
36+ if s , err := os .Stat (dockerConfigPath ); err != nil || s .Mode ()& os .ModeType != 0 {
37+ return nil
38+ }
39+
40+ configData , err := os .ReadFile (dockerConfigPath )
41+ if err != nil {
42+ return fmt .Errorf ("failed to read Docker config file: %w" , err )
43+ }
44+
45+ var buf bytes.Buffer
46+ tw := tar .NewWriter (& buf )
47+ header := & tar.Header {
48+ Name : ".docker/config.json" ,
49+ Mode : 0600 ,
50+ Size : int64 (len (configData )),
51+ }
52+ if err := tw .WriteHeader (header ); err != nil {
53+ return fmt .Errorf ("failed to write tar header: %w" , err )
54+ }
55+ if _ , err := tw .Write (configData ); err != nil {
56+ return fmt .Errorf ("failed to write config data to tar: %w" , err )
57+ }
58+ if err := tw .Close (); err != nil {
59+ return fmt .Errorf ("failed to close tar writer: %w" , err )
60+ }
61+
62+ // Ensure the .docker directory exists
63+ mkdirCmd := "mkdir -p /home/modelrunner/.docker && chown modelrunner:modelrunner /home/modelrunner/.docker"
64+ if err := execInContainer (ctx , dockerClient , containerID , mkdirCmd ); err != nil {
65+ return err
66+ }
67+
68+ // Copy directly into the .docker directory
69+ err = dockerClient .CopyToContainer (ctx , containerID , "/home/modelrunner" , & buf , container.CopyToContainerOptions {
70+ CopyUIDGID : true ,
71+ })
72+ if err != nil {
73+ return fmt .Errorf ("failed to copy config file to container: %w" , err )
74+ }
75+
76+ // Set correct ownership and permissions
77+ chmodCmd := "chown modelrunner:modelrunner /home/modelrunner/.docker/config.json && chmod 600 /home/modelrunner/.docker/config.json"
78+ if err := execInContainer (ctx , dockerClient , containerID , chmodCmd ); err != nil {
79+ return err
80+ }
81+
82+ return nil
83+ }
84+
85+ func execInContainer (ctx context.Context , dockerClient * client.Client , containerID , cmd string ) error {
86+ execConfig := container.ExecOptions {
87+ Cmd : []string {"sh" , "-c" , cmd },
88+ }
89+ execResp , err := dockerClient .ContainerExecCreate (ctx , containerID , execConfig )
90+ if err != nil {
91+ return fmt .Errorf ("failed to create exec for command '%s': %w" , cmd , err )
92+ }
93+ if err := dockerClient .ContainerExecStart (ctx , execResp .ID , container.ExecStartOptions {}); err != nil {
94+ return fmt .Errorf ("failed to start exec for command '%s': %w" , cmd , err )
95+ }
96+
97+ // Create a timeout context for the polling loop
98+ timeoutCtx , cancel := context .WithTimeout (ctx , 10 * time .Second )
99+ defer cancel ()
100+
101+ // Poll until the command finishes or timeout occurs
102+ for {
103+ inspectResp , err := dockerClient .ContainerExecInspect (ctx , execResp .ID )
104+ if err != nil {
105+ return fmt .Errorf ("failed to inspect exec for command '%s': %w" , cmd , err )
106+ }
107+
108+ if ! inspectResp .Running {
109+ // Command has finished, now we can safely check the exit code
110+ if inspectResp .ExitCode != 0 {
111+ return fmt .Errorf ("command '%s' failed with exit code %d" , cmd , inspectResp .ExitCode )
112+ }
113+ return nil
114+ }
115+
116+ // Brief sleep to avoid busy polling, with timeout check
117+ select {
118+ case <- time .After (100 * time .Millisecond ):
119+ // Continue polling
120+ case <- timeoutCtx .Done ():
121+ return fmt .Errorf ("command '%s' timed out after 10 seconds" , cmd )
122+ }
123+ }
124+ }
125+
30126// FindControllerContainer searches for a running controller container. It
31127// returns the ID of the container (if found), the container name (if any), the
32128// full container summary (if found), or any error that occurred.
@@ -196,6 +292,12 @@ func CreateControllerContainer(ctx context.Context, dockerClient *client.Client,
196292 _ = dockerClient .ContainerRemove (ctx , resp .ID , container.RemoveOptions {Force : true })
197293 return fmt .Errorf ("failed to start container %s: %w" , controllerContainerName , err )
198294 }
295+
296+ // Copy Docker config file if it exists
297+ if err := copyDockerConfigToContainer (ctx , dockerClient , resp .ID ); err != nil {
298+ // Log warning but continue - don't fail container creation
299+ printer .Printf ("Warning: failed to copy Docker config: %v\n " , err )
300+ }
199301 return nil
200302}
201303
0 commit comments