@@ -6,10 +6,44 @@ import (
66 "os/exec"
77 "path/filepath"
88 "strings"
9+
910 "github.com/rapidfort/kimia/internal/build"
1011 "github.com/rapidfort/kimia/pkg/logger"
1112)
1213
14+ // Environment represents the runtime environment
15+ type Environment int
16+
17+ const (
18+ EnvStandalone Environment = iota
19+ EnvDocker
20+ EnvKubernetes
21+ )
22+
23+ // DetectEnvironment determines if running in Kubernetes, Docker, or standalone
24+ func DetectEnvironment () Environment {
25+ // Check Kubernetes first (most specific)
26+ if os .Getenv ("KUBERNETES_SERVICE_HOST" ) != "" {
27+ return EnvKubernetes
28+ }
29+
30+ // Check Docker via .dockerenv file
31+ if _ , err := os .Stat ("/.dockerenv" ); err == nil {
32+ return EnvDocker
33+ }
34+
35+ // Check Docker via cgroups
36+ if data , err := os .ReadFile ("/proc/1/cgroup" ); err == nil {
37+ content := string (data )
38+ if strings .Contains (content , "docker" ) ||
39+ strings .Contains (content , "containerd" ) {
40+ return EnvDocker
41+ }
42+ }
43+
44+ return EnvStandalone
45+ }
46+
1347// CheckEnvironment performs comprehensive environment check
1448func CheckEnvironment () int {
1549 // Get storage driver from environment or default to vfs
@@ -34,19 +68,45 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
3468 builder := build .DetectBuilder ()
3569 logger .Info ("" )
3670 logger .Info ("Kimia Environment Check (%s)" , builder )
37- logger .Info ("═══════════════════════════════════════════════════════════ " )
71+ logger .Info ("═══════════════════════════════════════════════════════" )
3872 logger .Info ("" )
3973
4074 allGood := true
4175
4276 // Runtime Context
4377 logger .Info ("RUNTIME CONTEXT" )
4478 uid := os .Getuid ()
45- isK8s := IsInKubernetes ()
79+
80+ // CRITICAL: Kimia is rootless-only and does NOT support root mode
81+ if uid == 0 {
82+ logger .Error (" User ID: %d ✗" , uid )
83+ logger .Error ("" )
84+ logger .Error ("╔══════════════════════════════════════════════════════╗" )
85+ logger .Error ("║ KIMIA DOES NOT SUPPORT ROOT ║" )
86+ logger .Error ("╠══════════════════════════════════════════════════════╣" )
87+ logger .Error ("║ Kimia is designed to run as a non-root user with ║" )
88+ logger .Error ("║ capabilities, not as root. ║" )
89+ logger .Error ("║ ║" )
90+ logger .Error ("║ Please run as non-root user (UID > 0) ║" )
91+ logger .Error ("║ ║" )
92+ logger .Error ("║ For Kubernetes, set: ║" )
93+ logger .Error ("║ securityContext: ║" )
94+ logger .Error ("║ runAsUser: 1000 ║" )
95+ logger .Error ("║ runAsNonRoot: true ║" )
96+ logger .Error ("║ allowPrivilegeEscalation: true ║" )
97+ logger .Error ("║ capabilities: ║" )
98+ logger .Error ("║ drop: [ALL] ║" )
99+ logger .Error ("║ add: [SETUID, SETGID] ║" )
100+ logger .Error ("╚══════════════════════════════════════════════════════╝" )
101+ logger .Error ("" )
102+ return 1
103+ }
104+
105+ env := DetectEnvironment ()
46106
47107 checkmark := getCheckmark (true )
48108 logger .Info (" User ID: %d %s" , uid , checkmark )
49- logger .Info (" Environment: %s" , getEnvironment (isK8s ))
109+ logger .Info (" Environment: %s" , getEnvironment (env ))
50110 logger .Info (" Storage Driver: %s" , storageDriver )
51111
52112 if username := os .Getenv ("USER" ); username != "" {
@@ -78,12 +138,12 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
78138 case "overlay" :
79139 hasMknod := caps .HasCapability ("CAP_MKNOD" )
80140 hasDACOverride := caps .HasCapability ("CAP_DAC_OVERRIDE" )
81-
141+
82142 logger .Info (" CAP_MKNOD: %s %s (required for overlay)" ,
83143 getPresence (hasMknod ), getCheckmark (hasMknod ))
84144 logger .Info (" CAP_DAC_OVERRIDE: %s %s (required for overlay)" ,
85145 getPresence (hasDACOverride ), getCheckmark (hasDACOverride ))
86-
146+
87147 if ! hasMknod {
88148 logger .Warning (" Warning: MKNOD capability missing (required for overlay storage)" )
89149 allGood = false
@@ -138,7 +198,7 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
138198
139199 logger .Info ("" )
140200
141- // User Namespaces (always check - Kimia is rootless-only)
201+ // User Namespaces (Kimia is rootless-only, always check )
142202 logger .Info ("USER NAMESPACES" )
143203 userns , err := CheckUserNamespaces ()
144204 if err != nil {
@@ -174,8 +234,8 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
174234 hasRequiredCaps = caps .HasRequiredCapabilities ()
175235 // For overlay, also check MKNOD and DAC_OVERRIDE
176236 if storageDriver == "overlay" {
177- hasRequiredCaps = hasRequiredCaps &&
178- caps .HasCapability ("CAP_MKNOD" ) &&
237+ hasRequiredCaps = hasRequiredCaps &&
238+ caps .HasCapability ("CAP_MKNOD" ) &&
179239 caps .HasCapability ("CAP_DAC_OVERRIDE" )
180240 }
181241 }
@@ -199,7 +259,7 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
199259 }
200260 logger .Info ("" )
201261
202- // BUILD MODE
262+ // BUILD MODE (Kimia is rootless-only)
203263 fmt .Println ("BUILD MODE" )
204264
205265 var buildModeAvailable bool
@@ -274,13 +334,13 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
274334
275335 // Verdict
276336 logger .Info ("VERDICT" )
277- logger .Info ("═══════════════════════════════════════════════════════════ " )
337+ logger .Info ("═══════════════════════════════════════════════════════" )
278338 logger .Info ("" )
279339
280340 if allGood {
281341 logger .Info ("✓ Environment is properly configured for building images" )
282342 logger .Info ("✓ %s" , buildModeMethod )
283-
343+
284344 if storage != nil && storage .OverlayAvailable {
285345 logger .Info ("✓ Overlay storage available for better performance" )
286346 } else {
@@ -332,51 +392,85 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
332392 }
333393
334394 if needsHelp {
335- logger .Info ("Kubernetes Configuration Required:" )
336- logger .Info ("" )
337- logger .Info ("Some Kubernetes clusters require unconfined seccomp and AppArmor profiles" )
338- logger .Info ("for user namespace operations to work properly." )
339- logger .Info ("" )
340- logger .Info ("Complete Pod/Job specification:" )
341- logger .Info ("" )
342- logger .Info ("---" )
343- logger .Info ("apiVersion: batch/v1" )
344- logger .Info ("kind: Job" )
345- logger .Info ("metadata:" )
346- logger .Info (" name: kimia-build" )
347- logger .Info ("spec:" )
348- logger .Info (" template:" )
349- logger .Info (" spec:" )
350- logger .Info (" restartPolicy: Never" )
351- logger .Info (" securityContext:" )
352- logger .Info (" runAsUser: 1000" )
353- logger .Info (" runAsGroup: 1000" )
354- logger .Info (" fsGroup: 1000" )
355- logger .Info (" seccompProfile:" )
356- logger .Info (" type: Unconfined # May be required for user namespaces" )
357- logger .Info (" containers:" )
358- logger .Info (" - name: kimia" )
359- logger .Info (" image: ghcr.io/rapidfort/kimia:latest" )
360- logger .Info (" securityContext:" )
361- logger .Info (" runAsUser: 1000" )
362- logger .Info (" runAsGroup: 1000" )
363- logger .Info (" allowPrivilegeEscalation: true # CRITICAL: Required!" )
364- logger .Info (" appArmorProfile:" )
365- logger .Info (" type: Unconfined # May be required for user namespaces" )
366- logger .Info (" seccompProfile:" )
367- logger .Info (" type: Unconfined # May be required for user namespaces" )
368- logger .Info (" capabilities:" )
369- logger .Info (" drop: [ALL]" )
370-
371- if storageDriver == "overlay" {
372- logger .Info (" add: [SETUID, SETGID, MKNOD, DAC_OVERRIDE]" )
373- } else {
374- logger .Info (" add: [SETUID, SETGID]" )
395+ // Provide environment-specific guidance
396+ switch env {
397+ case EnvKubernetes :
398+ logger .Info ("Kubernetes Configuration Required:" )
399+ logger .Info ("" )
400+ logger .Info ("Some Kubernetes clusters require unconfined seccomp and AppArmor profiles" )
401+ logger .Info ("for user namespace operations to work properly." )
402+ logger .Info ("" )
403+ logger .Info ("Complete Pod/Job specification:" )
404+ logger .Info ("" )
405+ logger .Info ("---" )
406+ logger .Info ("apiVersion: batch/v1" )
407+ logger .Info ("kind: Job" )
408+ logger .Info ("metadata:" )
409+ logger .Info (" name: kimia-build" )
410+ logger .Info ("spec:" )
411+ logger .Info (" template:" )
412+ logger .Info (" spec:" )
413+ logger .Info (" restartPolicy: Never" )
414+ logger .Info (" securityContext:" )
415+ logger .Info (" runAsUser: 1000" )
416+ logger .Info (" runAsGroup: 1000" )
417+ logger .Info (" fsGroup: 1000" )
418+ logger .Info (" seccompProfile:" )
419+ logger .Info (" type: Unconfined # May be required for user namespaces" )
420+ logger .Info (" containers:" )
421+ logger .Info (" - name: kimia" )
422+ logger .Info (" image: ghcr.io/rapidfort/kimia:latest" )
423+ logger .Info (" securityContext:" )
424+ logger .Info (" runAsUser: 1000" )
425+ logger .Info (" runAsGroup: 1000" )
426+ logger .Info (" allowPrivilegeEscalation: true # CRITICAL: Required!" )
427+ logger .Info (" appArmorProfile:" )
428+ logger .Info (" type: Unconfined # May be required for user namespaces" )
429+ logger .Info (" seccompProfile:" )
430+ logger .Info (" type: Unconfined # May be required for user namespaces" )
431+ logger .Info (" capabilities:" )
432+ logger .Info (" drop: [ALL]" )
433+
434+ if storageDriver == "overlay" {
435+ logger .Info (" add: [SETUID, SETGID, MKNOD, DAC_OVERRIDE]" )
436+ } else {
437+ logger .Info (" add: [SETUID, SETGID]" )
438+ }
439+
440+ case EnvDocker :
441+ logger .Info ("Docker Configuration Required:" )
442+ logger .Info ("" )
443+ logger .Info ("Run Kimia with the following Docker options:" )
444+ logger .Info ("" )
445+ if storageDriver == "overlay" {
446+ logger .Info (" docker run --cap-add SETUID --cap-add SETGID --cap-add MKNOD \\ " )
447+ logger .Info (" --user 1000:1000 \\ " )
448+ logger .Info (" ghcr.io/rapidfort/kimia:latest" )
449+ } else {
450+ logger .Info (" docker run --cap-add SETUID --cap-add SETGID \\ " )
451+ logger .Info (" --user 1000:1000 \\ " )
452+ logger .Info (" ghcr.io/rapidfort/kimia:latest" )
453+ }
454+ logger .Info ("" )
455+ logger .Info ("If capabilities don't work, try with SETUID binaries:" )
456+ logger .Info (" docker run --security-opt seccomp=unconfined \\ " )
457+ logger .Info (" --user 1000:1000 \\ " )
458+ logger .Info (" ghcr.io/rapidfort/kimia:latest" )
459+
460+ case EnvStandalone :
461+ logger .Info ("Standalone/VM Configuration Required:" )
462+ logger .Info ("" )
463+ logger .Info ("Ensure the following are available:" )
464+ logger .Info (" 1. User namespaces enabled in kernel" )
465+ logger .Info (" 2. Subuid/subgid mappings configured in /etc/subuid and /etc/subgid" )
466+ logger .Info (" 3. Either:" )
467+ logger .Info (" - Run with capabilities (CAP_SETUID, CAP_SETGID)" )
468+ logger .Info (" - Have newuidmap/newgidmap SETUID binaries available" )
375469 }
376-
470+
377471 logger .Info ("" )
378472 logger .Info ("NOTE: seccompProfile and appArmorProfile set to Unconfined may be required" )
379- logger .Info (" depending on your cluster 's security policies." )
473+ logger .Info (" depending on your environment 's security policies." )
380474 logger .Info ("" )
381475
382476 // Provide specific guidance based on what's wrong
@@ -423,13 +517,24 @@ func CheckEnvironmentWithDriver(storageDriver string) int {
423517 logger .Error (" - Consider adding seccompProfile: Unconfined if still failing" )
424518 logger .Error ("" )
425519 }
426-
520+
427521 logger .Info ("Troubleshooting Steps:" )
428- logger .Info (" 1. Apply the YAML configuration above" )
429- logger .Info (" 2. Check pod status: kubectl get pod <pod-name>" )
430- logger .Info (" 3. Describe pod: kubectl describe pod <pod-name>" )
431- logger .Info (" 4. View logs: kubectl logs <pod-name>" )
432- logger .Info (" 5. Run preflight check: kubectl exec <pod-name> -- kimia check-environment" )
522+ if env == EnvKubernetes {
523+ logger .Info (" 1. Apply the YAML configuration above" )
524+ logger .Info (" 2. Check pod status: kubectl get pod <pod-name>" )
525+ logger .Info (" 3. Describe pod: kubectl describe pod <pod-name>" )
526+ logger .Info (" 4. View logs: kubectl logs <pod-name>" )
527+ logger .Info (" 5. Run preflight check: kubectl exec <pod-name> -- kimia check-environment" )
528+ } else if env == EnvDocker {
529+ logger .Info (" 1. Run with the recommended Docker options above" )
530+ logger .Info (" 2. Check container status: docker ps" )
531+ logger .Info (" 3. View logs: docker logs <container-name>" )
532+ logger .Info (" 4. Run preflight check: docker exec <container-name> kimia check-environment" )
533+ } else {
534+ logger .Info (" 1. Verify user namespace support: unshare --user --pid --map-root-user echo OK" )
535+ logger .Info (" 2. Check subuid/subgid: cat /etc/subuid /etc/subgid" )
536+ logger .Info (" 3. Run preflight check: kimia check-environment" )
537+ }
433538 logger .Info ("" )
434539 }
435540
@@ -466,11 +571,17 @@ func getSuccess(success bool) string {
466571 return "Failed"
467572}
468573
469- func getEnvironment (isK8s bool ) string {
470- if isK8s {
574+ func getEnvironment (env Environment ) string {
575+ switch env {
576+ case EnvKubernetes :
471577 return "Kubernetes"
578+ case EnvDocker :
579+ return "Docker"
580+ case EnvStandalone :
581+ return "Standalone"
582+ default :
583+ return "Unknown"
472584 }
473- return "Standalone"
474585}
475586
476587func checkDependency (name , path string ) {
@@ -493,4 +604,4 @@ func checkDependencyVersion(name, command string, versionArg string) {
493604 }
494605 logger .Info (" %s version:%-*s %s %s" , name , 12 - len (name ), "" , version , getCheckmark (true ))
495606 }
496- }
607+ }
0 commit comments