diff --git a/aiprompts/reflection.md b/aiprompts/reflection.md new file mode 100644 index 00000000..3e9739e5 --- /dev/null +++ b/aiprompts/reflection.md @@ -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` generates: +// fn decode_User(data string) User { +// mut result := User{} +// result.name = get_string(data, 'name') +// result.age = get_int(data, 'age') +// return result +// } +``` \ No newline at end of file diff --git a/cli/hero.v b/cli/hero.v index 44df18a6..53e20cec 100644 --- a/cli/hero.v +++ b/cli/hero.v @@ -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') { diff --git a/examples/clients/mycelium b/examples/clients/mycelium new file mode 100755 index 00000000..c07e3896 Binary files /dev/null and b/examples/clients/mycelium differ diff --git a/examples/clients/mycelium.vsh b/examples/clients/mycelium.vsh new file mode 100755 index 00000000..f688337a --- /dev/null +++ b/examples/clients/mycelium.vsh @@ -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}') \ No newline at end of file diff --git a/examples/develop/gittools/gittools_example.vsh b/examples/develop/gittools/gittools_example.vsh deleted file mode 100755 index f5817995..00000000 --- a/examples/develop/gittools/gittools_example.vsh +++ /dev/null @@ -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) diff --git a/examples/develop/gittools/gittools_path_get.vsh b/examples/develop/gittools/gittools_path_get.vsh new file mode 100755 index 00000000..d2bf5030 --- /dev/null +++ b/examples/develop/gittools/gittools_path_get.vsh @@ -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) + diff --git a/examples/develop/gittools/example3.vsh b/examples/develop/gittools/gittools_print.vsh similarity index 100% rename from examples/develop/gittools/example3.vsh rename to examples/develop/gittools/gittools_print.vsh diff --git a/examples/installers/mycelium.vsh b/examples/installers/mycelium.vsh index 13cc0021..2bec339e 100755 --- a/examples/installers/mycelium.vsh +++ b/examples/installers/mycelium.vsh @@ -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}') \ No newline at end of file diff --git a/examples/installers/zinit_installer.vsh b/examples/installers/zinit_installer.vsh new file mode 100755 index 00000000..f30cd39a --- /dev/null +++ b/examples/installers/zinit_installer.vsh @@ -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()! diff --git a/examples/osal/tun.vsh b/examples/osal/tun.vsh new file mode 100755 index 00000000..9d7c3198 --- /dev/null +++ b/examples/osal/tun.vsh @@ -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}') +} diff --git a/examples/virt/podman_buildah/.gitignore b/examples/virt/podman_buildah/.gitignore index 0649d990..c3a8f50a 100644 --- a/examples/virt/podman_buildah/.gitignore +++ b/examples/virt/podman_buildah/.gitignore @@ -1 +1,4 @@ buildah_example +buildah_run_clean +buildah_run_mdbook +buildah_run diff --git a/examples/virt/podman_buildah/buildah_example.vsh b/examples/virt/podman_buildah/buildah_example.vsh index d113186f..b06df52a 100755 --- a/examples/virt/podman_buildah/buildah_example.vsh +++ b/examples/virt/podman_buildah/buildah_example.vsh @@ -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()! diff --git a/examples/virt/podman_buildah/buildah_run.vsh b/examples/virt/podman_buildah/buildah_run.vsh index 504b21f5..b3448dcd 100755 --- a/examples/virt/podman_buildah/buildah_run.vsh +++ b/examples/virt/podman_buildah/buildah_run.vsh @@ -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( diff --git a/lib/clients/mycelium/mycelium.v b/lib/clients/mycelium/mycelium.v index 6ec905d6..16ac6987 100644 --- a/lib/clients/mycelium/mycelium.v +++ b/lib/clients/mycelium/mycelium.v @@ -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 )! } diff --git a/lib/clients/mycelium/mycelium_check.v b/lib/clients/mycelium/mycelium_check.v new file mode 100644 index 00000000..308e1097 --- /dev/null +++ b/lib/clients/mycelium/mycelium_check.v @@ -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 +} diff --git a/lib/clients/mycelium/mycelium_factory_.v b/lib/clients/mycelium/mycelium_factory_.v index d6d2c093..0321673d 100644 --- a/lib/clients/mycelium/mycelium_factory_.v +++ b/lib/clients/mycelium/mycelium_factory_.v @@ -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_ + + 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)! + } + } - if model.heroscript == '' { - model.heroscript = heroscript_default()! - } - mut plbook := model.plbook or { playbook.new(text: model.heroscript)! } - 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' } diff --git a/lib/clients/mycelium/mycelium_model.v b/lib/clients/mycelium/mycelium_model.v index e5079643..58a93783 100644 --- a/lib/clients/mycelium/mycelium_model.v +++ b/lib/clients/mycelium/mycelium_model.v @@ -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 } diff --git a/lib/clients/mycelium/openapi.yaml b/lib/clients/mycelium/openapi.yaml new file mode 100644 index 00000000..85fa8f45 --- /dev/null +++ b/lib/clients/mycelium/openapi.yaml @@ -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 \ No newline at end of file diff --git a/lib/clients/mycelium/readme.md b/lib/clients/mycelium/readme.md index 562e19a5..54c37101 100644 --- a/lib/clients/mycelium/readme.md +++ b/lib/clients/mycelium/readme.md @@ -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 diff --git a/lib/core/generator/generic/templates/objname_factory_.vtemplate b/lib/core/generator/generic/templates/objname_factory_.vtemplate index 429d0d49..d05454d0 100644 --- a/lib/core/generator/generic/templates/objname_factory_.vtemplate +++ b/lib/core/generator/generic/templates/objname_factory_.vtemplate @@ -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)! { diff --git a/lib/core/httpconnection/connection_methods.v b/lib/core/httpconnection/connection_methods.v index 5c06fcaa..ad71222c 100644 --- a/lib/core/httpconnection/connection_methods.v +++ b/lib/core/httpconnection/connection_methods.v @@ -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 } diff --git a/lib/data/encoderhero/decoder.v b/lib/data/encoderhero/decoder.v index e14031f6..659760bd 100644 --- a/lib/data/encoderhero/decoder.v +++ b/lib/data/encoderhero/decoder.v @@ -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 { diff --git a/lib/data/encoderhero/encoder.v b/lib/data/encoderhero/encoder.v index 3a4dbc70..e935ffdc 100644 --- a/lib/data/encoderhero/encoder.v +++ b/lib/data/encoderhero/encoder.v @@ -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)! } } } diff --git a/lib/data/encoderhero/encoder_ignorepropery_test.v b/lib/data/encoderhero/encoder_ignorepropery_test.v new file mode 100644 index 00000000..4efe5917 --- /dev/null +++ b/lib/data/encoderhero/encoder_ignorepropery_test.v @@ -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) + +} diff --git a/lib/data/paramsparser/params_reflection.v b/lib/data/paramsparser/params_reflection.v index 7a922749..455e5e8a 100644 --- a/lib/data/paramsparser/params_reflection.v +++ b/lib/data/paramsparser/params_reflection.v @@ -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)! diff --git a/lib/data/paramsparser/params_reflection_test.v b/lib/data/paramsparser/params_reflection_test.v index 60e68caa..f4e51759 100644 --- a/lib/data/paramsparser/params_reflection_test.v +++ b/lib/data/paramsparser/params_reflection_test.v @@ -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() { diff --git a/lib/develop/gittools/README.md b/lib/develop/gittools/README.md index 55aa81c6..68ff30aa 100644 --- a/lib/develop/gittools/README.md +++ b/lib/develop/gittools/README.md @@ -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 diff --git a/lib/develop/gittools/repos_get.v b/lib/develop/gittools/repos_get.v index 9f6dc5e6..b1d9c1e3 100644 --- a/lib/develop/gittools/repos_get.v +++ b/lib/develop/gittools/repos_get.v @@ -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 diff --git a/lib/installers/net/mycelium/.heroscript b/lib/installers/net/mycelium/.heroscript index 60ae9c5f..343369ba 100644 --- a/lib/installers/net/mycelium/.heroscript +++ b/lib/installers/net/mycelium/.heroscript @@ -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: "" diff --git a/lib/installers/net/mycelium/mycelium.v b/lib/installers/net/mycelium/mycelium.v deleted file mode 100644 index a11d8aa2..00000000 --- a/lib/installers/net/mycelium/mycelium.v +++ /dev/null @@ -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 -} diff --git a/lib/installers/net/mycelium/mycelium_actions.v b/lib/installers/net/mycelium/mycelium_actions.v index e2edcbaa..12e90283 100644 --- a/lib/installers/net/mycelium/mycelium_actions.v +++ b/lib/installers/net/mycelium/mycelium_actions.v @@ -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 - // ")! } + diff --git a/lib/installers/net/mycelium/mycelium_factory_.v b/lib/installers/net/mycelium/mycelium_factory_.v index 845c11ea..fefa2188 100644 --- a/lib/installers/net/mycelium/mycelium_factory_.v +++ b/lib/installers/net/mycelium/mycelium_factory_.v @@ -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' } diff --git a/lib/installers/net/mycelium/mycelium_model.v b/lib/installers/net/mycelium/mycelium_model.v index 2633229a..7571f48d 100644 --- a/lib/installers/net/mycelium/mycelium_model.v +++ b/lib/installers/net/mycelium/mycelium_model.v @@ -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 } diff --git a/lib/installers/net/mycelium/tun.v b/lib/installers/net/mycelium/tun.v new file mode 100644 index 00000000..22d36530 --- /dev/null +++ b/lib/installers/net/mycelium/tun.v @@ -0,0 +1,2 @@ +module mycelium + diff --git a/lib/installers/sysadmintools/zinit/readme.md b/lib/installers/sysadmintools/zinit/readme.md index a5130131..0d397590 100644 --- a/lib/installers/sysadmintools/zinit/readme.md +++ b/lib/installers/sysadmintools/zinit/readme.md @@ -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 - -``` - - diff --git a/lib/installers/sysadmintools/zinit/zinit_actions.v b/lib/installers/sysadmintools/zinit/zinit_actions.v index b06ac35a..a56bb27d 100644 --- a/lib/installers/sysadmintools/zinit/zinit_actions.v +++ b/lib/installers/sysadmintools/zinit/zinit_actions.v @@ -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')! diff --git a/lib/installers/sysadmintools/zinit/zinit_factory_.v b/lib/installers/sysadmintools/zinit/zinit_factory_.v index a57fd640..8b7269a0 100644 --- a/lib/installers/sysadmintools/zinit/zinit_factory_.v +++ b/lib/installers/sysadmintools/zinit/zinit_factory_.v @@ -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' } diff --git a/lib/installers/web/bun/bun_actions.v b/lib/installers/web/bun/bun_actions.v index 6d88911a..3f7bf343 100644 --- a/lib/installers/web/bun/bun_actions.v +++ b/lib/installers/web/bun/bun_actions.v @@ -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) diff --git a/lib/osal/cmds.v b/lib/osal/cmds.v index e7c5290b..695983da 100644 --- a/lib/osal/cmds.v +++ b/lib/osal/cmds.v @@ -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] } diff --git a/lib/osal/startupmanager/startupmanager.v b/lib/osal/startupmanager/startupmanager.v index 56e3d49e..c3730f1c 100644 --- a/lib/osal/startupmanager/startupmanager.v +++ b/lib/osal/startupmanager/startupmanager.v @@ -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}') } } } diff --git a/lib/osal/tun/readme.md b/lib/osal/tun/readme.md new file mode 100644 index 00000000..e85f272f --- /dev/null +++ b/lib/osal/tun/readme.md @@ -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 \ No newline at end of file diff --git a/lib/osal/tun/tun.v b/lib/osal/tun/tun.v new file mode 100644 index 00000000..7cdf8abc --- /dev/null +++ b/lib/osal/tun/tun.v @@ -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') +} diff --git a/lib/sysadmin/startupmanager/startupmanager.v b/lib/sysadmin/startupmanager/startupmanager.v index 56e3d49e..7a3c7d76 100644 --- a/lib/sysadmin/startupmanager/startupmanager.v +++ b/lib/sysadmin/startupmanager/startupmanager.v @@ -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}') } } } diff --git a/lib/virt/herocontainers/builder.v b/lib/virt/herocontainers/builder.v index 4a6d60f0..e457d62e 100644 --- a/lib/virt/herocontainers/builder.v +++ b/lib/virt/herocontainers/builder.v @@ -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)! } diff --git a/lib/virt/herocontainers/builder_solutions.v b/lib/virt/herocontainers/builder_solutions.v new file mode 100644 index 00000000..0d2b4c95 --- /dev/null +++ b/lib/virt/herocontainers/builder_solutions.v @@ -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')! + +} diff --git a/lib/virt/herocontainers/cengine_builder_solutions.v b/lib/virt/herocontainers/cengine_builder_solutions.v index 1bf3c24c..ee2353e7 100644 --- a/lib/virt/herocontainers/cengine_builder_solutions.v +++ b/lib/virt/herocontainers/cengine_builder_solutions.v @@ -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 diff --git a/lib/virt/herocontainers/cengine_builders.v b/lib/virt/herocontainers/cengine_builders.v index d48f9a95..4ba85520 100644 --- a/lib/virt/herocontainers/cengine_builders.v +++ b/lib/virt/herocontainers/cengine_builders.v @@ -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 } diff --git a/lib/virt/herocontainers/container.v b/lib/virt/herocontainers/container.v index da6f8c6c..e1ca6fca 100644 --- a/lib/virt/herocontainers/container.v +++ b/lib/virt/herocontainers/container.v @@ -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() ! { diff --git a/lib/virt/herocontainers/container_create.v b/lib/virt/herocontainers/container_create.v new file mode 100644 index 00000000..f0b73b36 --- /dev/null +++ b/lib/virt/herocontainers/container_create.v @@ -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)! +} diff --git a/lib/virt/herocontainers/readme.md b/lib/virt/herocontainers/readme.md index 46d1e9f0..eb16725f 100644 --- a/lib/virt/herocontainers/readme.md +++ b/lib/virt/herocontainers/readme.md @@ -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' + ] +)! + + + + ``` diff --git a/lib/web/docusaurus/dsite.v b/lib/web/docusaurus/dsite.v index b7624ddc..8db69123 100644 --- a/lib/web/docusaurus/dsite.v +++ b/lib/web/docusaurus/dsite.v @@ -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') diff --git a/lib/web/docusaurus/template.v b/lib/web/docusaurus/template.v index e2196c4b..d35b4018 100644 --- a/lib/web/docusaurus/template.v +++ b/lib/web/docusaurus/template.v @@ -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 ' diff --git a/lib/web/docusaurus/templates/build.sh b/lib/web/docusaurus/templates/build.sh index 1ffe4a63..d15d95d4 100755 --- a/lib/web/docusaurus/templates/build.sh +++ b/lib/web/docusaurus/templates/build.sh @@ -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("/")}/ diff --git a/lib/web/docusaurus/templates/build_dev.sh b/lib/web/docusaurus/templates/build_dev.sh index 8baa2596..8655b34b 100755 --- a/lib/web/docusaurus/templates/build_dev.sh +++ b/lib/web/docusaurus/templates/build_dev.sh @@ -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("/")}/ diff --git a/lib/web/docusaurus/templates/develop.sh b/lib/web/docusaurus/templates/develop.sh index 3f476f26..1c5823e2 100755 --- a/lib/web/docusaurus/templates/develop.sh +++ b/lib/web/docusaurus/templates/develop.sh @@ -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