@@ -21,7 +21,6 @@ import os from 'os';
2121import path from 'path' ;
2222import retry from 'async-retry' ;
2323import * as handlebars from 'handlebars' ;
24- import * as util from 'util' ;
2524import * as core from '@actions/core' ;
2625import * as httpm from '@actions/http-client' ;
2726import * as io from '@actions/io' ;
@@ -56,6 +55,7 @@ export interface InstallOpts {
5655 runDir : string ;
5756 contextName ?: string ;
5857 daemonConfig ?: string ;
58+ rootless ?: boolean ;
5959}
6060
6161interface LimaImage {
@@ -65,19 +65,21 @@ interface LimaImage {
6565}
6666
6767export class Install {
68- private readonly runDir : string ;
68+ private runDir : string ;
6969 private readonly source : InstallSource ;
7070 private readonly contextName : string ;
7171 private readonly daemonConfig ?: string ;
7272 private _version : string | undefined ;
7373 private _toolDir : string | undefined ;
74+ private rootless : boolean ;
7475
7576 private gitCommit : string | undefined ;
7677
7778 private readonly limaInstanceName = 'docker-actions-toolkit' ;
7879
7980 constructor ( opts : InstallOpts ) {
8081 this . runDir = opts . runDir ;
82+ this . rootless = opts . rootless || false ;
8183 this . source = opts . source || {
8284 type : 'archive' ,
8385 version : 'latest' ,
@@ -91,25 +93,25 @@ export class Install {
9193 return this . _toolDir || Context . tmpDir ( ) ;
9294 }
9395
94- async downloadStaticArchive ( src : InstallSourceArchive ) : Promise < string > {
96+ async downloadStaticArchive ( component : 'docker' | 'docker-rootless-extras' , src : InstallSourceArchive ) : Promise < string > {
9597 const release : GitHubRelease = await Install . getRelease ( src . version ) ;
9698 this . _version = release . tag_name . replace ( / ^ v + | v + $ / g, '' ) ;
9799 core . debug ( `docker.Install.download version: ${ this . _version } ` ) ;
98100
99- const downloadURL = this . downloadURL ( this . _version , src . channel ) ;
101+ const downloadURL = this . downloadURL ( component , this . _version , src . channel ) ;
100102 core . info ( `Downloading ${ downloadURL } ` ) ;
101103
102104 const downloadPath = await tc . downloadTool ( downloadURL ) ;
103105 core . debug ( `docker.Install.download downloadPath: ${ downloadPath } ` ) ;
104106
105- let extractFolder : string ;
107+ let extractFolder ;
106108 if ( os . platform ( ) == 'win32' ) {
107- extractFolder = await tc . extractZip ( downloadPath ) ;
109+ extractFolder = await tc . extractZip ( downloadPath , extractFolder ) ;
108110 } else {
109- extractFolder = await tc . extractTar ( downloadPath ) ;
111+ extractFolder = await tc . extractTar ( downloadPath , extractFolder ) ;
110112 }
111- if ( Util . isDirectory ( path . join ( extractFolder , 'docker' ) ) ) {
112- extractFolder = path . join ( extractFolder , 'docker' ) ;
113+ if ( Util . isDirectory ( path . join ( extractFolder , component ) ) ) {
114+ extractFolder = path . join ( extractFolder , component ) ;
113115 }
114116 core . debug ( `docker.Install.download extractFolder: ${ extractFolder } ` ) ;
115117 return extractFolder ;
@@ -164,7 +166,12 @@ export class Install {
164166 this . _version = version ;
165167
166168 core . info ( `Downloading Docker ${ version } from ${ this . source . channel } at download.docker.com` ) ;
167- extractFolder = await this . downloadStaticArchive ( this . source ) ;
169+ extractFolder = await this . downloadStaticArchive ( 'docker' , this . source ) ;
170+ if ( this . rootless ) {
171+ core . info ( `Downloading Docker rootless extras ${ version } from ${ this . source . channel } at download.docker.com` ) ;
172+ const extrasFolder = await this . downloadStaticArchive ( 'docker-rootless-extras' , this . source ) ;
173+ fs . copyFileSync ( path . join ( extrasFolder , 'dockerd-rootless.sh' ) , path . join ( extractFolder , 'dockerd-rootless.sh' ) ) ;
174+ }
168175 break ;
169176 }
170177 }
@@ -195,7 +202,13 @@ export class Install {
195202 if ( ! this . runDir ) {
196203 throw new Error ( 'runDir must be set' ) ;
197204 }
198- switch ( os . platform ( ) ) {
205+
206+ const platform = os . platform ( ) ;
207+ if ( this . rootless && platform != 'linux' ) {
208+ // TODO: Support on macOS (via lima)
209+ throw new Error ( `rootless is only supported on linux` ) ;
210+ }
211+ switch ( platform ) {
199212 case 'darwin' : {
200213 return await this . installDarwin ( ) ;
201214 }
@@ -339,21 +352,34 @@ export class Install {
339352 }
340353
341354 const envs = Object . assign ( { } , process . env , {
342- PATH : `${ this . toolDir } :${ process . env . PATH } `
355+ PATH : `${ this . toolDir } :${ process . env . PATH } ` ,
356+ XDG_RUNTIME_DIR : ( this . rootless && this . runDir ) || undefined
343357 } ) as {
344358 [ key : string ] : string ;
345359 } ;
346360
347361 await core . group ( 'Start Docker daemon' , async ( ) => {
348362 const bashPath : string = await io . which ( 'bash' , true ) ;
349- const cmd = `${ this . toolDir } /dockerd --host="${ dockerHost } " --config-file="${ daemonConfigPath } " --exec-root="${ this . runDir } /execroot" --data-root="${ this . runDir } /data" --pidfile="${ this . runDir } /docker.pid" --userland-proxy=false` ;
363+ let dockerPath = `${ this . toolDir } /dockerd` ;
364+ if ( this . rootless ) {
365+ dockerPath = `${ this . toolDir } /dockerd-rootless.sh` ;
366+ if ( fs . existsSync ( '/proc/sys/kernel/apparmor_restrict_unprivileged_userns' ) ) {
367+ await Exec . exec ( 'sudo' , [ 'sh' , '-c' , 'echo 0 > /proc/sys/kernel/apparmor_restrict_unprivileged_userns' ] ) ;
368+ }
369+ }
370+
371+ const cmd = `${ dockerPath } --host="${ dockerHost } " --config-file="${ daemonConfigPath } " --exec-root="${ this . runDir } /execroot" --data-root="${ this . runDir } /data" --pidfile="${ this . runDir } /docker.pid"` ;
350372 core . info ( `[command] ${ cmd } ` ) ; // https://github.com/actions/toolkit/blob/3d652d3133965f63309e4b2e1c8852cdbdcb3833/packages/exec/src/toolrunner.ts#L47
373+ let sudo = 'sudo' ;
374+ if ( this . rootless ) {
375+ sudo += ' -u \\#1001' ;
376+ }
351377 const proc = await child_process . spawn (
352378 // We can't use Exec.exec here because we need to detach the process to
353379 // avoid killing it when the action finishes running. Even if detached,
354380 // we also need to run dockerd in a subshell and unref the process so
355381 // GitHub Action doesn't wait for it to finish.
356- `sudo env "PATH=$PATH" ${ bashPath } << EOF
382+ `${ sudo } env "PATH=$PATH" ${ bashPath } << EOF
357383( ${ cmd } 2>&1 | tee "${ this . runDir } /dockerd.log" ) &
358384EOF` ,
359385 [ ] ,
@@ -517,11 +543,11 @@ EOF`,
517543 } ) ;
518544 }
519545
520- private downloadURL ( version : string , channel : string ) : string {
546+ private downloadURL ( component : 'docker' | 'docker-rootless-extras' , version : string , channel : string ) : string {
521547 const platformOS = Install . platformOS ( ) ;
522548 const platformArch = Install . platformArch ( ) ;
523549 const ext = platformOS === 'win' ? '.zip' : '.tgz' ;
524- return util . format ( ' https://download.docker.com/%s /static/%s/%s/docker-%s%s' , platformOS , channel , platformArch , version , ext ) ;
550+ return ` https://download.docker.com/${ platformOS } /static/${ channel } / ${ platformArch } / ${ component } - ${ version } ${ ext } ` ;
525551 }
526552
527553 private static platformOS ( ) : string {
0 commit comments