Merge pull request #147 from Incubaid/development_crun
Configure Crun module with Heropods module to work with configs
This commit is contained in:
@@ -13,7 +13,7 @@ println('=== HeroPods Refactored API Demo ===')
|
|||||||
// Step 1: factory.new() now only creates a container definition/handle
|
// Step 1: factory.new() now only creates a container definition/handle
|
||||||
// It does NOT create the actual container in the backend yet
|
// It does NOT create the actual container in the backend yet
|
||||||
mut container := factory.new(
|
mut container := factory.new(
|
||||||
name: 'myalpine'
|
name: 'demo_alpine'
|
||||||
image: .custom
|
image: .custom
|
||||||
custom_image_name: 'alpine_3_20'
|
custom_image_name: 'alpine_3_20'
|
||||||
docker_url: 'docker.io/library/alpine:3.20'
|
docker_url: 'docker.io/library/alpine:3.20'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ mut factory := heropods.new(
|
|||||||
) or { panic('Failed to init ContainerFactory: ${err}') }
|
) or { panic('Failed to init ContainerFactory: ${err}') }
|
||||||
|
|
||||||
mut container := factory.new(
|
mut container := factory.new(
|
||||||
name: 'myalpine'
|
name: 'alpine_demo'
|
||||||
image: .custom
|
image: .custom
|
||||||
custom_image_name: 'alpine_3_20'
|
custom_image_name: 'alpine_3_20'
|
||||||
docker_url: 'docker.io/library/alpine:3.20'
|
docker_url: 'docker.io/library/alpine:3.20'
|
||||||
|
|||||||
@@ -11,13 +11,22 @@ import freeflowuniverse.herolib.core.texttools
|
|||||||
pub struct ExecutorCrun {
|
pub struct ExecutorCrun {
|
||||||
pub mut:
|
pub mut:
|
||||||
container_id string // container ID for crun
|
container_id string // container ID for crun
|
||||||
|
crun_root string // custom crun root directory
|
||||||
retry int = 1
|
retry int = 1
|
||||||
debug bool = true
|
debug bool = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method to get crun command with custom root
|
||||||
|
fn (executor ExecutorCrun) crun_cmd(cmd string) string {
|
||||||
|
if executor.crun_root != '' {
|
||||||
|
return 'crun --root ${executor.crun_root} ${cmd}'
|
||||||
|
}
|
||||||
|
return 'crun ${cmd}'
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut executor ExecutorCrun) init() ! {
|
pub fn (mut executor ExecutorCrun) init() ! {
|
||||||
// Verify container exists and is running
|
// Verify container exists and is running
|
||||||
result := osal.exec(cmd: 'crun state ${executor.container_id}', stdout: false) or {
|
result := osal.exec(cmd: executor.crun_cmd('state ${executor.container_id}'), stdout: false) or {
|
||||||
return error('Container ${executor.container_id} not found or not accessible')
|
return error('Container ${executor.container_id} not found or not accessible')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +50,7 @@ pub fn (mut executor ExecutorCrun) exec(args_ ExecArgs) !string {
|
|||||||
console.print_debug('execute in container ${executor.container_id}: ${args.cmd}')
|
console.print_debug('execute in container ${executor.container_id}: ${args.cmd}')
|
||||||
}
|
}
|
||||||
|
|
||||||
mut cmd := 'crun exec ${executor.container_id} ${args.cmd}'
|
mut cmd := executor.crun_cmd('exec ${executor.container_id} ${args.cmd}')
|
||||||
if args.cmd.contains('\n') {
|
if args.cmd.contains('\n') {
|
||||||
// For multiline commands, write to temp file first
|
// For multiline commands, write to temp file first
|
||||||
temp_script := '/tmp/crun_script_${rand.uuid_v4()}.sh'
|
temp_script := '/tmp/crun_script_${rand.uuid_v4()}.sh'
|
||||||
@@ -50,7 +59,7 @@ pub fn (mut executor ExecutorCrun) exec(args_ ExecArgs) !string {
|
|||||||
|
|
||||||
// Copy script into container and execute
|
// Copy script into container and execute
|
||||||
executor.file_write('/tmp/exec_script.sh', script_content)!
|
executor.file_write('/tmp/exec_script.sh', script_content)!
|
||||||
cmd = 'crun exec ${executor.container_id} bash /tmp/exec_script.sh'
|
cmd = executor.crun_cmd('exec ${executor.container_id} bash /tmp/exec_script.sh')
|
||||||
}
|
}
|
||||||
|
|
||||||
res := osal.exec(cmd: cmd, stdout: args.stdout, debug: executor.debug)!
|
res := osal.exec(cmd: cmd, stdout: args.stdout, debug: executor.debug)!
|
||||||
@@ -66,7 +75,7 @@ pub fn (mut executor ExecutorCrun) exec_interactive(args_ ExecArgs) ! {
|
|||||||
args.cmd = 'bash /tmp/interactive_script.sh'
|
args.cmd = 'bash /tmp/interactive_script.sh'
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := 'crun exec -t ${executor.container_id} ${args.cmd}'
|
cmd := executor.crun_cmd('exec -t ${executor.container_id} ${args.cmd}')
|
||||||
console.print_debug(cmd)
|
console.print_debug(cmd)
|
||||||
osal.execute_interactive(cmd)!
|
osal.execute_interactive(cmd)!
|
||||||
}
|
}
|
||||||
@@ -82,7 +91,8 @@ pub fn (mut executor ExecutorCrun) file_write(path string, text string) ! {
|
|||||||
defer { os.rm(temp_file) or {} }
|
defer { os.rm(temp_file) or {} }
|
||||||
|
|
||||||
// Use crun exec to copy file content
|
// Use crun exec to copy file content
|
||||||
cmd := 'cat ${temp_file} | crun exec -i ${executor.container_id} tee ${path} > /dev/null'
|
sbcmd := executor.crun_cmd('exec -i ${executor.container_id} tee ${path}')
|
||||||
|
cmd := 'cat ${temp_file} | ${sbcmd} > /dev/null'
|
||||||
osal.exec(cmd: cmd, stdout: false)!
|
osal.exec(cmd: cmd, stdout: false)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
module crun
|
module crun
|
||||||
|
|
||||||
|
|
||||||
pub fn example_heropods_compatible() ! {
|
pub fn example_heropods_compatible() ! {
|
||||||
mut configs := map[string]&CrunConfig{}
|
mut configs := map[string]&CrunConfig{}
|
||||||
// Create a container configuration compatible with heropods template
|
// Create a container configuration compatible with heropods template
|
||||||
mut config := new(mut configs, name: 'heropods-example')!
|
mut config := new(mut configs, name: 'heropods-example')!
|
||||||
|
|
||||||
// Configure to match the template
|
// Configure to match the template - disable terminal for background containers
|
||||||
|
config.set_terminal(false)
|
||||||
config.set_command(['/bin/sh'])
|
config.set_command(['/bin/sh'])
|
||||||
.set_working_dir('/')
|
config.set_working_dir('/')
|
||||||
.set_user(0, 0, [])
|
config.set_user(0, 0, [])
|
||||||
.add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin')
|
config.add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin')
|
||||||
.add_env('TERM', 'xterm')
|
config.add_env('TERM', 'xterm')
|
||||||
.set_rootfs('${rootfs_path}', false) // This will be replaced by the actual path
|
config.set_rootfs('/tmp/rootfs', false) // This will be replaced by the actual path
|
||||||
.set_hostname('container')
|
config.set_hostname('container')
|
||||||
.set_no_new_privileges(true)
|
config.set_no_new_privileges(true)
|
||||||
|
|
||||||
// Add the specific rlimit from template
|
// Add the specific rlimit from template
|
||||||
config.add_rlimit(.rlimit_nofile, 1024, 1024)
|
config.add_rlimit(.rlimit_nofile, 1024, 1024)
|
||||||
|
|
||||||
// Validate the configuration
|
// Validate the configuration
|
||||||
config.validate()!
|
config.validate()!
|
||||||
|
|
||||||
// Generate and print JSON
|
// Generate and print JSON
|
||||||
json_output := config.to_json()!
|
json_output := config.to_json()!
|
||||||
println(json_output)
|
println(json_output)
|
||||||
|
|
||||||
// Save to file
|
// Save to file
|
||||||
config.save_to_file('/tmp/heropods_config.json')!
|
config.save_to_file('/tmp/heropods_config.json')!
|
||||||
println('Heropods-compatible configuration saved to /tmp/heropods_config.json')
|
println('Heropods-compatible configuration saved to /tmp/heropods_config.json')
|
||||||
@@ -35,33 +35,34 @@ pub fn example_custom() ! {
|
|||||||
mut configs := map[string]&CrunConfig{}
|
mut configs := map[string]&CrunConfig{}
|
||||||
// Create a more complex container configuration
|
// Create a more complex container configuration
|
||||||
mut config := new(mut configs, name: 'custom-container')!
|
mut config := new(mut configs, name: 'custom-container')!
|
||||||
|
|
||||||
config.set_command(['/usr/bin/my-app', '--config', '/etc/myapp/config.yaml'])
|
config.set_command(['/usr/bin/my-app', '--config', '/etc/myapp/config.yaml'])
|
||||||
.set_working_dir('/app')
|
config.set_working_dir('/app')
|
||||||
.set_user(1000, 1000, [1001, 1002])
|
config.set_user(1000, 1000, [1001, 1002])
|
||||||
.add_env('MY_VAR', 'my_value')
|
config.add_env('MY_VAR', 'my_value')
|
||||||
.add_env('ANOTHER_VAR', 'another_value')
|
config.add_env('ANOTHER_VAR', 'another_value')
|
||||||
.set_rootfs('/path/to/rootfs', false)
|
config.set_rootfs('/path/to/rootfs', false)
|
||||||
.set_hostname('my-custom-container')
|
config.set_hostname('my-custom-container')
|
||||||
.set_memory_limit(1024 * 1024 * 1024) // 1GB
|
config.set_memory_limit(1024 * 1024 * 1024) // 1GB
|
||||||
.set_cpu_limits(100000, 50000, 1024) // period, quota, shares
|
config.set_cpu_limits(100000, 50000, 1024) // period, quota, shares
|
||||||
.set_pids_limit(500)
|
config.set_pids_limit(500)
|
||||||
.add_mount('/host/path', '/container/path', .bind, [.rw])
|
config.add_mount('/host/path', '/container/path', .bind, [.rw])
|
||||||
.add_mount('/tmp/cache', '/app/cache', .tmpfs, [.rw, .noexec])
|
config.add_mount('/tmp/cache', '/app/cache', .tmpfs, [.rw, .noexec])
|
||||||
.add_capability(.cap_sys_admin)
|
config.add_capability(.cap_sys_admin)
|
||||||
.remove_capability(.cap_net_raw)
|
config.remove_capability(.cap_net_raw)
|
||||||
.add_rlimit(.rlimit_nproc, 100, 50)
|
config.add_rlimit(.rlimit_nproc, 100, 50)
|
||||||
.set_no_new_privileges(true)
|
config.set_no_new_privileges(true)
|
||||||
|
|
||||||
// Add some additional security hardening
|
// Add some additional security hardening
|
||||||
|
|
||||||
config.add_masked_path('/proc/kcore')
|
config.add_masked_path('/proc/kcore')
|
||||||
.add_readonly_path('/proc/sys')
|
config.add_readonly_path('/proc/sys')
|
||||||
|
|
||||||
// Validate before use
|
// Validate before use
|
||||||
config.validate()!
|
config.validate()!
|
||||||
|
|
||||||
// Get the JSON
|
// Get the JSON
|
||||||
json_str := config.to_json()!
|
json_str := config.to_json()!
|
||||||
println('Custom container config:')
|
println('Custom container config:')
|
||||||
println(json_str)
|
println(json_str)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ module crun
|
|||||||
|
|
||||||
import freeflowuniverse.herolib.core.texttools
|
import freeflowuniverse.herolib.core.texttools
|
||||||
|
|
||||||
|
|
||||||
@[params]
|
@[params]
|
||||||
pub struct FactoryArgs {
|
pub struct FactoryArgs {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string = "default"
|
name string = 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CrunConfig {
|
pub struct CrunConfig {
|
||||||
@@ -23,6 +22,8 @@ pub fn (mount_type MountType) to_string() string {
|
|||||||
.proc { 'proc' }
|
.proc { 'proc' }
|
||||||
.sysfs { 'sysfs' }
|
.sysfs { 'sysfs' }
|
||||||
.devpts { 'devpts' }
|
.devpts { 'devpts' }
|
||||||
|
.mqueue { 'mqueue' }
|
||||||
|
.cgroup { 'cgroup' }
|
||||||
.nfs { 'nfs' }
|
.nfs { 'nfs' }
|
||||||
.overlay { 'overlay' }
|
.overlay { 'overlay' }
|
||||||
}
|
}
|
||||||
@@ -120,21 +121,23 @@ pub fn (mut config CrunConfig) set_working_dir(cwd string) &CrunConfig {
|
|||||||
|
|
||||||
pub fn (mut config CrunConfig) set_user(uid u32, gid u32, additional_gids []u32) &CrunConfig {
|
pub fn (mut config CrunConfig) set_user(uid u32, gid u32, additional_gids []u32) &CrunConfig {
|
||||||
config.spec.process.user = User{
|
config.spec.process.user = User{
|
||||||
uid: uid
|
uid: uid
|
||||||
gid: gid
|
gid: gid
|
||||||
additional_gids: additional_gids.clone()
|
additional_gids: additional_gids.clone()
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut config CrunConfig) add_env(key string, value string) &CrunConfig {
|
pub fn (mut config CrunConfig) add_env(key string, value string) &CrunConfig {
|
||||||
|
// Remove existing env var with same key to avoid duplicates
|
||||||
|
config.spec.process.env = config.spec.process.env.filter(!it.starts_with('${key}='))
|
||||||
config.spec.process.env << '${key}=${value}'
|
config.spec.process.env << '${key}=${value}'
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut config CrunConfig) set_rootfs(path string, readonly bool) &CrunConfig {
|
pub fn (mut config CrunConfig) set_rootfs(path string, readonly bool) &CrunConfig {
|
||||||
config.spec.root = Root{
|
config.spec.root = Root{
|
||||||
path: path
|
path: path
|
||||||
readonly: readonly
|
readonly: readonly
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
@@ -165,16 +168,16 @@ pub fn (mut config CrunConfig) set_pids_limit(limit i64) &CrunConfig {
|
|||||||
pub fn (mut config CrunConfig) add_mount(destination string, source string, typ MountType, options []MountOption) &CrunConfig {
|
pub fn (mut config CrunConfig) add_mount(destination string, source string, typ MountType, options []MountOption) &CrunConfig {
|
||||||
config.spec.mounts << Mount{
|
config.spec.mounts << Mount{
|
||||||
destination: destination
|
destination: destination
|
||||||
typ: typ.to_string()
|
typ: typ.to_string()
|
||||||
source: source
|
source: source
|
||||||
options: options.map(it.to_string())
|
options: options.map(it.to_string())
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut config CrunConfig) add_capability(cap Capability) &CrunConfig {
|
pub fn (mut config CrunConfig) add_capability(cap Capability) &CrunConfig {
|
||||||
cap_str := cap.to_string()
|
cap_str := cap.to_string()
|
||||||
|
|
||||||
if cap_str !in config.spec.process.capabilities.bounding {
|
if cap_str !in config.spec.process.capabilities.bounding {
|
||||||
config.spec.process.capabilities.bounding << cap_str
|
config.spec.process.capabilities.bounding << cap_str
|
||||||
}
|
}
|
||||||
@@ -189,7 +192,7 @@ pub fn (mut config CrunConfig) add_capability(cap Capability) &CrunConfig {
|
|||||||
|
|
||||||
pub fn (mut config CrunConfig) remove_capability(cap Capability) &CrunConfig {
|
pub fn (mut config CrunConfig) remove_capability(cap Capability) &CrunConfig {
|
||||||
cap_str := cap.to_string()
|
cap_str := cap.to_string()
|
||||||
|
|
||||||
config.spec.process.capabilities.bounding = config.spec.process.capabilities.bounding.filter(it != cap_str)
|
config.spec.process.capabilities.bounding = config.spec.process.capabilities.bounding.filter(it != cap_str)
|
||||||
config.spec.process.capabilities.effective = config.spec.process.capabilities.effective.filter(it != cap_str)
|
config.spec.process.capabilities.effective = config.spec.process.capabilities.effective.filter(it != cap_str)
|
||||||
config.spec.process.capabilities.permitted = config.spec.process.capabilities.permitted.filter(it != cap_str)
|
config.spec.process.capabilities.permitted = config.spec.process.capabilities.permitted.filter(it != cap_str)
|
||||||
@@ -197,8 +200,11 @@ pub fn (mut config CrunConfig) remove_capability(cap Capability) &CrunConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut config CrunConfig) add_rlimit(typ RlimitType, hard u64, soft u64) &CrunConfig {
|
pub fn (mut config CrunConfig) add_rlimit(typ RlimitType, hard u64, soft u64) &CrunConfig {
|
||||||
|
// Remove existing rlimit with same type to avoid duplicates
|
||||||
|
typ_str := typ.to_string()
|
||||||
|
config.spec.process.rlimits = config.spec.process.rlimits.filter(it.typ != typ_str)
|
||||||
config.spec.process.rlimits << Rlimit{
|
config.spec.process.rlimits << Rlimit{
|
||||||
typ: typ.to_string()
|
typ: typ_str
|
||||||
hard: hard
|
hard: hard
|
||||||
soft: soft
|
soft: soft
|
||||||
}
|
}
|
||||||
@@ -210,6 +216,11 @@ pub fn (mut config CrunConfig) set_no_new_privileges(value bool) &CrunConfig {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn (mut config CrunConfig) set_terminal(value bool) &CrunConfig {
|
||||||
|
config.spec.process.terminal = value
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (mut config CrunConfig) add_masked_path(path string) &CrunConfig {
|
pub fn (mut config CrunConfig) add_masked_path(path string) &CrunConfig {
|
||||||
if path !in config.spec.linux.masked_paths {
|
if path !in config.spec.linux.masked_paths {
|
||||||
config.spec.linux.masked_paths << path
|
config.spec.linux.masked_paths << path
|
||||||
@@ -226,67 +237,65 @@ pub fn (mut config CrunConfig) add_readonly_path(path string) &CrunConfig {
|
|||||||
|
|
||||||
pub fn new(mut configs map[string]&CrunConfig, args FactoryArgs) !&CrunConfig {
|
pub fn new(mut configs map[string]&CrunConfig, args FactoryArgs) !&CrunConfig {
|
||||||
name := texttools.name_fix(args.name)
|
name := texttools.name_fix(args.name)
|
||||||
|
|
||||||
mut config := &CrunConfig{
|
mut config := &CrunConfig{
|
||||||
name: name
|
name: name
|
||||||
spec: create_default_spec()
|
spec: create_default_spec()
|
||||||
}
|
}
|
||||||
|
|
||||||
configs[name] = config
|
configs[name] = config
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(configs map[string]&CrunConfig, args FactoryArgs) !&CrunConfig {
|
pub fn get(configs map[string]&CrunConfig, args FactoryArgs) !&CrunConfig {
|
||||||
name := texttools.name_fix(args.name)
|
name := texttools.name_fix(args.name)
|
||||||
return configs[name] or {
|
return configs[name] or { return error('crun config with name "${name}" does not exist') }
|
||||||
return error('crun config with name "${name}" does not exist')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_default_spec() Spec {
|
fn create_default_spec() Spec {
|
||||||
// Create default spec that matches the heropods template
|
// Create default spec that matches the heropods template
|
||||||
mut spec := Spec{
|
mut spec := Spec{
|
||||||
oci_version: '1.0.2' // Set default here
|
oci_version: '1.0.2' // Set default here
|
||||||
platform: Platform{
|
platform: Platform{
|
||||||
os: 'linux'
|
os: 'linux'
|
||||||
arch: 'amd64'
|
arch: 'amd64'
|
||||||
}
|
}
|
||||||
process: Process{
|
process: Process{
|
||||||
terminal: true
|
terminal: true
|
||||||
user: User{
|
user: User{
|
||||||
uid: 0
|
uid: 0
|
||||||
gid: 0
|
gid: 0
|
||||||
}
|
}
|
||||||
args: ['/bin/sh']
|
args: ['/bin/sh']
|
||||||
env: [
|
env: [
|
||||||
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
||||||
'TERM=xterm'
|
'TERM=xterm',
|
||||||
]
|
]
|
||||||
cwd: '/'
|
cwd: '/'
|
||||||
capabilities: Capabilities{
|
capabilities: Capabilities{
|
||||||
bounding: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
bounding: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
||||||
effective: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
effective: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
||||||
inheritable: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
inheritable: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
||||||
permitted: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
permitted: ['CAP_AUDIT_WRITE', 'CAP_KILL', 'CAP_NET_BIND_SERVICE']
|
||||||
}
|
}
|
||||||
rlimits: [
|
rlimits: [
|
||||||
Rlimit{
|
Rlimit{
|
||||||
typ: 'RLIMIT_NOFILE'
|
typ: 'RLIMIT_NOFILE'
|
||||||
hard: 1024
|
hard: 1024
|
||||||
soft: 1024
|
soft: 1024
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
no_new_privileges: true // No JSON annotation needed here
|
no_new_privileges: true // No JSON annotation needed here
|
||||||
}
|
}
|
||||||
root: Root{
|
root: Root{
|
||||||
path: 'rootfs'
|
path: 'rootfs'
|
||||||
readonly: false
|
readonly: false
|
||||||
}
|
}
|
||||||
hostname: 'container'
|
hostname: 'container'
|
||||||
mounts: create_default_mounts()
|
mounts: create_default_mounts()
|
||||||
linux: Linux{
|
linux: Linux{
|
||||||
namespaces: create_default_namespaces()
|
namespaces: create_default_namespaces()
|
||||||
masked_paths: [
|
masked_paths: [
|
||||||
'/proc/acpi',
|
'/proc/acpi',
|
||||||
'/proc/kcore',
|
'/proc/kcore',
|
||||||
'/proc/keys',
|
'/proc/keys',
|
||||||
@@ -295,7 +304,7 @@ fn create_default_spec() Spec {
|
|||||||
'/proc/timer_stats',
|
'/proc/timer_stats',
|
||||||
'/proc/sched_debug',
|
'/proc/sched_debug',
|
||||||
'/proc/scsi',
|
'/proc/scsi',
|
||||||
'/sys/firmware'
|
'/sys/firmware',
|
||||||
]
|
]
|
||||||
readonly_paths: [
|
readonly_paths: [
|
||||||
'/proc/asound',
|
'/proc/asound',
|
||||||
@@ -303,21 +312,34 @@ fn create_default_spec() Spec {
|
|||||||
'/proc/fs',
|
'/proc/fs',
|
||||||
'/proc/irq',
|
'/proc/irq',
|
||||||
'/proc/sys',
|
'/proc/sys',
|
||||||
'/proc/sysrq-trigger'
|
'/proc/sysrq-trigger',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_default_namespaces() []LinuxNamespace {
|
fn create_default_namespaces() []LinuxNamespace {
|
||||||
return [
|
return [
|
||||||
LinuxNamespace{typ: 'pid'},
|
LinuxNamespace{
|
||||||
LinuxNamespace{typ: 'network'},
|
typ: 'pid'
|
||||||
LinuxNamespace{typ: 'ipc'},
|
},
|
||||||
LinuxNamespace{typ: 'uts'},
|
LinuxNamespace{
|
||||||
LinuxNamespace{typ: 'mount'},
|
typ: 'network'
|
||||||
|
},
|
||||||
|
LinuxNamespace{
|
||||||
|
typ: 'ipc'
|
||||||
|
},
|
||||||
|
LinuxNamespace{
|
||||||
|
typ: 'uts'
|
||||||
|
},
|
||||||
|
LinuxNamespace{
|
||||||
|
typ: 'cgroup'
|
||||||
|
},
|
||||||
|
LinuxNamespace{
|
||||||
|
typ: 'mount'
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,20 +347,44 @@ fn create_default_mounts() []Mount {
|
|||||||
return [
|
return [
|
||||||
Mount{
|
Mount{
|
||||||
destination: '/proc'
|
destination: '/proc'
|
||||||
typ: 'proc'
|
typ: 'proc'
|
||||||
source: 'proc'
|
source: 'proc'
|
||||||
},
|
},
|
||||||
Mount{
|
Mount{
|
||||||
destination: '/dev'
|
destination: '/dev'
|
||||||
typ: 'tmpfs'
|
typ: 'tmpfs'
|
||||||
source: 'tmpfs'
|
source: 'tmpfs'
|
||||||
options: ['nosuid', 'strictatime', 'mode=755', 'size=65536k']
|
options: ['nosuid', 'strictatime', 'mode=755', 'size=65536k']
|
||||||
|
},
|
||||||
|
Mount{
|
||||||
|
destination: '/dev/pts'
|
||||||
|
typ: 'devpts'
|
||||||
|
source: 'devpts'
|
||||||
|
options: ['nosuid', 'noexec', 'newinstance', 'ptmxmode=0666', 'mode=0620', 'gid=5']
|
||||||
|
},
|
||||||
|
Mount{
|
||||||
|
destination: '/dev/shm'
|
||||||
|
typ: 'tmpfs'
|
||||||
|
source: 'shm'
|
||||||
|
options: ['nosuid', 'noexec', 'nodev', 'mode=1777', 'size=65536k']
|
||||||
|
},
|
||||||
|
Mount{
|
||||||
|
destination: '/dev/mqueue'
|
||||||
|
typ: 'mqueue'
|
||||||
|
source: 'mqueue'
|
||||||
|
options: ['nosuid', 'noexec', 'nodev']
|
||||||
},
|
},
|
||||||
Mount{
|
Mount{
|
||||||
destination: '/sys'
|
destination: '/sys'
|
||||||
typ: 'sysfs'
|
typ: 'sysfs'
|
||||||
source: 'sysfs'
|
source: 'sysfs'
|
||||||
options: ['nosuid', 'noexec', 'nodev', 'ro']
|
options: ['nosuid', 'noexec', 'nodev', 'ro']
|
||||||
|
},
|
||||||
|
Mount{
|
||||||
|
destination: '/sys/fs/cgroup'
|
||||||
|
typ: 'cgroup'
|
||||||
|
source: 'cgroup'
|
||||||
|
options: ['nosuid', 'noexec', 'nodev', 'relatime', 'ro']
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module crun
|
|||||||
// OCI Runtime Spec structures that can be directly encoded to JSON
|
// OCI Runtime Spec structures that can be directly encoded to JSON
|
||||||
pub struct Spec {
|
pub struct Spec {
|
||||||
pub mut:
|
pub mut:
|
||||||
oci_version string
|
oci_version string @[json: 'ociVersion']
|
||||||
platform Platform
|
platform Platform
|
||||||
process Process
|
process Process
|
||||||
root Root
|
root Root
|
||||||
@@ -21,21 +21,21 @@ pub mut:
|
|||||||
|
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
pub mut:
|
pub mut:
|
||||||
terminal bool = true
|
terminal bool = true
|
||||||
user User
|
user User
|
||||||
args []string
|
args []string
|
||||||
env []string
|
env []string
|
||||||
cwd string = '/'
|
cwd string = '/'
|
||||||
capabilities Capabilities
|
capabilities Capabilities
|
||||||
rlimits []Rlimit
|
rlimits []Rlimit
|
||||||
no_new_privileges bool
|
no_new_privileges bool @[json: 'noNewPrivileges']
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub mut:
|
pub mut:
|
||||||
uid u32
|
uid u32
|
||||||
gid u32
|
gid u32
|
||||||
additional_gids []u32
|
additional_gids []u32 @[json: 'additionalGids']
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Capabilities {
|
pub struct Capabilities {
|
||||||
@@ -49,7 +49,7 @@ pub mut:
|
|||||||
|
|
||||||
pub struct Rlimit {
|
pub struct Rlimit {
|
||||||
pub mut:
|
pub mut:
|
||||||
typ string
|
typ string @[json: 'type']
|
||||||
hard u64
|
hard u64
|
||||||
soft u64
|
soft u64
|
||||||
}
|
}
|
||||||
@@ -63,26 +63,26 @@ pub mut:
|
|||||||
pub struct Mount {
|
pub struct Mount {
|
||||||
pub mut:
|
pub mut:
|
||||||
destination string
|
destination string
|
||||||
typ string
|
typ string @[json: 'type']
|
||||||
source string
|
source string
|
||||||
options []string
|
options []string
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Linux {
|
pub struct Linux {
|
||||||
pub mut:
|
pub mut:
|
||||||
namespaces []LinuxNamespace
|
namespaces []LinuxNamespace
|
||||||
resources LinuxResources
|
resources LinuxResources
|
||||||
devices []LinuxDevice
|
devices []LinuxDevice
|
||||||
masked_paths []string
|
masked_paths []string @[json: 'maskedPaths']
|
||||||
readonly_paths []string
|
readonly_paths []string @[json: 'readonlyPaths']
|
||||||
uid_mappings []LinuxIDMapping
|
uid_mappings []LinuxIDMapping @[json: 'uidMappings']
|
||||||
gid_mappings []LinuxIDMapping
|
gid_mappings []LinuxIDMapping @[json: 'gidMappings']
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LinuxNamespace {
|
pub struct LinuxNamespace {
|
||||||
pub mut:
|
pub mut:
|
||||||
typ string
|
typ string @[json: 'type']
|
||||||
path string
|
path string @[omitempty]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LinuxResources {
|
pub struct LinuxResources {
|
||||||
@@ -95,47 +95,47 @@ pub mut:
|
|||||||
|
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
pub mut:
|
pub mut:
|
||||||
limit u64
|
limit u64 @[omitempty]
|
||||||
reservation u64
|
reservation u64 @[omitempty]
|
||||||
swap u64
|
swap u64 @[omitempty]
|
||||||
kernel u64
|
kernel u64 @[omitempty]
|
||||||
swappiness i64
|
swappiness i64 @[omitempty]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CPU {
|
pub struct CPU {
|
||||||
pub mut:
|
pub mut:
|
||||||
shares u64
|
shares u64 @[omitempty]
|
||||||
quota i64
|
quota i64 @[omitempty]
|
||||||
period u64
|
period u64 @[omitempty]
|
||||||
cpus string
|
cpus string @[omitempty]
|
||||||
mems string
|
mems string @[omitempty]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pids {
|
pub struct Pids {
|
||||||
pub mut:
|
pub mut:
|
||||||
limit i64
|
limit i64 @[omitempty]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BlockIO {
|
pub struct BlockIO {
|
||||||
pub mut:
|
pub mut:
|
||||||
weight u16
|
weight u16 @[omitempty]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LinuxDevice {
|
pub struct LinuxDevice {
|
||||||
pub mut:
|
pub mut:
|
||||||
path string
|
path string
|
||||||
typ string
|
typ string @[json: 'type']
|
||||||
major i64
|
major i64
|
||||||
minor i64
|
minor i64
|
||||||
file_mode u32
|
file_mode u32 @[json: 'fileMode']
|
||||||
uid u32
|
uid u32
|
||||||
gid u32
|
gid u32
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LinuxIDMapping {
|
pub struct LinuxIDMapping {
|
||||||
pub mut:
|
pub mut:
|
||||||
container_id u32
|
container_id u32 @[json: 'containerID']
|
||||||
host_id u32
|
host_id u32 @[json: 'hostID']
|
||||||
size u32
|
size u32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +160,8 @@ pub enum MountType {
|
|||||||
proc
|
proc
|
||||||
sysfs
|
sysfs
|
||||||
devpts
|
devpts
|
||||||
|
mqueue
|
||||||
|
cgroup
|
||||||
nfs
|
nfs
|
||||||
overlay
|
overlay
|
||||||
}
|
}
|
||||||
@@ -235,4 +237,4 @@ pub enum RlimitType {
|
|||||||
rlimit_nice
|
rlimit_nice
|
||||||
rlimit_rtprio
|
rlimit_rtprio
|
||||||
rlimit_rttime
|
rlimit_rttime
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
{
|
|
||||||
"ociVersion": "1.0.2",
|
|
||||||
"process": {
|
|
||||||
"terminal": true,
|
|
||||||
"user": {
|
|
||||||
"uid": 0,
|
|
||||||
"gid": 0
|
|
||||||
},
|
|
||||||
"args": [
|
|
||||||
"/bin/sh",
|
|
||||||
"-c",
|
|
||||||
"while true; do sleep 30; done"
|
|
||||||
],
|
|
||||||
"env": [
|
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
||||||
"TERM=xterm"
|
|
||||||
],
|
|
||||||
"cwd": "/",
|
|
||||||
"capabilities": {
|
|
||||||
"bounding": [
|
|
||||||
"CAP_AUDIT_WRITE",
|
|
||||||
"CAP_KILL",
|
|
||||||
"CAP_NET_BIND_SERVICE"
|
|
||||||
],
|
|
||||||
"effective": [
|
|
||||||
"CAP_AUDIT_WRITE",
|
|
||||||
"CAP_KILL",
|
|
||||||
"CAP_NET_BIND_SERVICE"
|
|
||||||
],
|
|
||||||
"inheritable": [
|
|
||||||
"CAP_AUDIT_WRITE",
|
|
||||||
"CAP_KILL",
|
|
||||||
"CAP_NET_BIND_SERVICE"
|
|
||||||
],
|
|
||||||
"permitted": [
|
|
||||||
"CAP_AUDIT_WRITE",
|
|
||||||
"CAP_KILL",
|
|
||||||
"CAP_NET_BIND_SERVICE"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"rlimits": [
|
|
||||||
{
|
|
||||||
"type": "RLIMIT_NOFILE",
|
|
||||||
"hard": 1024,
|
|
||||||
"soft": 1024
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"noNewPrivileges": true
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"path": "${rootfs_path}",
|
|
||||||
"readonly": false
|
|
||||||
},
|
|
||||||
"mounts": [
|
|
||||||
{
|
|
||||||
"destination": "/proc",
|
|
||||||
"type": "proc",
|
|
||||||
"source": "proc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/dev",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": [
|
|
||||||
"nosuid",
|
|
||||||
"strictatime",
|
|
||||||
"mode=755",
|
|
||||||
"size=65536k"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/sys",
|
|
||||||
"type": "sysfs",
|
|
||||||
"source": "sysfs",
|
|
||||||
"options": [
|
|
||||||
"nosuid",
|
|
||||||
"noexec",
|
|
||||||
"nodev",
|
|
||||||
"ro"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"linux": {
|
|
||||||
"namespaces": [
|
|
||||||
{
|
|
||||||
"type": "pid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "network"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "ipc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "uts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "mount"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"maskedPaths": [
|
|
||||||
"/proc/acpi",
|
|
||||||
"/proc/kcore",
|
|
||||||
"/proc/keys",
|
|
||||||
"/proc/latency_stats",
|
|
||||||
"/proc/timer_list",
|
|
||||||
"/proc/timer_stats",
|
|
||||||
"/proc/sched_debug",
|
|
||||||
"/proc/scsi",
|
|
||||||
"/sys/firmware"
|
|
||||||
],
|
|
||||||
"readonlyPaths": [
|
|
||||||
"/proc/asound",
|
|
||||||
"/proc/bus",
|
|
||||||
"/proc/fs",
|
|
||||||
"/proc/irq",
|
|
||||||
"/proc/sys",
|
|
||||||
"/proc/sysrq-trigger"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,16 +3,19 @@ module heropods
|
|||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
import freeflowuniverse.herolib.osal.tmux
|
import freeflowuniverse.herolib.osal.tmux
|
||||||
import freeflowuniverse.herolib.osal.core as osal
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
import freeflowuniverse.herolib.virt.crun
|
||||||
import time
|
import time
|
||||||
import freeflowuniverse.herolib.builder
|
import freeflowuniverse.herolib.builder
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
@[heap]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string
|
name string
|
||||||
node ?&builder.Node
|
node ?&builder.Node
|
||||||
tmux_pane ?&tmux.Pane
|
tmux_pane ?&tmux.Pane
|
||||||
factory &ContainerFactory
|
crun_config ?&crun.CrunConfig
|
||||||
|
factory &ContainerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct to parse JSON output of `crun state`
|
// Struct to parse JSON output of `crun state`
|
||||||
@@ -31,10 +34,32 @@ pub fn (mut self Container) start() ! {
|
|||||||
if !container_exists {
|
if !container_exists {
|
||||||
// Container doesn't exist, create it first
|
// Container doesn't exist, create it first
|
||||||
console.print_debug('Container ${self.name} does not exist, creating it...')
|
console.print_debug('Container ${self.name} does not exist, creating it...')
|
||||||
osal.exec(
|
// Try to create the container, if it fails with "File exists" error,
|
||||||
cmd: 'crun create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
// try to force delete any leftover state and retry
|
||||||
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
create_result := osal.exec(
|
||||||
|
cmd: 'crun --root ${crun_root} create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
||||||
stdout: true
|
stdout: true
|
||||||
)!
|
) or {
|
||||||
|
if err.msg().contains('File exists') {
|
||||||
|
console.print_debug('Container creation failed with "File exists", attempting to clean up leftover state...')
|
||||||
|
// Force delete any leftover state - try multiple cleanup approaches
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} delete ${self.name}', stdout: false) or {}
|
||||||
|
osal.exec(cmd: 'crun delete ${self.name}', stdout: false) or {} // Also try default root
|
||||||
|
// Clean up any leftover runtime directories
|
||||||
|
osal.exec(cmd: 'rm -rf ${crun_root}/${self.name}', stdout: false) or {}
|
||||||
|
osal.exec(cmd: 'rm -rf /run/crun/${self.name}', stdout: false) or {}
|
||||||
|
// Wait a moment for cleanup to complete
|
||||||
|
time.sleep(500 * time.millisecond)
|
||||||
|
// Retry creation
|
||||||
|
osal.exec(
|
||||||
|
cmd: 'crun --root ${crun_root} create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
||||||
|
stdout: true
|
||||||
|
)!
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
console.print_debug('Container ${self.name} created')
|
console.print_debug('Container ${self.name} created')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,16 +73,18 @@ pub fn (mut self Container) start() ! {
|
|||||||
// because crun doesn't allow restarting a stopped container
|
// because crun doesn't allow restarting a stopped container
|
||||||
if container_exists && status != .running {
|
if container_exists && status != .running {
|
||||||
console.print_debug('Container ${self.name} exists but is stopped, recreating...')
|
console.print_debug('Container ${self.name} exists but is stopped, recreating...')
|
||||||
osal.exec(cmd: 'crun delete ${self.name}', stdout: false) or {}
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} delete ${self.name}', stdout: false) or {}
|
||||||
osal.exec(
|
osal.exec(
|
||||||
cmd: 'crun create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
cmd: 'crun --root ${crun_root} create --bundle ${self.factory.base_dir}/configs/${self.name} ${self.name}'
|
||||||
stdout: true
|
stdout: true
|
||||||
)!
|
)!
|
||||||
console.print_debug('Container ${self.name} recreated')
|
console.print_debug('Container ${self.name} recreated')
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the container (crun start doesn't have --detach flag)
|
// start the container (crun start doesn't have --detach flag)
|
||||||
osal.exec(cmd: 'crun start ${self.name}', stdout: true)!
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} start ${self.name}', stdout: true)!
|
||||||
console.print_green('Container ${self.name} started')
|
console.print_green('Container ${self.name} started')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,12 +95,13 @@ pub fn (mut self Container) stop() ! {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
osal.exec(cmd: 'crun kill ${self.name} SIGTERM', stdout: false) or {}
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} kill ${self.name} SIGTERM', stdout: false) or {}
|
||||||
time.sleep(2 * time.second)
|
time.sleep(2 * time.second)
|
||||||
|
|
||||||
// Force kill if still running
|
// Force kill if still running
|
||||||
if self.status()! == .running {
|
if self.status()! == .running {
|
||||||
osal.exec(cmd: 'crun kill ${self.name} SIGKILL', stdout: false) or {}
|
osal.exec(cmd: 'crun --root ${crun_root} kill ${self.name} SIGKILL', stdout: false) or {}
|
||||||
}
|
}
|
||||||
console.print_green('Container ${self.name} stopped')
|
console.print_green('Container ${self.name} stopped')
|
||||||
}
|
}
|
||||||
@@ -86,7 +114,8 @@ pub fn (mut self Container) delete() ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.stop()!
|
self.stop()!
|
||||||
osal.exec(cmd: 'crun delete ${self.name}', stdout: false) or {}
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} delete ${self.name}', stdout: false) or {}
|
||||||
|
|
||||||
// Remove from factory's container cache
|
// Remove from factory's container cache
|
||||||
if self.name in self.factory.containers {
|
if self.name in self.factory.containers {
|
||||||
@@ -110,7 +139,10 @@ pub fn (mut self Container) exec(cmd_ osal.Command) !string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn (self Container) status() !ContainerStatus {
|
pub fn (self Container) status() !ContainerStatus {
|
||||||
result := osal.exec(cmd: 'crun state ${self.name}', stdout: false) or { return .unknown }
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
result := osal.exec(cmd: 'crun --root ${crun_root} state ${self.name}', stdout: false) or {
|
||||||
|
return .unknown
|
||||||
|
}
|
||||||
|
|
||||||
// Parse JSON output from crun state
|
// Parse JSON output from crun state
|
||||||
state := json.decode(CrunState, result.output) or { return .unknown }
|
state := json.decode(CrunState, result.output) or { return .unknown }
|
||||||
@@ -126,7 +158,10 @@ pub fn (self Container) status() !ContainerStatus {
|
|||||||
// Check if container exists in crun (regardless of its state)
|
// Check if container exists in crun (regardless of its state)
|
||||||
fn (self Container) container_exists_in_crun() !bool {
|
fn (self Container) container_exists_in_crun() !bool {
|
||||||
// Try to get container state - if it fails, container doesn't exist
|
// Try to get container state - if it fails, container doesn't exist
|
||||||
result := osal.exec(cmd: 'crun state ${self.name}', stdout: false) or { return false }
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
result := osal.exec(cmd: 'crun --root ${crun_root} state ${self.name}', stdout: false) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// If we get here, the container exists (even if stopped/paused)
|
// If we get here, the container exists (even if stopped/paused)
|
||||||
return result.exit_code == 0
|
return result.exit_code == 0
|
||||||
@@ -206,7 +241,8 @@ pub fn (mut self Container) tmux_pane(args TmuxPaneArgs) !&tmux.Pane {
|
|||||||
|
|
||||||
// Execute command if provided
|
// Execute command if provided
|
||||||
if args.cmd != '' {
|
if args.cmd != '' {
|
||||||
pane.send_keys('crun exec ${self.name} ${args.cmd}')!
|
crun_root := '${self.factory.base_dir}/runtime'
|
||||||
|
pane.send_keys('crun --root ${crun_root} exec ${self.name} ${args.cmd}')!
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tmux_pane = pane
|
self.tmux_pane = pane
|
||||||
@@ -223,6 +259,7 @@ pub fn (mut self Container) node() !&builder.Node {
|
|||||||
|
|
||||||
mut exec := builder.ExecutorCrun{
|
mut exec := builder.ExecutorCrun{
|
||||||
container_id: self.name
|
container_id: self.name
|
||||||
|
crun_root: '${self.factory.base_dir}/runtime'
|
||||||
debug: false
|
debug: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,3 +279,58 @@ pub fn (mut self Container) node() !&builder.Node {
|
|||||||
self.node = node
|
self.node = node
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the crun configuration for this container
|
||||||
|
pub fn (self Container) config() !&crun.CrunConfig {
|
||||||
|
return self.crun_config or { return error('Container ${self.name} has no crun configuration') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container configuration customization methods
|
||||||
|
pub fn (mut self Container) set_memory_limit(limit_mb u64) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.set_memory_limit(limit_mb * 1024 * 1024) // Convert MB to bytes
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self Container) set_cpu_limits(period u64, quota i64, shares u64) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.set_cpu_limits(period, quota, shares)
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self Container) add_mount(source string, destination string, mount_type crun.MountType, options []crun.MountOption) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.add_mount(source, destination, mount_type, options)
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self Container) add_capability(cap crun.Capability) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.add_capability(cap)
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self Container) remove_capability(cap crun.Capability) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.remove_capability(cap)
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self Container) add_env(key string, value string) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.add_env(key, value)
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self Container) set_working_dir(dir string) !&Container {
|
||||||
|
mut config := self.config()!
|
||||||
|
config.set_working_dir(dir)
|
||||||
|
return &self
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the current configuration to disk
|
||||||
|
pub fn (self Container) save_config() ! {
|
||||||
|
config := self.config()!
|
||||||
|
config_path := '${self.factory.base_dir}/configs/${self.name}/config.json'
|
||||||
|
config.save_to_file(config_path)!
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ module heropods
|
|||||||
|
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
import freeflowuniverse.herolib.osal.core as osal
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
import freeflowuniverse.herolib.core.pathlib
|
import freeflowuniverse.herolib.virt.crun
|
||||||
import freeflowuniverse.herolib.installers.virt.herorunner as herorunner_installer
|
import freeflowuniverse.herolib.installers.virt.herorunner as herorunner_installer
|
||||||
import os
|
import os
|
||||||
import x.json2
|
|
||||||
|
|
||||||
// Updated enum to be more flexible
|
// Updated enum to be more flexible
|
||||||
pub enum ContainerImageType {
|
pub enum ContainerImageType {
|
||||||
@@ -27,7 +26,7 @@ pub:
|
|||||||
|
|
||||||
pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
|
pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
|
||||||
if args.name in self.containers && !args.reset {
|
if args.name in self.containers && !args.reset {
|
||||||
return self.containers[args.name]
|
return self.containers[args.name] or { panic('bug: container should exist') }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine image to use
|
// Determine image to use
|
||||||
@@ -67,8 +66,8 @@ pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
|
|||||||
return error('Image rootfs not found: ${rootfs_path}. Please ensure the image is available.')
|
return error('Image rootfs not found: ${rootfs_path}. Please ensure the image is available.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create container config (with terminal disabled) but don't create the container yet
|
// Create crun configuration using the crun module
|
||||||
self.create_container_config(args.name, rootfs_path)!
|
mut crun_config := self.create_crun_config(args.name, rootfs_path)!
|
||||||
|
|
||||||
// Ensure crun is installed on host
|
// Ensure crun is installed on host
|
||||||
if !osal.cmd_exists('crun') {
|
if !osal.cmd_exists('crun') {
|
||||||
@@ -79,41 +78,45 @@ pub fn (mut self ContainerFactory) new(args ContainerNewArgs) !&Container {
|
|||||||
// Create container struct but don't create the actual container in crun yet
|
// Create container struct but don't create the actual container in crun yet
|
||||||
// The actual container creation will happen in container.start()
|
// The actual container creation will happen in container.start()
|
||||||
mut container := &Container{
|
mut container := &Container{
|
||||||
name: args.name
|
name: args.name
|
||||||
factory: &self
|
crun_config: crun_config
|
||||||
|
factory: &self
|
||||||
}
|
}
|
||||||
|
|
||||||
self.containers[args.name] = container
|
self.containers[args.name] = container
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create OCI config.json from template
|
// Create crun configuration using the crun module
|
||||||
fn (self ContainerFactory) create_container_config(container_name string, rootfs_path string) ! {
|
fn (mut self ContainerFactory) create_crun_config(container_name string, rootfs_path string) !&crun.CrunConfig {
|
||||||
|
// Create crun configuration using the factory pattern
|
||||||
|
mut config := crun.new(mut self.crun_configs, name: container_name)!
|
||||||
|
|
||||||
|
// Configure for heropods use case - disable terminal for background containers
|
||||||
|
config.set_terminal(false)
|
||||||
|
config.set_command(['/bin/sh', '-c', 'while true; do sleep 30; done'])
|
||||||
|
config.set_working_dir('/')
|
||||||
|
config.set_user(0, 0, [])
|
||||||
|
config.add_env('PATH', '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin')
|
||||||
|
config.add_env('TERM', 'xterm')
|
||||||
|
config.set_rootfs(rootfs_path, false)
|
||||||
|
config.set_hostname('container')
|
||||||
|
config.set_no_new_privileges(true)
|
||||||
|
|
||||||
|
// Add the specific rlimit for file descriptors
|
||||||
|
config.add_rlimit(.rlimit_nofile, 1024, 1024)
|
||||||
|
|
||||||
|
// Validate the configuration
|
||||||
|
config.validate()!
|
||||||
|
|
||||||
|
// Create config directory and save JSON
|
||||||
config_dir := '${self.base_dir}/configs/${container_name}'
|
config_dir := '${self.base_dir}/configs/${container_name}'
|
||||||
osal.exec(cmd: 'mkdir -p ${config_dir}', stdout: false)!
|
osal.exec(cmd: 'mkdir -p ${config_dir}', stdout: false)!
|
||||||
|
|
||||||
// Load template
|
|
||||||
mut config_content := $tmpl('config_template.json')
|
|
||||||
|
|
||||||
// Parse JSON with json2
|
|
||||||
mut root := json2.raw_decode(config_content)!
|
|
||||||
mut config := root.as_map()
|
|
||||||
|
|
||||||
// Get or create process map
|
|
||||||
mut process := if 'process' in config {
|
|
||||||
config['process'].as_map()
|
|
||||||
} else {
|
|
||||||
map[string]json2.Any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force disable terminal
|
|
||||||
process['terminal'] = json2.Any(false)
|
|
||||||
config['process'] = json2.Any(process)
|
|
||||||
|
|
||||||
// Write back to config.json
|
|
||||||
config_path := '${config_dir}/config.json'
|
config_path := '${config_dir}/config.json'
|
||||||
mut p := pathlib.get_file(path: config_path, create: true)!
|
config.save_to_file(config_path)!
|
||||||
p.write(json2.encode_pretty(json2.Any(config)))!
|
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use podman to pull image and extract rootfs
|
// Use podman to pull image and extract rootfs
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module heropods
|
|||||||
|
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
import freeflowuniverse.herolib.osal.core as osal
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
import time
|
import freeflowuniverse.herolib.virt.crun
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@[heap]
|
@[heap]
|
||||||
@@ -11,6 +11,7 @@ pub mut:
|
|||||||
tmux_session string
|
tmux_session string
|
||||||
containers map[string]&Container
|
containers map[string]&Container
|
||||||
images map[string]&ContainerImage
|
images map[string]&ContainerImage
|
||||||
|
crun_configs map[string]&crun.CrunConfig
|
||||||
base_dir string
|
base_dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +46,11 @@ fn (mut self ContainerFactory) init(args FactoryInitArgs) ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up any leftover crun state if reset is requested
|
||||||
|
if args.reset {
|
||||||
|
self.cleanup_crun_state()!
|
||||||
|
}
|
||||||
|
|
||||||
// Load existing images into cache
|
// Load existing images into cache
|
||||||
self.load_existing_images()!
|
self.load_existing_images()!
|
||||||
|
|
||||||
@@ -104,7 +110,7 @@ pub fn (mut self ContainerFactory) get(args ContainerNewArgs) !&Container {
|
|||||||
if args.name !in self.containers {
|
if args.name !in self.containers {
|
||||||
return error('Container "${args.name}" does not exist. Use factory.new() to create it first.')
|
return error('Container "${args.name}" does not exist. Use factory.new() to create it first.')
|
||||||
}
|
}
|
||||||
return self.containers[args.name]
|
return self.containers[args.name] or { panic('bug: container should exist') }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get image by name
|
// Get image by name
|
||||||
@@ -112,7 +118,7 @@ pub fn (mut self ContainerFactory) image_get(name string) !&ContainerImage {
|
|||||||
if name !in self.images {
|
if name !in self.images {
|
||||||
return error('Image "${name}" not found in cache. Try importing or downloading it.')
|
return error('Image "${name}" not found in cache. Try importing or downloading it.')
|
||||||
}
|
}
|
||||||
return self.images[name]
|
return self.images[name] or { panic('bug: image should exist') }
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all containers currently managed by crun
|
// List all containers currently managed by crun
|
||||||
@@ -136,3 +142,34 @@ pub fn (self ContainerFactory) list() ![]Container {
|
|||||||
}
|
}
|
||||||
return containers
|
return containers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up any leftover crun state
|
||||||
|
fn (mut self ContainerFactory) cleanup_crun_state() ! {
|
||||||
|
console.print_debug('Cleaning up leftover crun state...')
|
||||||
|
crun_root := '${self.base_dir}/runtime'
|
||||||
|
|
||||||
|
// Stop and delete all containers in our custom root
|
||||||
|
result := osal.exec(cmd: 'crun --root ${crun_root} list -q', stdout: false) or { return }
|
||||||
|
|
||||||
|
for container_name in result.output.split_into_lines() {
|
||||||
|
if container_name.trim_space() != '' {
|
||||||
|
console.print_debug('Cleaning up container: ${container_name}')
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} kill ${container_name} SIGKILL', stdout: false) or {}
|
||||||
|
osal.exec(cmd: 'crun --root ${crun_root} delete ${container_name}', stdout: false) or {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also clean up any containers in the default root that might be ours
|
||||||
|
result2 := osal.exec(cmd: 'crun list -q', stdout: false) or { return }
|
||||||
|
for container_name in result2.output.split_into_lines() {
|
||||||
|
if container_name.trim_space() != '' && container_name in self.containers {
|
||||||
|
console.print_debug('Cleaning up container from default root: ${container_name}')
|
||||||
|
osal.exec(cmd: 'crun kill ${container_name} SIGKILL', stdout: false) or {}
|
||||||
|
osal.exec(cmd: 'crun delete ${container_name}', stdout: false) or {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up runtime directories
|
||||||
|
osal.exec(cmd: 'rm -rf ${crun_root}/*', stdout: false) or {}
|
||||||
|
osal.exec(cmd: 'find /run/crun -name "*" -type d -exec rm -rf {} + 2>/dev/null', stdout: false) or {}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user