@leverege/build-tools 2.116.0 → 2.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/Docker.mjs +4 -0
- package/src/branch-protect-it.mjs +178 -0
- package/src/docker-to-registry.mjs +1 -1
- package/src/github-actions-it.mjs +3 -0
- package/src/helm-charts/authz-server/values-local.yaml +0 -3
- package/src/helm-charts/elasticsearch8/elasticsearch-local.yaml +6 -6
- package/src/helm-charts/message-processor/values-local.yaml +0 -3
- package/src/repo-panopticon.mjs +12 -9
- package/src/templates/dockerfileTemplateNodeJs.hbs +6 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.118.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"test-all": "mocha -t 60000 --exit 'test/**/**.mjs'"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
+
"branch-protect-it": "src/branch-protect-it.mjs",
|
|
22
23
|
"build-cnpg-image": "src/build-cnpg-image.mjs",
|
|
23
24
|
"build-tools": "src/build-tools.mjs",
|
|
24
25
|
"chart-compass": "src/chart-compass.mjs",
|
|
@@ -103,7 +104,7 @@
|
|
|
103
104
|
"googleapis": "^176.0.0",
|
|
104
105
|
"handlebars": "^4.7.9",
|
|
105
106
|
"ignore": "^7.0.6",
|
|
106
|
-
"inquirer": "^14.
|
|
107
|
+
"inquirer": "^14.2.0",
|
|
107
108
|
"js-yaml": "^4.2.0",
|
|
108
109
|
"jsdoc": "^4.0.5",
|
|
109
110
|
"ms": "^2.1.3",
|
package/src/Docker.mjs
CHANGED
|
@@ -148,6 +148,10 @@ const generateDockerfile = async ( options ) => {
|
|
|
148
148
|
if ( fs.existsSync( dockerfilePrePlugin ) ) {
|
|
149
149
|
settings.preInstallPluginfile = fs.readFileSync( dockerfilePrePlugin )
|
|
150
150
|
}
|
|
151
|
+
const dockerfileFinalStagePrePlugin = './docker/DockerfileFinalStagePreInstall.plugin'
|
|
152
|
+
if ( fs.existsSync( dockerfileFinalStagePrePlugin ) ) {
|
|
153
|
+
settings.finalStagePreInstallPluginfile = fs.readFileSync( dockerfileFinalStagePrePlugin )
|
|
154
|
+
}
|
|
151
155
|
|
|
152
156
|
const compiled = handlebars.compile( dockerfileTemplate, { noEscape : true } )
|
|
153
157
|
const replaced = compiled( settings )
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { $ } from 'zx'
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
|
|
6
|
+
import { enableDebug, errorExit, log, proceed } from './Utils.mjs'
|
|
7
|
+
|
|
8
|
+
$.verbose = false
|
|
9
|
+
|
|
10
|
+
const DEFAULT_ORG = 'Leverege'
|
|
11
|
+
const REQUIRED_CHECK_PREFIX = 'node-ci'
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name( 'branch-protect-it' )
|
|
15
|
+
.description( chalk.green( 'Applies the standard branch-protection + merge-method bundle to a repo' ) )
|
|
16
|
+
.argument( '<repo>', 'Repository name (without org prefix)' )
|
|
17
|
+
.option( '--org <org>', 'GitHub org', DEFAULT_ORG )
|
|
18
|
+
.option( '--branch <branch>', "Branch to protect (defaults to the repo's actual default branch)" )
|
|
19
|
+
.option( '--require-approvals <n>', 'Required approving review count', '0' )
|
|
20
|
+
.option( '--dry-run', 'Show what would be applied without making changes' )
|
|
21
|
+
.option( '--check', 'Read-only: exits 0 (matches the standard bundle) or 1 (does not) — ' +
|
|
22
|
+
'no prompt, no writes, no output at all by default. For scripting/dashboards (e.g. ' +
|
|
23
|
+
'repo-panopticon) that only care about the exit code. Pair with --check-verbose for output.' )
|
|
24
|
+
.option( '--check-verbose', 'With --check, also print which specific settings are missing/wrong ' +
|
|
25
|
+
'(or a success line). Has no effect without --check.' )
|
|
26
|
+
.option( '--debug', 'Enable debug logging' )
|
|
27
|
+
.addHelpText( 'after', `
|
|
28
|
+
${chalk.yellow( 'What this applies:' )}
|
|
29
|
+
- Require a pull request before merging (0+ approvals, configurable)
|
|
30
|
+
- Require the repo's node-ci status check to pass, and the branch to be up to date
|
|
31
|
+
- Block force pushes and branch deletion
|
|
32
|
+
- Admins can still bypass (enforce_admins: false) for emergencies
|
|
33
|
+
- Repo-level merge method restricted to squash-only
|
|
34
|
+
- Auto-delete head branches on merge (delete_branch_on_merge)
|
|
35
|
+
- Suggest updating out-of-date PR branches with one click (allow_update_branch)
|
|
36
|
+
|
|
37
|
+
${chalk.yellow( 'Scope:' )}
|
|
38
|
+
Only supports repos on the ${chalk.green( 'node-ci' )} reusable workflow for now — it looks up
|
|
39
|
+
the real check-run name from the target branch's latest commit (never guesses one) and
|
|
40
|
+
errors out if nothing starting with "node-ci" is found there.
|
|
41
|
+
|
|
42
|
+
${chalk.yellow( 'Examples:' )}
|
|
43
|
+
${chalk.green( 'branch-protect-it secrets' )}
|
|
44
|
+
${chalk.green( 'branch-protect-it secrets --dry-run' )}
|
|
45
|
+
${chalk.green( 'branch-protect-it secrets --require-approvals 1' )}
|
|
46
|
+
${chalk.green( 'branch-protect-it secrets --check && echo protected' )}
|
|
47
|
+
${chalk.green( 'branch-protect-it secrets --check --check-verbose' )}
|
|
48
|
+
` )
|
|
49
|
+
.parse()
|
|
50
|
+
|
|
51
|
+
const [ repo ] = program.args
|
|
52
|
+
const opts = program.opts()
|
|
53
|
+
if ( opts.debug ) { enableDebug(); $.verbose = true }
|
|
54
|
+
|
|
55
|
+
const fullRepo = `${opts.org}/${repo}`
|
|
56
|
+
// --check is silent by default (exit code only) — only chatty with --check-verbose.
|
|
57
|
+
// Every other mode (apply/dry-run) always logs normally.
|
|
58
|
+
const quietCheck = opts.check && !opts.checkVerbose
|
|
59
|
+
const checkLog = ( ...args ) => { if ( !quietCheck ) { log( ...args ) } }
|
|
60
|
+
|
|
61
|
+
// ---------- Resolve branch ----------
|
|
62
|
+
|
|
63
|
+
let branch = opts.branch
|
|
64
|
+
if ( !branch ) {
|
|
65
|
+
const { stdout } = await $`gh api -X GET repos/${fullRepo} -q .default_branch`
|
|
66
|
+
branch = stdout.trim()
|
|
67
|
+
}
|
|
68
|
+
checkLog( chalk.blue( `Target: ${fullRepo}@${branch}` ) )
|
|
69
|
+
|
|
70
|
+
// ---------- --check: read-only status, exit 0/1, no writes, no prompt ----------
|
|
71
|
+
|
|
72
|
+
if ( opts.check ) {
|
|
73
|
+
const reasons = []
|
|
74
|
+
|
|
75
|
+
let protection = null
|
|
76
|
+
try {
|
|
77
|
+
// .quiet() suppresses gh's own stderr (e.g. "Branch not protected") — that's an
|
|
78
|
+
// expected, handled outcome here, not something that should leak into --check's output
|
|
79
|
+
const { stdout } = await $`gh api -X GET repos/${fullRepo}/branches/${branch}/protection`.quiet()
|
|
80
|
+
protection = JSON.parse( stdout )
|
|
81
|
+
} catch {
|
|
82
|
+
reasons.push( 'no branch protection configured at all' )
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if ( protection ) {
|
|
86
|
+
if ( !protection.required_pull_request_reviews ) { reasons.push( 'PR not required' ) }
|
|
87
|
+
if ( !protection.required_status_checks?.strict ) { reasons.push( 'status checks not required / not strict' ) }
|
|
88
|
+
if ( !protection.required_status_checks?.contexts?.some( c => c.startsWith( REQUIRED_CHECK_PREFIX ) ) ) {
|
|
89
|
+
reasons.push( `no required check starting with "${REQUIRED_CHECK_PREFIX}"` )
|
|
90
|
+
}
|
|
91
|
+
if ( protection.allow_force_pushes?.enabled !== false ) { reasons.push( 'force pushes not blocked' ) }
|
|
92
|
+
if ( protection.allow_deletions?.enabled !== false ) { reasons.push( 'branch deletion not blocked' ) }
|
|
93
|
+
if ( protection.enforce_admins?.enabled !== false ) { reasons.push( 'enforce_admins is not false (expected: admins can bypass)' ) }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let repoSettings = null
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await $`gh api -X GET repos/${fullRepo} -q '{allow_squash_merge, allow_merge_commit, allow_rebase_merge, delete_branch_on_merge, allow_update_branch}'`
|
|
99
|
+
repoSettings = JSON.parse( stdout )
|
|
100
|
+
} catch ( error ) {
|
|
101
|
+
reasons.push( `could not read repo merge settings: ${error.stderr ?? error.message ?? error}` )
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if ( repoSettings ) {
|
|
105
|
+
if ( repoSettings.allow_squash_merge !== true ) { reasons.push( 'squash merge not enabled' ) }
|
|
106
|
+
if ( repoSettings.allow_merge_commit !== false ) { reasons.push( 'merge commits not disabled' ) }
|
|
107
|
+
if ( repoSettings.allow_rebase_merge !== false ) { reasons.push( 'rebase merge not disabled' ) }
|
|
108
|
+
if ( repoSettings.delete_branch_on_merge !== true ) { reasons.push( 'delete_branch_on_merge not enabled' ) }
|
|
109
|
+
if ( repoSettings.allow_update_branch !== true ) { reasons.push( 'allow_update_branch not enabled' ) }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ( reasons.length === 0 ) {
|
|
113
|
+
checkLog( chalk.green( `✔ ${fullRepo}@${branch} matches the standard branch-protection bundle` ) )
|
|
114
|
+
process.exit( 0 )
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
checkLog( chalk.red( `✘ ${fullRepo}@${branch} does not match the standard bundle:` ) )
|
|
118
|
+
reasons.forEach( r => checkLog( chalk.red( ` - ${r}` ) ) )
|
|
119
|
+
process.exit( 1 )
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------- Find the real node-ci check name — never guess it ----------
|
|
123
|
+
|
|
124
|
+
let checkName
|
|
125
|
+
try {
|
|
126
|
+
const { stdout : shaRaw } = await $`gh api -X GET repos/${fullRepo}/commits/${branch} -q .sha`
|
|
127
|
+
const sha = shaRaw.trim()
|
|
128
|
+
const { stdout : checksRaw } = await $`gh api -X GET repos/${fullRepo}/commits/${sha}/check-runs -q '.check_runs[].name'`
|
|
129
|
+
const checks = checksRaw.trim().split( '\n' ).filter( Boolean )
|
|
130
|
+
checkName = checks.find( c => c.startsWith( REQUIRED_CHECK_PREFIX ) )
|
|
131
|
+
} catch ( error ) {
|
|
132
|
+
errorExit( `Failed to look up check runs: ${error.stderr ?? error.message ?? error}` )
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if ( !checkName ) {
|
|
136
|
+
errorExit(
|
|
137
|
+
`No check run starting with "${REQUIRED_CHECK_PREFIX}" found on ${fullRepo}@${branch}'s latest commit.\n` +
|
|
138
|
+
' Has the node-ci workflow actually run on this branch yet? Push something first, ' +
|
|
139
|
+
'or pass --branch if development is not the right target.'
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
log( chalk.green( `Found check: ${checkName}` ) )
|
|
143
|
+
|
|
144
|
+
// ---------- Build the payload ----------
|
|
145
|
+
|
|
146
|
+
const protectionPayload = {
|
|
147
|
+
required_status_checks : { strict : true, contexts : [ checkName ] },
|
|
148
|
+
enforce_admins : false,
|
|
149
|
+
required_pull_request_reviews : { required_approving_review_count : Number( opts.requireApprovals ) },
|
|
150
|
+
restrictions : null,
|
|
151
|
+
allow_force_pushes : false,
|
|
152
|
+
allow_deletions : false,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
log( chalk.yellow( '\nWill apply branch protection:' ) )
|
|
156
|
+
log( JSON.stringify( protectionPayload, null, 2 ) )
|
|
157
|
+
log( chalk.yellow( 'Will also set merge method to squash-only ' +
|
|
158
|
+
'(allow_squash_merge=true, allow_merge_commit=false, allow_rebase_merge=false), ' +
|
|
159
|
+
'enable auto-delete of head branches on merge (delete_branch_on_merge=true), ' +
|
|
160
|
+
'and enable the "Update branch" suggestion button on out-of-date PRs (allow_update_branch=true)' ) )
|
|
161
|
+
|
|
162
|
+
if ( opts.dryRun ) {
|
|
163
|
+
log( chalk.green( '\n[dry-run] No changes made.' ) )
|
|
164
|
+
process.exit( 0 )
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await proceed( `\nApply this to ${fullRepo}@${branch}?` )
|
|
168
|
+
|
|
169
|
+
// ---------- Apply ----------
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await $( { input : JSON.stringify( protectionPayload ) } )`gh api --method PUT repos/${fullRepo}/branches/${branch}/protection --input -`
|
|
173
|
+
await $`gh api --method PATCH repos/${fullRepo} -F allow_squash_merge=true -F allow_merge_commit=false -F allow_rebase_merge=false -F delete_branch_on_merge=true -F allow_update_branch=true`
|
|
174
|
+
} catch ( error ) {
|
|
175
|
+
errorExit( `Failed to apply settings: ${error.stderr ?? error.message ?? error}` )
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
log( chalk.green( `\n✔ Branch protection applied to ${fullRepo}@${branch}` ) )
|
|
@@ -129,7 +129,7 @@ const dockerfileOptions = {
|
|
|
129
129
|
isNpmWorkspace,
|
|
130
130
|
buildEnv : leveregeStanza.buildEnv || leveregeStanza['build-env'] || 'alpine',
|
|
131
131
|
imageBase : leveregeStanza.imageBase || leveregeStanza['image-base'],
|
|
132
|
-
runuser : leveregeStanza.runUser || leveregeStanza['run-user'] || isNodeJsProject ? 'node' : 'python',
|
|
132
|
+
runuser : leveregeStanza.runUser || leveregeStanza['run-user'] || ( isNodeJsProject ? 'node' : 'python' ),
|
|
133
133
|
nodeimage : leveregeStanza.nodeimg, // left as nodeimg in package.json for backwards compat
|
|
134
134
|
regvers : repoDescr.packageVersion,
|
|
135
135
|
pythonVersion : repoDescr.rootPyProjectToml?.pythonVersion,
|
|
@@ -97,11 +97,11 @@ data:
|
|
|
97
97
|
limits:
|
|
98
98
|
cpu: 750m
|
|
99
99
|
ephemeral-storage: 2Gi
|
|
100
|
-
memory:
|
|
100
|
+
memory: 8Gi
|
|
101
101
|
requests:
|
|
102
102
|
cpu: 500m
|
|
103
103
|
ephemeral-storage: 50Mi
|
|
104
|
-
memory:
|
|
104
|
+
memory: 4Gi
|
|
105
105
|
persistence:
|
|
106
106
|
enabled: true
|
|
107
107
|
size: 20Gi # scale up based on expected data volume
|
|
@@ -114,7 +114,7 @@ ingest:
|
|
|
114
114
|
limits:
|
|
115
115
|
cpu: 750m
|
|
116
116
|
ephemeral-storage: 2Gi
|
|
117
|
-
memory:
|
|
117
|
+
memory: 1536Mi
|
|
118
118
|
requests:
|
|
119
119
|
cpu: 250m
|
|
120
120
|
ephemeral-storage: 50Mi
|
|
@@ -123,13 +123,13 @@ ingest:
|
|
|
123
123
|
master:
|
|
124
124
|
<<: *esScheduling
|
|
125
125
|
replicaCount: 3
|
|
126
|
-
heapSize:
|
|
126
|
+
heapSize: 512m
|
|
127
127
|
resources:
|
|
128
128
|
limits:
|
|
129
129
|
cpu: 500m
|
|
130
130
|
ephemeral-storage: 2Gi
|
|
131
|
-
memory:
|
|
131
|
+
memory: 1536Mi
|
|
132
132
|
requests:
|
|
133
133
|
cpu: 250m
|
|
134
134
|
ephemeral-storage: 50Mi
|
|
135
|
-
memory:
|
|
135
|
+
memory: 1Gi
|
package/src/repo-panopticon.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const SYM_GOOD = chalk.green( '✔' )
|
|
|
19
19
|
const SYM_BAD = chalk.red( '✘' )
|
|
20
20
|
|
|
21
21
|
// ---------- Checks ----------
|
|
22
|
-
// Each check: { label: string, fn: ({ dir, pkg, isWorkspace }) => { value: string } }
|
|
22
|
+
// Each check: { label: string, fn: ({ dir, pkg, isWorkspace, gitRoot }) => { value: string } | Promise<{ value: string }> }
|
|
23
23
|
// Add new checks here — the table expands automatically.
|
|
24
24
|
|
|
25
25
|
let latestOrbVersion = null
|
|
@@ -83,14 +83,17 @@ const CHECKS = [
|
|
|
83
83
|
},
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
|
-
label : '
|
|
87
|
-
fn : ( { gitRoot } ) => {
|
|
86
|
+
label : 'bpr',
|
|
87
|
+
fn : async ( { gitRoot, isWorkspace } ) => {
|
|
88
|
+
if ( isWorkspace ) return { value : chalk.dim( '-' ) }
|
|
88
89
|
try {
|
|
89
90
|
const gitConfig = fs.readFileSync( path.join( gitRoot, '.git', 'config' ), 'utf8' )
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
const match = gitConfig.match( /github\.com[:/][^/]+\/(.+?)(?:\.git)?(?:\s|$)/m )
|
|
92
|
+
if ( !match ) return { value : chalk.dim( '?' ) }
|
|
93
|
+
await $`branch-protect-it --check ${match[1]}`
|
|
94
|
+
return { value : SYM_GOOD }
|
|
95
|
+
} catch ( err ) {
|
|
96
|
+
if ( err.exitCode === 1 ) return { value : SYM_BAD }
|
|
94
97
|
return { value : chalk.dim( '?' ) }
|
|
95
98
|
}
|
|
96
99
|
},
|
|
@@ -263,7 +266,7 @@ async function checkRepo( dir, { pull } ) {
|
|
|
263
266
|
dirty : !syncOk,
|
|
264
267
|
scope : fmtScope( pkg ),
|
|
265
268
|
isWorkspace,
|
|
266
|
-
checks : CHECKS.map( ( { label, fn } ) => ( { label, ...fn( { dir, pkg, isWorkspace, gitRoot } ) } ) ),
|
|
269
|
+
checks : await Promise.all( CHECKS.map( async ( { label, fn } ) => ( { label, ...await fn( { dir, pkg, isWorkspace, gitRoot } ) } ) ) ),
|
|
267
270
|
needsPush,
|
|
268
271
|
}
|
|
269
272
|
}
|
|
@@ -346,7 +349,7 @@ ${chalk.yellow( 'Columns:' )}
|
|
|
346
349
|
${chalk.green( 'ESM' )} "type": "module" in package.json
|
|
347
350
|
${chalk.green( 'har' )} .har directory present at repo root
|
|
348
351
|
${chalk.green( 'lnt' )} ESLint version from node_modules (✘=no lint script, ?=not installed, yellow=v8, green=v9+)
|
|
349
|
-
${chalk.green( '
|
|
352
|
+
${chalk.green( 'bpr' )} branch protection: ✔=standard bundle applied, ✘=missing/incomplete, -=workspace package, ?=not on GitHub
|
|
350
353
|
|
|
351
354
|
${chalk.yellow( 'Monorepos:' )}
|
|
352
355
|
Workspace packages are discovered automatically — passing a monorepo root
|
|
@@ -70,6 +70,7 @@ RUN npm ci --only=production --no-optional {{npmlogging}} && \
|
|
|
70
70
|
# --------------------------------------------------------------
|
|
71
71
|
|
|
72
72
|
FROM {{image}}
|
|
73
|
+
{{finalStagePreInstallPluginfile}}
|
|
73
74
|
|
|
74
75
|
ARG BUILD_ENV="{{buildEnv}}"
|
|
75
76
|
|
|
@@ -97,7 +98,7 @@ RUN if [ "$BUILD_ENV" = "debian" ]; then \
|
|
|
97
98
|
npm install -g {{npmVersion}} && \
|
|
98
99
|
{{/if}}
|
|
99
100
|
rm /bin/sh && ln -s /bin/bash /bin/sh && \
|
|
100
|
-
mkdir -p /usr/src/app /tmp/levlog && chown
|
|
101
|
+
mkdir -p /usr/src/app /tmp/levlog && chown {{runuser}}:{{runuser}} /usr/src/app; \
|
|
101
102
|
else \
|
|
102
103
|
apk update && \
|
|
103
104
|
apk add --no-cache bash curl tini vim {{apkadds}} && \
|
|
@@ -108,7 +109,7 @@ RUN if [ "$BUILD_ENV" = "debian" ]; then \
|
|
|
108
109
|
npm install -g {{npmVersion}} && \
|
|
109
110
|
{{/if}}
|
|
110
111
|
rm /bin/sh && ln -s /bin/bash /bin/sh && \
|
|
111
|
-
mkdir -p /usr/src/app /tmp/levlog && chown
|
|
112
|
+
mkdir -p /usr/src/app /tmp/levlog && chown {{runuser}}:{{runuser}} /usr/src/app; \
|
|
112
113
|
fi
|
|
113
114
|
|
|
114
115
|
|
|
@@ -125,8 +126,8 @@ RUN rm -f ${NPM_CONFIG_USERCONFIG}
|
|
|
125
126
|
ENTRYPOINT [ "tini", "--" ]
|
|
126
127
|
WORKDIR /usr/src/app
|
|
127
128
|
|
|
128
|
-
COPY --chown=
|
|
129
|
+
COPY --chown={{runuser}}:{{runuser}} --from=intermediate /usr/src/app /usr/src/app
|
|
129
130
|
|
|
130
131
|
USER {{runuser}}
|
|
131
|
-
COPY ./bashrc /home/
|
|
132
|
-
CMD [ "/bin/bash", "-c", "source /home/
|
|
132
|
+
COPY --chown={{runuser}}:{{runuser}} ./bashrc /home/{{runuser}}/.bashrc
|
|
133
|
+
CMD [ "/bin/bash", "-c", "source /home/{{runuser}}/.bashrc && {{runCommand}}" ]
|