This commit is contained in:
2025-02-09 08:52:42 +01:00
parent 690b1b68c3
commit 1d631fec21
55 changed files with 2560 additions and 930 deletions

187
aiprompts/reflection.md Normal file
View File

@@ -0,0 +1,187 @@
## Compile time reflection
$ is used as a prefix for compile time (also referred to as 'comptime') operations.
Having built-in JSON support is nice, but V also allows you to create efficient serializers for any data format. V has compile time if and for constructs:
.fields
You can iterate over struct fields using .fields, it also works with generic types (e.g. T.fields) and generic arguments (e.g. param.fields where fn gen[T](param T) {).
struct User {
name string
age int
}
fn main() {
$for field in User.fields {
$if field.typ is string {
println('${field.name} is of type string')
}
}
}
// Output:
// name is of type string
.values
You can read Enum values and their attributes.
enum Color {
red @[RED] // first attribute
blue @[BLUE] // second attribute
}
fn main() {
$for e in Color.values {
println(e.name)
println(e.attrs)
}
}
// Output:
// red
// ['RED']
// blue
// ['BLUE']
.attributes
You can read Struct attributes.
@[COLOR]
struct Foo {
a int
}
fn main() {
$for e in Foo.attributes {
println(e)
}
}
// Output:
// StructAttribute{
// name: 'COLOR'
// has_arg: false
// arg: ''
// kind: plain
// }
.variants
You can read variant types from Sum type.
type MySum = int | string
fn main() {
$for v in MySum.variants {
$if v.typ is int {
println('has int type')
} $else $if v.typ is string {
println('has string type')
}
}
}
// Output:
// has int type
// has string type
.methods
You can retrieve information about struct methods.
struct Foo {
}
fn (f Foo) test() int {
return 123
}
fn (f Foo) test2() string {
return 'foo'
}
fn main() {
foo := Foo{}
$for m in Foo.methods {
$if m.return_type is int {
print('${m.name} returns int: ')
println(foo.$method())
} $else $if m.return_type is string {
print('${m.name} returns string: ')
println(foo.$method())
}
}
}
// Output:
// test returns int: 123
// test2 returns string: foo
.params
You can retrieve information about struct method params.
struct Test {
}
fn (t Test) foo(arg1 int, arg2 string) {
}
fn main() {
$for m in Test.methods {
$for param in m.params {
println('${typeof(param.typ).name}: ${param.name}')
}
}
}
// Output:
// int: arg1
// string: arg2
## Example
```v
// An example deserializer implementation
struct User {
name string
age int
}
fn main() {
data := 'name=Alice\nage=18'
user := decode[User](data)
println(user)
}
fn decode[T](data string) T {
mut result := T{}
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
$if field.typ is string {
// $(string_expr) produces an identifier
result.$(field.name) = get_string(data, field.name)
} $else $if field.typ is int {
result.$(field.name) = get_int(data, field.name)
}
}
return result
}
fn get_string(data string, field_name string) string {
for line in data.split_into_lines() {
key_val := line.split('=')
if key_val[0] == field_name {
return key_val[1]
}
}
return ''
}
fn get_int(data string, field string) int {
return get_string(data, field).int()
}
// `decode<User>` generates:
// fn decode_User(data string) User {
// mut result := User{}
// result.name = get_string(data, 'name')
// result.age = get_int(data, 'age')
// return result
// }
```

View File

@@ -19,6 +19,26 @@ fn playcmds_do(path string) ! {
}
fn do() ! {
if ! core.is_osx()! {
if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' {
println('Error: Please do not run this program with sudo!')
exit(1) // Exit with error code
}
}
if os.getuid() == 0 {
if core.is_osx()! {
eprintln("please do not run hero as root in osx.")
exit(1)
}
} else {
if ! core.is_osx()! {
eprintln("please do run hero as root, don't use sudo.")
exit(1)
}
}
if os.args.len == 2 {
mypath := os.args[1]
if mypath.to_lower().ends_with('.hero') {

BIN
examples/clients/mycelium Executable file

Binary file not shown.

43
examples/clients/mycelium.vsh Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.mycelium
mycelium.delete()!
mut r:=mycelium.inspect()!
println("My pub key: ${r.public_key}")
mut client := mycelium.get()!
println(client)
// Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client.send_msg(
r.public_key, // destination public key
'Hello World', // message payload
'greetings', // optional topic
false // wait for reply
)!
println('Sent message ID: ${msg.id}')
println("send succeeded")
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client.receive_msg(true, false, 'greetings')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${received.payload}')
// // Reply to a message
// client.reply_msg(
// received.id, // original message ID
// received.src_pk, // sender's public key
// 'Got your message!', // reply payload
// 'greetings' // topic
// )!
// // Check message status
// status := client.get_msg_status(msg.id)!
// println('Message status: ${status.state}')
// println('Created at: ${status.created}')
// println('Expires at: ${status.deadline}')

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal
import time
mut gs_default := gittools.new()!
println(gs_default)
// // Initializes the Git structure with the coderoot path.
// coderoot := '/tmp/code'
// mut gs_tmo := gittools.new(coderoot: coderoot)!
// // Retrieve the specified repository.
// mut repo := gs_default.get_repo(name: 'herolib')!
// println(repo)

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal
import time
mut gs := gittools.new()!
mydocs_path:=gs.get_path(
pull:true,
reset:false,
url:'https://git.ourworld.tf/tfgrid/info_docs_depin/src/branch/main/docs'
)!
println(mydocs_path)

View File

@@ -1,5 +1,44 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
import freeflowuniverse.herolib.clients.mycelium
mycelium_installer.start()!
mut installer:=mycelium_installer.get()!
installer.start()!
mut r:=mycelium.inspect()!
println(r)
mut client := mycelium.get()!
// Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client.send_msg(
'abc123...', // destination public key
'Hello World', // message payload
'greetings', // optional topic
true // wait for reply
)!
println('Sent message ID: ${msg.id}')
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client.receive_msg(true, false, 'greetings')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${received.payload}')
// Reply to a message
client.reply_msg(
received.id, // original message ID
received.src_pk, // sender's public key
'Got your message!', // reply payload
'greetings' // topic
)!
// Check message status
status := client.get_msg_status(msg.id)!
println('Message status: ${status.state}')
println('Created at: ${status.created}')
println('Expires at: ${status.deadline}')

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.sysadmintools.zinit as zinit_installer
mut installer:=zinit_installer.get()!
installer.start()!

25
examples/osal/tun.vsh Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tun
// Check if TUN is available
if available := tun.available() {
if available {
println('TUN is available on this system')
// Get a free TUN interface name
if interface_name := tun.free() {
println('Found free TUN interface: ${interface_name}')
// Example: Now you could use this interface name
// to set up your tunnel
} else {
println('Error finding free interface: ${err}')
}
} else {
println('TUN is not available on this system')
}
} else {
println('Error checking TUN availability: ${err}')
}

View File

@@ -1 +1,4 @@
buildah_example
buildah_run_clean
buildah_run_mdbook
buildah_run

View File

@@ -16,13 +16,13 @@ podman_installer0.install()!
mut engine := herocontainers.new(install: true, herocompile: false)!
engine.reset_all()!
// engine.reset_all()!
mut builder_gorust := engine.builder_go_rust()!
// mut builder_gorust := engine.builder_go_rust()!
// will build nodejs, python build & herolib, hero
// mut builder_hero := engine.builder_hero(reset:true)!
// mut builder_web := engine.builder_heroweb(reset:true)!
builder_gorust.shell()!
// builder_gorust.shell()!

View File

@@ -7,14 +7,22 @@ import freeflowuniverse.herolib.core.base
import time
import os
mut pm := herocontainers.new(herocompile: true, install: false)!
//herocompile means we do it for the host system
mut pm := herocontainers.new(herocompile: false, install: false)!
mut mybuildcontainer := pm.builder_get('builder_heroweb')!
//pm.builder_base(reset:true)!
mut builder := pm.builder_get('base')!
builder.shell()!
println(builder)
// builder.install_zinit()!
// bash & python can be executed directly in build container
// any of the herocommands can be executed like this
mybuildcontainer.run(cmd: 'installers -n heroweb', runtime: .herocmd)!
//mybuildcontainer.run(cmd: 'installers -n heroweb', runtime: .herocmd)!
// //following will execute heroscript in the buildcontainer
// mybuildcontainer.run(

View File

@@ -1,92 +1,136 @@
module mycelium
import json
import encoding.base64
import freeflowuniverse.herolib.core.httpconnection
// Represents a destination for a message, can be either IP or public key
pub struct MessageDestination {
pub:
pk string
ip string @[omitempty] // IP in the subnet of the receiver node
pk string @[omitempty] // hex encoded public key of the receiver node
}
// Body of a message to be sent
pub struct PushMessageBody {
pub:
dst MessageDestination
payload string
topic string // optional message topic
payload string // base64 encoded message
}
// Response containing message ID after pushing
pub struct PushMessageResponseId {
pub:
id string // hex encoded message ID
}
// A message received by the system
pub struct InboundMessage {
pub:
id string
src_ip string @[json: 'srcIP']
src_pk string @[json: 'srcPk']
dst_ip string @[json: 'dstIp']
dst_pk string @[json: 'dstPk']
payload string
src_ip string @[json: 'srcIp'] // Sender overlay IP address
src_pk string @[json: 'srcPk'] // Sender public key, hex encoded
dst_ip string @[json: 'dstIp'] // Receiver overlay IP address
dst_pk string @[json: 'dstPk'] // Receiver public key, hex encoded
topic string // Optional message topic
payload string // Message payload, base64 encoded
}
// Information about an outbound message
pub struct MessageStatusResponse {
pub:
id string
dst string
state string
created string
deadline string
msg_len string @[json: 'msgLen']
dst string // IP address of receiving node
state string // pending, received, read, aborted or sending object
created i64 // Unix timestamp of creation
deadline i64 // Unix timestamp of expiry
msg_len int @[json: 'msgLen'] // Length in bytes
}
// General information about a node
pub struct Info {
pub:
node_subnet string @[json: 'nodeSubnet'] // subnet owned by node
}
// Response containing public key for a node IP
pub struct PublicKeyResponse {
pub:
node_pub_key string @[json: 'NodePubKey'] // hex encoded public key
}
// Get connection to mycelium server
pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
mut c := self.conn or {
mut c2 := httpconnection.new(
name: 'mycelium'
url: self.server_url
name: 'mycelium'
url: self.server_url
retry: 3
)!
c2
}
return c
}
pub fn (mut self Mycelium) send_msg(pk string, payload string, wait bool) !InboundMessage {
// Send a message to a node identified by public key
pub fn (mut self Mycelium) send_msg(pk string, payload string, topic string, wait bool) !InboundMessage {
mut conn := self.connection()!
mut params := {
'dst': json.encode(MessageDestination{ pk: pk })
'payload': payload
mut body := PushMessageBody{
dst: MessageDestination{
pk: pk
ip: ''
}
payload: base64.encode_str(payload)
topic: base64.encode_str(topic)
}
mut prefix := ''
mut prefix := '/api/v1/messages'
if wait {
prefix = '?reply_timeout=120'
prefix += '?reply_timeout=120'
}
return conn.post_json_generic[InboundMessage](
method: .post
prefix: prefix
params: params
method: .post
prefix: prefix
data: json.encode(body)
dataformat: .json
)!
}
pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
// Receive a message from the queue
pub fn (mut self Mycelium) receive_msg(wait bool, peek bool, topic string) !InboundMessage {
mut conn := self.connection()!
mut prefix := ''
mut prefix := '/api/v1/messages?'
if wait {
prefix = '?timeout=60'
prefix += 'timeout=60&'
}
if peek {
prefix += 'peek=true&'
}
if topic.len > 0 {
prefix += 'topic=${base64.encode_str(topic)}'
}
return conn.get_json_generic[InboundMessage](
method: .get
prefix: prefix
method: .get
prefix: prefix
dataformat: .json
)!
}
pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
mut conn := self.connection()!
mut prefix := ''
// Optional version of receive_msg that returns none on 204
pub fn (mut self Mycelium) receive_msg_opt(wait bool, peek bool, topic string) ?InboundMessage {
mut conn := self.connection() or { panic(err) }
mut prefix := '/api/v1/messages?'
if wait {
prefix = '?timeout=60'
prefix += 'timeout=60&'
}
if peek {
prefix += 'peek=true&'
}
if topic.len > 0 {
prefix += 'topic=${base64.encode_str(topic)}'
}
res := conn.get_json_generic[InboundMessage](
method: .get
prefix: prefix
method: .get
prefix: prefix
dataformat: .json
) or {
if err.msg().contains('204') {
@@ -97,25 +141,52 @@ pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
return res
}
// Get status of a message by ID
pub fn (mut self Mycelium) get_msg_status(id string) !MessageStatusResponse {
mut conn := self.connection()!
return conn.get_json_generic[MessageStatusResponse](
method: .get
prefix: 'status/${id}'
method: .get
prefix: '/api/v1/messages/status/${id}'
dataformat: .json
)!
}
pub fn (mut self Mycelium) reply_msg(id string, pk string, payload string) ! {
// Reply to a message
pub fn (mut self Mycelium) reply_msg(id string, pk string, payload string, topic string) ! {
mut conn := self.connection()!
mut params := {
'dst': json.encode(MessageDestination{ pk: pk })
'payload': payload
mut body := PushMessageBody{
dst: MessageDestination{
pk: pk
ip: ''
}
payload: base64.encode_str(payload)
topic: base64.encode_str(topic)
}
conn.post_json_generic[json.Any](
method: .post
prefix: 'reply/${id}'
params: params
_ := conn.post_json_generic[MessageDestination](
method: .post
prefix: '/api/v1/messages/reply/${id}'
data: json.encode(body)
dataformat: .json
)!
}
// curl -v -H 'Content-Type: application/json' -d '{"dst": {"pk": "be4bf135d60b7e43a46be1ad68f955cdc1209a3c55dc30d00c4463b1dace4377"}, "payload": "xuV+"}' http://localhost:8989/api/v1/messages\
// Get node info
pub fn (mut self Mycelium) get_info() !Info {
mut conn := self.connection()!
return conn.get_json_generic[Info](
method: .get
prefix: '/api/v1/admin'
dataformat: .json
)!
}
// Get public key for a node IP
pub fn (mut self Mycelium) get_pubkey_from_ip(ip string) !PublicKeyResponse {
mut conn := self.connection()!
return conn.get_json_generic[PublicKeyResponse](
method: .get
prefix: '/api/v1/pubkey/${ip}'
dataformat: .json
)!
}

View File

@@ -0,0 +1,64 @@
module mycelium
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.installers.lang.rust
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.osal.screen
import freeflowuniverse.herolib.ui
import freeflowuniverse.herolib.sysadmin.startupmanager
import os
import time
import json
pub fn check() bool {
// if core.is_osx()! {
// mut scr := screen.new(reset: false) or {return False}
// name := 'mycelium'
// if !scr.exists(name) {
// return false
// }
// }
// if !(osal.process_exists_byname('mycelium') or {return False}) {
// return false
// }
// TODO: might be dangerous if that one goes out
ping_result := osal.ping(address: '40a:152c:b85b:9646:5b71:d03a:eb27:2462', retry: 2) or {
return false
}
if ping_result == .ok {
console.print_debug('could reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
return true
}
console.print_stderr('could not reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
return false
}
pub struct MyceliumInspectResult {
pub:
public_key string @[json: publicKey]
address string
}
pub fn inspect() !MyceliumInspectResult {
command := 'mycelium inspect --key-file /root/hero/cfg/priv_key.bin --json'
result := os.execute(command)
if result.exit_code != 0 {
return error('Command failed: ${result.output}')
}
inspect_result := json.decode(MyceliumInspectResult, result.output) or {
return error('Failed to parse JSON: ${err}')
}
return inspect_result
}
// if returns empty then probably mycelium is not installed
pub fn ipaddr() string {
r := inspect() or { MyceliumInspectResult{} }
return r.address
}

View File

@@ -3,68 +3,122 @@ module mycelium
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.data.paramsparser
__global (
mycelium_global map[string]&Mycelium
mycelium_default string
mycelium_global map[string]&Mycelium
mycelium_default string
)
/////////FACTORY
// set the model in mem and the config on the filesystem
pub fn set(o Mycelium) ! {
mut o2 := obj_init(o)!
mycelium_global[o.name] = &o2
mycelium_default = o.name
@[params]
pub struct ArgsGet{
pub mut:
name string
}
// check we find the config on the filesystem
pub fn exists(args_ ArgsGet) bool {
mut model := args_get(args_)
mut context := base.context() or { panic('bug') }
return context.hero_config_exists('mycelium', model.name)
fn args_get (args_ ArgsGet) ArgsGet {
mut args:=args_
if args.name == ""{
args.name = "default"
}
return args
}
// load the config error if it doesn't exist
pub fn load(args_ ArgsGet) ! {
mut model := args_get(args_)
mut context := base.context()!
mut heroscript := context.hero_config_get('mycelium', model.name)!
play(heroscript: heroscript)!
pub fn get(args_ ArgsGet) !&Mycelium {
mut context:=base.context()!
mut args := args_get(args_)
mut obj := Mycelium{}
if !(args.name in mycelium_global) {
if ! exists(args)!{
set(obj)!
}else{
heroscript := context.hero_config_get("mycelium",args.name)!
mut obj_:=heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return mycelium_global[args.name] or {
println(mycelium_global)
//bug if we get here because should be in globals
panic("could not get config for mycelium with name, is bug:${args.name}")
}
}
// save the config to the filesystem in the context
pub fn save(o Mycelium) ! {
mut context := base.context()!
heroscript := encoderhero.encode[Mycelium](o)!
context.hero_config_set('mycelium', model.name, heroscript)!
//register the config for the future
pub fn set(o Mycelium)! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set("mycelium", o.name, heroscript)!
}
//does the config exists?
pub fn exists(args_ ArgsGet)! bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists("mycelium", args.name)
}
pub fn delete(args_ ArgsGet)! {
mut args := args_get(args_)
mut context:=base.context()!
context.hero_config_delete("mycelium",args.name)!
if args.name in mycelium_global {
//del mycelium_global[args.name]
}
}
//only sets in mem, does not set as config
fn set_in_mem(o Mycelium)! {
mut o2:=obj_init(o)!
mycelium_global[o.name] = &o2
mycelium_default = o.name
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string // if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
heroscript string //if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut model := args_
if model.heroscript == '' {
model.heroscript = heroscript_default()!
}
mut plbook := model.plbook or { playbook.new(text: model.heroscript)! }
mut args:=args_
mut plbook := args.plbook or {
playbook.new(text: args.heroscript)!
}
mut install_actions := plbook.find(filter: 'mycelium.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript:=install_action.heroscript()
mut obj2:=heroscript_loads(heroscript)!
set(obj2)!
}
}
mut configure_actions := plbook.find(filter: 'mycelium.configure')!
if configure_actions.len > 0 {
for config_action in configure_actions {
mut p := config_action.params
mycfg := cfg_play(p)!
console.print_debug('install action mycelium.configure\n${mycfg}')
set(mycfg)!
save(mycfg)!
}
}
}
//switch instance to be used for mycelium
pub fn switch(name string) {
mycelium_default = name
}
//helpers
@[params]
pub struct DefaultConfigArgs{
instance string = 'default'
}

View File

@@ -1,39 +1,37 @@
module mycelium
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.core.httpconnection
import freeflowuniverse.herolib.data.encoderhero
import os
pub const version = '0.0.0'
const singleton = true
const default = true
pub fn heroscript_default() !string {
heroscript := "
!!mycelium.configure
name:'mycelium'
"
return heroscript
}
@[heap]
pub struct Mycelium {
pub mut:
name string = 'default'
server_url string
conn ?&httpconnection.HTTPConnection
server_url string = "http://localhost:8989"
conn ?&httpconnection.HTTPConnection @[skip; str: skip]
}
fn cfg_play(p paramsparser.Params) ! {
mut mycfg := Mycelium{
name: p.get_default('name', 'default')!
server_url: p.get_default('server_url', 'http://localhost:8989/api/v1/messages')!
}
set(mycfg)!
//your checking & initialization code if needed
fn obj_init(mycfg_ Mycelium)!Mycelium{
mut mycfg:=mycfg_
return mycfg
}
fn obj_init(obj_ Mycelium) !Mycelium {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
return obj
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj Mycelium) !string {
return encoderhero.encode[Mycelium ](obj)!
}
pub fn heroscript_loads(heroscript string) !Mycelium {
mut obj := encoderhero.decode[Mycelium](heroscript)!
return obj
}

View File

@@ -0,0 +1,602 @@
openapi: 3.0.2
info:
version: '1.0.0'
title: Mycelium management
contact:
url: 'https://github.com/threefoldtech/mycelium'
license:
name: Apache 2.0
url: 'https://github.com/threefoldtech/mycelium/blob/master/LICENSE'
description: |
This is the specification of the **mycelium** management API. It is used to perform admin tasks on the system, and
to perform administrative duties.
externalDocs:
description: For full documentation, check out the mycelium github repo.
url: 'https://github.com/threefoldtech/mycelium'
tags:
- name: Admin
description: Administrative operations
- name: Peer
description: Operations related to peer management
- name: Route
description: Operations related to network routes
- name: Message
description: Operations on the embedded message subsystem
servers:
- url: 'http://localhost:8989'
paths:
'/api/v1/admin':
get:
tags:
- Admin
summary: Get general info about the node
description: |
Get general info about the node, which is not related to other more specific functionality
operationId: getInfo
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Info'
'/api/v1/admin/peers':
get:
tags:
- Admin
- Peer
summary: List known peers
description: |
List all peers known in the system, and info about their connection.
This includes the endpoint, how we know about the peer, the connection state, and if the connection is alive the amount
of bytes we've sent to and received from the peer.
operationId: getPeers
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/PeerStats'
post:
tags:
- Admin
- Peer
summary: Add a new peer
description: |
Add a new peer identified by the provided endpoint.
The peer is added to the list of known peers. It will eventually be connected
to by the standard connection loop of the peer manager. This means that a peer
which can't be connected to will stay in the system, as it might be reachable
later on.
operationId: addPeer
responses:
'204':
description: Peer added
'400':
description: Malformed endpoint
content:
text/plain:
schema:
type: string
description: Details about why the endpoint is not valid
'409':
description: Peer already exists
content:
text/plain:
schema:
type: string
description: message saying we already know this peer
'/api/v1/admin/peers/{endpoint}':
delete:
tags:
- Admin
- Peer
summary: Remove an existing peer
description: |
Remove an existing peer identified by the provided endpoint.
The peer is removed from the list of known peers. If a connection to it
is currently active, it will be closed.
operationId: deletePeer
responses:
'204':
description: Peer removed
'400':
description: Malformed endpoint
content:
text/plain:
schema:
type: string
description: Details about why the endpoint is not valid
'404':
description: Peer doesn't exist
content:
text/plain:
schema:
type: string
description: message saying we don't know this peer
'/api/v1/admin/routes/selected':
get:
tags:
- Admin
- Route
summary: List all selected routes
description: |
List all selected routes in the system, and their next hop identifier, metric and sequence number.
It is possible for a route to be selected and have an infinite metric. This route will however not forward packets.
operationId: getSelectedRoutes
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Route'
'/api/v1/admin/routes/fallback':
get:
tags:
- Admin
- Route
summary: List all active fallback routes
description: |
List all fallback routes in the system, and their next hop identifier, metric and sequence number.
These routes are available to be selected in case the selected route for a destination suddenly fails, or gets retracted.
operationId: getSelectedRoutes
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Route'
'/api/v1/messages':
get:
tags:
- Message
summary: Get a message from the inbound message queue
description: |
Get a message from the inbound message queue. By default, the message is removed from the queue and won't be shown again.
If the peek query parameter is set to true, the message will be peeked, and the next call to this endpoint will show the same message.
This method returns immediately by default: a message is returned if one is ready, and if there isn't nothing is returned. If the timeout
query parameter is set, this call won't return for the given amount of seconds, unless a message is received
operationId: popMessage
parameters:
- in: query
name: peek
required: false
schema:
type: boolean
description: Whether to peek the message or not. If this is true, the message won't be removed from the inbound queue when it is read
example: true
- in: query
name: timeout
required: false
schema:
type: integer
format: int64
minimum: 0
description: |
Amount of seconds to wait for a message to arrive if one is not available. Setting this to 0 is valid and will return
a message if present, or return immediately if there isn't
example: 60
- in: query
name: topic
required: false
schema:
type: string
format: byte
minLength: 0
maxLength: 340
description: |
Optional filter for loading messages. If set, the system checks if the message has the given string at the start. This way
a topic can be encoded.
example: example.topic
responses:
'200':
description: Message retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/InboundMessage'
'204':
description: No message ready
post:
tags:
- Message
summary: Submit a new message to the system.
description: |
Push a new message to the systems outbound message queue. The system will continuously attempt to send the message until
it is either fully transmitted, or the send deadline is expired.
operationId: pushMessage
parameters:
- in: query
name: reply_timeout
required: false
schema:
type: integer
format: int64
minimum: 0
description: |
Amount of seconds to wait for a reply to this message to come in. If not set, the system won't wait for a reply and return
the ID of the message, which can be used later. If set, the system will wait for at most the given amount of seconds for a reply
to come in. If a reply arrives, it is returned to the client. If not, the message ID is returned for later use.
example: 120
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageBody'
responses:
'200':
description: We received a reply within the specified timeout
content:
application/json:
schema:
$ref: '#/components/schemas/InboundMessage'
'201':
description: Message pushed successfully, and not waiting for a reply
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageResponseId'
'408':
description: The system timed out waiting for a reply to the message
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageResponseId'
'/api/v1/messsages/reply/{id}':
post:
tags:
- Message
summary: Reply to a message with the given ID
description: |
Submits a reply message to the system, where ID is an id of a previously received message. If the sender is waiting
for a reply, it will bypass the queue of open messages.
operationId: pushMessageReply
parameters:
- in: path
name: id
required: true
schema:
type: string
format: hex
minLength: 16
maxLength: 16
example: abcdef0123456789
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PushMessageBody'
responses:
'204':
description: successfully submitted the reply
'/api/v1/messages/status/{id}':
get:
tags:
- Message
summary: Get the status of an outbound message
description: |
Get information about the current state of an outbound message. This can be used to check the transmission
state, size and destination of the message.
operationId: getMessageInfo
parameters:
- in: path
name: id
required: true
schema:
type: string
format: hex
minLength: 16
maxLength: 16
example: abcdef0123456789
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/MessageStatusResponse'
'404':
description: Message not found
'/api/v1/pubkey/{mycelium_ip}':
get:
summary: Get the pubkey from node ip
description: |
Get the node's public key from it's IP address.
operationId: getPublicKeyFromIp
parameters:
- in: path
name: mycelium_ip
required: true
schema:
type: string
format: ipv6
example: 5fd:7636:b80:9ad0::1
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/PublicKeyResponse'
'404':
description: Public key not found
components:
schemas:
Info:
description: General information about a node
type: object
properties:
nodeSubnet:
description: The subnet owned by the node and advertised to peers
type: string
example: 54f:b680:ba6e:7ced::/64
Endpoint:
description: Identification to connect to a peer
type: object
properties:
proto:
description: Protocol used
type: string
enum:
- 'tcp'
- 'quic'
example: tcp
socketAddr:
description: The socket address used
type: string
example: 192.0.2.6:9651
PeerStats:
description: Info about a peer
type: object
properties:
endpoint:
$ref: '#/components/schemas/Endpoint'
type:
description: How we know about this peer
type: string
enum:
- 'static'
- 'inbound'
- 'linkLocalDiscovery'
example: static
connectionState:
description: The current state of the connection to the peer
type: string
enum:
- 'alive'
- 'connecting'
- 'dead'
example: alive
txBytes:
description: The amount of bytes transmitted to this peer
type: integer
format: int64
minimum: 0
example: 464531564
rxBytes:
description: The amount of bytes received from this peer
type: integer
format: int64
minimum: 0
example: 64645089
Route:
description: Information about a route
type: object
properties:
subnet:
description: The overlay subnet for which this is the route
type: string
example: 469:1348:ab0c:a1d8::/64
nextHop:
description: A way to identify the next hop of the route, where forwarded packets will be sent
type: string
example: TCP 203.0.113.2:60128 <-> 198.51.100.27:9651
metric:
description: The metric of the route, an estimation of how long the packet will take to arrive at its final destination
oneOf:
- description: A finite metric value
type: integer
format: int32
minimum: 0
maximum: 65534
example: 13
- description: An infinite (unreachable) metric. This is always `infinite`
type: string
example: infinite
seqno:
description: the sequence number advertised with this route by the source
type: integer
format: int32
minimum: 0
maximum: 65535
example: 1
InboundMessage:
description: A message received by the system
type: object
properties:
id:
description: Id of the message, hex encoded
type: string
format: hex
minLength: 16
maxLength: 16
example: 0123456789abcdef
srcIp:
description: Sender overlay IP address
type: string
format: ipv6
example: 449:abcd:0123:defa::1
srcPk:
description: Sender public key, hex encoded
type: string
format: hex
minLength: 64
maxLength: 64
example: fedbca9876543210fedbca9876543210fedbca9876543210fedbca9876543210
dstIp:
description: Receiver overlay IP address
type: string
format: ipv6
example: 34f:b680:ba6e:7ced:355f:346f:d97b:eecb
dstPk:
description: Receiver public key, hex encoded. This is the public key of the system
type: string
format: hex
minLength: 64
maxLength: 64
example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
topic:
description: An optional message topic
type: string
format: byte
minLength: 0
maxLength: 340
example: hpV+
payload:
description: The message payload, encoded in standard alphabet base64
type: string
format: byte
example: xuV+
PushMessageBody:
description: A message to send to a given receiver
type: object
properties:
dst:
$ref: '#/components/schemas/MessageDestination'
topic:
description: An optional message topic
type: string
format: byte
minLength: 0
maxLength: 340
example: hpV+
payload:
description: The message to send, base64 encoded
type: string
format: byte
example: xuV+
MessageDestination:
oneOf:
- description: An IP in the subnet of the receiver node
type: object
properties:
ip:
description: The target IP of the message
format: ipv6
example: 449:abcd:0123:defa::1
- description: The hex encoded public key of the receiver node
type: object
properties:
pk:
description: The hex encoded public key of the target node
type: string
minLength: 64
maxLength: 64
example: bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32
PushMessageResponseId:
description: The ID generated for a message after pushing it to the system
type: object
properties:
id:
description: Id of the message, hex encoded
type: string
format: hex
minLength: 16
maxLength: 16
example: 0123456789abcdef
MessageStatusResponse:
description: Information about an outbound message
type: object
properties:
dst:
description: IP address of the receiving node
type: string
format: ipv6
example: 449:abcd:0123:defa::1
state:
$ref: '#/components/schemas/TransmissionState'
created:
description: Unix timestamp of when this message was created
type: integer
format: int64
example: 1649512789
deadline:
description: Unix timestamp of when this message will expire. If the message is not received before this, the system will give up
type: integer
format: int64
example: 1649513089
msgLen:
description: Length of the message in bytes
type: integer
minimum: 0
example: 27
TransmissionState:
description: The state of an outbound message in it's lifetime
oneOf:
- type: string
enum: ['pending', 'received', 'read', 'aborted']
example: 'received'
- type: object
properties:
sending:
type: object
properties:
pending:
type: integer
minimum: 0
example: 5
sent:
type: integer
minimum: 0
example: 17
acked:
type: integer
minimum: 0
example: 3
example: 'received'
PublicKeyResponse:
description: Public key requested based on a node's IP
type: object
properties:
NodePubKey:
type: string
format: hex
minLength: 64
maxLength: 64
example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf

View File

@@ -1,6 +1,13 @@
# Mycelium Client
A V client library for interacting with the Mycelium messaging system. This client provides functionality for sending, receiving, and managing messages through a Mycelium server.
A V client library for interacting with the Mycelium messaging system. This client provides functionality for configuring and inspecting a Mycelium node.
## Components
The Mycelium integration consists of two main components:
1. **Mycelium Client** (this package) - For interacting with a running Mycelium node
2. **Mycelium Installer** (in `installers/net/mycelium/`) - For installing and managing Mycelium nodes
## Configuration
@@ -11,131 +18,101 @@ The client can be configured either through V code or using heroscript.
```v
import freeflowuniverse.herolib.clients.mycelium
// Get default client instance
mut client := mycelium.get()!
// By default connects to http://localhost:8989/api/v1/messages
// To use a different server:
mut client := mycelium.get(name: "custom", server_url: "http://myserver:8989/api/v1/messages")!
// Get named client instance
mut client := mycelium.get(name: "custom")!
```
### Heroscript Configuration
## Core Functions
```hero
!!mycelium.configure
name:'custom' # optional, defaults to 'default'
server_url:'http://myserver:8989/api/v1/messages' # optional, defaults to localhost:8989
```
### Inspect Node
Note: Configuration is not needed if using a locally running Mycelium server with default settings.
## Example Script
Save as `mycelium_example.vsh`:
Get information about the local Mycelium node:
```v
import freeflowuniverse.herolib.clients.mycelium
// Get node info including public key and address
result := mycelium.inspect()!
println('Public Key: ${result.public_key}')
println('Address: ${result.address}')
// Get just the IP address
addr := mycelium.ipaddr()
println('IP Address: ${addr}')
```
### Check Node Status
Check if the Mycelium node is running and reachable:
```v
import freeflowuniverse.herolib.clients.mycelium
is_running := mycelium.check()
if is_running {
println('Mycelium node is running and reachable')
} else {
println('Mycelium node is not running or unreachable')
}
```
### Sending and Receiving Messages
The client provides several functions for sending and receiving messages between nodes:
```v
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.mycelium
// Initialize client
mut client := mycelium.get()!
// Send a message and wait for reply
// Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client.send_msg(
pk: "recipient_public_key"
payload: "Hello!"
wait: true // wait for reply (timeout 120s)
'abc123...', // destination public key
'Hello World', // message payload
'greetings', // optional topic
true // wait for reply
)!
println('Sent message ID: ${msg.id}')
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client.receive_msg(true, false, 'greetings')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${received.payload}')
// Reply to a message
client.reply_msg(
received.id, // original message ID
received.src_pk, // sender's public key
'Got your message!', // reply payload
'greetings' // topic
)!
println('Message sent with ID: ${msg.id}')
// Check message status
status := client.get_msg_status(msg.id)!
println('Message status: ${status.state}')
// Receive messages with timeout
if incoming := client.receive_msg_opt(wait: true) {
println('Received message: ${incoming.payload}')
println('From: ${incoming.src_pk}')
// Reply to the message
client.reply_msg(
id: incoming.id
pk: incoming.src_pk
payload: "Got your message!"
)!
}
println('Created at: ${status.created}')
println('Expires at: ${status.deadline}')
```
## API Reference
The messaging API supports:
- Sending messages to nodes identified by public key
- Optional message topics for filtering
- Waiting for replies when sending messages
- Peeking at messages without removing them from the queue
- Tracking message delivery status
- Base64 encoded message payloads for binary data
### Sending Messages
## Installation and Management
```v
// Send a message to a specific public key
// wait=true means wait for reply (timeout 120s)
msg := client.send_msg(pk: "recipient_public_key", payload: "Hello!", wait: true)!
For installing and managing Mycelium nodes, use the Mycelium Installer package located in `installers/net/mycelium/`. The installer provides functionality for:
// Get status of a sent message
status := client.get_msg_status(id: "message_id")!
```
### Receiving Messages
```v
// Receive a message (non-blocking)
msg := client.receive_msg(wait: false)!
// Receive a message with timeout (blocking for 60s)
msg := client.receive_msg(wait: true)!
// Receive a message (returns none if no message available)
if msg := client.receive_msg_opt(wait: false) {
println('Received: ${msg.payload}')
}
```
### Replying to Messages
```v
// Reply to a specific message
client.reply_msg(
id: "original_message_id",
pk: "sender_public_key",
payload: "Reply message"
)!
```
## Message Types
### InboundMessage
```v
struct InboundMessage {
id string
src_ip string
src_pk string
dst_ip string
dst_pk string
payload string
}
```
### MessageStatusResponse
```v
struct MessageStatusResponse {
id string
dst string
state string
created string
deadline string
msg_len string
}
```
## Heroscript Complete Example
```hero
!!mycelium.configure
name:'mycelium'
server_url:'http://localhost:8989/api/v1/messages'
# More heroscript commands can be added here as the API expands
- Installing Mycelium nodes
- Starting/stopping nodes
- Managing node configuration
- Setting up TUN interfaces
- Configuring peer connections

View File

@@ -42,8 +42,8 @@ pub fn get(args_ ArgsGet) !&${args.classname} {
set(obj)!
}else{
heroscript := context.hero_config_get("${args.name}",args.name)!
mut obj:=heroscript_loads(heroscript)!
set_in_mem(obj)!
mut obj_:=heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return ${args.name}_global[args.name] or {
@@ -58,14 +58,14 @@ pub fn set(o ${args.classname})! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set("gitea", o.name, heroscript)!
context.hero_config_set("${args.name}", o.name, heroscript)!
}
//does the config exists?
pub fn exists(args_ ArgsGet)! {
pub fn exists(args_ ArgsGet)! bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists("gitea", args.name)
return context.hero_config_exists("${args.name}", args.name)
}
pub fn delete(args_ ArgsGet)! {

View File

@@ -189,6 +189,7 @@ pub fn (mut h HTTPConnection) get(req_ Request) !string {
req.debug = true
req.method = .get
result := h.send(req)!
println(result)
return result.data
}

View File

@@ -38,23 +38,33 @@ fn decode_struct[T](_ T, data string) !T {
// return t_
$for field in T.fields {
// $if fiel
$if field.is_struct {
$if field.typ !is time.Time {
if !field.name[0].is_capital() {
// skip embedded ones
// Check if field has skip attribute
mut should_skip := false
for attr in field.attrs {
if attr.contains('skip') {
should_skip = true
break
}
}
if ! should_skip {
$if field.is_struct {
$if field.typ !is time.Time {
if !field.name[0].is_capital() {
// skip embedded ones
mut data_fmt := data.replace(action_str, '')
data_fmt = data.replace('define.${obj_name}', 'define')
typ.$(field.name) = decode_struct(typ.$(field.name), data_fmt)!
}
}
} $else $if field.is_array {
if is_struct_array(typ.$(field.name))! {
mut data_fmt := data.replace(action_str, '')
data_fmt = data.replace('define.${obj_name}', 'define')
typ.$(field.name) = decode_struct(typ.$(field.name), data_fmt)!
arr := decode_array(typ.$(field.name), data_fmt)!
typ.$(field.name) = arr
}
}
} $else $if field.is_array {
if is_struct_array(typ.$(field.name))! {
mut data_fmt := data.replace(action_str, '')
data_fmt = data.replace('define.${obj_name}', 'define')
arr := decode_array(typ.$(field.name), data_fmt)!
typ.$(field.name) = arr
}
}
}
} $else {

View File

@@ -28,6 +28,7 @@ pub fn encode[T](val T) !string {
$if T is $struct {
e.encode_struct[T](val)!
} $else $if T is $array {
//TODO: need to make comma separated list only works if int,u8,u16,i8... or string if string put all elements in \''...\'',...
e.add_child_list[T](val, 'TODO')
} $else {
return error('can only add elements for struct or array of structs. \n${val}')
@@ -128,18 +129,30 @@ pub fn (mut e Encoder) encode_struct[T](t T) ! {
// encode children structs and array of structs
$for field in T.fields {
val := t.$(field.name)
// time is encoded in the above params encoding step so skip and dont treat as recursive struct
$if val is time.Time || val is ourtime.OurTime {
} $else $if val is $struct {
if field.name[0].is_capital() {
embedded_params := paramsparser.encode(val, recursive: false)!
e.params.params << embedded_params.params
} else {
e.add(val)!
// Check if field has skip attribute
mut should_skip := false
for attr in field.attrs {
if attr.contains('skip') {
should_skip = true
break
}
}
if ! should_skip {
val := t.$(field.name)
// time is encoded in the above params encoding step so skip and dont treat as recursive struct
$if val is time.Time || val is ourtime.OurTime {
} $else $if val is $struct {
if field.name[0].is_capital() {
embedded_params := paramsparser.encode(val, recursive: false)!
e.params.params << embedded_params.params
} else {
e.add(val)!
}
} $else $if val is $array {
e.encode_array(val)!
}
} $else $if val is $array {
e.encode_array(val)!
}
}
}

View File

@@ -0,0 +1,45 @@
module encoderhero
import freeflowuniverse.herolib.data.paramsparser
import time
import v.reflection
struct MyStruct {
id int
name string
//skip attributes would be best way how to do the encoding but can't get it to work
other ?&Remark @[skip; str: skip]
}
//is the one we should skip
struct Remark {
id int
}
fn test_encode() ! {
mut o := MyStruct{
id: 1
name: 'test'
other: &Remark{
id: 123
}
}
script := encode[MyStruct](o)!
assert script.trim_space() == "!!define.my_struct id:1 name:test"
println(script)
o2:=decode[MyStruct](script)!
assert o2== MyStruct{
id: 1
name: 'test'
}
println(o2)
}

View File

@@ -17,11 +17,17 @@ pub fn (params Params) decode_struct[T](_ T) !T {
$if field.is_enum {
t.$(field.name) = params.get_int(field.name) or { 0 }
} $else {
if field.name[0].is_capital() {
// embed := params.decode_struct(t.$(field.name))!
t.$(field.name) = params.decode_struct(t.$(field.name))!
} else {
t.$(field.name) = params.decode_value(t.$(field.name), field.name)!
//super annoying didn't find other way, then to ignore options
$if field.is_option{
}$else{
if field.name[0].is_capital() {
// embed := params.decode_struct(t.$(field.name))!
t.$(field.name) = params.decode_struct(t.$(field.name))!
// panic("to implement")
} else {
t.$(field.name) = params.decode_value(t.$(field.name), field.name)!
}
}
}
}
@@ -30,11 +36,7 @@ pub fn (params Params) decode_struct[T](_ T) !T {
pub fn (params Params) decode_value[T](_ T, key string) !T {
// $if T is $option {
// // unwrap and encode optionals
// workaround := t
// if workaround != none {
// encode(t, args)!
// }
// return error("is option")
// }
// value := params.get(field.name)!

View File

@@ -33,7 +33,6 @@ const test_child = TestChild{
const test_struct = TestStruct{
name: 'test'
nick: 'test_nick'
birthday: time.new(
day: 12
month: 12
@@ -104,9 +103,12 @@ fn test_decode() {
decoded_child := test_child_params.decode[TestChild]()!
assert decoded_child == test_child
//IMPORTANT OPTIONALS ARE NOT SUPPORTED AND WILL NOT BE ENCODED FOR NOW (unless we find ways how to deal with attributes to not encode skipped elements)
// test recursive decode struct with child
decoded := test_params.decode[TestStruct]()!
assert decoded == test_struct
}
fn test_encode() {

View File

@@ -1,20 +1,21 @@
# Git Tools Module
A comprehensive Git management module for V that provides high-level abstractions for Git operations, repository management, and automation of common Git workflows.
### Get a specific path starting from url
## Features
below is powerful command, will get the repo, put on right location, you can force a pull or even reset everything
- Repository management (clone, load, delete)
- Branch operations (create, switch, checkout)
- Tag management (create, switch, verify)
- Change tracking and commits
- Remote operations (push, pull)
- SSH key integration
- Submodule support
- Repository status tracking
- Light cloning option for large repositories
```v
import freeflowuniverse.herolib.develop.gittools
mut gs := gittools.new()!
mydocs_path:=gs.get_path(
pull:true,
reset:false,
url:'https://git.ourworld.tf/tfgrid/info_docs_depin/src/branch/main/docs'
)!
## Basic Usage
println(mydocs_path)
```
### Repository Management

View File

@@ -88,7 +88,6 @@ pub fn (mut gitstructure GitStructure) get_repos(args_ ReposGetArgs) ![]&GitRepo
// provider string // Git provider (e.g., GitHub).
// pull bool // Pull the last changes.
// reset bool // Reset the changes.
// reload bool // Reload the repo into redis cache
// url string // Repository URL, used if cloning is needed.
//```
//
@@ -151,7 +150,6 @@ fn repo_match_check(repo GitRepo, args ReposGetArgs) !bool {
// provider string // Git provider (e.g., GitHub).
// pull bool // Pull the last changes.
// reset bool // Reset the changes.
// reload bool // Reload the repo into redis cache
// url string // Repository URL, used if cloning is needed.
//```
//
@@ -161,6 +159,10 @@ fn repo_match_check(repo GitRepo, args ReposGetArgs) !bool {
// Raises:
// - Error: If multiple repositories are found with similar names or if cloning fails.
pub fn (mut gitstructure GitStructure) get_path(args_ ReposGetArgs) !string {
mut args:=args_
if args.pull{
args.status_clean = true
}
mut r := gitstructure.get_repo(args_)!
mut mypath := r.get_path_of_url(args_.url)!
return mypath

View File

@@ -1,11 +1,12 @@
!!hero_code.generate_installer
name: "mycelium"
classname: "MyceliumInstaller"
hasconfig: false
singleton: true
hasconfig: true
singleton: false
default: true
title: ""
templates: false
build: true
startupmanager: true
supported_platforms: ""

View File

@@ -1,227 +0,0 @@
module mycelium
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.installers.lang.rust
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.osal.screen
import freeflowuniverse.herolib.ui
import freeflowuniverse.herolib.sysadmin.startupmanager
import os
import time
import json
// install mycelium will return true if it was already installed
pub fn installss(args_ InstallArgs) ! {
mut args := args_
console.print_header('install mycelium.')
version := '0.5.6'
res := os.execute('${osal.profile_path_source_and()!} mycelium -V')
if res.exit_code == 0 {
r := res.output.split_into_lines().filter(it.trim_space().starts_with('mycelium'))
if r.len != 1 {
return error("couldn't parse mycelium version.\n${res.output}")
}
if texttools.version(version) > texttools.version(r[0].all_after_first('mycelium')) {
args.reset = true
}
} else {
args.reset = true
}
if args.reset {
console.print_header('install mycelium')
mut url := ''
if core.is_linux_arm()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-aarch64-unknown-linux-musl.tar.gz'
} else if core.is_linux_intel()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-x86_64-unknown-linux-musl.tar.gz'
} else if core.is_osx_arm()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-aarch64-apple-darwin.tar.gz'
} else if core.is_osx_intel()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-x86_64-apple-darwin.tar.gz'
} else {
return error('unsported platform')
}
// console.print_debug(url)
mut dest := osal.download(
url: url
minsize_kb: 1000
reset: true
expand_dir: '/tmp/myceliumnet'
)!
mut myceliumfile := dest.file_get('mycelium')! // file in the dest
// console.print_debug(myceliumfile.str())
osal.cmd_add(
source: myceliumfile.path
)!
}
// if args.restart {
// stop()!
// }
start()!
console.print_debug('install mycelium ok')
}
pub fn restart() ! {
stop()!
start()!
}
pub fn stop() ! {
name := 'mycelium'
console.print_debug('stop ${name}')
if core.is_osx()! {
mut scr := screen.new(reset: false)!
scr.kill(name)!
} else {
mut sm := startupmanager.get()!
sm.stop(name)!
}
}
pub fn start(args InstallArgs) ! {
if check() {
console.print_header('mycelium was already running')
return
}
myinitname := core.initname()!
name := 'mycelium'
console.print_debug('start ${name} (startupmanger:${myinitname})')
mut cmd := ''
if core.is_osx()! {
cmd = 'sudo -s '
}
cmd += 'mycelium --key-file ${osal.hero_path()!}/cfg/priv_key.bin --peers tcp://188.40.132.242:9651 quic://185.69.166.7:9651 tcp://65.21.231.58:9651 --tun-name utun9'
console.print_debug(cmd)
if core.is_osx()! {
// do not change, because we need this on osx at least
mut scr := screen.new(reset: false)!
if scr.exists(name) {
console.print_header('mycelium was already running')
return
}
mut s := scr.add(name: name, start: true, reset: args.reset)!
s.cmd_send(cmd)!
mut myui := ui.new()!
console.clear()
console.print_stderr("
On the next screen you will be able to fill in your password.
Once done and the server is started: do 'control a + d'
")
_ = myui.ask_yesno(question: 'Please confirm you understand?')!
s.attach()! // to allow filling in passwd
} else {
mut sm := startupmanager.get()!
sm.new(
name: name
cmd: cmd
start: true
)!
}
console.print_debug('startup manager started')
time.sleep(100 * time.millisecond)
if !check() {
return error('cound not start mycelium')
}
console.print_header('mycelium is running')
}
pub fn check() bool {
// if core.is_osx()! {
// mut scr := screen.new(reset: false) or {return False}
// name := 'mycelium'
// if !scr.exists(name) {
// return false
// }
// }
// if !(osal.process_exists_byname('mycelium') or {return False}) {
// return false
// }
// TODO: might be dangerous if that one goes out
ping_result := osal.ping(address: '40a:152c:b85b:9646:5b71:d03a:eb27:2462', retry: 2) or {
return false
}
if ping_result == .ok {
console.print_debug('could reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
return true
}
console.print_stderr('could not reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
return false
}
// install mycelium will return true if it was already installed
pub fn build_() ! {
rust.install()!
console.print_header('build mycelium')
if !osal.done_exists('build_mycelium') && !osal.cmd_exists('mycelium') {
panic('implement')
// USE OUR PRIMITIVES (TODO, needs to change, was from zola)
cmd := '
source ~/.cargo/env
cd /tmp
rm -rf mycelium
git clone https://github.com/getmycelium/mycelium.git
cd mycelium
cargo install --path . --locked
mycelium --version
cargo build --release --locked --no-default-features --features=native-tls
cp target/release/mycelium ~/.cargo/bin/mycelium
'
osal.execute_stdout(cmd)!
osal.done_set('build_mycelium', 'OK')!
console.print_header('mycelium installed')
} else {
console.print_header('mycelium already installed')
}
}
struct MyceliumInspectResult {
public_key string @[json: publicKey]
address string
}
pub fn inspect() !MyceliumInspectResult {
command := 'mycelium inspect --key-file /root/hero/cfg/priv_key.bin --json'
result := os.execute(command)
if result.exit_code != 0 {
return error('Command failed: ${result.output}')
}
inspect_result := json.decode(MyceliumInspectResult, result.output) or {
return error('Failed to parse JSON: ${err}')
}
return inspect_result
}
// if returns empty then probably mycelium is not installed
pub fn ipaddr() string {
r := inspect() or { MyceliumInspectResult{} }
return r.address
}

View File

@@ -3,185 +3,179 @@ module mycelium
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.osal.systemd
import freeflowuniverse.herolib.installers.sysadmintools.zinit as zinit_installer
import freeflowuniverse.herolib.clients.mycelium
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal.zinit
import freeflowuniverse.herolib.installers.ulist
import freeflowuniverse.herolib.installers.lang.golang
import freeflowuniverse.herolib.installers.lang.rust
import freeflowuniverse.herolib.installers.lang.python
import os
fn startupcmd() ![]zinit.ZProcessNewArgs {
mut installer := get()!
mut res := []zinit.ZProcessNewArgs{}
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// res << zinit.ZProcessNewArgs{
// name: 'mycelium'
// cmd: 'mycelium server'
// env: {
// 'HOME': '/root'
// }
// }
fn startupcmd () ![]zinit.ZProcessNewArgs{
mut installer := get()!
mut res := []zinit.ZProcessNewArgs{}
return res
mut peers_str := installer.peers.join(' ')
mut tun_name := 'tun${installer.tun_nr}'
res << zinit.ZProcessNewArgs{
name: 'mycelium'
cmd: 'mycelium --key-file ${osal.hero_path()!}/cfg/priv_key.bin --peers ${peers_str} --tun-name ${tun_name}'
env: {
'HOME': '/root'
}
}
return res
}
fn running_() !bool {
mut installer := get()!
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// this checks health of mycelium
// curl http://localhost:3333/api/v1/s --oauth2-bearer 1234 works
// url:='http://127.0.0.1:${cfg.port}/api/v1'
// mut conn := httpconnection.new(name: 'mycelium', url: url)!
// if cfg.secret.len > 0 {
// conn.default_header.add(.authorization, 'Bearer ${cfg.secret}')
// }
// conn.default_header.add(.content_type, 'application/json')
// console.print_debug("curl -X 'GET' '${url}'/tags --oauth2-bearer ${cfg.secret}")
// r := conn.get_json_dict(prefix: 'tags', debug: false) or {return false}
// println(r)
// if true{panic("ssss")}
// tags := r['Tags'] or { return false }
// console.print_debug(tags)
// console.print_debug('mycelium is answering.')
return false
fn running() !bool {
mycelium.inspect() or {return false}
return true
}
fn start_pre() ! {
fn start_pre()!{
}
fn start_post() ! {
fn start_post()!{
}
fn stop_pre() ! {
fn stop_pre()!{
}
fn stop_post() ! {
fn stop_post()!{
}
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed_() !bool {
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// res := os.execute('${osal.profile_path_source_and()!} mycelium version')
// if res.exit_code != 0 {
// return false
// }
// r := res.output.split_into_lines().filter(it.trim_space().len > 0)
// if r.len != 1 {
// return error("couldn't parse mycelium version.\n${res.output}")
// }
// if texttools.version(version) == texttools.version(r[0]) {
// return true
// }
return false
fn installed() !bool {
cmd:='${osal.profile_path_source_and()!} mycelium -V'
// println(cmd)
res := os.execute(cmd)
if res.exit_code != 0 {
println(res)
return false
}
r := res.output.split_into_lines().filter(it.trim_space().len > 0)
if r.len != 1 {
return error("couldn't parse mycelium version.\n${res.output}")
}
if texttools.version(version) == texttools.version(r[0].all_after_last("mycelium")) {
return true
}
return false
}
// get the Upload List of the files
//get the Upload List of the files
fn ulist_get() !ulist.UList {
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
//optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
}
// uploads to S3 server if configured
fn upload_() ! {
// installers.upload(
// cmdname: 'mycelium'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/mycelium'
// )!
//uploads to S3 server if configured
fn upload() ! {
// installers.upload(
// cmdname: 'mycelium'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/mycelium'
// )!
}
fn install_() ! {
console.print_header('install mycelium')
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// mut url := ''
// if core.is_linux_arm()! {
// url = 'https://github.com/mycelium-dev/mycelium/releases/download/v${version}/mycelium_${version}_linux_arm64.tar.gz'
// } else if core.is_linux_intel()! {
// url = 'https://github.com/mycelium-dev/mycelium/releases/download/v${version}/mycelium_${version}_linux_amd64.tar.gz'
// } else if core.is_osx_arm()! {
// url = 'https://github.com/mycelium-dev/mycelium/releases/download/v${version}/mycelium_${version}_darwin_arm64.tar.gz'
// } else if core.is_osx_intel()! {
// url = 'https://github.com/mycelium-dev/mycelium/releases/download/v${version}/mycelium_${version}_darwin_amd64.tar.gz'
// } else {
// return error('unsported platform')
// }
fn install() ! {
console.print_header('install mycelium')
// mut dest := osal.download(
// url: url
// minsize_kb: 9000
// expand_dir: '/tmp/mycelium'
// )!
mut z_installer:=zinit_installer.get()!
z_installer.start()!
// //dest.moveup_single_subdir()!
mut url := ''
if core.is_linux_arm()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-aarch64-unknown-linux-musl.tar.gz'
} else if core.is_linux_intel()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-x86_64-unknown-linux-musl.tar.gz'
} else if core.is_osx_arm()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-aarch64-apple-darwin.tar.gz'
} else if core.is_osx_intel()! {
url = 'https://github.com/threefoldtech/mycelium/releases/download/v${version}/mycelium-x86_64-apple-darwin.tar.gz'
} else {
return error('unsported platform')
}
// mut binpath := dest.file_get('mycelium')!
// osal.cmd_add(
// cmdname: 'mycelium'
// source: binpath.path
// )!
pathlib.get_dir(
path: '${osal.hero_path()!}/cfg'
create: true
)!
mut dest := osal.download(
url: url
minsize_kb: 5000
expand_dir: '/tmp/mycelium'
)!
mut binpath := dest.file_get('mycelium')!
osal.cmd_add(
cmdname: 'mycelium'
source: binpath.path
)!
}
fn build_() ! {
// url := 'https://github.com/threefoldtech/mycelium'
fn build() ! {
url := 'https://github.com/threefoldtech/mycelium'
myplatform:=core.platform()!
if myplatform != .ubuntu {
return error('only support ubuntu for now')
}
rust.install()!
// make sure we install base on the node
// if core.platform()!= .ubuntu {
// return error('only support ubuntu for now')
// }
// golang.install()!
console.print_header('build mycelium')
// console.print_header('build mycelium')
mut gs := gittools.new()!
gitpath:=gs.get_path(
pull:true,
reset:false,
url:url
)!
// gitpath := gittools.get_repo(coderoot: '/tmp/builder', url: url, reset: true, pull: true)!
panic("implement")
cmd := '
cd ${gitpath}
source ~/.cargo/env
cargo install --path . --locked
cargo build --release --locked --no-default-features --features=native-tls
cp target/release/mycelium ~/.cargo/bin/mycelium
mycelium --version
'
osal.execute_stdout(cmd)!
// //now copy to the default bin path
// mut binpath := dest.file_get('...')!
// adds it to path
// osal.cmd_add(
// cmdname: 'griddriver2'
// source: binpath.path
// )!
// cmd := '
// cd ${gitpath}
// source ~/.cargo/env
// exit 1 #todo
// '
// osal.execute_stdout(cmd)!
//
// //now copy to the default bin path
// mut binpath := dest.file_get('...')!
// adds it to path
// osal.cmd_add(
// cmdname: 'griddriver2'
// source: binpath.path
// )!
}
fn destroy_() ! {
// mut systemdfactory := systemd.new()!
// systemdfactory.destroy("zinit")!
fn destroy() ! {
// osal.process_kill_recursive(name:'zinit')!
// osal.cmd_delete('zinit')!
// osal.package_remove('
// podman
// conmon
// buildah
// skopeo
// runc
// ')!
osal.process_kill_recursive(name:'mycelium')!
osal.cmd_delete('mycelium')!
// //will remove all paths where go/bin is found
// osal.profile_path_add_remove(paths2delete:"go/bin")!
osal.rm("
mycelium
")!
// osal.rm("
// podman
// conmon
// buildah
// skopeo
// runc
// /var/lib/containers
// /var/lib/podman
// /var/lib/buildah
// /tmp/podman
// /tmp/conmon
// ")!
}

View File

@@ -3,135 +3,289 @@ module mycelium
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.sysadmin.startupmanager
import freeflowuniverse.herolib.osal.zinit
import time
__global (
mycelium_global map[string]&MyceliumInstaller
mycelium_default string
mycelium_global map[string]&MyceliumInstaller
mycelium_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet{
pub mut:
name string
}
fn args_get (args_ ArgsGet) ArgsGet {
mut args:=args_
if args.name == ""{
args.name = "default"
}
return args
}
pub fn get(args_ ArgsGet) !&MyceliumInstaller {
mut context:=base.context()!
mut args := args_get(args_)
mut obj := MyceliumInstaller{}
if !(args.name in mycelium_global) {
if ! exists(args)!{
set(obj)!
}else{
heroscript := context.hero_config_get("mycelium",args.name)!
mut obj_:=heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return mycelium_global[args.name] or {
println(mycelium_global)
//bug if we get here because should be in globals
panic("could not get config for mycelium with name, is bug:${args.name}")
}
}
//register the config for the future
pub fn set(o MyceliumInstaller)! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set("mycelium", o.name, heroscript)!
}
//does the config exists?
pub fn exists(args_ ArgsGet)! bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists("mycelium", args.name)
}
pub fn delete(args_ ArgsGet)! {
mut args := args_get(args_)
mut context:=base.context()!
context.hero_config_delete("mycelium",args.name)!
if args.name in mycelium_global {
//del mycelium_global[args.name]
}
}
//only sets in mem, does not set as config
fn set_in_mem(o MyceliumInstaller)! {
mut o2:=obj_init(o)!
mycelium_global[o.name] = &o2
mycelium_default = o.name
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string //if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut args:=args_
mut plbook := args.plbook or {
playbook.new(text: args.heroscript)!
}
mut install_actions := plbook.find(filter: 'mycelium.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript:=install_action.heroscript()
mut obj2:=heroscript_loads(heroscript)!
set(obj2)!
}
}
mut other_actions := plbook.find(filter: 'mycelium.')!
for other_action in other_actions {
if other_action.name in ["destroy","install","build"]{
mut p := other_action.params
reset:=p.get_default_false("reset")
if other_action.name == "destroy" || reset{
console.print_debug("install action mycelium.destroy")
destroy()!
}
if other_action.name == "install"{
console.print_debug("install action mycelium.install")
install()!
}
}
if other_action.name in ["start","stop","restart"]{
mut p := other_action.params
name := p.get('name')!
mut mycelium_obj:=get(name:name)!
console.print_debug("action object:\n${mycelium_obj}")
if other_action.name == "start"{
console.print_debug("install action mycelium.${other_action.name}")
mycelium_obj.start()!
}
if other_action.name == "stop"{
console.print_debug("install action mycelium.${other_action.name}")
mycelium_obj.stop()!
}
if other_action.name == "restart"{
console.print_debug("install action mycelium.${other_action.name}")
mycelium_obj.restart()!
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.zinit {
console.print_debug('startupmanager: zinit')
return startupmanager.get(cat: .zinit)!
}
.systemd {
console.print_debug('startupmanager: systemd')
return startupmanager.get(cat: .systemd)!
}
else {
console.print_debug('startupmanager: auto')
return startupmanager.get()!
}
}
// unknown
// screen
// zinit
// tmux
// systemd
match cat{
.zinit{
console.print_debug("startupmanager: zinit")
return startupmanager.get(cat:.zinit)!
}
.systemd{
console.print_debug("startupmanager: systemd")
return startupmanager.get(cat:.systemd)!
}else{
console.print_debug("startupmanager: auto")
return startupmanager.get()!
}
}
}
//load from disk and make sure is properly intialized
pub fn (mut self MyceliumInstaller) reload() ! {
switch(self.name)
self=obj_init(self)!
}
pub fn (mut self MyceliumInstaller) start() ! {
switch(self.name)
if self.running()! {
return
}
switch(self.name)
if self.running()!{
return
}
console.print_header('mycelium start')
console.print_header('mycelium start')
if !installed_()! {
install_()!
}
if ! installed()!{
install()!
}
configure()!
configure()!
start_pre()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
console.print_debug('starting mycelium with ${zprocess.startuptype}...')
console.print_debug('starting mycelium with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.new(zprocess)!
sm.start(zprocess.name)!
}
sm.start(zprocess.name)!
}
start_post()!
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('mycelium did not install properly.')
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('mycelium did not install properly.')
}
pub fn (mut self MyceliumInstaller) install_start(model InstallArgs) ! {
switch(self.name)
self.install(model)!
self.start()!
pub fn (mut self MyceliumInstaller) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self MyceliumInstaller) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
switch(self.name)
stop_pre()!
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self MyceliumInstaller) restart() ! {
switch(self.name)
self.stop()!
self.start()!
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self MyceliumInstaller) running() !bool {
switch(self.name)
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
return running()!
//walk over the generic processes, if not running return
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
r:=sm.running(zprocess.name)!
if r==false{
return false
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub struct InstallArgs{
pub mut:
reset bool
reset bool
}
pub fn install(args InstallArgs) ! {
if args.reset {
destroy()!
}
if !(installed_()!) {
install_()!
}
pub fn (mut self MyceliumInstaller) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn destroy() ! {
destroy_()!
pub fn (mut self MyceliumInstaller) build() ! {
switch(self.name)
build()!
}
pub fn build() ! {
build_()!
pub fn (mut self MyceliumInstaller) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
//switch instance to be used for mycelium
pub fn switch(name string) {
mycelium_default = name
}
//helpers
@[params]
pub struct DefaultConfigArgs{
instance string = 'default'
}

View File

@@ -1,27 +1,72 @@
module mycelium
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.osal.tun
import os
pub const version = '0.0.0'
pub const version = '0.5.7'
const singleton = true
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
//THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct MyceliumInstaller {
pub mut:
name string = 'default'
name string = 'default'
peers []string = [
"tcp://188.40.132.242:9651",
"quic://[2a01:4f8:212:fa6::2]:9651",
"tcp://185.69.166.7:9651",
"quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651",
"tcp://65.21.231.58:9651",
"quic://[2a01:4f9:5a:1042::2]:9651",
"tcp://[2604:a00:50:17b:9e6b:ff:fe1f:e054]:9651",
"quic://5.78.122.16:9651",
"tcp://[2a01:4ff:2f0:3621::1]:9651",
"quic://142.93.217.194:9651",
]
tun_nr int
}
fn obj_init(obj_ MyceliumInstaller) !MyceliumInstaller {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
panic('implement')
return obj
//your checking & initialization code if needed
fn obj_init(mycfg_ MyceliumInstaller)!MyceliumInstaller{
mut mycfg:=mycfg_
return mycfg
}
// called before start if done
//called before start if done
fn configure() ! {
// mut installer := get()!
mut installer := get()!
if installer.tun_nr == 0 {
// Check if TUN is available first
if available := tun.available() {
if !available {
return error('TUN is not available on this system')
}
// Get free TUN interface name
if interface_name := tun.free() {
// Parse the interface number from the name (e.g. "tun0" -> 0)
nr := interface_name.trim_string_left('tun').int()
installer.tun_nr = nr
} else {
return error('Failed to get free TUN interface: ${err}')
}
} else {
return error('Failed to check TUN availability: ${err}')
}
set(installer)!
}
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj MyceliumInstaller) !string {
return encoderhero.encode[MyceliumInstaller ](obj)!
}
pub fn heroscript_loads(heroscript string) !MyceliumInstaller {
mut obj := encoderhero.decode[MyceliumInstaller](heroscript)!
return obj
}

View File

@@ -0,0 +1,2 @@
module mycelium

View File

@@ -1,34 +1,17 @@
# zinit
Zinit is threefold startup manager, in linux will be launched inside systemd
```v
To get started
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
```vlang
import freeflowuniverse.herolib.installers.something. zinit
mut installer:= zinit.get()!
import freeflowuniverse.herolib.installers.sysadmintools.zinit as zinit_installer
mut installer:=zinit_installer.get()!
installer.start()!
```
## example heroscript
```hero
!!zinit.install
homedir: '/home/user/zinit'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
```

View File

@@ -3,6 +3,7 @@ module zinit
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.osal.zinit
import freeflowuniverse.herolib.installers.ulist
import freeflowuniverse.herolib.installers.lang.rust
@@ -11,7 +12,7 @@ import freeflowuniverse.herolib.osal.systemd
import os
// checks if a certain version or above is installed
fn installed_() !bool {
fn installed() !bool {
cmd := 'zinit --version'
// console.print_debug(cmd)
res := os.execute(cmd)
@@ -28,7 +29,7 @@ fn installed_() !bool {
return false
}
fn install_() ! {
fn install() ! {
console.print_header('install zinit')
if !core.is_linux()! {
return error('only support linux for now')
@@ -52,7 +53,7 @@ fn install_() ! {
console.print_header('install zinit done')
}
fn build_() ! {
fn build() ! {
if !core.is_linux()! {
return error('only support linux for now')
}
@@ -91,12 +92,12 @@ fn ulist_get() !ulist.UList {
}
// uploads to S3 server if configured
fn upload_() ! {
fn upload() ! {
}
fn startupcmd() ![]ZProcessNewArgs {
fn startupcmd () ![]zinit.ZProcessNewArgs{
mut res := []zinit.ZProcessNewArgs{}
res << ZProcessNewArgs{
res << zinit.ZProcessNewArgs{
name: 'zinit'
cmd: '/usr/local/bin/zinit init'
startuptype: .systemd
@@ -106,7 +107,7 @@ fn startupcmd() ![]ZProcessNewArgs {
return res
}
fn running_() !bool {
fn running() !bool {
cmd := 'zinit list'
return osal.execute_ok(cmd)
}
@@ -123,7 +124,7 @@ fn stop_pre() ! {
fn stop_post() ! {
}
fn destroy_() ! {
fn destroy() ! {
mut systemdfactory := systemd.new()!
systemdfactory.destroy('zinit')!

View File

@@ -3,135 +3,219 @@ module zinit
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.sysadmin.startupmanager
import freeflowuniverse.herolib.osal.zinit
import time
__global (
zinit_global map[string]&Zinit
zinit_default string
zinit_global map[string]&Zinit
zinit_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet{
pub mut:
name string
}
pub fn get(args_ ArgsGet) !&Zinit {
return &Zinit{}
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string //if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut args:=args_
mut plbook := args.plbook or {
playbook.new(text: args.heroscript)!
}
mut other_actions := plbook.find(filter: 'zinit.')!
for other_action in other_actions {
if other_action.name in ["destroy","install","build"]{
mut p := other_action.params
reset:=p.get_default_false("reset")
if other_action.name == "destroy" || reset{
console.print_debug("install action zinit.destroy")
destroy()!
}
if other_action.name == "install"{
console.print_debug("install action zinit.install")
install()!
}
}
if other_action.name in ["start","stop","restart"]{
mut p := other_action.params
name := p.get('name')!
mut zinit_obj:=get(name:name)!
console.print_debug("action object:\n${zinit_obj}")
if other_action.name == "start"{
console.print_debug("install action zinit.${other_action.name}")
zinit_obj.start()!
}
if other_action.name == "stop"{
console.print_debug("install action zinit.${other_action.name}")
zinit_obj.stop()!
}
if other_action.name == "restart"{
console.print_debug("install action zinit.${other_action.name}")
zinit_obj.restart()!
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.zinit {
console.print_debug('startupmanager: zinit')
return startupmanager.get(cat: .zinit)!
}
.systemd {
console.print_debug('startupmanager: systemd')
return startupmanager.get(cat: .systemd)!
}
else {
console.print_debug('startupmanager: auto')
return startupmanager.get()!
}
}
// unknown
// screen
// zinit
// tmux
// systemd
match cat{
.zinit{
console.print_debug("startupmanager: zinit")
return startupmanager.get(cat:.zinit)!
}
.systemd{
console.print_debug("startupmanager: systemd")
return startupmanager.get(cat:.systemd)!
}else{
console.print_debug("startupmanager: auto")
return startupmanager.get()!
}
}
}
pub fn (mut self Zinit) start() ! {
switch(self.name)
if self.running()! {
return
}
switch(self.name)
if self.running()!{
return
}
console.print_header('zinit start')
console.print_header('zinit start')
if !installed_()! {
install_()!
}
if ! installed()!{
install()!
}
configure()!
configure()!
start_pre()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
console.print_debug('starting zinit with ${zprocess.startuptype}...')
console.print_debug('starting zinit with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.new(zprocess)!
sm.start(zprocess.name)!
}
sm.start(zprocess.name)!
}
start_post()!
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('zinit did not install properly.')
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('zinit did not install properly.')
}
pub fn (mut self Zinit) install_start(model InstallArgs) ! {
switch(self.name)
self.install(model)!
self.start()!
pub fn (mut self Zinit) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self Zinit) stop() ! {
switch(self.name)
stop_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
switch(self.name)
stop_pre()!
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
sm.stop(zprocess.name)!
}
stop_post()!
}
pub fn (mut self Zinit) restart() ! {
switch(self.name)
self.stop()!
self.start()!
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self Zinit) running() !bool {
switch(self.name)
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
return running()!
//walk over the generic processes, if not running return
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
r:=sm.running(zprocess.name)!
if r==false{
return false
}
}
return running()!
}
@[params]
pub struct InstallArgs {
pub struct InstallArgs{
pub mut:
reset bool
reset bool
}
pub fn install(args InstallArgs) ! {
if args.reset {
destroy()!
}
if !(installed_()!) {
install_()!
}
pub fn (mut self Zinit) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn destroy() ! {
destroy_()!
pub fn (mut self Zinit) build() ! {
switch(self.name)
build()!
}
pub fn build() ! {
build_()!
pub fn (mut self Zinit) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
//switch instance to be used for zinit
pub fn switch(name string) {
zinit_default = name
}
//helpers
@[params]
pub struct DefaultConfigArgs{
instance string = 'default'
}

View File

@@ -11,7 +11,7 @@ import os
// checks if a certain version or above is installed
fn installed() !bool {
checkcmd := '${osal.profile_path_source_and()!} bun -version'
checkcmd := '${os.home_dir()}/.bun/bin/bun -version'
res := os.execute(checkcmd)
if res.exit_code != 0 {
println(res)

View File

@@ -109,12 +109,12 @@ pub fn profile_path_source() !string {
}
pp := profile_path()!
if os.exists(pp) {
res := os.execute('source ${pp}')
res := os.execute('/bin/sh ${pp}')
if res.exit_code != 0 {
console.print_stderr('WARNING: your profile is corrupt: ${pp}')
console.print_stderr('WARNING: your profile is corrupt, did:\nsource ${pp}\n${res}')
return error('profile corrupt')
} else {
return 'source ${pp}'
return '. ${pp}'
}
}
return ''
@@ -300,7 +300,7 @@ pub fn profile_paths_preferred() ![]string {
for file in toadd {
if os.exists(file) {
println('${file} exists')
//println('${file} exists')
profile_files2 << file
}
}
@@ -308,9 +308,9 @@ pub fn profile_paths_preferred() ![]string {
}
pub fn profile_path() !string {
if core.is_osx()! {
return '${os.home_dir()}/.zprofile'
} else {
return '${os.home_dir()}/.bash_profile'
mut preferred := profile_paths_preferred()!
if preferred.len==0{
return error("can't find profile_path, found none")
}
return preferred[0]
}

View File

@@ -109,7 +109,7 @@ pub fn (mut sm StartupManager) new(args zinit.ZProcessNewArgs) ! {
zinitfactory.new(args)!
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now: ${mycat}')
}
}
// if args.start {
@@ -222,7 +222,7 @@ pub fn (mut sm StartupManager) delete(name string) ! {
}
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now ${mycat}')
}
}
}
@@ -280,7 +280,7 @@ pub fn (mut sm StartupManager) status(name string) !ProcessStatus {
}
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now ${mycat}')
}
}
}
@@ -303,7 +303,7 @@ pub fn (mut sm StartupManager) output(name string) !string {
return systemd.journalctl(service: name)!
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now ${mycat}')
}
}
}
@@ -326,7 +326,7 @@ pub fn (mut sm StartupManager) exists(name string) !bool {
return zinitfactory.exists(name)
}
else {
panic('to implement. startup manager only support screen & systemd for now')
panic('to implement. startup manager only support screen & systemd for now ${mycat}')
}
}
}
@@ -347,7 +347,7 @@ pub fn (mut sm StartupManager) list() ![]string {
return zinitfactory.names()
}
else {
panic('to implement. startup manager only support screen & systemd for now')
panic('to implement. startup manager only support screen & systemd for now: ${mycat}')
}
}
}

62
lib/osal/tun/readme.md Normal file
View File

@@ -0,0 +1,62 @@
# TUN Interface Management
This module provides functionality to manage TUN (network tunnel) interfaces on Linux and macOS systems.
## Functions
### available() !bool
Checks if TUN/TAP functionality is available on the system:
- Linux: Verifies `/dev/net/tun` exists and is a character device
- macOS: Checks for `utun` interfaces using `ifconfig` and `sysctl`
### free() !string
Returns the name of an available TUN interface:
- Linux: Returns first available interface from tun0-tun10
- macOS: Returns next available utun interface number
## Example Usage
```v
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tun
// Check if TUN is available
if available := tun.available() {
if available {
println('TUN is available on this system')
// Get a free TUN interface name
if interface_name := tun.free() {
println('Found free TUN interface: ${interface_name}')
// Example: Now you could use this interface name
// to set up your tunnel
} else {
println('Error finding free interface: ${err}')
}
} else {
println('TUN is not available on this system')
}
} else {
println('Error checking TUN availability: ${err}')
}
```
## Platform Support
The module automatically detects the platform (Linux/macOS) and uses the appropriate methods:
- On Linux: Uses `/dev/net/tun` and `ip link` commands
- On macOS: Uses `utun` interfaces via `ifconfig`
## Error Handling
Both functions return a Result type, so errors should be handled appropriately:
- Unsupported platform errors
- Interface availability errors
- System command execution errors

65
lib/osal/tun/tun.v Normal file
View File

@@ -0,0 +1,65 @@
module tun
import os
import freeflowuniverse.herolib.core
// available checks if TUN/TAP is available on the system
pub fn available() !bool {
if core.is_linux()! {
// Check if /dev/net/tun exists and is a character device
if !os.exists('/dev/net/tun') {
return false
}
// Try to get file info to verify it's a character device
res := os.execute('test -c /dev/net/tun')
return res.exit_code == 0
} else if core.is_osx()! {
// On macOS, check for utun interfaces
res := os.execute('ifconfig | grep utun')
if res.exit_code == 0 && res.output.len > 0 {
return true
}
// Also try sysctl as alternative check
res2 := os.execute('sysctl -a | grep net.inet.ip.tun')
return res2.exit_code == 0 && res2.output.len > 0
}
return error('Unsupported platform')
}
// free returns the name of an available TUN interface e.g. returns 'utun1'
pub fn free() !string {
if core.is_linux()! {
// Try tun0 through tun10
for i in 1 .. 11 {
name := 'tun${i}'
res := os.execute('ip link show ${name}')
if res.exit_code != 0 {
// Interface doesn't exist, so it's free
return name
}
}
return error('No free tun interface found')
} else if core.is_osx()! {
// On macOS, list existing utun interfaces to find highest number
res := os.execute('ifconfig | grep utun')
if res.exit_code != 0 {
// No utun interfaces exist, so utun0 would be next
return 'utun0'
}
// Find highest utun number
mut max_num := -1
lines := res.output.split('\n')
for line in lines {
if line.starts_with('utun') {
mynum := line[4..].all_before(':').int()
if mynum > max_num {
max_num = mynum
}
}
}
// Next available number
return 'utun${max_num + 1}'
}
return error('Unsupported platform')
}

View File

@@ -109,7 +109,7 @@ pub fn (mut sm StartupManager) new(args zinit.ZProcessNewArgs) ! {
zinitfactory.new(args)!
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now: ${mycat}')
}
}
// if args.start {
@@ -141,7 +141,7 @@ pub fn (mut sm StartupManager) start(name string) ! {
zinitfactory.start(name)!
}
else {
panic('to implement, startup manager only support screen for now')
panic('to implement, startup manager only support screen for now: ${sm.cat}')
}
}
}
@@ -171,7 +171,7 @@ pub fn (mut sm StartupManager) stop(name string) ! {
}
}
else {
panic('to implement, startup manager only support screen for now')
panic('to implement, startup manager only support screen for now: ${sm.cat}')
}
}
}
@@ -195,7 +195,7 @@ pub fn (mut sm StartupManager) restart(name string) ! {
zinitfactory.start(name)!
}
else {
panic('to implement, startup manager only support screen for now')
panic('to implement, startup manager only support screen for now: ${sm.cat}')
}
}
}
@@ -222,7 +222,7 @@ pub fn (mut sm StartupManager) delete(name string) ! {
}
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now: ${sm.cat}')
}
}
}
@@ -280,7 +280,7 @@ pub fn (mut sm StartupManager) status(name string) !ProcessStatus {
}
}
else {
panic('to implement, startup manager only support screen & systemd for now')
panic('to implement, startup manager only support screen & systemd for now: ${sm.cat}')
}
}
}
@@ -326,7 +326,7 @@ pub fn (mut sm StartupManager) exists(name string) !bool {
return zinitfactory.exists(name)
}
else {
panic('to implement. startup manager only support screen & systemd for now')
panic('to implement. startup manager only support screen & systemd for now: ${sm.cat}')
}
}
}
@@ -347,7 +347,7 @@ pub fn (mut sm StartupManager) list() ![]string {
return zinitfactory.names()
}
else {
panic('to implement. startup manager only support screen & systemd for now')
panic('to implement. startup manager only support screen & systemd for now: ${sm.cat}')
}
}
}

View File

@@ -214,7 +214,7 @@ pub fn (mut self Builder) inspect() !BuilderInfo {
return r
}
// mount the build container to a path and return
// mount the build container to a path and return the path where its mounted
pub fn (mut self Builder) mount_to_path() !string {
cmd := 'buildah mount ${self.containername}'
out := osal.execute_silent(cmd)!
@@ -227,7 +227,7 @@ pub fn (mut self Builder) commit(image_name string) ! {
}
pub fn (self Builder) set_entrypoint(entrypoint string) ! {
cmd := 'buildah config --entrypoint ${entrypoint} ${self.containername}'
cmd := 'buildah config --entrypoint \'${entrypoint}\' ${self.containername}'
osal.exec(cmd: cmd)!
}

View File

@@ -0,0 +1,47 @@
module herocontainers
import freeflowuniverse.herolib.osal
// import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.installers.lang.herolib
import freeflowuniverse.herolib.core.pathlib
import os
import json
// copies the hero from host into guest
pub fn (mut self Builder) install_zinit() ! {
self.run(
cmd: '
wget https://github.com/threefoldtech/zinit/releases/download/v0.2.5/zinit -O /sbin/zinit
chmod +x /sbin/zinit
touch /etc/environment
mkdir -p /etc/zinit/
'
// # Define the command to check if zinit is running
// CHECK_CMD="pgrep -x zinit"
// START_CMD="/sbin/zinit init --container"
// # Check if zinit is already running
// if ! \$CHECK_CMD > /dev/null; then
// echo "Zinit is not running. Starting it in a screen session..."
// # Check if the screen session already exists
// if screen -list | grep -q zinitscreen; then
// #echo "Screen session zinitscreen already exists. Attaching..."
// #screen -r zinitscreen
// else
// echo "Creating new screen session and starting zinit..."
// screen -dmS zinitscreen bash -c "/sbin/zinit init --container"
// fi
// else
// echo "Zinit is already running."
// fi
// zinit list
// '
)!
self.set_entrypoint('/sbin/zinit init --container')!
}

View File

@@ -6,6 +6,7 @@ import os
@[params]
pub struct GetArgs {
pub mut:
reset bool
}
@@ -51,7 +52,7 @@ pub fn (mut e CEngine) builder_base(args GetArgs) !Builder {
# Install required packages
echo "Installing essential packages..."
chroot \${MOUNT_PATH} apt-get install -yq screen bash coreutils curl mc unzip sudo which openssh-client openssh-server redis
chroot \${MOUNT_PATH} apt-get install -yq screen bash coreutils curl mc unzip sudo which openssh-client openssh-server redis wget
echo "Cleaning up..."
umount "\${MOUNT_PATH}/dev" || true
@@ -60,6 +61,7 @@ pub fn (mut e CEngine) builder_base(args GetArgs) !Builder {
'
)!
builder.install_zinit()!
// builder.set_entrypoint('redis-server')!
builder.commit('localhost/${name}')!
return builder

View File

@@ -18,8 +18,7 @@ fn (mut e CEngine) builders_load() ! {
pub struct BuilderNewArgs {
pub mut:
name string = 'default'
from string = 'docker.io/archlinux:latest'
// arch_scratch bool // means start from scratch with arch linux
from string = 'docker.io/ubuntu:latest'
delete bool = true
}

View File

@@ -28,36 +28,6 @@ pub mut:
command string
}
@[params]
pub struct ContainerCreateArgs {
name string
hostname string
forwarded_ports []string // ["80:9000/tcp", "1000, 10000/udp"]
mounted_volumes []string // ["/root:/root", ]
env map[string]string // map of environment variables that will be passed to the container
privileged bool
remove_when_done bool = true // remove the container when it shuts down
pub mut:
image_repo string
image_tag string
command string = '/bin/bash'
}
// TODO: implement
// import a container into an image, run podman container with it
// image_repo examples ['myimage', 'myimage:latest']
// if ContainerCreateArgs contains a name, container will be created and restarted
// pub fn (mut e CEngine) container_import(path string, mut args ContainerCreateArgs) !&Container {
// mut image := args.image_repo
// if args.image_tag != '' {
// image = image + ':${args.image_tag}'
// }
// exec(cmd: 'herocontainers import ${path} ${image}', stdout: false)!
// // make sure we start from loaded image
// return e.container_create(args)
// }
// create/start container (first need to get a herocontainerscontainer before we can start)
pub fn (mut container Container) start() ! {

View File

@@ -0,0 +1,220 @@
module herocontainers
import time
import freeflowuniverse.herolib.osal { exec }
import freeflowuniverse.herolib.data.ipaddress { IPAddress }
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.virt.utils
import freeflowuniverse.herolib.ui.console
//info see https://docs.podman.io/en/latest/markdown/podman-run.1.html
@[params]
pub struct ContainerCreateArgs {
name string
hostname string
forwarded_ports []string // ["80:9000/tcp", "1000, 10000/udp"]
mounted_volumes []string // ["/root:/root", ]
env map[string]string // map of environment variables that will be passed to the container
privileged bool
remove_when_done bool = true // remove the container when it shuts down
// Resource limits
memory string // Memory limit (e.g. "100m", "2g")
memory_reservation string // Memory soft limit
memory_swap string // Memory + swap limit
cpus f64 // Number of CPUs (e.g. 1.5)
cpu_shares int // CPU shares (relative weight)
cpu_period int // CPU CFS period in microseconds (default: 100000)
cpu_quota int // CPU CFS quota in microseconds (e.g. 50000 for 0.5 CPU)
cpuset_cpus string // CPUs in which to allow execution (e.g. "0-3", "1,3")
// Network configuration
network string // Network mode (bridge, host, none, container:id)
network_aliases []string // Add network-scoped aliases
exposed_ports []string // Ports to expose without publishing (e.g. "80/tcp", "53/udp")
// DNS configuration
dns_servers []string // Set custom DNS servers
dns_options []string // Set custom DNS options
dns_search []string // Set custom DNS search domains
// Device configuration
devices []string // Host devices to add (e.g. "/dev/sdc:/dev/xvdc:rwm")
device_cgroup_rules []string // Add rules to cgroup allowed devices list
// Runtime configuration
detach bool = true // Run container in background
attach []string // Attach to STDIN, STDOUT, and/or STDERR
interactive bool // Keep STDIN open even if not attached (-i)
// Storage configuration
rootfs string // Use directory as container's root filesystem
mounts []string // Mount filesystem (type=bind,src=,dst=,etc)
volumes []string // Bind mount a volume (alternative to mounted_volumes)
published_ports []string // Publish container ports to host (alternative to forwarded_ports)
pub mut:
image_repo string
image_tag string
command string = '/bin/bash'
}
// create a new container from an image
pub fn (mut e CEngine) container_create(args_ ContainerCreateArgs) !&Container {
mut args:=args_
mut cmd := 'podman run --systemd=false'
// Handle detach/attach options
if args.detach {
cmd += ' -d'
}
for stream in args.attach {
cmd += ' -a ${stream}'
}
if args.name != '' {
cmd += ' --name ${texttools.name_fix(args.name)}'
}
if args.hostname != '' {
cmd += ' --hostname ${args.hostname}'
}
if args.privileged {
cmd += ' --privileged'
}
if args.remove_when_done {
cmd += ' --rm'
}
// Handle interactive mode
if args.interactive {
cmd += ' -i'
}
// Handle rootfs
if args.rootfs != '' {
cmd += ' --rootfs ${args.rootfs}'
}
// Add mount points
for mount in args.mounts {
cmd += ' --mount ${mount}'
}
// Add volumes (--volume syntax)
for volume in args.volumes {
cmd += ' --volume ${volume}'
}
// Add published ports (--publish syntax)
for port in args.published_ports {
cmd += ' --publish ${port}'
}
// Add resource limits
if args.memory != '' {
cmd += ' --memory ${args.memory}'
}
if args.memory_reservation != '' {
cmd += ' --memory-reservation ${args.memory_reservation}'
}
if args.memory_swap != '' {
cmd += ' --memory-swap ${args.memory_swap}'
}
if args.cpus > 0 {
cmd += ' --cpus ${args.cpus}'
}
if args.cpu_shares > 0 {
cmd += ' --cpu-shares ${args.cpu_shares}'
}
if args.cpu_period > 0 {
cmd += ' --cpu-period ${args.cpu_period}'
}
if args.cpu_quota > 0 {
cmd += ' --cpu-quota ${args.cpu_quota}'
}
if args.cpuset_cpus != '' {
cmd += ' --cpuset-cpus ${args.cpuset_cpus}'
}
// Add network configuration
if args.network != '' {
cmd += ' --network ${args.network}'
}
// Add network aliases
for alias in args.network_aliases {
cmd += ' --network-alias ${alias}'
}
// Add exposed ports
for port in args.exposed_ports {
cmd += ' --expose ${port}'
}
// Add devices
for device in args.devices {
cmd += ' --device ${device}'
}
// Add device cgroup rules
for rule in args.device_cgroup_rules {
cmd += ' --device-cgroup-rule ${rule}'
}
// Add DNS configuration
for server in args.dns_servers {
cmd += ' --dns ${server}'
}
for opt in args.dns_options {
cmd += ' --dns-option ${opt}'
}
for search in args.dns_search {
cmd += ' --dns-search ${search}'
}
// Add port forwarding
for port in args.forwarded_ports {
cmd += ' -p ${port}'
}
// Add volume mounts
for volume in args.mounted_volumes {
cmd += ' -v ${volume}'
}
// Add environment variables
for key, value in args.env {
cmd += ' -e ${key}=${value}'
}
// Add image name and tag
mut image_name := args.image_repo
if args.image_tag != '' {
image_name += ':${args.image_tag}'
}
cmd += ' ${image_name}'
// Add command if specified
if args.command != '' {
cmd += ' ${args.command}'
}
// Create the container
mut ljob := exec(cmd: cmd, stdout: false)!
container_id := ljob.output.trim_space()
// Reload containers to get the new one
e.load()!
// Return the newly created container
return e.container_get(name: args.name, id: container_id)!
}

View File

@@ -62,8 +62,53 @@ buildah run --terminal --env TERM=xterm base_go_rust /bin/bash
to check inside the container about diskusage
```bash
pacman -Su ncdu
apt install ncdu
ncdu
```
## create container
```go
import freeflowuniverse.herolib.virt.herocontainers
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.builder
//interative means will ask for login/passwd
console.print_header("Get a container.")
mut e:=herocontainers.new()!
//info see https://docs.podman.io/en/latest/markdown/podman-run.1.html
mut c:=e.container_create(
name: 'mycontainer'
image_repo: 'ubuntu'
// Resource limits
memory: '1g'
cpus: 0.5
// Network config
network: 'bridge'
network_aliases: ['myapp', 'api']
// DNS config
dns_servers: ['8.8.8.8', '8.8.4.4']
dns_search: ['example.com']
interactive: true // Keep STDIN open
mounts: [
'type=bind,src=/data,dst=/container/data,ro=true'
]
volumes: [
'/config:/etc/myapp:ro'
]
published_ports: [
'127.0.0.1:8080:80'
]
)!
```

View File

@@ -258,6 +258,9 @@ fn (mut site DocSite) template_install() ! {
cfg := site.config
profile_include := osal.profile_path_source()!
develop := $tmpl('templates/develop.sh')
build := $tmpl('templates/build.sh')
build_dev := $tmpl('templates/build_dev.sh')

View File

@@ -3,6 +3,7 @@ module docusaurus
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.installers.web.bun
import os
fn (mut site DocusaurusFactory) template_install(update bool) ! {
mut gs := gittools.new()!
@@ -24,6 +25,8 @@ fn (mut site DocusaurusFactory) template_install(update bool) ! {
osal.exec(
cmd: '
${osal.profile_path_source_and()!}
export PATH=/tmp/docusaurus_build/node_modules/.bin:${os.home_dir()}/.bun/bin/:??PATH
cd ${site.path_build.path}
bun install
'

View File

@@ -9,10 +9,12 @@ echo "Docs directory: ??script_dir"
cd ${site.path_build.path}
export PATH=/tmp/docusaurus_build/node_modules/.bin:??PATH
export PATH=/tmp/docusaurus_build/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
rm -rf ${site.path_build.path}/build/
${profile_include}
bun docusaurus build
rsync -rv --delete ${site.path_build.path}/build/ ${cfg.main.build_dest.trim_right("/")}/${cfg.main.name.trim_right("/")}/

View File

@@ -10,10 +10,12 @@ echo "Docs directory: ??script_dir"
cd ${site.path_build.path}
export PATH=/tmp/docusaurus_build/node_modules/.bin:??PATH
export PATH=/tmp/docusaurus_build/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
rm -rf ${site.path_build.path}/build/
${profile_include}
bun docusaurus build
rsync -rv --delete ${site.path_build.path}/build/ ${cfg.main.build_dest_dev.trim_right("/")}/${cfg.main.name.trim_right("/")}/

View File

@@ -9,6 +9,8 @@ echo "Docs directory: ??script_dir"
cd ${site.path_build.path}
export PATH=/tmp/docusaurus_build/node_modules/.bin:??PATH
export PATH=/tmp/docusaurus_build/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
${profile_include}
bun run start -p 3100