...
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
# HeroRun - Remote Container Management
|
||||
|
||||
A V library for managing remote containers using runc and tmux, with support for multiple cloud providers.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-provider support**: Currently supports Hetzner, with ThreeFold coming soon
|
||||
- **Automatic setup**: Installs required packages (runc, tmux, curl, xz-utils) automatically
|
||||
- **Container isolation**: Uses runc for lightweight container management
|
||||
- **tmux integration**: Each container gets its own tmux session for multiple concurrent shells
|
||||
- **Clean API**: Simple interface that hides infrastructure complexity
|
||||
|
||||
## Project Structure
|
||||
|
||||
```txt
|
||||
lib/virt/herorun/
|
||||
├── interfaces.v # Shared interfaces and parameter structs
|
||||
├── nodes.v # Node management and SSH connectivity
|
||||
├── container.v # Container struct and lifecycle operations
|
||||
├── executor.v # Optimized command execution engine
|
||||
├── factory.v # Provider abstraction and backend creation
|
||||
├── hetzner_backend.v # Hetzner cloud implementation
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.virt.herorun
|
||||
|
||||
// Create user with SSH key
|
||||
mut user := herorun.new_user(keyname: 'id_ed25519')!
|
||||
|
||||
// Create Hetzner backend
|
||||
mut backend := herorun.new_hetzner_backend(
|
||||
node_ip: '65.21.132.119'
|
||||
user: 'root'
|
||||
)!
|
||||
|
||||
// Connect to node (installs required packages automatically)
|
||||
backend.connect(keyname: user.keyname)!
|
||||
|
||||
// Send a test command to the node
|
||||
backend.send_command(cmd: 'ls')!
|
||||
|
||||
// Get or create container (uses tmux behind the scenes)
|
||||
mut container := backend.get_or_create_container(name: 'test_container')!
|
||||
|
||||
// Attach to container tmux session
|
||||
container.attach()!
|
||||
|
||||
// Send command to container
|
||||
container.send_command(cmd: 'ls')!
|
||||
|
||||
// Get container logs
|
||||
logs := container.get_logs()!
|
||||
println('Container logs:')
|
||||
println(logs)
|
||||
|
||||
```
|
||||
|
||||
### Running the Example
|
||||
|
||||
```bash
|
||||
# Make the example executable
|
||||
chmod +x examples/virt/herorun/herorun.vsh
|
||||
|
||||
# Run it
|
||||
./examples/virt/herorun/herorun.vsh
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **NodeBackend**: Defines operations for connecting to and managing remote nodes
|
||||
- **ContainerBackend**: Defines operations for container lifecycle management
|
||||
|
||||
### Providers
|
||||
|
||||
- **HetznerBackend**: Implementation for Hetzner cloud servers
|
||||
- **ThreeFoldBackend**: (Coming soon) Implementation for ThreeFold nodes
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **SSH Integration**: Uses herolib's sshagent module for secure connections
|
||||
2. **tmux Management**: Uses herolib's tmux module for session management
|
||||
3. **Container Runtime**: Uses runc for lightweight container execution
|
||||
4. **Hetzner Integration**: Uses herolib's hetznermanager module
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `freeflowuniverse.herolib.osal.sshagent`
|
||||
- `freeflowuniverse.herolib.osal.tmux`
|
||||
- `freeflowuniverse.herolib.installers.web.hetznermanager`
|
||||
- `freeflowuniverse.herolib.ui.console`
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- ThreeFold backend implementation
|
||||
- Support for additional cloud providers (AWS, GCP, etc.)
|
||||
- Container image management
|
||||
- Network configuration
|
||||
- Volume mounting
|
||||
- Resource limits and monitoring
|
||||
|
||||
@@ -2,94 +2,82 @@ module herorun
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import time
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
// Container struct and related functionality
|
||||
pub struct Container {
|
||||
pub:
|
||||
name string
|
||||
node Node
|
||||
pub mut:
|
||||
tmux tmux.Tmux
|
||||
name string
|
||||
//TODO: add properties we need for crun usage
|
||||
node ?builder.Node
|
||||
tmux ?tmux.Pane
|
||||
factory &ContainerFactory
|
||||
}
|
||||
|
||||
// Implement ContainerBackend interface for Container
|
||||
pub fn (mut c Container) attach() ! {
|
||||
console.print_header('🔗 Attaching to container: ${c.name}')
|
||||
|
||||
// Create or get the session for this container
|
||||
if !c.tmux.session_exist(c.name) {
|
||||
console.print_stdout('Starting new tmux session for container ${c.name}')
|
||||
pub fn (self Container) start() ! {
|
||||
|
||||
// Use the tmux convenience method to create session and window in one go
|
||||
shell_cmd := 'ssh ${c.node.settings.user}@${c.node.settings.node_ip}'
|
||||
c.tmux.window_new(
|
||||
session_name: c.name
|
||||
name: 'main'
|
||||
cmd: shell_cmd
|
||||
reset: true
|
||||
)!
|
||||
|
||||
// Wait for the session and window to be properly created
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Rescan to make sure everything is properly registered
|
||||
c.tmux.scan()!
|
||||
}
|
||||
|
||||
console.print_green('Attached to container session ${c.name}')
|
||||
}
|
||||
|
||||
pub fn (mut c Container) send_command(args ContainerCommandArgs) ! {
|
||||
console.print_header('📝 Exec in container ${c.name}')
|
||||
|
||||
// Ensure session exists
|
||||
if !c.tmux.session_exist(c.name) {
|
||||
return error('Container session ${c.name} does not exist. Call attach() first.')
|
||||
}
|
||||
pub fn (self Container) stop() ! {
|
||||
|
||||
// Debug: print session info
|
||||
mut session := c.tmux.session_get(c.name)!
|
||||
console.print_debug('Session ${c.name} has ${session.windows.len} windows')
|
||||
for window in session.windows {
|
||||
console.print_debug(' Window: ${window.name} (ID: ${window.id})')
|
||||
}
|
||||
|
||||
// Try to get the main window
|
||||
mut window := session.window_get(name: 'main') or {
|
||||
// If main window doesn't exist, try to get the first window
|
||||
if session.windows.len > 0 {
|
||||
session.windows[0]
|
||||
} else {
|
||||
return error('No windows available in session ${c.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh window state to get current panes
|
||||
window.scan()!
|
||||
|
||||
// Get the first pane and send the command
|
||||
if window.panes.len > 0 {
|
||||
mut pane := window.panes[0]
|
||||
|
||||
// Send command to enter the container first, then the actual command
|
||||
container_enter_cmd := 'cd /containers/${c.name} && runc exec ${c.name} ${args.cmd}'
|
||||
pane.send_command(container_enter_cmd)!
|
||||
} else {
|
||||
return error('No panes available in container ${c.name}')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Container) get_logs() !string {
|
||||
// Get the session and window
|
||||
mut session := c.tmux.session_get(c.name)!
|
||||
mut window := session.window_get(name: 'main')!
|
||||
|
||||
// Get logs from the first pane
|
||||
if window.panes.len > 0 {
|
||||
mut pane := window.panes[0]
|
||||
return pane.logs_all()!
|
||||
} else {
|
||||
return error('No panes available in container ${c.name}')
|
||||
}
|
||||
//execute command inside the container
|
||||
pub fn (self Container) exec(args osal.ExecArgs) ! {
|
||||
//TODO: use same args as osal.exec but then run inside the builder.node, use self.node()!
|
||||
self.node()!.exec(args)!
|
||||
}
|
||||
|
||||
//TODO: add whatever else we need
|
||||
|
||||
|
||||
//return as enum
|
||||
pub fn (self Container) status() !ContainerStatus {
|
||||
//TODO
|
||||
}
|
||||
|
||||
pub enum ContainerStatus {
|
||||
running
|
||||
stopped
|
||||
paused
|
||||
unknown
|
||||
}
|
||||
|
||||
//in percentage??? is that per core???
|
||||
pub fn (self Container) cpu_usage() !f64 {
|
||||
//TODO
|
||||
}
|
||||
|
||||
//in MByte
|
||||
pub fn (self Container) mem_usage() !f64 {
|
||||
//TODO
|
||||
}
|
||||
|
||||
|
||||
pub struct TmuxPaneargs {
|
||||
pub mut:
|
||||
window_name string
|
||||
pane_nr int
|
||||
pane_name string //optional
|
||||
cmd string //optional, will execute this cmd
|
||||
reset bool //if true will reset everything and restart a cmd
|
||||
env map[string]string //optional, will set these env vars in the pane
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
pub fn (self Container) tmux_pane() !tmux.Pane {
|
||||
//TODO: check if tmux session exist, if not create if sessionname is given in factory
|
||||
//TODO: check if window exist, if not create
|
||||
//TODO: check if pane exist, if not create
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
pub fn (self Container) node() !builder.Node {
|
||||
//TODO: check if builder.Node is already there, if not initialize it and return
|
||||
}
|
||||
@@ -1,30 +1,98 @@
|
||||
module herorun
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import time
|
||||
import freeflowuniverse.herolib.builder
|
||||
|
||||
// Provider types
|
||||
pub enum Provider {
|
||||
hetzner
|
||||
threefold
|
||||
// Container struct and related functionality
|
||||
pub struct ContainerFactory {
|
||||
pub mut:
|
||||
tmux_session string //this is the name for tmux session if we will use it
|
||||
}
|
||||
|
||||
// Factory function to create appropriate backend
|
||||
pub fn new_backend(provider Provider, args NewNodeArgs) !NodeBackend {
|
||||
match provider {
|
||||
.hetzner {
|
||||
console.print_header('🏭 Creating Hetzner Backend')
|
||||
backend := new_hetzner_backend(args)!
|
||||
return backend
|
||||
}
|
||||
.threefold {
|
||||
console.print_header('🏭 Creating ThreeFold Backend')
|
||||
// TODO: Implement ThreeFold backend
|
||||
return error('ThreeFold backend not implemented yet')
|
||||
@[params]
|
||||
pub struct FactoryInitArgs {
|
||||
pub:
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn new(args FactoryInitArgs) !ContainerFactory {
|
||||
mut f:= ContainerFactory{}
|
||||
f.init(args)!
|
||||
return f
|
||||
}
|
||||
fn (self ContainerFactory) init(args ContainerFactoryInitArgs) ! {
|
||||
// Alpine (as before)
|
||||
alpine_ver := '3.20.3'
|
||||
alpine_file := 'alpine-minirootfs-${alpine_ver}-x86_64.tar.gz'
|
||||
alpine_url := 'https://dl-cdn.alpinelinux.org/alpine/v${alpine_ver[..4]}/releases/x86_64/${alpine_file}'
|
||||
alpine_dest := '/containers/images/alpine/${alpine_file}'
|
||||
alpine_rootfs := '/containers/images/alpine/rootfs'
|
||||
osal.download(
|
||||
url: alpine_url
|
||||
dest: alpine_dest
|
||||
reset: args.reset
|
||||
minsize_kb: 1024
|
||||
expand_dir: alpine_rootfs
|
||||
)!
|
||||
console.print_green('Alpine ${alpine_ver} rootfs prepared at ${alpine_rootfs}')
|
||||
|
||||
// Ubuntu versions with proper codename paths
|
||||
ubuntu_info := [
|
||||
{ver: '24.04', codename: 'noble'},
|
||||
{ver: '25.04', codename: 'plucky'}
|
||||
]
|
||||
|
||||
for info in ubuntu_info {
|
||||
file := 'ubuntu-${info.ver}-minimal-cloudimg-amd64-root.tar.xz'
|
||||
url := 'https://cloud-images.ubuntu.com/minimal/releases/${info.codename}/release/${file}'
|
||||
// Use us.cloud-images domain for 25.04 daily if needed
|
||||
if info.ver == '25.04' {
|
||||
url = 'https://us.cloud-images.ubuntu.com/daily/server/server/minimal/releases/${info.codename}/release/${file}'
|
||||
}
|
||||
dest := '/containers/images/ubuntu/${info.ver}/${file}'
|
||||
rootfs := '/containers/images/ubuntu/${info.ver}/rootfs'
|
||||
|
||||
osal.download(
|
||||
url: url
|
||||
dest: dest
|
||||
reset: args.reset
|
||||
minsize_kb: 10240
|
||||
expand_dir: rootfs
|
||||
)!
|
||||
|
||||
console.print_green('Ubuntu ${info.ver} (${info.codename}) rootfs prepared at ${rootfs}')
|
||||
}
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct ContainerNewArgs {
|
||||
pub:
|
||||
name string
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn (self ContainerFactory) list() ![]Container {
|
||||
mut containers := []Container{}
|
||||
// Get list of containers using runc
|
||||
result := osal.exec(cmd: 'runc list', stdout: true, name: 'list_containers') or { '' }
|
||||
lines := result.split_into_lines()
|
||||
if lines.len <= 1 {
|
||||
return containers // No containers found
|
||||
}
|
||||
for line in lines[1..] {
|
||||
parts := line.split(' ')
|
||||
if parts.len > 0 {
|
||||
containers << Container{
|
||||
name: parts[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
||||
// Convenience function for Hetzner (most common case)
|
||||
pub fn new_hetzner_node(args NewNodeArgs) !NodeBackend {
|
||||
return new_backend(.hetzner, args)!
|
||||
}
|
||||
pub fn (self ContainerFactory) get(args ContainerNewArgs ) ! {
|
||||
//TODO: implement get, give error if not exist
|
||||
|
||||
}
|
||||
5
lib/virt/herorun/instructions.md
Normal file
5
lib/virt/herorun/instructions.md
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
- use builder... for remote execution inside the container
|
||||
- make an executor like we have for SSH but then for the container, so we can use this to execute commands inside the container
|
||||
-
|
||||
867
lib/virt/herorun/instructions_crun.md
Normal file
867
lib/virt/herorun/instructions_crun.md
Normal file
@@ -0,0 +1,867 @@
|
||||
crun(1) General Commands Manual crun(1)
|
||||
|
||||
|
||||
|
||||
NAME
|
||||
crun - a fast and lightweight OCI runtime
|
||||
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
crun [global options] command [command options] [arguments...]
|
||||
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
crun is a command line program for running Linux containers that follow
|
||||
the Open Container Initiative (OCI) format.
|
||||
|
||||
|
||||
|
||||
COMMANDS
|
||||
create Create a container. The runtime detaches from the container
|
||||
process once the container environment is created. It is necessary to
|
||||
successively use start for starting the container.
|
||||
|
||||
|
||||
delete Remove definition for a container.
|
||||
|
||||
|
||||
exec Exec a command in a running container.
|
||||
|
||||
|
||||
list List known containers.
|
||||
|
||||
|
||||
mounts add Add mounts while the container is running. It requires two
|
||||
arguments: the container ID and a JSON file containing the mounts
|
||||
section of the OCI config file. Each mount listed there is added to
|
||||
the running container. The command is experimental and can be changed
|
||||
without notice.
|
||||
|
||||
|
||||
mounts remove Remove mounts while the container is running. It
|
||||
requires two arguments: the container ID and a JSON file containing the
|
||||
mounts section of the OCI config file. Only the destination attribute
|
||||
for each mount is used. The command is experimental and can be changed
|
||||
without notice.
|
||||
|
||||
|
||||
kill Send the specified signal to the container init process. If no
|
||||
signal is specified, SIGTERM is used.
|
||||
|
||||
|
||||
ps Show the processes running in a container.
|
||||
|
||||
|
||||
run Create and immediately start a container.
|
||||
|
||||
|
||||
spec Generate a configuration file.
|
||||
|
||||
|
||||
start Start a container that was previously created. A container
|
||||
cannot be started multiple times.
|
||||
|
||||
|
||||
state Output the state of a container.
|
||||
|
||||
|
||||
pause Pause all the processes in the container.
|
||||
|
||||
|
||||
resume Resume the processes in the container.
|
||||
|
||||
|
||||
update Update container resource constraints.
|
||||
|
||||
|
||||
checkpoint Checkpoint a running container using CRIU.
|
||||
|
||||
|
||||
restore Restore a container from a checkpoint.
|
||||
|
||||
|
||||
|
||||
STATE
|
||||
By default, when running as root user, crun saves its state under the
|
||||
/run/crun directory. As unprivileged user, instead the XDG_RUNTIME_DIR
|
||||
environment variable is honored, and the directory
|
||||
$XDG_RUNTIME_DIR/crun is used. The global option --root overrides this
|
||||
setting.
|
||||
|
||||
|
||||
|
||||
GLOBAL OPTIONS
|
||||
--debug Produce verbose output.
|
||||
|
||||
|
||||
--log=LOG-DESTINATION Define the destination for the error and warning
|
||||
messages generated by crun. If the error happens late in the container
|
||||
init process, when crun already stopped watching it, then it will be
|
||||
printed to the container stderr.
|
||||
|
||||
|
||||
It is specified in the form BACKEND:SPECIFIER.
|
||||
|
||||
|
||||
These following backends are supported:
|
||||
|
||||
o file:PATH
|
||||
|
||||
o journald:IDENTIFIER
|
||||
|
||||
o syslog:IDENTIFIER
|
||||
|
||||
|
||||
If no backend is specified, then file: is used by default.
|
||||
|
||||
|
||||
--log-format=FORMAT Define the format of the log messages. It can
|
||||
either be text, or json. The default is text.
|
||||
|
||||
|
||||
--log-level=LEVEL Define the log level. It can either be debug,
|
||||
warning or error. The default is error.
|
||||
|
||||
|
||||
--no-pivot Use chroot(2) instead of pivot_root(2) when creating the
|
||||
container. This option is not safe, and should be avoided.
|
||||
|
||||
|
||||
--root=DIR Defines where to store the state for crun containers.
|
||||
|
||||
|
||||
--systemd-cgroup Use systemd for configuring cgroups. If not
|
||||
specified, the cgroup is created directly using the cgroupfs backend.
|
||||
|
||||
|
||||
--cgroup-manager=MANAGER Specify what cgroup manager must be used.
|
||||
Permitted values are cgroupfs, systemd and disabled.
|
||||
|
||||
|
||||
-?, --help Print a help list.
|
||||
|
||||
|
||||
--usage Print a short usage message.
|
||||
|
||||
|
||||
-V, --version Print program version
|
||||
|
||||
|
||||
CREATE OPTIONS
|
||||
crun [global options] create [options] CONTAINER
|
||||
|
||||
|
||||
--bundle=PATH Path to the OCI bundle, by default it is the current
|
||||
directory.
|
||||
|
||||
|
||||
--config=FILE Override the configuration file to use. The default
|
||||
value is config.json.
|
||||
|
||||
|
||||
--console-socket=SOCKET Path to a UNIX socket that will receive the
|
||||
ptmx end of the tty for the container.
|
||||
|
||||
|
||||
--no-new-keyring Keep the same session key
|
||||
|
||||
|
||||
--preserve-fds=N Additional number of FDs to pass into the container.
|
||||
|
||||
|
||||
--pid-file=PATH Path to the file that will contain the container
|
||||
process PID.
|
||||
|
||||
|
||||
RUN OPTIONS
|
||||
crun [global options] run [options] CONTAINER
|
||||
|
||||
|
||||
--bundle=BUNDLE Path to the OCI bundle, by default it is the current
|
||||
directory.
|
||||
|
||||
|
||||
--config=FILE Override the configuration file to use. The default
|
||||
value is config.json.
|
||||
|
||||
|
||||
--console-socket=SOCKET Path to a UNIX socket that will receive the
|
||||
ptmx end of the tty for the container.
|
||||
|
||||
|
||||
--no-new-keyring Keep the same session key.
|
||||
|
||||
|
||||
--preserve-fds=N Additional number of FDs to pass into the container.
|
||||
|
||||
|
||||
--pid-file=PATH Path to the file that will contain the container
|
||||
process PID.
|
||||
|
||||
|
||||
--detach Detach the container process from the current session.
|
||||
|
||||
|
||||
DELETE OPTIONS
|
||||
crun [global options] delete [options] CONTAINER
|
||||
|
||||
|
||||
--force Delete the container even if it is still running.
|
||||
|
||||
|
||||
--regex=REGEX Delete all the containers that satisfy the specified
|
||||
regex.
|
||||
|
||||
|
||||
EXEC OPTIONS
|
||||
crun [global options] exec [options] CONTAINER CMD
|
||||
|
||||
|
||||
--apparmor=PROFILE Set the apparmor profile for the process.
|
||||
|
||||
|
||||
--console-socket=SOCKET Path to a UNIX socket that will receive the
|
||||
ptmx end of the tty for the container.
|
||||
|
||||
|
||||
--cwd=PATH Set the working directory for the process to PATH.
|
||||
|
||||
|
||||
--cap=CAP Specify an additional capability to add to the process.
|
||||
|
||||
|
||||
--detach Detach the container process from the current session.
|
||||
|
||||
|
||||
--cgroup=PATH Specify a sub-cgroup path inside the container cgroup.
|
||||
The path must already exist in the container cgroup.
|
||||
|
||||
|
||||
--env=ENV Specify an environment variable.
|
||||
|
||||
|
||||
--no-new-privs Set the no new privileges value for the process.
|
||||
|
||||
|
||||
--preserve-fds=N Additional number of FDs to pass into the container.
|
||||
|
||||
|
||||
--process=FILE Path to a file containing the process JSON
|
||||
configuration.
|
||||
|
||||
|
||||
--process-label=VALUE Set the asm process label for the process
|
||||
commonly used with selinux.
|
||||
|
||||
|
||||
--pid-file=PATH Path to the file that will contain the new process PID.
|
||||
|
||||
|
||||
-t --tty Allocate a pseudo TTY.
|
||||
|
||||
|
||||
**-u USERSPEC --user=USERSPEC Specify the user in the form UID[:GID].
|
||||
|
||||
|
||||
LIST OPTIONS
|
||||
crun [global options] list [options]
|
||||
|
||||
|
||||
-q --quiet Show only the container ID.
|
||||
|
||||
|
||||
KILL OPTIONS
|
||||
crun [global options] kill [options] CONTAINER SIGNAL
|
||||
|
||||
|
||||
--all Kill all the processes in the container.
|
||||
|
||||
|
||||
--regex=REGEX Kill all the containers that satisfy the specified regex.
|
||||
|
||||
|
||||
PS OPTIONS
|
||||
crun [global options] ps [options]
|
||||
|
||||
|
||||
--format=FORMAT Specify the output format. It must be either table or
|
||||
json. By default table is used.
|
||||
|
||||
|
||||
SPEC OPTIONS
|
||||
crun [global options] spec [options]
|
||||
|
||||
|
||||
-b DIR --bundle=DIR Path to the root of the bundle dir (default ".").
|
||||
|
||||
|
||||
--rootless Generate a config.json file that is usable by an
|
||||
unprivileged user.
|
||||
|
||||
|
||||
UPDATE OPTIONS
|
||||
crun [global options] update [options] CONTAINER
|
||||
|
||||
|
||||
--blkio-weight=VALUE Specifies per cgroup weight.
|
||||
|
||||
|
||||
--cpu-period=VALUE CPU CFS period to be used for hardcapping.
|
||||
|
||||
|
||||
--cpu-quota=VALUE CPU CFS hardcap limit.
|
||||
|
||||
|
||||
--cpu-rt-period=VALUE CPU realtime period to be used for hardcapping.
|
||||
|
||||
|
||||
--cpu-rt-runtime=VALUE CPU realtime hardcap limit.
|
||||
|
||||
|
||||
--cpu-share=VALUE CPU shares.
|
||||
|
||||
|
||||
--cpuset-cpus=VALUE CPU(s) to use.
|
||||
|
||||
|
||||
--cpuset-mems=VALUE Memory node(s) to use.
|
||||
|
||||
|
||||
--kernel-memory=VALUE Kernel memory limit.
|
||||
|
||||
|
||||
--kernel-memory-tcp=VALUE Kernel memory limit for TCP buffer.
|
||||
|
||||
|
||||
--memory=VALUE Memory limit.
|
||||
|
||||
|
||||
--memory-reservation=VALUE Memory reservation or soft_limit.
|
||||
|
||||
|
||||
--memory-swap=VALUE Total memory usage.
|
||||
|
||||
|
||||
--pids-limit=VALUE Maximum number of pids allowed in the container.
|
||||
|
||||
|
||||
-r, --resources=FILE Path to the file containing the resources to
|
||||
update.
|
||||
|
||||
|
||||
CHECKPOINT OPTIONS
|
||||
crun [global options] checkpoint [options] CONTAINER
|
||||
|
||||
|
||||
--image-path=DIR Path for saving CRIU image files
|
||||
|
||||
|
||||
--work-path=DIR Path for saving work files and logs
|
||||
|
||||
|
||||
--leave-running Leave the process running after checkpointing
|
||||
|
||||
|
||||
--tcp-established Allow open TCP connections
|
||||
|
||||
|
||||
--ext-unix-sk Allow external UNIX sockets
|
||||
|
||||
|
||||
--shell-job Allow shell jobs
|
||||
|
||||
|
||||
--pre-dump Only checkpoint the container's memory without stopping the
|
||||
container. It is not possible to restore a container from a pre-dump.
|
||||
A pre-dump always needs a final checkpoint (without --pre-dump). It is
|
||||
possible to make as many pre-dumps as necessary. For a second pre-dump
|
||||
or for a final checkpoint it is necessary to use --parent-path to point
|
||||
crun (and thus CRIU) to the pre-dump.
|
||||
|
||||
|
||||
--parent-path=DIR Doing multiple pre-dumps or the final checkpoint
|
||||
after one or multiple pre-dumps requires that crun (and thus CRIU)
|
||||
knows the location of the pre-dump. It is important to use a relative
|
||||
path from the actual checkpoint directory specified via --image-path.
|
||||
It will fail if an absolute path is used.
|
||||
|
||||
|
||||
--manage-cgroups-mode=MODE Specify which CRIU manage cgroup mode should
|
||||
be used. Permitted values are soft, ignore, full or strict. Default is
|
||||
soft.
|
||||
|
||||
|
||||
RESTORE OPTIONS
|
||||
crun [global options] restore [options] CONTAINER
|
||||
|
||||
|
||||
-b DIR --bundle=DIR Container bundle directory (default ".")
|
||||
|
||||
|
||||
--image-path=DIR Path for saving CRIU image files
|
||||
|
||||
|
||||
--work-path=DIR Path for saving work files and logs
|
||||
|
||||
|
||||
--tcp-established Allow open TCP connections
|
||||
|
||||
|
||||
--ext-unix Allow external UNIX sockets
|
||||
|
||||
|
||||
--shell-job Allow shell jobs
|
||||
|
||||
|
||||
--detach Detach from the container's process
|
||||
|
||||
|
||||
--pid-file=FILE Where to write the PID of the container
|
||||
|
||||
|
||||
--manage-cgroups-mode=MODE Specify which CRIU manage cgroup mode should
|
||||
be used. Permitted values are soft, ignore, full or strict. Default is
|
||||
soft.
|
||||
|
||||
|
||||
--lsm-profile=TYPE:NAME Specify an LSM profile to be used during
|
||||
restore. TYPE can be either apparmor or selinux.
|
||||
|
||||
|
||||
--lsm-mount-context=VALUE Specify a new LSM mount context to be used
|
||||
during restore. This option replaces an existing mount context
|
||||
information with the specified value. This is useful when restoring a
|
||||
container into an existing Pod and selinux labels need to be changed
|
||||
during restore.
|
||||
|
||||
|
||||
|
||||
Extensions to OCI
|
||||
run.oci.mount_context_type=context
|
||||
Set the mount context type on volumes mounted with SELinux labels.
|
||||
|
||||
|
||||
Valid context types are:
|
||||
context (default)
|
||||
fscontext
|
||||
defcontext
|
||||
rootcontext
|
||||
|
||||
|
||||
More information on how the context mount flags works see the mount(8)
|
||||
man page.
|
||||
|
||||
|
||||
run.oci.seccomp.receiver=PATH
|
||||
If the annotation run.oci.seccomp.receiver=PATH is specified, the
|
||||
seccomp listener is sent to the UNIX socket listening on the specified
|
||||
path. It can also set with the RUN_OCI_SECCOMP_RECEIVER environment
|
||||
variable. It is an experimental feature, and the annotation will be
|
||||
removed once it is supported in the OCI runtime specs. It must be an
|
||||
absolute path.
|
||||
|
||||
|
||||
run.oci.seccomp.plugins=PATH
|
||||
If the annotation run.oci.seccomp.plugins=PLUGIN1[:PLUGIN2]... is
|
||||
specified, the seccomp listener fd is handled through the specified
|
||||
plugins. The plugin must either be an absolute path or a file name
|
||||
that is looked up by dlopen(3). More information on how the lookup is
|
||||
performed are available on the ld.so(8) man page.
|
||||
|
||||
|
||||
run.oci.seccomp_fail_unknown_syscall=1
|
||||
If the annotation run.oci.seccomp_fail_unknown_syscall is present, then
|
||||
crun will fail when an unknown syscall is encountered in the seccomp
|
||||
configuration.
|
||||
|
||||
|
||||
run.oci.seccomp_bpf_data=PATH
|
||||
If the annotation run.oci.seccomp_bpf_data is present, then crun
|
||||
ignores the seccomp section in the OCI configuration file and use the
|
||||
specified data as the raw data to the seccomp(SECCOMP_SET_MODE_FILTER)
|
||||
syscall. The data must be encoded in base64.
|
||||
|
||||
|
||||
It is an experimental feature, and the annotation will be removed once
|
||||
it is supported in the OCI runtime specs.
|
||||
|
||||
|
||||
run.oci.keep_original_groups=1
|
||||
If the annotation run.oci.keep_original_groups is present, then crun
|
||||
will skip the setgroups syscall that is used to either set the
|
||||
additional groups specified in the OCI configuration, or to reset the
|
||||
list of additional groups if none is specified.
|
||||
|
||||
|
||||
run.oci.pidfd_receiver=PATH
|
||||
It is an experimental feature and will be removed once the feature is
|
||||
in the OCI runtime specs.
|
||||
|
||||
|
||||
If present, specify the path to the UNIX socket that will receive the
|
||||
pidfd for the container process.
|
||||
|
||||
|
||||
run.oci.systemd.force_cgroup_v1=/PATH
|
||||
If the annotation run.oci.systemd.force_cgroup_v1=/PATH is present,
|
||||
then crun will override the specified mount point /PATH with a cgroup
|
||||
v1 mount made of a single hierarchy none,name=systemd. It is useful to
|
||||
run on a cgroup v2 system containers using older versions of systemd
|
||||
that lack support for cgroup v2.
|
||||
|
||||
|
||||
Note: Your container host has to have the cgroup v1 mount already
|
||||
present, otherwise this will not work. If you want to run the container
|
||||
rootless, the user it runs under has to have permissions to this
|
||||
mountpoint.
|
||||
|
||||
|
||||
For example, as root:
|
||||
|
||||
mkdir /sys/fs/cgroup/systemd
|
||||
mount cgroup -t cgroup /sys/fs/cgroup/systemd -o none,name=systemd,xattr
|
||||
chown -R the_user.the_user /sys/fs/cgroup/systemd
|
||||
|
||||
|
||||
run.oci.systemd.subgroup=SUBGROUP
|
||||
Override the name for the systemd sub cgroup created under the systemd
|
||||
scope, so the final cgroup will be like:
|
||||
|
||||
/sys/fs/cgroup/$PATH/$SUBGROUP
|
||||
|
||||
|
||||
When it is set to the empty string, a sub cgroup is not created.
|
||||
|
||||
|
||||
If not specified, it defaults to container on cgroup v2, and to "" on
|
||||
cgroup v1.
|
||||
|
||||
|
||||
e.g.
|
||||
|
||||
/sys/fs/cgroup//system.slice/foo-352700.scope/container
|
||||
|
||||
|
||||
run.oci.delegate-cgroup=DELEGATED-CGROUP
|
||||
If the run.oci.systemd.subgroup annotation is specified, yet another
|
||||
sub-cgroup is created and the container process is moved here.
|
||||
|
||||
|
||||
If a cgroup namespace is used, the cgroup namespace is created before
|
||||
moving the container to the delegated cgroup.
|
||||
|
||||
/sys/fs/cgroup/$PATH/$SUBGROUP/$DELEGATED-CGROUP
|
||||
|
||||
|
||||
The runtime doesn't apply any limit to the $DELEGATED-CGROUP sub-
|
||||
cgroup, the runtime uses only $PATH/$SUBGROUP.
|
||||
|
||||
|
||||
The container payload fully manages $DELEGATE-CGROUP, the limits
|
||||
applied to $PATH/$SUBGROUP still applies to $DELEGATE-CGROUP.
|
||||
|
||||
|
||||
Since cgroup delegation is not safe on cgroup v1, this option is
|
||||
supported only on cgroup v2.
|
||||
|
||||
|
||||
run.oci.hooks.stdout=FILE
|
||||
If the annotation run.oci.hooks.stdout is present, then crun will open
|
||||
the specified file and use it as the stdout for the hook processes.
|
||||
The file is opened in append mode and it is created if it doesn't
|
||||
already exist.
|
||||
|
||||
|
||||
run.oci.hooks.stderr=FILE
|
||||
If the annotation run.oci.hooks.stderr is present, then crun will open
|
||||
the specified file and use it as the stderr for the hook processes.
|
||||
The file is opened in append mode and it is created if it doesn't
|
||||
already exist.
|
||||
|
||||
|
||||
run.oci.handler=HANDLER
|
||||
It is an experimental feature.
|
||||
|
||||
|
||||
If specified, run the specified handler for execing the container. The
|
||||
only supported values are krun and wasm.
|
||||
|
||||
o krun: When krun is specified, the libkrun.so shared object is loaded
|
||||
and it is used to launch the container using libkrun.
|
||||
|
||||
o wasm: If specified, run the wasm handler for container. Allows
|
||||
running wasm workload natively. Accepts a .wasm binary as input and
|
||||
if .wat is provided it will be automatically compiled into a wasm
|
||||
module. Stdout of wasm module is relayed back via crun.
|
||||
|
||||
|
||||
tmpcopyup mount options
|
||||
If the tmpcopyup option is specified for a tmpfs, then the path that is
|
||||
shadowed by the tmpfs mount is recursively copied up to the tmpfs
|
||||
itself.
|
||||
|
||||
|
||||
copy-symlink mount options
|
||||
If the copy-symlink option is specified, if the source of a bind mount
|
||||
is a symlink, the symlink is recreated at the specified destination
|
||||
instead of attempting a mount that would resolve the symlink itself.
|
||||
If the destination already exists and it is not a symlink with the
|
||||
expected content, crun will return an error.
|
||||
|
||||
|
||||
dest-nofollow
|
||||
When this option is specified for a bind mount, and the destination of
|
||||
the bind mount is a symbolic link, crun will mount the symbolic link
|
||||
itself at the target destination.
|
||||
|
||||
|
||||
src-nofollow
|
||||
When this option is specified for a bind mount, and the source of the
|
||||
bind mount is a symbolic link, crun will use the symlink itself rather
|
||||
than the file or directory the symbolic link points to.
|
||||
|
||||
|
||||
r$FLAG mount options
|
||||
If a r$FLAG mount option is specified then the flag $FLAG is set
|
||||
recursively for each children mount.
|
||||
|
||||
|
||||
These flags are supported:
|
||||
|
||||
o "rro"
|
||||
|
||||
o "rrw"
|
||||
|
||||
o "rsuid"
|
||||
|
||||
o "rnosuid"
|
||||
|
||||
o "rdev"
|
||||
|
||||
o "rnodev"
|
||||
|
||||
o "rexec"
|
||||
|
||||
o "rnoexec"
|
||||
|
||||
o "rsync"
|
||||
|
||||
o "rasync"
|
||||
|
||||
o "rdirsync"
|
||||
|
||||
o "rmand"
|
||||
|
||||
o "rnomand"
|
||||
|
||||
o "ratime"
|
||||
|
||||
o "rnoatime"
|
||||
|
||||
o "rdiratime"
|
||||
|
||||
o "rnodiratime"
|
||||
|
||||
o "rrelatime"
|
||||
|
||||
o "rnorelatime"
|
||||
|
||||
o "rstrictatime"
|
||||
|
||||
o "rnostrictatime"
|
||||
|
||||
|
||||
idmap mount options
|
||||
If the idmap option is specified then the mount is ID mapped using the
|
||||
container target user namespace. This is an experimental feature and
|
||||
can change at any time without notice.
|
||||
|
||||
|
||||
The idmap option supports a custom mapping that can be different than
|
||||
the user namespace used by the container.
|
||||
|
||||
|
||||
The mapping can be specified after the idmap option like:
|
||||
idmap=uids=0-1-10#10-11-10;gids=0-100-10.
|
||||
|
||||
|
||||
For each triplet, the first value is the start of the backing file
|
||||
system IDs that are mapped to the second value on the host. The length
|
||||
of this mapping is given in the third value.
|
||||
|
||||
|
||||
Multiple ranges are separated with #.
|
||||
|
||||
|
||||
These values are written to the /proc/$PID/uid_map and
|
||||
/proc/$PID/gid_map files to create the user namespace for the idmapped
|
||||
mount.
|
||||
|
||||
|
||||
The only two options that are currently supported after idmap are uids
|
||||
and gids.
|
||||
|
||||
|
||||
When a custom mapping is specified, a new user namespace is created for
|
||||
the idmapped mount.
|
||||
|
||||
|
||||
If no option is specified, then the container user namespace is used.
|
||||
|
||||
|
||||
If the specified mapping is prepended with a '@' then the mapping is
|
||||
considered relative to the container user namespace. The host ID for
|
||||
the mapping is changed to account for the relative position of the
|
||||
container user in the container user namespace.
|
||||
|
||||
|
||||
For example, the mapping: uids=@1-3-10, given a configuration like
|
||||
|
||||
"uidMappings": [
|
||||
{
|
||||
"containerID": 0,
|
||||
"hostID": 0,
|
||||
"size": 1
|
||||
},
|
||||
{
|
||||
"containerID": 1,
|
||||
"hostID": 2,
|
||||
"size": 1000
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
will be converted to the absolute value uids=1-4-10, where 4 is
|
||||
calculated by adding 3 (container ID in the uids= mapping) and 1
|
||||
(hostID - containerID for the user namespace mapping where containerID
|
||||
= 1 is found).
|
||||
|
||||
|
||||
The current implementation doesn't take into account multiple user
|
||||
namespace ranges, so it is the caller's responsibility to split a
|
||||
mapping if it overlaps multiple ranges in the user namespace. In such
|
||||
a case, there won't be any error reported.
|
||||
|
||||
|
||||
Automatically create user namespace
|
||||
When running as user different than root, an user namespace is
|
||||
automatically created even if it is not specified in the config file.
|
||||
The current user is mapped to the ID 0 in the container, and any
|
||||
additional id specified in the files /etc/subuid and /etc/subgid is
|
||||
automatically added starting with ID 1.
|
||||
|
||||
|
||||
|
||||
CGROUP v1
|
||||
Support for cgroup v1 is deprecated and will be removed in a future
|
||||
release.
|
||||
|
||||
|
||||
|
||||
CGROUP v2
|
||||
Note: cgroup v2 does not yet support control of realtime processes and
|
||||
the cpu controller can only be enabled when all RT processes are in the
|
||||
root cgroup. This will make crun fail while running alongside RT
|
||||
processes.
|
||||
|
||||
|
||||
If the cgroup configuration found is for cgroup v1, crun attempts a
|
||||
conversion when running on a cgroup v2 system.
|
||||
|
||||
|
||||
These are the OCI resources currently supported with cgroup v2 and how
|
||||
they are converted when needed from the cgroup v1 configuration.
|
||||
|
||||
|
||||
Memory controller
|
||||
|
||||
+------------+--------------------+----------------------+------------------+
|
||||
|OCI (x) | cgroup 2 value (y) | conversion | comment |
|
||||
+------------+--------------------+----------------------+------------------+
|
||||
|limit | memory.max | y = x | |
|
||||
+------------+--------------------+----------------------+------------------+
|
||||
|swap | memory.swap.max | y = x - memory_limit | the swap limit |
|
||||
| | | | on cgroup v1 |
|
||||
| | | | includes the |
|
||||
| | | | memory usage too |
|
||||
+------------+--------------------+----------------------+------------------+
|
||||
|reservation | memory.low | y = x | |
|
||||
+------------+--------------------+----------------------+------------------+
|
||||
|
||||
PIDs controller
|
||||
|
||||
+--------+--------------------+------------+---------+
|
||||
|OCI (x) | cgroup 2 value (y) | conversion | comment |
|
||||
+--------+--------------------+------------+---------+
|
||||
|limit | pids.max | y = x | |
|
||||
+--------+--------------------+------------+---------+
|
||||
|
||||
CPU controller
|
||||
|
||||
+--------+--------------------+------------------+------------------+
|
||||
|OCI (x) | cgroup 2 value (y) | conversion | comment |
|
||||
+--------+--------------------+------------------+------------------+
|
||||
|shares | cpu.weight | y=10^((log2(x)^2 | |
|
||||
| | | + 125 * log2(x)) | |
|
||||
| | | / 612.0 - 7.0 / | |
|
||||
| | | 34.0) | |
|
||||
+--------+--------------------+------------------+------------------+
|
||||
| | convert from | | |
|
||||
| | [2-262144] to | | |
|
||||
| | [1-10000] | | |
|
||||
+--------+--------------------+------------------+------------------+
|
||||
|period | cpu.max | y = x | period and quota |
|
||||
| | | | are written |
|
||||
| | | | together |
|
||||
+--------+--------------------+------------------+------------------+
|
||||
|quota | cpu.max | y = x | period and quota |
|
||||
| | | | are written |
|
||||
| | | | together |
|
||||
+--------+--------------------+------------------+------------------+
|
||||
|
||||
blkio controller
|
||||
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|OCI (x) | cgroup 2 value (y) | conversion | comment |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|weight | io.bfq.weight | y = x | |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|weight_device | io.bfq.weight | y = x | |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|weight | io.weight (fallback) | y = 1 + (x-10)*9999/990 | convert linearly |
|
||||
| | | | from [10-1000] |
|
||||
| | | | to [1-10000] |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|weight_device | io.weight (fallback) | y = 1 + (x-10)*9999/990 | convert linearly |
|
||||
| | | | from [10-1000] |
|
||||
| | | | to [1-10000] |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|rbps | io.max | y=x | |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|wbps | io.max | y=x | |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|riops | io.max | y=x | |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|wiops | io.max | y=x | |
|
||||
+--------------+----------------------+-------------------------+------------------+
|
||||
|
||||
cpuset controller
|
||||
|
||||
+--------+--------------------+------------+---------+
|
||||
|OCI (x) | cgroup 2 value (y) | conversion | comment |
|
||||
+--------+--------------------+------------+---------+
|
||||
|cpus | cpuset.cpus | y = x | |
|
||||
+--------+--------------------+------------+---------+
|
||||
|mems | cpuset.mems | y = x | |
|
||||
+--------+--------------------+------------+---------+
|
||||
|
||||
hugetlb controller
|
||||
|
||||
+----------------+--------------------+------------+---------+
|
||||
|OCI (x) | cgroup 2 value (y) | conversion | comment |
|
||||
+----------------+--------------------+------------+---------+
|
||||
|.limit_in_bytes | hugetlb..max | y = x | |
|
||||
+----------------+--------------------+------------+---------+
|
||||
User Commands crun(1)
|
||||
0
lib/virt/herorun/readme.md
Normal file
0
lib/virt/herorun/readme.md
Normal file
107
lib/virt/herorun2/README.md
Normal file
107
lib/virt/herorun2/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# HeroRun - Remote Container Management
|
||||
|
||||
A V library for managing remote containers using runc and tmux, with support for multiple cloud providers.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-provider support**: Currently supports Hetzner, with ThreeFold coming soon
|
||||
- **Automatic setup**: Installs required packages (runc, tmux, curl, xz-utils) automatically
|
||||
- **Container isolation**: Uses runc for lightweight container management
|
||||
- **tmux integration**: Each container gets its own tmux session for multiple concurrent shells
|
||||
- **Clean API**: Simple interface that hides infrastructure complexity
|
||||
|
||||
## Project Structure
|
||||
|
||||
```txt
|
||||
lib/virt/herorun/
|
||||
├── interfaces.v # Shared interfaces and parameter structs
|
||||
├── nodes.v # Node management and SSH connectivity
|
||||
├── container.v # Container struct and lifecycle operations
|
||||
├── executor.v # Optimized command execution engine
|
||||
├── factory.v # Provider abstraction and backend creation
|
||||
├── hetzner_backend.v # Hetzner cloud implementation
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.virt.herorun
|
||||
|
||||
// Create user with SSH key
|
||||
mut user := herorun.new_user(keyname: 'id_ed25519')!
|
||||
|
||||
// Create Hetzner backend
|
||||
mut backend := herorun.new_hetzner_backend(
|
||||
node_ip: '65.21.132.119'
|
||||
user: 'root'
|
||||
)!
|
||||
|
||||
// Connect to node (installs required packages automatically)
|
||||
backend.connect(keyname: user.keyname)!
|
||||
|
||||
// Send a test command to the node
|
||||
backend.send_command(cmd: 'ls')!
|
||||
|
||||
// Get or create container (uses tmux behind the scenes)
|
||||
mut container := backend.get_or_create_container(name: 'test_container')!
|
||||
|
||||
// Attach to container tmux session
|
||||
container.attach()!
|
||||
|
||||
// Send command to container
|
||||
container.send_command(cmd: 'ls')!
|
||||
|
||||
// Get container logs
|
||||
logs := container.get_logs()!
|
||||
println('Container logs:')
|
||||
println(logs)
|
||||
|
||||
```
|
||||
|
||||
### Running the Example
|
||||
|
||||
```bash
|
||||
# Make the example executable
|
||||
chmod +x examples/virt/herorun/herorun.vsh
|
||||
|
||||
# Run it
|
||||
./examples/virt/herorun/herorun.vsh
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **NodeBackend**: Defines operations for connecting to and managing remote nodes
|
||||
- **ContainerBackend**: Defines operations for container lifecycle management
|
||||
|
||||
### Providers
|
||||
|
||||
- **HetznerBackend**: Implementation for Hetzner cloud servers
|
||||
- **ThreeFoldBackend**: (Coming soon) Implementation for ThreeFold nodes
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **SSH Integration**: Uses herolib's sshagent module for secure connections
|
||||
2. **tmux Management**: Uses herolib's tmux module for session management
|
||||
3. **Container Runtime**: Uses runc for lightweight container execution
|
||||
4. **Hetzner Integration**: Uses herolib's hetznermanager module
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `freeflowuniverse.herolib.osal.sshagent`
|
||||
- `freeflowuniverse.herolib.osal.tmux`
|
||||
- `freeflowuniverse.herolib.installers.web.hetznermanager`
|
||||
- `freeflowuniverse.herolib.ui.console`
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- ThreeFold backend implementation
|
||||
- Support for additional cloud providers (AWS, GCP, etc.)
|
||||
- Container image management
|
||||
- Network configuration
|
||||
- Volume mounting
|
||||
- Resource limits and monitoring
|
||||
95
lib/virt/herorun2/container.v
Normal file
95
lib/virt/herorun2/container.v
Normal file
@@ -0,0 +1,95 @@
|
||||
module herorun2
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import time
|
||||
|
||||
// Container struct and related functionality
|
||||
pub struct Container {
|
||||
pub:
|
||||
name string
|
||||
node Node
|
||||
pub mut:
|
||||
tmux tmux.Tmux
|
||||
}
|
||||
|
||||
// Implement ContainerBackend interface for Container
|
||||
pub fn (mut c Container) attach() ! {
|
||||
console.print_header('🔗 Attaching to container: ${c.name}')
|
||||
|
||||
// Create or get the session for this container
|
||||
if !c.tmux.session_exist(c.name) {
|
||||
console.print_stdout('Starting new tmux session for container ${c.name}')
|
||||
|
||||
// Use the tmux convenience method to create session and window in one go
|
||||
shell_cmd := 'ssh ${c.node.settings.user}@${c.node.settings.node_ip}'
|
||||
c.tmux.window_new(
|
||||
session_name: c.name
|
||||
name: 'main'
|
||||
cmd: shell_cmd
|
||||
reset: true
|
||||
)!
|
||||
|
||||
// Wait for the session and window to be properly created
|
||||
time.sleep(500 * time.millisecond)
|
||||
|
||||
// Rescan to make sure everything is properly registered
|
||||
c.tmux.scan()!
|
||||
}
|
||||
|
||||
console.print_green('Attached to container session ${c.name}')
|
||||
}
|
||||
|
||||
pub fn (mut c Container) send_command(args ContainerCommandArgs) ! {
|
||||
console.print_header('📝 Exec in container ${c.name}')
|
||||
|
||||
// Ensure session exists
|
||||
if !c.tmux.session_exist(c.name) {
|
||||
return error('Container session ${c.name} does not exist. Call attach() first.')
|
||||
}
|
||||
|
||||
// Debug: print session info
|
||||
mut session := c.tmux.session_get(c.name)!
|
||||
console.print_debug('Session ${c.name} has ${session.windows.len} windows')
|
||||
for window in session.windows {
|
||||
console.print_debug(' Window: ${window.name} (ID: ${window.id})')
|
||||
}
|
||||
|
||||
// Try to get the main window
|
||||
mut window := session.window_get(name: 'main') or {
|
||||
// If main window doesn't exist, try to get the first window
|
||||
if session.windows.len > 0 {
|
||||
session.windows[0]
|
||||
} else {
|
||||
return error('No windows available in session ${c.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh window state to get current panes
|
||||
window.scan()!
|
||||
|
||||
// Get the first pane and send the command
|
||||
if window.panes.len > 0 {
|
||||
mut pane := window.panes[0]
|
||||
|
||||
// Send command to enter the container first, then the actual command
|
||||
container_enter_cmd := 'cd /containers/${c.name} && runc exec ${c.name} ${args.cmd}'
|
||||
pane.send_command(container_enter_cmd)!
|
||||
} else {
|
||||
return error('No panes available in container ${c.name}')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c Container) get_logs() !string {
|
||||
// Get the session and window
|
||||
mut session := c.tmux.session_get(c.name)!
|
||||
mut window := session.window_get(name: 'main')!
|
||||
|
||||
// Get logs from the first pane
|
||||
if window.panes.len > 0 {
|
||||
mut pane := window.panes[0]
|
||||
return pane.logs_all()!
|
||||
} else {
|
||||
return error('No panes available in container ${c.name}')
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
module herorun
|
||||
module herorun2
|
||||
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
import freeflowuniverse.herolib.virt.hetznermanager
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import time
|
||||
import os
|
||||
@@ -18,7 +17,6 @@ pub mut:
|
||||
session_name string
|
||||
window_name string
|
||||
agent sshagent.SSHAgent
|
||||
hetzner &hetznermanager.HetznerManager
|
||||
}
|
||||
|
||||
@[params]
|
||||
@@ -218,7 +216,7 @@ fn (mut e Executor) create_container() ! {
|
||||
}
|
||||
}
|
||||
|
||||
setup_cmd=texttools.dedent(setup_cmd)
|
||||
setup_cmd = texttools.dedent(setup_cmd)
|
||||
|
||||
remote_cmd := 'ssh ${e.node.settings.user}@${e.node.settings.node_ip} "${setup_cmd}"'
|
||||
osal.exec(cmd: remote_cmd, stdout: false, name: 'container_create')!
|
||||
30
lib/virt/herorun2/factory.v
Normal file
30
lib/virt/herorun2/factory.v
Normal file
@@ -0,0 +1,30 @@
|
||||
module herorun2
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// Provider types
|
||||
pub enum Provider {
|
||||
hetzner
|
||||
threefold
|
||||
}
|
||||
|
||||
// Factory function to create appropriate backend
|
||||
pub fn new_backend(provider Provider, args NewNodeArgs) !NodeBackend {
|
||||
match provider {
|
||||
.hetzner {
|
||||
console.print_header('🏭 Creating Hetzner Backend')
|
||||
backend := new_hetzner_backend(args)!
|
||||
return backend
|
||||
}
|
||||
.threefold {
|
||||
console.print_header('🏭 Creating ThreeFold Backend')
|
||||
// TODO: Implement ThreeFold backend
|
||||
return error('ThreeFold backend not implemented yet')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience function for Hetzner (most common case)
|
||||
pub fn new_hetzner_node(args NewNodeArgs) !NodeBackend {
|
||||
return new_backend(.hetzner, args)!
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module herorun
|
||||
module herorun2
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
@@ -1,4 +1,4 @@
|
||||
module herorun
|
||||
module herorun2
|
||||
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module herorun
|
||||
module herorun2
|
||||
|
||||
// Base image types for containers
|
||||
pub enum BaseImage {
|
||||
@@ -1,4 +1,4 @@
|
||||
module herorun
|
||||
module herorun2
|
||||
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
|
||||
Reference in New Issue
Block a user