Compare commits

...

39 Commits

Author SHA1 Message Date
b731c4c388 bump version to 1.0.8 2025-02-09 17:45:34 +03:00
e929ce029d Merge branch 'development_fix' of github.com:freeflowuniverse/herolib into development_fix 2025-02-09 13:51:43 +01:00
5160096a1a ... 2025-02-09 13:51:40 +01:00
f219a4041a ... 2025-02-09 15:50:56 +03:00
674eae1c11 ... 2025-02-09 13:33:21 +01:00
f62369bd01 ... 2025-02-09 13:32:44 +01:00
7a6660ebd8 ... 2025-02-09 13:32:34 +01:00
e20d1bdcc5 traeffik first version, coredns 2025-02-09 13:32:11 +01:00
3e309b6379 ... 2025-02-09 12:24:12 +01:00
ae4e92e090 ... 2025-02-09 08:55:41 +01:00
7b69719f0e ... 2025-02-09 08:55:01 +01:00
1d631fec21 ... 2025-02-09 08:52:42 +01:00
690b1b68c3 bump version to 1.0.7 2025-02-08 15:17:47 +01:00
6e619622d2 ... 2025-02-08 14:11:51 +01:00
eb38bc5e60 bump version to 1.0.6 2025-02-08 14:07:15 +01:00
b0da6d1bd2 ... 2025-02-08 09:54:29 +03:00
1377953dcf ... 2025-02-07 16:40:44 +03:00
aa85172700 Merge branch 'development_hetzner' into development_webdav 2025-02-07 13:00:16 +03:00
eff269e911 Merge branch 'development' into development_hetzner 2025-02-07 12:55:30 +03:00
65ec6ee1a3 Merge branch 'development_hetzner' of https://github.com/freeflowuniverse/herolib into development_hetzner 2025-02-07 12:55:21 +03:00
a86b23b2e9 release 2025-02-07 12:52:49 +03:00
bcccd5f247 Merge branch 'development' 2025-02-07 12:52:29 +03:00
cb8c550ed1 chore: bump version to 1.0.5 2025-02-07 12:38:49 +03:00
5fc7019dcc chore: bump version to 1.0.4 2025-02-07 12:30:56 +03:00
8c9248fd94 ... 2025-02-07 12:25:15 +03:00
d1a5f1c268 ... 2025-02-07 12:22:23 +03:00
96bc5c9e5d ... 2025-02-07 12:17:34 +03:00
20927485d9 ... 2025-02-07 12:13:39 +03:00
a034708f21 ... 2025-02-07 12:08:25 +03:00
19a2577564 ... 2025-02-07 12:07:32 +03:00
e34d804dda ... 2025-02-07 11:59:52 +03:00
cd6c899661 ... 2025-02-07 11:45:28 +03:00
4a2753d32c refactor(webdav): remove lock_manager from webdav app
- Remove unused `lock_manager` from the `App` struct.
- Comment out the lock and unlock handlers.
- Improve `propfind` response XML generation.
- Fix path handling in `generate_response_element`.
- Update content type handling for files.
- Improve XML generation for resource responses.

Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
2025-02-06 15:37:21 +02:00
1c0535a8b4 WIP: Implement WebDAV server
- Add a WebDAV server implementation using the `vweb` framework.
- The server supports basic authentication, request logging, and essential WebDAV methods.
- Implements file operations, authentication, and request logging.

Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
2025-02-03 18:02:16 +02:00
a0c253fa05 refactor(tfgrid3deployer): simplify Hetzner node filtering
- Remove `get_hetzner_farm_ids` function.
- Directly filter nodes using the `features` field.

Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
2025-02-03 13:57:46 +02:00
17a67870ef WIP(tfgrid3deployer): Support deploying on hetzner nodes
- Add support for deploying VMs, ZDBs, and gateways on Hetzner nodes.
- Introduce `use_hetzner_node` flag to VM, ZDB, and WebName.
- Update `filter_nodes` to filter by Hetzner farm IDs if `on_hetzner` flag is set.
- Implement `get_hetzner_farm_ids` function (currently a placeholder).

Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
2025-02-02 18:25:04 +02:00
54cfd4c353 feat: add ip-api client
- Add a new ip-api client to the project.
- This client uses the ip-api.com API to get IP information.
- An example is provided in `examples/develop/ipapi`.

Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
2025-02-02 18:04:00 +02:00
717eb1e7d8 WIP: feat: add Hetzner deployment example
- Added a new example demonstrating deployment on Hetzner using the `tfgrid3deployer`.
- The example creates a VM and adds a webname.

Co-authored-by: mahmmoud.hassanein <mahmmoud.hassanein@gmail.com>
2025-02-02 17:29:53 +02:00
03f5885980 Merge pull request #38 from freeflowuniverse/development
dev main
2025-01-28 09:49:18 +03:00
151 changed files with 6901 additions and 1982 deletions

View File

@@ -17,6 +17,7 @@ concurrency:
jobs:
deploy-documentation:
#if: startsWith(github.ref, 'refs/tags/')
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
@@ -26,12 +27,11 @@ jobs:
uses: actions/checkout@v4
- name: Setup Vlang
run: ./install_v.sh --github-actions
run: ./install_v.sh
- name: Generate documentation
run: |
./doc.vsh
# ls /home/runner/work/herolib/docs
./doc.vsh
find .
- name: Setup Pages

View File

@@ -5,11 +5,7 @@ permissions:
on:
push:
branches: ["main","development"]
tags:
- 'v*'
workflow_dispatch:
branches: ["main","development"]
jobs:
build:

2
.gitignore vendored
View File

@@ -7,6 +7,7 @@ vls.*
vls.log
node_modules/
docs/
vdocs/
photonwrapper.so
x
.env
@@ -25,7 +26,6 @@ dump.rdb
output/
*.db
.stellar
vdocs/
data.ms/
test_basic
cli/hero

View File

@@ -6,7 +6,7 @@
## hero install for users
```bash
curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development_kristof10/install_hero.sh > /tmp/install_hero.sh
curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_hero.sh > /tmp/install_hero.sh
bash /tmp/install_hero.sh
```
@@ -16,7 +16,7 @@ this tool can be used to work with git, build books, play with hero AI, ...
## automated install for developers
```bash
curl 'https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/main/install_v.sh' > /tmp/install_v.sh
curl 'https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_v.sh' > /tmp/install_v.sh
bash /tmp/install_v.sh --analyzer --herolib
#DONT FORGET TO START A NEW SHELL (otherwise the paths will not be set)
```

187
aiprompts/reflection.md Normal file
View File

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

View File

@@ -19,6 +19,26 @@ fn playcmds_do(path string) ! {
}
fn do() ! {
if ! core.is_osx()! {
if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' {
println('Error: Please do not run this program with sudo!')
exit(1) // Exit with error code
}
}
if os.getuid() == 0 {
if core.is_osx()! {
eprintln("please do not run hero as root in osx.")
exit(1)
}
} else {
if ! core.is_osx()! {
eprintln("please do run hero as root, don't use sudo.")
exit(1)
}
}
if os.args.len == 2 {
mypath := os.args[1]
if mypath.to_lower().ends_with('.hero') {
@@ -31,7 +51,7 @@ fn do() ! {
mut cmd := Command{
name: 'hero'
description: 'Your HERO toolset.'
version: '2.0.6'
version: '1.0.8'
}
// herocmds.cmd_run_add_flags(mut cmd)
@@ -94,4 +114,4 @@ fn main() {
fn pre_func(cmd Command) ! {
herocmds.plbook_run(cmd)!
}
}

38
doc.vsh
View File

@@ -26,9 +26,9 @@ os.chdir(herolib_path) or {
panic('Failed to change directory to herolib: ${err}')
}
os.rmdir_all('_docs') or {}
os.rmdir_all('docs') or {}
os.rmdir_all('vdocs') or {}
os.mkdir_all('_docs') or {}
os.mkdir_all('docs') or {}
os.mkdir_all('vdocs') or {}
// Generate HTML documentation
println('Generating HTML documentation...')
@@ -42,13 +42,12 @@ os.chdir(abs_dir_of_script) or {
// Generate Markdown documentation
println('Generating Markdown documentation...')
os.rmdir_all('vdocs') or {}
// if os.system('v doc -m -no-color -f md -o ../vdocs/v/') != 0 {
// panic('Failed to generate V markdown documentation')
// }
if os.system('v doc -m -no-color -f md -o vdocs/herolib/') != 0 {
if os.system('v doc -m -no-color -f md -o vdocs/') != 0 {
panic('Failed to generate Hero markdown documentation')
}
@@ -62,4 +61,33 @@ $if !linux {
}
}
// Create Jekyll required files
println('Creating Jekyll files...')
os.mkdir_all('docs/assets/css') or {}
// Create style.scss
style_content := '---\n---\n\n@import "{{ site.theme }}";'
os.write_file('docs/assets/css/style.scss', style_content) or {
panic('Failed to create style.scss: ${err}')
}
// Create _config.yml
config_content := 'title: HeroLib Documentation
description: Documentation for the HeroLib project
theme: jekyll-theme-primer
baseurl: /herolib
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/'
os.write_file('docs/_config.yml', config_content) or {
panic('Failed to create _config.yml: ${err}')
}
println('Documentation generation completed successfully!')

View File

@@ -1,11 +1,9 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.mailclient
import freeflowuniverse.herolib.clients. mailclient
//remove the previous one, otherwise the env variables are not read
mailclient.config_delete(name:"test")!
// remove the previous one, otherwise the env variables are not read
mailclient.config_delete(name: 'test')!
// env variables which need to be set are:
// - MAIL_FROM=...
@@ -14,11 +12,14 @@ mailclient.config_delete(name:"test")!
// - MAIL_SERVER=...
// - MAIL_USERNAME=...
mut client:= mailclient.get(name:"test")!
mut client := mailclient.get(name: 'test')!
println(client)
client.send(subject:'this is a test',to:'kristof@incubaid.com',body:'
client.send(
subject: 'this is a test'
to: 'kristof@incubaid.com'
body: '
this is my email content
')!
'
)!

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

@@ -0,0 +1,108 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.mycelium
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
import freeflowuniverse.herolib.osal
import time
import os
import encoding.base64
const server1_port = 9001
const server2_port = 9002
fn terminate(port int) ! {
// Step 1: Run lsof to get process details
res := os.execute('lsof -i:${port}')
if res.exit_code != 0 {
return error('no service running at port ${port} due to: ${res.output}')
}
// Step 2: Parse the output to extract the PID
lines := res.output.split('\n')
if lines.len < 2 {
return error('no process found running on port ${port}')
}
// The PID is the second column in the output
fields := lines[1].split(' ')
if fields.len < 2 {
return error('failed to parse lsof output')
}
pid := fields[1]
// Step 3: Kill the process using the PID
kill_res := os.execute('kill ${pid}')
if kill_res.exit_code != 0 {
return error('failed to kill process ${pid}: ${kill_res.output}')
}
println('Successfully terminated process ${pid} running on port ${port}')
}
// Check if not installed install it.
mut installer := mycelium_installer.get()!
installer.install()!
mycelium.delete()!
spawn fn () {
os.execute('mkdir -p /tmp/mycelium_server1 && cd /tmp/mycelium_server1 && mycelium --peers 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-name tun2 --tcp-listen-port 9652 --quic-listen-port 9653 --api-addr 127.0.0.1:${server1_port}')
}()
spawn fn () {
os.execute('mkdir -p /tmp/mycelium_server2 && cd /tmp/mycelium_server2 && mycelium --peers 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-name tun3 --tcp-listen-port 9654 --quic-listen-port 9655 --api-addr 127.0.0.1:${server2_port}')
}()
defer {
terminate(server1_port) or {}
terminate(server2_port) or {}
}
time.sleep(2 * time.second)
mut client1 := mycelium.get()!
client1.server_url = 'http://localhost:${server1_port}'
client1.name = 'client1'
println(client1)
mut client2 := mycelium.get()!
client2.server_url = 'http://localhost:${server2_port}'
client2.name = 'client2'
println(client2)
inspect1 := mycelium.inspect(key_file_path: '/tmp/mycelium_server1/priv_key.bin')!
inspect2 := mycelium.inspect(key_file_path: '/tmp/mycelium_server2/priv_key.bin')!
println('Server 1 public key: ${inspect1.public_key}')
println('Server 2 public key: ${inspect2.public_key}')
// Send a message to a node by public key
// Parameters: public_key, payload, topic, wait_for_reply
msg := client1.send_msg(
public_key: inspect2.public_key // destination public key
payload: 'Sending a message from the client 1 to the client 2' // message payload
topic: 'testing' // optional topic
)!
println('Sent message ID: ${msg.id}')
println('send succeeded')
// Receive messages
// Parameters: wait_for_message, peek_only, topic_filter
received := client2.receive_msg(wait: true, peek: false, topic: 'testing')!
println('Received message from: ${received.src_pk}')
println('Message payload: ${base64.decode_str(received.payload)}')
// Reply to a message
// client1.reply_msg(
// id: received.id
// public_key: received.src_pk
// payload: 'Got your message!'
// topic: 'greetings'
// )!
// // // Check message status
// // status := client.get_msg_status(msg.id)!
// // println('Message status: ${status.state}')
// // println('Created at: ${status.created}')
// // println('Expires at: ${status.deadline}')

View File

@@ -3,7 +3,6 @@
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.clients.postgresql_client
// Configure PostgreSQL client
heroscript := "
!!postgresql_client.configure
@@ -19,7 +18,7 @@ heroscript := "
postgresql_client.play(heroscript: heroscript)!
// Get the configured client
mut db_client := postgresql_client.get(name: "test")!
mut db_client := postgresql_client.get(name: 'test')!
// Check if test database exists, create if not
if !db_client.db_exists('test')! {
@@ -31,15 +30,14 @@ if !db_client.db_exists('test')! {
db_client.dbname = 'test'
// Create table if not exists
create_table_sql := "CREATE TABLE IF NOT EXISTS users (
create_table_sql := 'CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)"
)'
println('Creating table users if not exists...')
db_client.exec(create_table_sql)!
println('Database and table setup completed successfully!')

View File

@@ -12,8 +12,8 @@ mut:
}
mut person := Person{
name: 'Bob'
birthday: time.now()
name: 'Bob'
birthday: time.now()
}
heroscript := encoderhero.encode[Person](person)!
@@ -22,9 +22,8 @@ println(heroscript)
person2 := encoderhero.decode[Person](heroscript)!
println(person2)
//show that it doesn't matter which action & method is used
heroscript2:="!!a.b name:Bob age:20 birthday:'2025-02-06 09:57:30'"
// show that it doesn't matter which action & method is used
heroscript2 := "!!a.b name:Bob age:20 birthday:'2025-02-06 09:57:30'"
person3 := encoderhero.decode[Person](heroscript)!
println(person3)

View File

@@ -1,23 +1,22 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import json
enum JobTitle {
manager
executive
worker
manager
executive
worker
}
struct Employee {
mut:
name string
family string @[json: '-'] // this field will be skipped
age int
salary f32
title JobTitle @[json: 'ETitle'] // the key for this field will be 'ETitle', not 'title'
notes string @[omitempty] // the JSON property is not created if the string is equal to '' (an empty string).
// TODO: document @[raw]
name string
family string @[json: '-'] // this field will be skipped
age int
salary f32
title JobTitle @[json: 'ETitle'] // the key for this field will be 'ETitle', not 'title'
notes string @[omitempty] // the JSON property is not created if the string is equal to '' (an empty string).
// TODO: document @[raw]
}
x := Employee{'Peter', 'Begins', 28, 95000.5, .worker, ''}
@@ -34,4 +33,3 @@ println(y)
ss := json.encode(y)
println('JSON encoding of employee y: ${ss}')
assert ss == s

View File

@@ -18,8 +18,7 @@ heroscript := "
postgresql_client.play(heroscript: heroscript)!
// Get the configured client
mut db_client := postgresql_client.get(name: "test")!
mut db_client := postgresql_client.get(name: 'test')!
// Create a new location instance
mut loc := location.new(mut db_client, false) or { panic(err) }

View File

@@ -18,8 +18,7 @@ heroscript := "
postgresql_client.play(heroscript: heroscript)!
// Get the configured client
mut db_client := postgresql_client.get(name: "test")!
mut db_client := postgresql_client.get(name: 'test')!
// Create a new location instance
mut loc := location.new(mut db_client, false) or { panic(err) }

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env -S v -n -w -gc none -no-retry-compilation -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.ipapi
import os
mut ip_api_client := ipapi.get()!
info := ip_api_client.get_ip_info('37.27.132.46')!
println('info: ${info}')

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
import freeflowuniverse.herolib.osal
coredns_installer.install()!
// coredns_installer.delete()!
mut installer := coredns_installer.get()!
installer.build()!

View File

@@ -2,14 +2,15 @@
import freeflowuniverse.herolib.installers.infra.gitea as gitea_installer
mut installer := gitea_installer.get(name: 'test')!
mut installer:= gitea_installer.get(name:'test')!
//if you want to configure using heroscript
gitea_installer.play(heroscript:"
// if you want to configure using heroscript
gitea_installer.play(
heroscript: "
!!gitea.configure name:test
passwd:'something'
domain: 'docs.info.com'
")!
"
)!
installer.start()!

View File

@@ -1,5 +1,40 @@
#!/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}')

12
examples/installers/traefik.vsh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import os
import freeflowuniverse.herolib.installers.web.traefik as traefik_installer
traefik_installer.delete()!
mut installer := traefik_installer.get()!
installer.password = 'planet'
traefik_installer.set(installer)!
installer.start()!

View File

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

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

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

View File

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

View File

@@ -1,15 +1,14 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.virt.herocontainers
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.installers.virt.pacman
import freeflowuniverse.herolib.installers.virt.podman as podman_installer
mut installer := pacman.get()!
// installer.destroy()!
// installer.install()!
mut podman_installer0 := podman_installer.get()!
// podman_installer0.destroy()!
podman_installer0.install()!
// exit(0)
@@ -17,13 +16,13 @@ mut installer := pacman.get()!
mut engine := herocontainers.new(install: true, herocompile: false)!
engine.reset_all()!
// engine.reset_all()!
mut builder_gorust := engine.builder_go_rust()!
// mut builder_gorust := engine.builder_go_rust()!
// will build nodejs, python build & herolib, hero
// mut builder_hero := engine.builder_hero(reset:true)!
// mut builder_web := engine.builder_heroweb(reset:true)!
builder_gorust.shell()!
// builder_gorust.shell()!

View File

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

View File

@@ -2,7 +2,6 @@
import freeflowuniverse.herolib.virt.herocontainers
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.base
// import freeflowuniverse.herolib.builder
import time
import os

View File

@@ -5,16 +5,15 @@ import freeflowuniverse.herolib.web.docusaurus
// Create a new docusaurus factory
mut docs := docusaurus.new(
// build_path: '/tmp/docusaurus_build'
build_path: '/tmp/docusaurus_build'
)!
// Create a new docusaurus site
mut site := docs.dev(
url:'https://git.ourworld.tf/despiegk/docs_kristof'
url: 'https://git.ourworld.tf/despiegk/docs_kristof'
)!
//FOR FUTURE TO ADD CONTENT FROM DOCTREE
// FOR FUTURE TO ADD CONTENT FROM DOCTREE
// Create a doctree for content
// mut tree := doctree.new(name: 'content')!
@@ -34,10 +33,10 @@ mut site := docs.dev(
// )!
// Build the docusaurus site
//site.build()!
// site.build()!
// Generate the static site
//site.generate()!
// site.generate()!
// Optionally open the site in a browser
// site.open()!

View File

@@ -4,19 +4,38 @@ set -e
os_name="$(uname -s)"
arch_name="$(uname -m)"
version='1.0.8'
# Base URL for GitHub releases
base_url="https://github.com/freeflowuniverse/herolib/releases/download/v${version}"
# Select the URL based on the platform
if [[ "$os_name" == "Linux" && "$arch_name" == "x86_64" ]]; then
url="https://f003.backblazeb2.com/file/threefold/linux-i64/hero"
url="$base_url/hero-x86_64-unknown-linux-musl"
elif [[ "$os_name" == "Linux" && "$arch_name" == "aarch64" ]]; then
url="$base_url/hero-aarch64-unknown-linux-musl"
elif [[ "$os_name" == "Darwin" && "$arch_name" == "arm64" ]]; then
url="https://f003.backblazeb2.com/file/threefold/macos-arm64/hero"
# elif [[ "$os_name" == "Darwin" && "$arch_name" == "x86_64" ]]; then
# url="https://f003.backblazeb2.com/file/threefold/macos-i64/hero"
url="$base_url/hero-aarch64-apple-darwin"
elif [[ "$os_name" == "Darwin" && "$arch_name" == "x86_64" ]]; then
url="$base_url/hero-x86_64-apple-darwin"
else
echo "Unsupported platform."
echo "Unsupported platform: $os_name $arch_name"
exit 1
fi
# # Select the URL based on the platform
# if [[ "$os_name" == "Linux" && "$arch_name" == "x86_64" ]]; then
# url="https://f003.backblazeb2.com/file/threefold/linux-i64/hero"
# elif [[ "$os_name" == "Darwin" && "$arch_name" == "arm64" ]]; then
# url="https://f003.backblazeb2.com/file/threefold/macos-arm64/hero"
# # elif [[ "$os_name" == "Darwin" && "$arch_name" == "x86_64" ]]; then
# # url="https://f003.backblazeb2.com/file/threefold/macos-i64/hero"
# else
# echo "Unsupported platform."
# exit 1
# fi
# Check for existing hero installations
existing_hero=$(which hero 2>/dev/null || true)
if [ ! -z "$existing_hero" ]; then
@@ -122,4 +141,4 @@ if [ "$file_size" -ge 2 ]; then
else
echo "Downloaded file is less than 10 MB. Process aborted."
exit 1
fi
fi

View File

@@ -0,0 +1,8 @@
!!hero_code.generate_client
name:'ipapi'
classname:'IPApi'
singleton:0
default:1
hasconfig:1
reset:0

View File

@@ -0,0 +1,29 @@
module ipapi
import json
pub struct IPInfo {
pub:
query string
status string
country string
country_code string @[json: 'countryCode']
region string
region_name string @[json: 'regionName']
city string
zip string
lat f32
lon f32
timezone string
isp string
org string
as string
}
pub fn (mut a IPApi) get_ip_info(ip string) !IPInfo {
mut conn := a.connection()!
res := conn.get_json(prefix: 'json/${ip}')!
info := json.decode(IPInfo, res)!
return info
}

View File

@@ -0,0 +1,102 @@
module ipapi
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
__global (
ipapi_global map[string]&IPApi
ipapi_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 = ipapi_default
}
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&IPApi {
mut args := args_get(args_)
if args.name !in ipapi_global {
if args.name == 'default' {
if !config_exists(args) {
if default {
config_save(args)!
}
}
config_load(args)!
}
}
return ipapi_global[args.name] or {
println(ipapi_global)
panic('could not get config for ipapi with name:${args.name}')
}
}
fn config_exists(args_ ArgsGet) bool {
mut args := args_get(args_)
mut context := base.context() or { panic('bug') }
return context.hero_config_exists('ipapi', args.name)
}
fn config_load(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
mut heroscript := context.hero_config_get('ipapi', args.name)!
play(heroscript: heroscript)!
}
fn config_save(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_set('ipapi', args.name, heroscript_default()!)!
}
fn set(o IPApi) ! {
mut o2 := obj_init(o)!
ipapi_global[o.name] = &o2
ipapi_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_
if args.heroscript == '' {
args.heroscript = heroscript_default()!
}
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
mut install_actions := plbook.find(filter: 'ipapi.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
mut p := install_action.params
cfg_play(p)!
}
}
}
// switch instance to be used for ipapi
pub fn switch(name string) {
ipapi_default = name
}

View File

@@ -0,0 +1,58 @@
module ipapi
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.core.httpconnection
import os
pub const version = '1.14.3'
const singleton = false
const default = true
// TODO: THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE TO STRUCT BELOW, IS STRUCTURED AS HEROSCRIPT
pub fn heroscript_default() !string {
heroscript := "
!!ipapi.configure
name:'default'
"
return heroscript
}
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct IPApi {
pub mut:
name string = 'default'
conn ?&httpconnection.HTTPConnection @[str: skip]
}
fn cfg_play(p paramsparser.Params) ! {
// THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
mut mycfg := IPApi{
name: p.get_default('name', 'default')!
}
set(mycfg)!
}
fn obj_init(obj_ IPApi) !IPApi {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
return obj
}
pub fn (mut client IPApi) connection() !&httpconnection.HTTPConnection {
mut c := client.conn or {
mut c2 := httpconnection.new(
name: 'ipapi_${client.name}'
url: 'http://ip-api.com'
cache: false
retry: 20
)!
c2
}
client.conn = c
return c
}

View File

@@ -0,0 +1,30 @@
# ipapi
To get started
```vlang
import freeflowuniverse.herolib.clients. ipapi
mut client:= ipapi.get()!
client...
```
## example heroscript
```hero
!!ipapi.configure
secret: '...'
host: 'localhost'
port: 8888
```

View File

@@ -1,127 +1,112 @@
module mailclient
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
__global (
mailclient_global map[string]&MailClient
mailclient_default string
mailclient_global map[string]&MailClient
mailclient_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet{
pub struct ArgsGet {
pub mut:
name string
name string
}
fn args_get (args_ ArgsGet) ArgsGet {
mut args:=args_
if args.name == ""{
args.name = mailclient_default
}
if args.name == ""{
args.name = "default"
}
return args
fn args_get(args_ ArgsGet) ArgsGet {
mut args := args_
if args.name == '' {
args.name = mailclient_default
}
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&MailClient {
mut args := args_get(args_)
if !(args.name in mailclient_global) {
if ! config_exists(args){
config_save(args)!
}
config_load(args)!
}
return mailclient_global[args.name] or {
println(mailclient_global)
//bug if we get here because should be in globals
panic("could not get config for mailclient with name, is bug:${args.name}")
}
pub fn get(args_ ArgsGet) !&MailClient {
mut args := args_get(args_)
if args.name !in mailclient_global {
if !config_exists(args) {
config_save(args)!
}
config_load(args)!
}
return mailclient_global[args.name] or {
println(mailclient_global)
// bug if we get here because should be in globals
panic('could not get config for mailclient with name, is bug:${args.name}')
}
}
pub fn config_exists(args_ ArgsGet) bool {
mut args := args_get(args_)
mut context:=base.context() or { panic("bug") }
return context.hero_config_exists("mailclient",args.name)
mut args := args_get(args_)
mut context := base.context() or { panic('bug') }
return context.hero_config_exists('mailclient', args.name)
}
pub fn config_load(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context:=base.context()!
mut heroscript := context.hero_config_get("mailclient",args.name)!
play(heroscript:heroscript)!
mut args := args_get(args_)
mut context := base.context()!
mut heroscript := context.hero_config_get('mailclient', args.name)!
play(heroscript: heroscript)!
}
pub fn config_save(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context:=base.context()!
context.hero_config_set("mailclient",args.name,heroscript_default(instance:args.name)!)!
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_set('mailclient', args.name, heroscript_default(instance: args.name)!)!
}
pub fn config_delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context:=base.context()!
context.hero_config_delete("mailclient",args.name)!
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('mailclient', args.name)!
}
fn set(o MailClient)! {
mut o2:=obj_init(o)!
mailclient_global[o.name] = &o2
mailclient_default = o.name
fn set(o MailClient) ! {
mut o2 := obj_init(o)!
mailclient_global[o.name] = &o2
mailclient_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 args:=args_
if args.heroscript == "" {
args.heroscript = heroscript_default()!
}
mut plbook := args.plbook or {
playbook.new(text: args.heroscript)!
}
mut install_actions := plbook.find(filter: 'mailclient.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
mut p := install_action.params
cfg_play(p)!
}
}
mut args := args_
if args.heroscript == '' {
args.heroscript = heroscript_default()!
}
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
mut install_actions := plbook.find(filter: 'mailclient.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
mut p := install_action.params
cfg_play(p)!
}
}
}
//switch instance to be used for mailclient
// switch instance to be used for mailclient
pub fn switch(name string) {
mailclient_default = name
mailclient_default = name
}
//helpers
// helpers
@[params]
pub struct DefaultConfigArgs{
instance string = 'default'
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,4 +1,5 @@
module mailclient
import freeflowuniverse.herolib.data.paramsparser
import os
@@ -6,7 +7,6 @@ pub const version = '0.0.0'
const singleton = false
const default = true
pub fn heroscript_default(args DefaultConfigArgs) !string {
mail_from := os.getenv_opt('MAIL_FROM') or { 'info@example.com' }
mail_password := os.getenv_opt('MAIL_PASSWORD') or { 'secretpassword' }
@@ -23,7 +23,7 @@ pub fn heroscript_default(args DefaultConfigArgs) !string {
mail_username: '${mail_username}'
"
return heroscript
return heroscript
}
@[heap]
@@ -40,22 +40,18 @@ pub mut:
}
fn cfg_play(p paramsparser.Params) ! {
mut mycfg := MailClient{
mut mycfg := MailClient{
name: p.get_default('name', 'default')!
mail_from: p.get('mail_from')!
mail_password: p.get('mail_password')!
mail_port: p.get_int_default('mail_port', 465)!
mail_server: p.get('mail_server')!
mail_username: p.get('mail_username')!
}
set(mycfg)!
}
fn obj_init(obj_ MailClient)!MailClient{
mut obj:=obj_
return obj
}
set(mycfg)!
}
fn obj_init(obj_ MailClient) !MailClient {
mut obj := obj_
return obj
}

View File

@@ -1,39 +1,65 @@
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(
@@ -47,30 +73,63 @@ pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
return c
}
pub fn (mut self Mycelium) send_msg(pk string, payload string, wait bool) !InboundMessage {
@[params]
pub struct SendMessageArgs {
pub mut:
public_key string @[required]
payload string @[required]
topic ?string
wait bool
}
// Send a message to a node identified by public key
pub fn (mut self Mycelium) send_msg(args SendMessageArgs) !InboundMessage {
mut conn := self.connection()!
mut params := {
'dst': json.encode(MessageDestination{ pk: pk })
'payload': payload
mut body := PushMessageBody{
dst: MessageDestination{
pk: args.public_key
ip: ''
}
payload: base64.encode_str(args.payload)
topic: if v := args.topic {
base64.encode_str(v)
} else {
none
}
}
mut prefix := ''
if wait {
prefix = '?reply_timeout=120'
mut prefix := '/api/v1/messages'
if args.wait {
prefix += '?reply_timeout=120'
}
return conn.post_json_generic[InboundMessage](
method: .post
prefix: prefix
params: params
data: json.encode(body)
dataformat: .json
)!
}
pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
@[params]
pub struct ReceiveMessageArgs {
pub mut:
topic ?string
wait bool
peek bool
}
// Receive a message from the queue
pub fn (mut self Mycelium) receive_msg(args ReceiveMessageArgs) !InboundMessage {
mut conn := self.connection()!
mut prefix := ''
if wait {
prefix = '?timeout=60'
mut prefix := '/api/v1/messages?peek=${args.peek}&'
if args.wait {
prefix += 'timeout=120&'
}
if v := args.topic {
prefix += 'topic=${base64.encode_str(v)}'
}
return conn.get_json_generic[InboundMessage](
method: .get
prefix: prefix
@@ -78,17 +137,9 @@ pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
)!
}
pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
mut conn := self.connection()!
mut prefix := ''
if wait {
prefix = '?timeout=60'
}
res := conn.get_json_generic[InboundMessage](
method: .get
prefix: prefix
dataformat: .json
) or {
// Optional version of receive_msg that returns none on 204
pub fn (mut self Mycelium) receive_msg_opt(args ReceiveMessageArgs) ?InboundMessage {
res := self.receive_msg(args) or {
if err.msg().contains('204') {
return none
}
@@ -97,25 +148,62 @@ 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}'
prefix: '/api/v1/messages/status/${id}'
dataformat: .json
)!
}
pub fn (mut self Mycelium) reply_msg(id string, pk string, payload string) ! {
@[params]
pub struct ReplyMessageArgs {
pub mut:
id string @[required]
public_key string @[required]
payload string @[required]
topic ?string
}
// Reply to a message
pub fn (mut self Mycelium) reply_msg(args ReplyMessageArgs) ! {
mut conn := self.connection()!
mut params := {
'dst': json.encode(MessageDestination{ pk: pk })
'payload': payload
mut body := PushMessageBody{
dst: MessageDestination{
pk: args.public_key
ip: ''
}
payload: base64.encode_str(args.payload)
topic: if v := args.topic { base64.encode_str(v) } else { none }
}
conn.post_json_generic[json.Any](
_ := conn.post_json_str(
method: .post
prefix: 'reply/${id}'
params: params
prefix: '/api/v1/messages/reply/${args.id}'
data: json.encode(body)
dataformat: .json
)!
}
// curl -v -H 'Content-Type: application/json' -d '{"dst": {"pk": "be4bf135d60b7e43a46be1ad68f955cdc1209a3c55dc30d00c4463b1dace4377"}, "payload": "xuV+"}' http://localhost:8989/api/v1/messages\
// Get node info
pub fn (mut self Mycelium) get_info() !Info {
mut conn := self.connection()!
return conn.get_json_generic[Info](
method: .get
prefix: '/api/v1/admin'
dataformat: .json
)!
}
// Get public key for a node IP
pub fn (mut self Mycelium) get_pubkey_from_ip(ip string) !PublicKeyResponse {
mut conn := self.connection()!
return conn.get_json_generic[PublicKeyResponse](
method: .get
prefix: '/api/v1/pubkey/${ip}'
dataformat: .json
)!
}

View File

@@ -0,0 +1,71 @@
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
}
@[params]
pub struct MyceliumInspectArgs {
pub:
key_file_path string = '/root/hero/cfg/priv_key.bin'
}
pub fn inspect(args MyceliumInspectArgs) !MyceliumInspectResult {
command := 'mycelium inspect --key-file ${args.key_file_path} --json'
result := os.execute(command)
if result.exit_code != 0 {
return error('Command failed: ${result.output}')
}
inspect_result := json.decode(MyceliumInspectResult, result.output) or {
return error('Failed to parse JSON: ${err}')
}
return inspect_result
}
// if returns empty then probably mycelium is not installed
pub fn ipaddr() string {
r := inspect() or { MyceliumInspectResult{} }
return r.address
}

View File

@@ -2,8 +2,6 @@ module mycelium
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.encoderhero
__global (
mycelium_global map[string]&Mycelium
@@ -12,35 +10,71 @@ __global (
/////////FACTORY
// set the model in mem and the config on the filesystem
@[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) !&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}')
}
}
// 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
}
// 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)
}
// 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)!
}
// 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)!
}
@[params]
pub struct PlayArgs {
pub mut:
@@ -50,21 +84,28 @@ pub mut:
}
pub fn play(args_ PlayArgs) ! {
mut model := args_
mut args := args_
if model.heroscript == '' {
model.heroscript = heroscript_default()!
}
mut plbook := model.plbook or { playbook.new(text: model.heroscript)! }
mut plbook := args.plbook or { playbook.new(text: args.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)!
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)!
}
}
}
// switch instance to be used for mycelium
pub fn switch(name string) {
mycelium_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,39 +1,33 @@
module mycelium
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.core.httpconnection
import os
import freeflowuniverse.herolib.data.encoderhero
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_
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj Mycelium) !string {
return encoderhero.encode[Mycelium](obj)!
}
pub fn heroscript_loads(heroscript string) !Mycelium {
mut obj := encoderhero.decode[Mycelium](heroscript)!
return obj
}

View File

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

View File

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

View File

@@ -152,11 +152,16 @@ fn build_() ! {
// if core.platform()!= .ubuntu {
// return error('only support ubuntu for now')
// }
// golang.install()!
//mut g:=golang.get()!
//g.install()!
//console.print_header('build coredns')
//mut gs := gittools.new(coderoot: '~/code')!
// console.print_header('build ${model.name}')
// gitpath := gittools.get_repo(coderoot: '/tmp/builder', url: url, reset: true, pull: true)!
// gitpath := gittools.get_repo(url: url, reset: true, pull: true)!
// cmd := '
// cd ??{gitpath}

View File

@@ -144,7 +144,6 @@ pub fn (mut self Context) hero_config_delete(cat string, name string) ! {
config_file.delete()!
}
pub fn (mut self Context) hero_config_exists(cat string, name string) bool {
path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml'
return os.exists(path)

View File

@@ -115,11 +115,11 @@ fn install() ! {
console.print_header('install ${args.name}')
//THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// mut url := ''
// if osal.is_linux_arm() {
// if core.is_linux_arm() {
// url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_linux_arm64.tar.gz'
// } else if osal.is_linux_intel() {
// } else if core.is_linux_intel() {
// url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_linux_amd64.tar.gz'
// } else if osal.is_osx_arm() {
// } else if core.is_osx_arm() {
// url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_darwin_arm64.tar.gz'
// } else if osal.is_osx_intel() {
// url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_darwin_amd64.tar.gz'

View File

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

View File

@@ -53,7 +53,6 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
description: 'update your environment the template and the repo you are working on (git pull).'
})
cmd_run.add_flag(Flag{
flag: .bool
required: false
@@ -84,29 +83,29 @@ fn cmd_docusaurus_execute(cmd Command) ! {
// exit(1)
// }
mut docs := docusaurus.new(update:update)!
mut docs := docusaurus.new(update: update)!
if build {
// Create a new docusaurus site
_ := docs.build(
url: url
update:update
url: url
update: update
)!
}
if builddev {
// Create a new docusaurus site
_ := docs.build_dev(
url: url
update:update
url: url
update: update
)!
}
if dev {
// Create a new docusaurus site
_ := docs.dev(
url: url
update:update
url: url
update: update
)!
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,16 +10,16 @@ import freeflowuniverse.herolib.clients.postgresql_client
// LocationDB handles all database operations for locations
pub struct LocationDB {
pub mut:
db pg.DB
db pg.DB
db_client postgresql_client.PostgresClient
tmp_dir pathlib.Path
db_dir pathlib.Path
tmp_dir pathlib.Path
db_dir pathlib.Path
}
// new_location_db creates a new LocationDB instance
pub fn new_location_db(mut db_client postgresql_client.PostgresClient, reset bool) !LocationDB {
mut db_dir := pathlib.get_dir(path:'${os.home_dir()}/hero/var/db/location.db',create: true)!
mut db_dir := pathlib.get_dir(path: '${os.home_dir()}/hero/var/db/location.db', create: true)!
// Create locations database if it doesn't exist
if !db_client.db_exists('locations')! {
db_client.db_create('locations')!
@@ -27,15 +27,15 @@ pub fn new_location_db(mut db_client postgresql_client.PostgresClient, reset boo
// Switch to locations database
db_client.dbname = 'locations'
// Get the underlying pg.DB connection
db := db_client.db()!
mut loc_db := LocationDB{
db: db
db: db
db_client: db_client
tmp_dir: pathlib.get_dir(path: '/tmp/location/',create: true)!
db_dir: db_dir
tmp_dir: pathlib.get_dir(path: '/tmp/location/', create: true)!
db_dir: db_dir
}
loc_db.init_tables(reset)!
return loc_db
@@ -50,7 +50,7 @@ fn (mut l LocationDB) init_tables(reset bool) ! {
drop table Country
}!
}
sql l.db {
create table Country
create table City

View File

@@ -5,7 +5,7 @@ import freeflowuniverse.herolib.clients.postgresql_client
// Location represents the main API for location operations
pub struct Location {
mut:
db LocationDB
db LocationDB
db_client postgresql_client.PostgresClient
}
@@ -13,7 +13,7 @@ mut:
pub fn new(mut db_client postgresql_client.PostgresClient, reset bool) !Location {
db := new_location_db(mut db_client, reset)!
return Location{
db: db
db: db
db_client: db_client
}
}
@@ -28,7 +28,7 @@ pub fn (mut l Location) download_and_import(redownload bool) ! {
fn main() ! {
// Configure and get PostgreSQL client
heroscript := "
!!postgresql_client.configure
!!postgresql_client.configure
name:'test'
user: 'postgres'
port: 5432

View File

@@ -1,4 +1,3 @@
module location
//https://www.geonames.org/export/codes.html
// https://www.geonames.org/export/codes.html

View File

@@ -7,27 +7,24 @@ import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
const (
geonames_url = 'https://download.geonames.org/export/dump'
)
const geonames_url = 'https://download.geonames.org/export/dump'
// download_and_import_data downloads and imports GeoNames data
pub fn (mut l LocationDB) download_and_import_data(redownload bool) ! {
// Download country info
if redownload{
if redownload {
l.reset_import_dates()!
}
country_file := osal.download(
url: '${geonames_url}/countryInfo.txt'
dest: '${l.tmp_dir.path}/country.txt'
url: '${geonames_url}/countryInfo.txt'
dest: '${l.tmp_dir.path}/country.txt'
minsize_kb: 10
)!
l.import_country_data(country_file.path)!
l.import_cities()!
}
// reset_import_dates sets all country import_dates to 0
@@ -41,9 +38,9 @@ pub fn (mut l LocationDB) reset_import_dates() ! {
// should_import_cities checks if a city should be imported based on its last import date on country level
fn (mut l LocationDB) should_import_cities(iso2 string) !bool {
console.print_debug('Checking if should import country: ${iso2}')
country := sql l.db {
select from Country where iso2 == "${iso2}" limit 1
select from Country where iso2 == '${iso2}' limit 1
} or { []Country{} }
console.print_debug('SQL query result: ${country.len} records found')
@@ -58,11 +55,11 @@ fn (mut l LocationDB) should_import_cities(iso2 string) !bool {
one_month := i64(30 * 24 * 60 * 60) // 30 days in seconds
last_import := country[0].import_date
time_since_import := now - last_import
console.print_debug('Last import: ${last_import}, Time since import: ${time_since_import} seconds (${time_since_import/86400} days)')
should_import := (time_since_import > one_month) || (last_import == 0)
console.print_debug('Last import: ${last_import}, Time since import: ${time_since_import} seconds (${time_since_import / 86400} days)')
should_import := time_since_import > one_month || last_import == 0
console.print_debug('Should import ${iso2}: ${should_import}')
return should_import
}
@@ -70,10 +67,10 @@ fn (mut l LocationDB) should_import_cities(iso2 string) !bool {
fn (mut l LocationDB) import_country_data(filepath string) ! {
console.print_header('Starting import from: ${filepath}')
l.db.exec('BEGIN TRANSACTION')!
mut file := os.open(filepath) or {
mut file := os.open(filepath) or {
console.print_stderr('Failed to open country file: ${err}')
return err
return err
}
defer { file.close() }
@@ -98,39 +95,34 @@ fn (mut l LocationDB) import_country_data(filepath string) ! {
} or { []Country{} }
country := Country{
iso2: iso2
iso3: fields[1]
name: fields[4]
continent: fields[8]
iso2: iso2
iso3: fields[1]
name: fields[4]
continent: fields[8]
population: fields[7].i64()
timezone: fields[17]
timezone: fields[17]
}
if existing_country.len > 0 {
// Update existing country
sql l.db {
update Country set
iso3 = country.iso3,
name = country.name,
continent = country.continent,
population = country.population,
timezone = country.timezone
where iso2 == iso2
update Country set iso3 = country.iso3, name = country.name, continent = country.continent,
population = country.population, timezone = country.timezone where iso2 == iso2
}!
//console.print_debug("Updated country: ${country}")
// console.print_debug("Updated country: ${country}")
} else {
// Insert new country
sql l.db {
insert country into Country
}!
//console.print_debug("Inserted country: ${country}")
// console.print_debug("Inserted country: ${country}")
}
count++
if count % 10 == 0 {
console.print_header('Processed ${count} countries')
}
}
l.db.exec('COMMIT')!
console.print_header('Finished importing countries. Total records: ${count}')
}
@@ -158,13 +150,13 @@ fn (mut l LocationDB) import_cities() ! {
// Download and process cities for this country
cities_file := osal.download(
url: '${geonames_url}/${iso2}.zip'
dest: '${l.tmp_dir.path}/${iso2}.zip'
url: '${geonames_url}/${iso2}.zip'
dest: '${l.tmp_dir.path}/${iso2}.zip'
expand_file: '${l.tmp_dir.path}/${iso2}'
minsize_kb: 2
minsize_kb: 2
)!
l.import_city_data("${l.tmp_dir.path}/${iso2}/${iso2}.txt")!
l.import_city_data('${l.tmp_dir.path}/${iso2}/${iso2}.txt')!
// Update the country's import date after successful city import
now := time.now().unix()
@@ -193,36 +185,34 @@ fn (mut l LocationDB) import_city_data(filepath string) ! {
// country code : ISO-3166 2-letter country code, 2 characters
// cc2 : alternate country codes, comma separated, ISO-3166 2-letter country code, 200 characters
// admin1 code : fipscode (subject to change to iso code), see exceptions below, see file admin1Codes.txt for display names of this code; varchar(20)
// admin2 code : code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
// admin2 code : code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
// admin3 code : code for third level administrative division, varchar(20)
// admin4 code : code for fourth level administrative division, varchar(20)
// population : bigint (8 byte int)
// population : bigint (8 byte int)
// elevation : in meters, integer
// dem : digital elevation model, srtm3 or gtopo30, average elevation of 3''x3'' (ca 90mx90m) or 30''x30'' (ca 900mx900m) area in meters, integer. srtm processed by cgiar/ciat.
// timezone : the iana timezone id (see file timeZone.txt) varchar(40)
// modification date : date of last modification in yyyy-MM-dd format
l.db.exec('BEGIN TRANSACTION')!
mut file := os.open(filepath) or {
mut file := os.open(filepath) or {
console.print_stderr('Failed to open city file: ${err}')
return err
return err
}
defer { file.close() }
mut reader := io.new_buffered_reader(reader:file)
mut reader := io.new_buffered_reader(reader: file)
defer { reader.free() }
mut count := 0
console.print_header('Start import ${filepath}')
for {
line := reader.read_line() or {
//console.print_debug('End of file reached')
break
line := reader.read_line() or {
// console.print_debug('End of file reached')
break
}
//console.print_debug(line)
// console.print_debug(line)
fields := line.split('\t')
if fields.len < 12 { // Need at least 12 fields for required data
console.print_stderr('fields < 12: ${line}')
@@ -234,65 +224,52 @@ fn (mut l LocationDB) import_city_data(filepath string) ! {
name := fields[1]
ascii_name := texttools.name_fix(fields[2])
country_iso2 := fields[8].to_upper()
// Check if city exists
existing_city := sql l.db {
select from City where id == geoname_id
} or { []City{} }
city := City{
id: geoname_id
name: name
ascii_name: ascii_name
country_iso2: country_iso2
postal_code: '' // Not provided in this format
state_name: '' // Will need separate admin codes file
state_code: fields[10]
county_name: ''
county_code: fields[11]
community_name: ''
community_code: ''
latitude: fields[4].f64()
longitude: fields[5].f64()
accuracy: 4 // Using geonameid, so accuracy is 4
population: fields[14].i64()
timezone: fields[17]
feature_class: fields[6]
feature_code: fields[7]
id: geoname_id
name: name
ascii_name: ascii_name
country_iso2: country_iso2
postal_code: '' // Not provided in this format
state_name: '' // Will need separate admin codes file
state_code: fields[10]
county_name: ''
county_code: fields[11]
community_name: ''
community_code: ''
latitude: fields[4].f64()
longitude: fields[5].f64()
accuracy: 4 // Using geonameid, so accuracy is 4
population: fields[14].i64()
timezone: fields[17]
feature_class: fields[6]
feature_code: fields[7]
search_priority: 0 // Default priority
}
if existing_city.len > 0 {
// Update existing city
sql l.db {
update City set
name = city.name,
ascii_name = city.ascii_name,
country_iso2 = city.country_iso2,
postal_code = city.postal_code,
state_name = city.state_name,
state_code = city.state_code,
county_name = city.county_name,
county_code = city.county_code,
community_name = city.community_name,
community_code = city.community_code,
latitude = city.latitude,
longitude = city.longitude,
accuracy = city.accuracy,
population = city.population,
timezone = city.timezone,
feature_class = city.feature_class,
feature_code = city.feature_code,
search_priority = city.search_priority
where id == geoname_id
update City set name = city.name, ascii_name = city.ascii_name, country_iso2 = city.country_iso2,
postal_code = city.postal_code, state_name = city.state_name, state_code = city.state_code,
county_name = city.county_name, county_code = city.county_code, community_name = city.community_name,
community_code = city.community_code, latitude = city.latitude, longitude = city.longitude,
accuracy = city.accuracy, population = city.population, timezone = city.timezone,
feature_class = city.feature_class, feature_code = city.feature_code,
search_priority = city.search_priority where id == geoname_id
}!
//console.print_debug("Updated city: ${city}")
// console.print_debug("Updated city: ${city}")
} else {
// Insert new city
sql l.db {
insert city into City
}!
//console.print_debug("Inserted city: ${city}")
// console.print_debug("Inserted city: ${city}")
}
count++
// if count % 1000 == 0 {
@@ -300,8 +277,8 @@ fn (mut l LocationDB) import_city_data(filepath string) ! {
// }
}
console.print_debug( 'Processed ${count} cities')
console.print_debug('Processed ${count} cities')
l.db.exec('COMMIT')!
console.print_header('Finished importing cities for ${filepath}. Total records: ${count}')
}

View File

@@ -2,44 +2,44 @@ module location
pub struct Country {
pub:
iso2 string @[primary; sql: 'iso2'; max_len: 2; unique; index]
name string @[required; unique; index]
iso3 string @[required; sql: 'iso3'; max_len: 3; unique; index]
iso2 string @[index; max_len: 2; primary; sql: 'iso2'; unique]
name string @[index; required; unique]
iso3 string @[index; max_len: 3; required; sql: 'iso3'; unique]
continent string @[max_len: 2]
population i64
timezone string @[max_len: 40]
import_date i64 // Epoch timestamp of last import
import_date i64 // Epoch timestamp of last import
}
pub struct City {
pub:
id int @[unique; index]
name string @[required; max_len: 200; index]
ascii_name string @[required; max_len: 200; index] // Normalized name without special characters
country_iso2 string @[required; fkey: 'Country.iso2']
postal_code string @[max_len: 20; index ] //postal code
state_name string @[max_len: 100] // State/Province name
state_code string @[max_len: 20] // State/Province code
county_name string @[max_len: 100]
county_code string @[max_len: 20]
community_name string @[max_len: 100]
community_code string @[max_len: 20]
latitude f64 @[index: 'idx_coords']
longitude f64 @[index: 'idx_coords']
population i64
timezone string @[max_len: 40]
feature_class string @[max_len: 1] // For filtering (P for populated places)
feature_code string @[max_len: 10] // Detailed type (PPL, PPLA, etc.)
id int @[index; unique]
name string @[index; max_len: 200; required]
ascii_name string @[index; max_len: 200; required] // Normalized name without special characters
country_iso2 string @[fkey: 'Country.iso2'; required]
postal_code string @[index; max_len: 20] // postal code
state_name string @[max_len: 100] // State/Province name
state_code string @[max_len: 20] // State/Province code
county_name string @[max_len: 100]
county_code string @[max_len: 20]
community_name string @[max_len: 100]
community_code string @[max_len: 20]
latitude f64 @[index: 'idx_coords']
longitude f64 @[index: 'idx_coords']
population i64
timezone string @[max_len: 40]
feature_class string @[max_len: 1] // For filtering (P for populated places)
feature_code string @[max_len: 10] // Detailed type (PPL, PPLA, etc.)
search_priority int
accuracy i16 = 1 //1=estimated, 4=geonameid, 6=centroid of addresses or shape
accuracy i16 = 1 // 1=estimated, 4=geonameid, 6=centroid of addresses or shape
}
pub struct AlternateName {
pub:
id int @[primary; sql: serial]
city_id int @[required; fkey: 'City.id']
name string @[required; max_len: 200; index]
language_code string @[max_len: 2]
id int @[primary; sql: serial]
city_id int @[fkey: 'City.id'; required]
name string @[index; max_len: 200; required]
language_code string @[max_len: 2]
is_preferred bool
is_short bool
}
@@ -47,9 +47,9 @@ pub:
// SearchResult represents a location search result with combined city and country info
pub struct SearchResult {
pub:
city City
country Country
similarity f64 // Search similarity score
city City
country Country
similarity f64 // Search similarity score
}
// Coordinates represents a geographic point

View File

@@ -51,11 +51,11 @@ import db.pg
// where_clause := if query_conditions.len > 0 { 'WHERE ' + query_conditions.join(' AND ') } else { '' }
// query := '
// SELECT c.*, co.*
// SELECT c.*, co.*
// FROM City c
// JOIN Country co ON c.country_iso2 = co.iso2
// ${where_clause}
// ORDER BY c.search_priority DESC, c.population DESC
// ORDER BY c.search_priority DESC, c.population DESC
// LIMIT ${opts.limit}
// '
@@ -111,8 +111,8 @@ import db.pg
// query := "
// WITH distances AS (
// SELECT c.*, co.*,
// (6371 * acos(cos(radians($1)) * cos(radians(latitude)) *
// cos(radians(longitude) - radians($2)) + sin(radians($1)) *
// (6371 * acos(cos(radians($1)) * cos(radians(latitude)) *
// cos(radians(longitude) - radians($2)) + sin(radians($1)) *
// sin(radians(latitude)))) AS distance
// FROM City c
// JOIN Country co ON c.country_iso2 = co.iso2
@@ -122,7 +122,7 @@ import db.pg
// ORDER BY distance
// LIMIT $4
// "
// params := [
// opts.coordinates.latitude.str(),
// opts.coordinates.longitude.str(),

View File

@@ -17,11 +17,16 @@ 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 +35,7 @@ pub fn (params Params) decode_struct[T](_ T) !T {
pub fn (params Params) decode_value[T](_ T, key string) !T {
// $if T is $option {
// // unwrap and encode optionals
// workaround := t
// if workaround != none {
// encode(t, args)!
// }
// return error("is option")
// }
// value := params.get(field.name)!

View File

@@ -33,7 +33,6 @@ const test_child = TestChild{
const test_struct = TestStruct{
name: 'test'
nick: 'test_nick'
birthday: time.new(
day: 12
month: 12
@@ -104,6 +103,8 @@ 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

View File

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

View File

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

View File

@@ -97,13 +97,13 @@ fn (mut repo GitRepo) load_tags() ! {
tags_result := repo.exec('git tag --list') or {
return error('Failed to list tags: ${err}. Please ensure git is installed and repository is accessible.')
}
//println(tags_result)
// println(tags_result)
for line in tags_result.split('\n') {
line_trimmed := line.trim_space()
if line_trimmed != '' {
parts := line_trimmed.split(' ')
if parts.len < 2 {
//console.print_debug('Skipping malformed tag line: ${line_trimmed}')
// console.print_debug('Skipping malformed tag line: ${line_trimmed}')
continue
}
commit_hash := parts[0].trim_space()

View File

@@ -0,0 +1,13 @@
!!hero_code.generate_installer
name:'coredns'
classname:'CoreDNS'
singleton:1
templates:1
default:1
title:'coredns'
supported_platforms:''
reset:0
startupmanager:1
hasconfig:1
build:1

View File

@@ -1,138 +0,0 @@
module coredns
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.osal.screen
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.httpconnection
import os
@[params]
pub struct InstallArgs {
pub mut:
reset bool // this means we re-install and forgot what we did before
start bool = true
stop bool
restart bool // this means we stop if started, otherwise just start
homedir string // not sure what this is?
config_path string // path to Corefile, if empty will install default one
config_url string // path to Corefile through e.g. git url, will pull it if it is not local yet
dnszones_path string // path to where all the dns zones are
dnszones_url string // path on git url pull if needed
plugins []string // list of plugins to build CoreDNS with
example bool // if true we will install examples
}
pub fn install_(args_ InstallArgs) ! {
mut args := args_
version := '1.11.1'
res := os.execute('${osal.profile_path_source_and()!} coredns version')
if res.exit_code == 0 {
r := res.output.split_into_lines().filter(it.trim_space().starts_with('CoreDNS-'))
if r.len != 1 {
return error("couldn't parse coredns version.\n${res.output}")
}
if texttools.version(version) > texttools.version(r[0].all_after_first('CoreDNS-')) {
args.reset = true
}
} else {
args.reset = true
}
if args.reset {
console.print_header('install coredns')
mut url := ''
if core.is_linux_arm()! {
url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_linux_arm64.tgz'
} else if core.is_linux_intel()! {
url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_linux_amd64.tgz'
} else if core.is_osx_arm()! {
url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_darwin_arm64.tgz'
} else if core.is_osx_intel()! {
url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_darwin_amd64.tgz'
} else {
return error('unsported platform')
}
mut dest := osal.download(
url: url
minsize_kb: 13000
expand_dir: '/tmp/coredns'
)!
mut binpath := dest.file_get('coredns')!
osal.cmd_add(
cmdname: 'coredns'
source: binpath.path
)!
}
configure(args)!
if args.example {
example_configure(args)!
}
if args.restart {
restart(args)!
return
}
if args.start {
start(args)!
}
}
pub fn restart(args_ InstallArgs) ! {
stop(args_)!
start(args_)!
}
pub fn stop(args_ InstallArgs) ! {
console.print_header('coredns stop')
name := 'coredns'
// use startup manager, see caddy
mut scr := screen.new()!
scr.kill(name)!
}
pub fn start(args_ InstallArgs) ! {
mut args := args_
configure(args)!
if check()! {
return
}
console.print_header('coredns start')
name := 'coredns'
mut scr := screen.new()!
mut s := scr.add(name: name, reset: true)!
cmd2 := "coredns -conf '${args.config_path}'"
s.cmd_send(cmd2)!
if !check()! {
return error("coredns did not install propertly, do: curl 'http://localhost:3334/health'")
}
console.print_header('coredns running')
}
pub fn check() !bool {
// this checks health of coredns
mut conn := httpconnection.new(name: 'coredns', url: 'http://localhost:3334')!
r := conn.get(prefix: 'health')!
if r.trim_space() == 'OK' {
return true
}
return false
}

View File

@@ -1,49 +0,0 @@
module coredns
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.installers.base
import os
pub fn play(mut plbook playbook.PlayBook) ! {
base.play(playbook)!
coredns_actions := plbook.find(filter: 'coredns.')!
if coredns_actions.len == 0 {
return
}
mut install_actions := plbook.find(filter: 'coredns.install')!
if install_actions.len > 0 {
for install_action in install_actions {
mut p := install_action.params
// CoreDNS parameters
reset := p.get_default_false('reset')
start := p.get_default_true('start')
stop := p.get_default_false('stop')
restart := p.get_default_false('restart')
homedir := p.get_default('homedir', '${os.home_dir()}/hero/var/coredns')!
config_path := p.get_default('config_path', '${os.home_dir()}/hero/cfg/Corefile')!
config_url := p.get_default('config_url', '')!
dnszones_path := p.get_default('dnszones_path', '${os.home_dir()}/hero/var/coredns/zones')!
dnszones_url := p.get_default('dnszones_url', '')!
plugins := p.get_list_default('plugins', [])!
example := p.get_default_false('example')
install(
reset: reset
start: start
stop: stop
restart: restart
homedir: homedir
config_path: config_path
config_url: config_url
dnszones_path: dnszones_path
dnszones_url: dnszones_url
plugins: plugins
example: example
)!
}
}
}

View File

@@ -0,0 +1,180 @@
module coredns
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal.zinit
import freeflowuniverse.herolib.installers.ulist
import freeflowuniverse.herolib.core.httpconnection
import freeflowuniverse.herolib.installers.lang.golang
import os
fn startupcmd() ![]zinit.ZProcessNewArgs {
mut args := get()!
mut res := []zinit.ZProcessNewArgs{}
cmd := "coredns -conf '${args.config_path}'"
res << zinit.ZProcessNewArgs{
name: 'coredns'
cmd: cmd
}
return res
}
fn running() !bool {
mut installer := get()!
mut conn := httpconnection.new(name: 'coredns', url: 'http://localhost:3334')!
r := conn.get(prefix: 'health')!
if r.trim_space() == 'OK' {
return true
}
return false
}
fn start_pre() ! {
fix()!
}
fn start_post() ! {
set_local_dns()
}
fn stop_pre() ! {
}
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 {
res := os.execute('${osal.profile_path_source_and()!} coredns version')
if res.exit_code != 0 {
return false
}
r := res.output.split_into_lines().filter(it.trim_space().starts_with('CoreDNS-'))
if r.len != 1 {
return error("couldn't parse coredns version.\n${res.output}")
}
if texttools.version(version) == texttools.version(r[0].all_after_first('CoreDNS-')) {
return true
}
return false
}
// 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{}
}
// uploads to S3 server if configured
fn upload() ! {
// installers.upload(
// cmdname: 'coredns'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/coredns'
// )!
}
fn install() ! {
console.print_header('install coredns')
build()! // because we need the plugins
// mut url := ''
// if core.is_linux_arm()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_linux_arm64.tgz'
// } else if core.is_linux_intel()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_linux_amd64.tgz'
// } else if core.is_osx_arm()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_darwin_arm64.tgz'
// } else if core.is_osx_intel()! {
// url = 'https://github.com/coredns/coredns/releases/download/v${version}/coredns_${version}_darwin_amd64.tgz'
// } else {
// return error('unsported platform')
// }
// mut dest := osal.download(
// url: url
// minsize_kb: 13000
// expand_dir: '/tmp/coredns'
// )!
// mut binpath := dest.file_get('coredns')!
// osal.cmd_add(
// cmdname: 'coredns'
// source: binpath.path
// )!
}
fn build() ! {
url := 'https://github.com/coredns/coredns'
if core.platform()! != .ubuntu {
return error('only support ubuntu for now')
}
mut g := golang.get()!
g.install()!
console.print_header('build coredns')
mut gs := gittools.new()!
gitpath := gs.get_path(
pull: true
reset: true
url: url
)!
// set the plugins file on right location
pluginsfile := $tmpl('templates/plugin.cfg')
mut path := pathlib.get_file(path: '${gitpath}/plugin.cfg', create: true)!
path.write(pluginsfile)!
cmd := '
cd ${gitpath}
make
'
osal.execute_stdout(cmd)!
// now copy to the default bin path
mut codedir := pathlib.get_dir(path: '${gitpath}', create: false)!
mut binpath := codedir.file_get('coredns')!
osal.cmd_add(
cmdname: 'coredns'
source: binpath.path
)!
}
fn destroy() ! {
// mut systemdfactory := systemd.new()!
// systemdfactory.destroy("zinit")!
// osal.process_kill_recursive(name:'zinit')!
// osal.cmd_delete('zinit')!
// osal.package_remove('
// podman
// conmon
// buildah
// skopeo
// runc
// ')!
// //will remove all paths where go/bin is found
// osal.profile_path_add_remove(paths2delete:"go/bin")!
// osal.rm("
// podman
// conmon
// buildah
// skopeo
// runc
// /var/lib/containers
// /var/lib/podman
// /var/lib/buildah
// /tmp/podman
// /tmp/conmon
// ")!
}

View File

@@ -1,14 +1,17 @@
module coredns
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.develop.gittools
import os
pub fn configure(args_ InstallArgs) ! {
mut args := args_
pub fn configure() ! {
mut args := get()!
mut gs := gittools.get()!
mut repo_path := ''
set_global_dns()
if args.config_url.len > 0 {
mut repo := gs.get_repo(
url: args.config_url
@@ -37,15 +40,21 @@ pub fn configure(args_ InstallArgs) ! {
mycorefile := $tmpl('templates/Corefile')
mut path := pathlib.get_file(path: args.config_path, create: true)!
path.write(mycorefile)!
if args.example {
example_configure()!
}
}
pub fn example_configure(args_ InstallArgs) ! {
mut args := args_
pub fn example_configure() ! {
mut args := get()!
exampledbfile := $tmpl('templates/db.example.org')
myipaddr := osal.ipaddr_pub_get_check()!
exampledbfile := $tmpl('templates/ourexample.org')
mut path_testzone := pathlib.get_file(
path: '${args_.dnszones_path}/db.example.org'
path: '${args.dnszones_path}/ourexample.org'
create: true
)!
path_testzone.template_write(exampledbfile, true)!

View File

@@ -0,0 +1,280 @@
module coredns
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 (
coredns_global map[string]&CoreDNS
coredns_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) !&CoreDNS {
mut context := base.context()!
mut args := args_get(args_)
mut obj := CoreDNS{}
if args.name !in coredns_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('coredns', args.name)!
mut obj_ := heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return coredns_global[args.name] or {
println(coredns_global)
// bug if we get here because should be in globals
panic('could not get config for coredns with name, is bug:${args.name}')
}
}
// register the config for the future
pub fn set(o CoreDNS) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('coredns', 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('coredns', args.name)
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('coredns', args.name)!
if args.name in coredns_global {
// del coredns_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o CoreDNS) ! {
mut o2 := obj_init(o)!
coredns_global[o.name] = &o2
coredns_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: 'coredns.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: 'coredns.')!
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 coredns.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action coredns.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut coredns_obj := get(name: name)!
console.print_debug('action object:\n${coredns_obj}')
if other_action.name == 'start' {
console.print_debug('install action coredns.${other_action.name}')
coredns_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action coredns.${other_action.name}')
coredns_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action coredns.${other_action.name}')
coredns_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()!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self CoreDNS) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self CoreDNS) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('coredns start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('starting coredns with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('coredns did not install properly.')
}
pub fn (mut self CoreDNS) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self CoreDNS) stop() ! {
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 CoreDNS) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self CoreDNS) running() !bool {
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()!
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self CoreDNS) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self CoreDNS) build() ! {
switch(self.name)
build()!
}
pub fn (mut self CoreDNS) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for coredns
pub fn switch(name string) {
coredns_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -0,0 +1,62 @@
module coredns
import os
import net
import freeflowuniverse.herolib.ui.console
fn is_systemd_resolved_active() bool {
result := os.execute('systemctl is-active systemd-resolved')
return result.exit_code == 0 && result.output.trim_space() == 'active'
}
fn disable_systemd_resolved() {
console.print_debug('Stopping and disabling systemd-resolved...')
os.execute('sudo systemctl stop systemd-resolved')
os.execute('sudo systemctl disable systemd-resolved')
os.execute('sudo systemctl mask systemd-resolved')
}
fn is_dns_port_free() bool {
result := os.execute("sudo ss -tunlp | grep ':53 '")
return result.exit_code != 0
}
fn set_local_dns() {
console.print_debug('Updating /etc/resolv.conf to use local DNS...')
os.execute('sudo rm -f /etc/resolv.conf')
os.write_file('/etc/resolv.conf', 'nameserver 127.0.0.1\n') or {
console.print_debug('Failed to update /etc/resolv.conf')
return
}
console.print_debug('/etc/resolv.conf updated successfully.')
}
fn set_global_dns() {
console.print_debug('Updating /etc/resolv.conf to use local DNS...')
os.execute('sudo rm -f /etc/resolv.conf')
os.write_file('/etc/resolv.conf', 'nameserver 8.8.8.8\n') or {
console.print_debug('Failed to update /etc/resolv.conf')
return
}
console.print_debug('/etc/resolv.conf updated successfully for global.')
}
pub fn fix() ! {
console.print_debug('Checking if systemd-resolved is active...')
if is_systemd_resolved_active() {
disable_systemd_resolved()
} else {
println('systemd-resolved is already disabled.')
}
console.print_debug('Checking if DNS UDP port 53 is free...')
if is_dns_port_free() {
console.print_debug('UDP port 53 is free.')
} else {
console.print_debug('UDP port 53 is still in use. Ensure CoreDNS or another service is properly set up.')
return
}
set_global_dns()
console.print_debug('Setup complete. Ensure CoreDNS is running.')
}

View File

@@ -0,0 +1,39 @@
module coredns
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import os
pub const version = '1.12.0'
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
@[heap]
pub struct CoreDNS {
pub mut:
name string = 'default'
config_path string
config_url string // path to Corefile through e.g. git url, will pull it if it is not local yet
dnszones_path string // path to where all the dns zones are
dnszones_url string // path on git url pull if needed (is comma or \n separated list)
plugins string // list of plugins to build CoreDNS with (is comma or \n separated list)
example bool = true // if true we will install examples
}
// your checking & initialization code if needed
fn obj_init(mycfg_ CoreDNS) !CoreDNS {
mut mycfg := mycfg_
return mycfg
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj CoreDNS) !string {
return encoderhero.encode[CoreDNS](obj)!
}
pub fn heroscript_loads(heroscript string) !CoreDNS {
mut obj := encoderhero.decode[CoreDNS](heroscript)!
return obj
}

View File

@@ -0,0 +1,43 @@
# coredns
coredns
To get started
```vlang
import freeflowuniverse.herolib.lib.installers.infra.coredns as coredns_installer
heroscript:="
!!coredns.configure name:'test'
config_path: '/etc/coredns/Corefile'
dnszones_path: '/etc/coredns/zones'
plugins: 'forward,cache'
example: true
!!coredns.start name:'test' reset:1
"
coredns_installer.play(heroscript=heroscript)!
//or we can call the default and do a start with reset
//mut installer:= coredns_installer.get()!
//installer.start(reset:true)!
```
## example heroscript
```hero
!!coredns.configure
name: 'custom'
config_path: '/etc/coredns/Corefile'
config_url: 'https://github.com/example/coredns-config'
dnszones_path: '/etc/coredns/zones'
dnszones_url: 'https://github.com/example/dns-zones'
plugins: 'forward,cache'
example: false
```

View File

@@ -3,5 +3,11 @@
log
errors
health :3334
import '${args.dnszones_path}/*'
redis {
address localhost:6379
connect_timeout 100
read_timeout 100
ttl 360
prefix dns:
}
}

View File

@@ -1,14 +0,0 @@
??ORIGIN example.org.
^^ 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. (
2017042745 ; serial
7200 ; refresh (2 hours)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
3600 ; minimum (1 hour)
)
3600 IN NS a.iana-servers.net.
3600 IN NS b.iana-servers.net.
www IN A 127.0.0.1
IN AAAA ::1

View File

@@ -0,0 +1,17 @@
??ORIGIN ourexample.org.
^^ 3600 IN SOA ns1.ourexample.org. hostmaster.ourexample.org. (
2017042745 ; serial
7200 ; refresh (2 hours)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
3600 ; minimum (1 hour)
)
3600 IN NS ns1.ourexample.org.
3600 IN NS ns2.ourexample.org.
ns1 IN A ${myipaddr}
ns2 IN A ${myipaddr}
www IN A ${myipaddr}
test IN A ${myipaddr}
IN AAAA ::1

View File

@@ -0,0 +1,68 @@
# Directives are registered in the order they should be executed.
#
# Ordering is VERY important. Every plugin will feel the effects of all other
# plugin below (after) them during a request, but they must not care what plugin
# above them are doing.
# How to rebuild with updated plugin configurations: Modify the list below and
# run `go generate && go build`
# The parser takes the input format of:
#
# <plugin-name>:<package-name>
# Or
# <plugin-name>:<fully-qualified-package-name>
#
# External plugin example:
#
# log:github.com/coredns/coredns/plugin/log
# Local plugin example:
# log:log
#etcd:etcd
root:root
metadata:metadata
geoip:geoip
cancel:cancel
tls:tls
timeouts:timeouts
multisocket:multisocket
reload:reload
nsid:nsid
bufsize:bufsize
bind:bind
debug:debug
trace:trace
ready:ready
health:health
pprof:pprof
prometheus:metrics
errors:errors
log:log
dnstap:dnstap
local:local
dns64:dns64
acl:acl
any:any
chaos:chaos
loadbalance:loadbalance
tsig:tsig
cache:cache
rewrite:rewrite
header:header
dnssec:dnssec
autopath:autopath
minimal:minimal
template:template
transfer:transfer
hosts:hosts
file:file
secondary:secondary
loop:loop
forward:forward
erratic:erratic
whoami:whoami
on:github.com/coredns/caddy/onevent
sign:sign
view:view
redis:github.com/codysnider/coredns-redis

View File

@@ -25,22 +25,21 @@ fn installed() !bool {
}
fn install() ! {
console.print_header('install gitea')
baseurl:="https://github.com/go-gitea/gitea/releases/download/v${version}/gitea-${version}"
baseurl := 'https://github.com/go-gitea/gitea/releases/download/v${version}/gitea-${version}'
mut url := ''
if core.is_linux_arm()! {
//https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-linux-arm64.xz
if core.is_linux_arm()! {
// https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-linux-arm64.xz
url = '${baseurl}-linux-arm64.xz'
} else if core.is_linux_intel()! {
// https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-linux-amd64.xz
url = '${baseurl}-linux-amd64.xz'
} else if core.is_osx_arm()! {
//https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-darwin-10.12-arm64.xz
// https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-darwin-10.12-arm64.xz
url = '${baseurl}-darwin-10.12-arm64.xz'
} else if core.is_osx_intel()! {
//https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-darwin-10.12-amd64.xz
// https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-darwin-10.12-amd64.xz
url = '${baseurl}-darwin-10.12-amd64.xz'
} else {
return error('unsported platform')
@@ -104,68 +103,66 @@ fn startupcmd() ![]zinit.ZProcessNewArgs {
}
return res
// mut res := []zinit.ZProcessNewArgs{}
// cfg := get()!
// res << zinit.ZProcessNewArgs{
// name: 'gitea'
// // cmd: 'GITEA_WORK_DIR=${cfg.path} sudo -u git /var/lib/git/gitea web -c /etc/gitea_app.ini'
// cmd: '
// # Variables
// GITEA_USER="${cfg.run_user}"
// GITEA_HOME="${cfg.path}"
// GITEA_BINARY="/usr/local/bin/gitea"
// GITEA_CONFIG="/etc/gitea_app.ini"
// GITEA_DATA_PATH="\$GITEA_HOME/data"
// GITEA_CUSTOM_PATH="\$GITEA_HOME/custom"
// GITEA_LOG_PATH="\$GITEA_HOME/log"
// mut res := []zinit.ZProcessNewArgs{}
// cfg := get()!
// res << zinit.ZProcessNewArgs{
// name: 'gitea'
// // cmd: 'GITEA_WORK_DIR=${cfg.path} sudo -u git /var/lib/git/gitea web -c /etc/gitea_app.ini'
// cmd: '
// # Ensure the script is run as root
// if [[ \$EUID -ne 0 ]]; then
// echo "This script must be run as root."
// exit 1
// fi
// # Variables
// GITEA_USER="${cfg.run_user}"
// GITEA_HOME="${cfg.path}"
// GITEA_BINARY="/usr/local/bin/gitea"
// GITEA_CONFIG="/etc/gitea_app.ini"
// GITEA_DATA_PATH="\$GITEA_HOME/data"
// GITEA_CUSTOM_PATH="\$GITEA_HOME/custom"
// GITEA_LOG_PATH="\$GITEA_HOME/log"
// echo "Setting up Gitea..."
// # Ensure the script is run as root
// if [[ \$EUID -ne 0 ]]; then
// echo "This script must be run as root."
// exit 1
// fi
// # Create Gitea user if it doesn\'t exist
// if id -u "\$GITEA_USER" &>/dev/null; then
// echo "User \$GITEA_USER already exists."
// else
// echo "Creating Gitea user..."
// if ! sudo adduser --system --shell /bin/bash --group --disabled-password --home "/var/lib/\$GITEA_USER" "\$GITEA_USER"; then
// echo "Failed to create user \$GITEA_USER."
// exit 1
// fi
// fi
// echo "Setting up Gitea..."
// # Create necessary directories
// echo "Creating directories..."
// mkdir -p "\$GITEA_DATA_PATH" "\$GITEA_CUSTOM_PATH" "\$GITEA_LOG_PATH"
// chown -R "\$GITEA_USER:\$GITEA_USER" "\$GITEA_HOME"
// chmod -R 750 "\$GITEA_HOME"
// # Create Gitea user if it doesn\'t exist
// if id -u "\$GITEA_USER" &>/dev/null; then
// echo "User \$GITEA_USER already exists."
// else
// echo "Creating Gitea user..."
// if ! sudo adduser --system --shell /bin/bash --group --disabled-password --home "/var/lib/\$GITEA_USER" "\$GITEA_USER"; then
// echo "Failed to create user \$GITEA_USER."
// exit 1
// fi
// fi
// chown "\$GITEA_USER:\$GITEA_USER" "\$GITEA_CONFIG"
// chmod 640 "\$GITEA_CONFIG"
// # Create necessary directories
// echo "Creating directories..."
// mkdir -p "\$GITEA_DATA_PATH" "\$GITEA_CUSTOM_PATH" "\$GITEA_LOG_PATH"
// chown -R "\$GITEA_USER:\$GITEA_USER" "\$GITEA_HOME"
// chmod -R 750 "\$GITEA_HOME"
// chown "\$GITEA_USER:\$GITEA_USER" "\$GITEA_CONFIG"
// chmod 640 "\$GITEA_CONFIG"
// GITEA_WORK_DIR=\$GITEA_HOME sudo -u git gitea web -c \$GITEA_CONFIG
// '
// workdir: cfg.path
// }
// res << zinit.ZProcessNewArgs{
// name: 'restart_gitea'
// cmd: 'sleep 30 && zinit restart gitea && exit 1'
// after: ['gitea']
// oneshot: true
// workdir: cfg.path
// }
// return res
// GITEA_WORK_DIR=\$GITEA_HOME sudo -u git gitea web -c \$GITEA_CONFIG
// '
// workdir: cfg.path
// }
// res << zinit.ZProcessNewArgs{
// name: 'restart_gitea'
// cmd: 'sleep 30 && zinit restart gitea && exit 1'
// after: ['gitea']
// oneshot: true
// workdir: cfg.path
// }
// return res
}
fn running() !bool {
//TODO: extend with proper gitea client
// TODO: extend with proper gitea client
res := os.execute('curl -fsSL http://localhost:3000 || exit 1')
return res.exit_code == 0
}

View File

@@ -4,287 +4,277 @@ 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 (
gitea_global map[string]&GiteaServer
gitea_default string
gitea_global map[string]&GiteaServer
gitea_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet{
pub struct ArgsGet {
pub mut:
name string
name string
}
fn args_get (args_ ArgsGet) ArgsGet {
mut args:=args_
if args.name == ""{
args.name = "default"
}
return args
fn args_get(args_ ArgsGet) ArgsGet {
mut args := args_
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&GiteaServer {
mut context:=base.context()!
mut args := args_get(args_)
mut obj := GiteaServer{}
if !(args.name in gitea_global) {
if ! exists(args)!{
set(obj)!
}else{
heroscript := context.hero_config_get("gitea",args.name)!
mut obj2:=heroscript_loads(heroscript)!
set_in_mem(obj2)!
}
}
return gitea_global[args.name] or {
println(gitea_global)
//bug if we get here because should be in globals
panic("could not get config for gitea with name, is bug:${args.name}")
}
pub fn get(args_ ArgsGet) !&GiteaServer {
mut context := base.context()!
mut args := args_get(args_)
mut obj := GiteaServer{}
if args.name !in gitea_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('gitea', args.name)!
mut obj2 := heroscript_loads(heroscript)!
set_in_mem(obj2)!
}
}
return gitea_global[args.name] or {
println(gitea_global)
// bug if we get here because should be in globals
panic('could not get config for gitea with name, is bug:${args.name}')
}
}
//register the config for the future
pub fn set(o GiteaServer)! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set("gitea", o.name, heroscript)!
// register the config for the future
pub fn set(o GiteaServer) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('gitea', o.name, heroscript)!
}
//does the config exists?
// does the config exists?
pub fn exists(args_ ArgsGet) !bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists("gitea", args.name)
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists('gitea', args.name)
}
pub fn delete(args_ ArgsGet)! {
mut args := args_get(args_)
mut context:=base.context()!
context.hero_config_delete("gitea",args.name)!
if args.name in gitea_global {
//del gitea_global[args.name]
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('gitea', args.name)!
if args.name in gitea_global {
// del gitea_global[args.name]
}
}
//only sets in mem, does not set as config
fn set_in_mem(o GiteaServer)! {
mut o2:=obj_init(o)!
gitea_global[o.name] = &o2
gitea_default = o.name
// only sets in mem, does not set as config
fn set_in_mem(o GiteaServer) ! {
mut o2 := obj_init(o)!
gitea_global[o.name] = &o2
gitea_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 args:=args_
mut args := args_
mut plbook := args.plbook or {
playbook.new(text: args.heroscript)!
}
mut install_actions := plbook.find(filter: 'gitea.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript:=install_action.heroscript()
mut obj:=heroscript_loads(heroscript)!
set(obj)!
}
}
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
mut other_actions := plbook.find(filter: 'gitea.')!
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 gitea.destroy")
destroy()!
}
if other_action.name == "install"{
console.print_debug("install action gitea.install")
install()!
}
}
if other_action.name in ["start","stop","restart"]{
mut p := other_action.params
name := p.get('name')!
mut gitea_obj:=get(name:name)!
console.print_debug("action object:\n${gitea_obj}")
if other_action.name == "start"{
console.print_debug("install action gitea.${other_action.name}")
gitea_obj.start()!
}
mut install_actions := plbook.find(filter: 'gitea.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript := install_action.heroscript()
mut obj := heroscript_loads(heroscript)!
set(obj)!
}
}
if other_action.name == "stop"{
console.print_debug("install action gitea.${other_action.name}")
gitea_obj.stop()!
}
if other_action.name == "restart"{
console.print_debug("install action gitea.${other_action.name}")
gitea_obj.restart()!
}
}
}
mut other_actions := plbook.find(filter: 'gitea.')!
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 gitea.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action gitea.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut gitea_obj := get(name: name)!
console.print_debug('action object:\n${gitea_obj}')
if other_action.name == 'start' {
console.print_debug('install action gitea.${other_action.name}')
gitea_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action gitea.${other_action.name}')
gitea_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action gitea.${other_action.name}')
gitea_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
// load from disk and make sure is properly intialized
pub fn (mut self GiteaServer) reload() ! {
switch(self.name)
self=obj_init(self)!
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self GiteaServer) start() ! {
switch(self.name)
if self.running()!{
return
}
switch(self.name)
if self.running()! {
return
}
console.print_header('gitea start')
console.print_header('gitea 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 gitea with ${zprocess.startuptype}...')
console.print_debug('starting gitea with ${zprocess.startuptype}...')
sm.new(zprocess)!
sm.new(zprocess)!
sm.start(zprocess.name)!
}
sm.start(zprocess.name)!
}
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('gitea did not install properly.')
start_post()!
for _ in 0 .. 50 {
if self.running()! {
return
}
time.sleep(100 * time.millisecond)
}
return error('gitea did not install properly.')
}
pub fn (mut self GiteaServer) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self GiteaServer) 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 GiteaServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self GiteaServer) 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 (mut self GiteaServer) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self GiteaServer) build() ! {
switch(self.name)
build()!
switch(self.name)
build()!
}
pub fn (mut self GiteaServer) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
switch(self.name)
self.stop() or {}
destroy()!
}
//switch instance to be used for gitea
// switch instance to be used for gitea
pub fn switch(name string) {
gitea_default = name
gitea_default = name
}
//helpers
// helpers
@[params]
pub struct DefaultConfigArgs{
instance string = 'default'
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,4 +1,5 @@
module gitea
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.core.pathlib
@@ -16,30 +17,29 @@ const default = false
@[heap]
pub struct GiteaServer {
pub mut:
name string = 'default'
path string = '${os.home_dir()}/hero/var/gitea'
passwd string
domain string = "git.test.com"
jwt_secret string = rand.hex(12)
lfs_jwt_secret string
internal_token string
secret_key string
postgresql_client_name string = "default"
mail_client_name string = "default"
name string = 'default'
path string = '${os.home_dir()}/hero/var/gitea'
passwd string
domain string = 'git.test.com'
jwt_secret string = rand.hex(12)
lfs_jwt_secret string
internal_token string
secret_key string
postgresql_client_name string = 'default'
mail_client_name string = 'default'
}
pub fn (obj GiteaServer) config_path() string {
return '${obj.path}/config.ini'
return '${obj.path}/config.ini'
}
//your checking & initialization code if needed
fn obj_init(mycfg_ GiteaServer)!GiteaServer{
mut mycfg:=mycfg_
return mycfg
// your checking & initialization code if needed
fn obj_init(mycfg_ GiteaServer) !GiteaServer {
mut mycfg := mycfg_
return mycfg
}
//called before start if done
// called before start if done
fn configure() ! {
mut server := get()!
@@ -68,7 +68,7 @@ fn configure() ! {
return error('Failed to initialize mail client "${server.mail_client_name}": ${err}')
}
//TODO: check database exists
// TODO: check database exists
if !db_client.db_exists('gitea_${server.name}')! {
console.print_header('Creating database gitea_${server.name} for gitea.')
db_client.db_create('gitea_${server.name}')!
@@ -85,10 +85,10 @@ fn configure() ! {
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj GiteaServer) !string {
return encoderhero.encode[GiteaServer ](obj)!
return encoderhero.encode[GiteaServer](obj)!
}
pub fn heroscript_loads(heroscript string) !GiteaServer {
mut obj := encoderhero.decode[GiteaServer](heroscript)!
return obj
mut obj := encoderhero.decode[GiteaServer](heroscript)!
return obj
}

View File

@@ -54,9 +54,9 @@ pub fn v_analyzer_install(args_ InstallArgs) ! {
// if pl == .ubuntu {
// }else{
// mut url := ''
// if osal.is_linux_intel() {
// if core.is_linux_intel() {
// url = 'https://github.com/vlang/v-analyzer/releases/download/nightly/v-analyzer-linux-x86_64.zip'
// } else if osal.is_osx_arm() {
// } else if core.is_osx_arm() {
// url = 'https://github.com/vlang/v-analyzer/releases/download/nightly/v-analyzer-darwin-arm64.zip'
// } else if osal.is_osx_intel() {
// url = 'https://github.com/vlang/v-analyzer/releases/download/nightly/v-analyzer-darwin-x86_64.zip'

View File

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

View File

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

View File

@@ -3,50 +3,37 @@ 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'
// }
// }
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() ! {
@@ -64,19 +51,21 @@ 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
// }
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
}
@@ -87,64 +76,78 @@ fn ulist_get() !ulist.UList {
}
// uploads to S3 server if configured
fn upload_() ! {
fn upload() ! {
// installers.upload(
// cmdname: 'mycelium'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/mycelium'
// )!
}
fn install_() ! {
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')
// }
// 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)!
// 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
@@ -154,34 +157,11 @@ fn build_() ! {
// )!
}
fn destroy_() ! {
// mut systemdfactory := systemd.new()!
// systemdfactory.destroy("zinit")!
fn destroy() ! {
osal.process_kill_recursive(name: 'mycelium')!
osal.cmd_delete('mycelium')!
// osal.process_kill_recursive(name:'zinit')!
// osal.cmd_delete('zinit')!
// osal.package_remove('
// podman
// conmon
// buildah
// skopeo
// runc
// ')!
// //will remove all paths where go/bin is found
// osal.profile_path_add_remove(paths2delete:"go/bin")!
// osal.rm("
// podman
// conmon
// buildah
// skopeo
// runc
// /var/lib/containers
// /var/lib/podman
// /var/lib/buildah
// /tmp/podman
// /tmp/conmon
// ")!
osal.rm('
mycelium
')!
}

View File

@@ -3,17 +3,141 @@ 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_installer_global map[string]&MyceliumInstaller
mycelium_installer_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_installer_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_installer_global[args.name] or {
println(mycelium_installer_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_installer_global {
// del mycelium_installer_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o MyceliumInstaller) ! {
mut o2 := obj_init(o)!
mycelium_installer_global[o.name] = &o2
mycelium_installer_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 ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -40,6 +164,12 @@ fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManag
}
}
// 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()! {
@@ -48,8 +178,8 @@ pub fn (mut self MyceliumInstaller) start() ! {
console.print_header('mycelium start')
if !installed_()! {
install_()!
if !installed()! {
install()!
}
configure()!
@@ -77,9 +207,9 @@ pub fn (mut self MyceliumInstaller) start() ! {
return error('mycelium did not install properly.')
}
pub fn (mut self MyceliumInstaller) install_start(model InstallArgs) ! {
pub fn (mut self MyceliumInstaller) install_start(args InstallArgs) ! {
switch(self.name)
self.install(model)!
self.install(args)!
self.start()!
}
@@ -119,19 +249,32 @@ pub mut:
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_installer_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,9 +1,9 @@
module mycelium
import freeflowuniverse.herolib.data.paramsparser
import os
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.osal.tun
pub const version = '0.0.0'
pub const version = '0.5.7'
const singleton = true
const default = true
@@ -11,17 +11,59 @@ const default = true
@[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
fn configure() ! {
// mut installer := get()!
mut installer := get()!
if installer.tun_nr == 0 {
// Check if TUN is available first
if available := tun.available() {
if !available {
return error('TUN is not available on this system')
}
// Get free TUN interface name
if interface_name := tun.free() {
// Parse the interface number from the name (e.g. "tun0" -> 0)
nr := interface_name.trim_string_left('tun').int()
installer.tun_nr = nr
} else {
return error('Failed to get free TUN interface: ${err}')
}
} else {
return error('Failed to check TUN availability: ${err}')
}
set(installer)!
}
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj MyceliumInstaller) !string {
return encoderhero.encode[MyceliumInstaller](obj)!
}
pub fn heroscript_loads(heroscript string) !MyceliumInstaller {
mut obj := encoderhero.decode[MyceliumInstaller](heroscript)!
return obj
}

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ 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
@@ -14,6 +15,65 @@ __global (
/////////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 ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -48,8 +108,8 @@ pub fn (mut self Zinit) start() ! {
console.print_header('zinit start')
if !installed_()! {
install_()!
if !installed()! {
install()!
}
configure()!
@@ -77,9 +137,9 @@ pub fn (mut self Zinit) start() ! {
return error('zinit did not install properly.')
}
pub fn (mut self Zinit) install_start(model InstallArgs) ! {
pub fn (mut self Zinit) install_start(args InstallArgs) ! {
switch(self.name)
self.install(model)!
self.install(args)!
self.start()!
}
@@ -119,19 +179,32 @@ pub mut:
reset bool
}
pub fn install(args InstallArgs) ! {
if args.reset {
destroy()!
}
if !(installed_()!) {
install_()!
pub fn (mut self Zinit) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn destroy() ! {
destroy_()!
pub fn (mut self Zinit) build() ! {
switch(self.name)
build()!
}
pub fn build() ! {
build_()!
pub fn (mut self Zinit) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for zinit
pub fn switch(name string) {
zinit_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,12 +0,0 @@
!!hero_code.generate_installer
name:'buildah'
classname:'BuildahInstaller'
singleton:1
templates:0
default:1
title:''
supported_platforms:''
reset:0
startupmanager:0
hasconfig:0
build:0

View File

@@ -1,36 +0,0 @@
module buildah
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.installers.ulist
import freeflowuniverse.herolib.core
// checks if a certain version or above is installed
fn installed() !bool {
osal.execute_silent('buildah -v') or { return false }
return true
}
fn install() ! {
console.print_header('install buildah')
if core.platform()! != .ubuntu {
return error('Only ubuntu is supported for now')
}
cmd := 'sudo apt-get -y update && sudo apt-get -y install buildah'
osal.execute_stdout(cmd)!
console.print_header('Buildah Installed Successfuly')
}
// get the Upload List of the files
fn ulist_get() !ulist.UList {
// mut installer := get()!
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
return ulist.UList{}
}
fn destroy() ! {
osal.execute_stdout('sudo apt remove --purge -y buildah')!
}

View File

@@ -1,74 +0,0 @@
module buildah
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.sysadmin.startupmanager
import freeflowuniverse.herolib.osal.zinit
import time
__global (
buildah_global map[string]&BuildahInstaller
buildah_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string
}
pub fn get(args_ ArgsGet) !&BuildahInstaller {
return &BuildahInstaller{}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# 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()!
}
}
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self BuildahInstaller) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self BuildahInstaller) destroy() ! {
switch(self.name)
destroy()!
}
// switch instance to be used for buildah
pub fn switch(name string) {
buildah_default = name
}

View File

@@ -1,22 +0,0 @@
module buildah
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
@[heap]
pub struct BuildahInstaller {
pub mut:
name string = 'default'
}
fn obj_init(obj_ BuildahInstaller) !BuildahInstaller {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
return obj
}
// called before start if done
fn configure() ! {
// mut installer := get()!
}

View File

@@ -1,34 +0,0 @@
# buildah
To get started
```vlang
import freeflowuniverse.herolib.installers.something. buildah
mut installer:= buildah.get()!
installer.start()!
```
## example heroscript
```hero
!!buildah.install
homedir: '/home/user/buildah'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
```

View File

@@ -3,6 +3,7 @@ module pacman
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core
import os
// checks if a certain version or above is installed
@@ -19,7 +20,7 @@ fn install_() ! {
return
}
if core.platform() != .ubuntu {
if core.platform()! != .ubuntu {
return error('only ubuntu supported for this installer.')
}

View File

@@ -1,87 +1,93 @@
module podman
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.installers.ulist
import os
// Check if Podman is installed
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed() !bool {
console.print_header('Checking if Podman is installed...')
result := os.execute('podman -v')
return result.exit_code == 0
res := os.execute('${osal.profile_path_source_and()!} podman -v')
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 podman version.\n${res.output}")
}
if texttools.version(version) <= texttools.version(r[0].all_after('version')) {
return true
}
return false
}
// 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{}
}
fn upload() ! {
}
// Install Podman
fn install() ! {
if installed()! {
return error('Podman is already installed.')
console.print_header('install podman')
mut url := ''
if core.is_linux_arm()! || core.is_linux_intel()! {
osal.package_install('podman,buildah,crun,mmdebstrap')!
return
} else if core.is_linux_intel()! {
url = 'https://github.com/containers/podman/releases/download/v${version}/podman-installer-macos-arm64.pkg'
} else if core.is_osx_intel()! {
url = 'https://github.com/containers/podman/releases/download/v${version}/podman-installer-macos-amd64.pkg'
} else {
return error('unsported platform')
}
console.print_header('Installing Podman...')
platform := core.platform()!
command := get_platform_command(platform, 'install')!
execute_command(command, 'installing Podman')!
console.print_header('Podman installed successfully.')
mut dest := osal.download(
url: url
minsize_kb: 9000
expand_dir: '/tmp/podman'
)!
// dest.moveup_single_subdir()!
panic('implement')
}
// Remove Podman
fn destroy() ! {
if !installed()! {
return error('Podman is not installed.')
}
// mut systemdfactory := systemd.new()!
// systemdfactory.destroy("zinit")!
console.print_header('Removing Podman...')
platform := core.platform()!
command := get_platform_command(platform, 'remove')!
execute_command(command, 'removing Podman')!
console.print_header('Podman removed successfully.')
}
// osal.process_kill_recursive(name:'zinit')!
// osal.cmd_delete('zinit')!
// Build Podman (install it)
fn build() ! {
install()!
}
osal.package_remove('
podman
conmon
buildah
skopeo
runc
')!
// Get platform-specific commands for installing/removing Podman
fn get_platform_command(platform core.PlatformType, action string) !string {
return match platform {
.ubuntu {
if action == 'install' {
'sudo apt-get -y install podman'
} else if action == 'remove' {
'sudo apt-get -y remove podman'
} else {
return error('Invalid action: ${action}')
}
}
.arch {
if action == 'install' {
'sudo pacman -S --noconfirm podman'
} else if action == 'remove' {
'sudo pacman -R --noconfirm podman'
} else {
return error('Invalid action: ${action}')
}
}
.osx {
if action == 'install' {
'brew install podman'
} else if action == 'remove' {
'brew uninstall podman'
} else {
return error('Invalid action: ${action}')
}
}
else {
return error('Only Ubuntu, Arch, and macOS are supported.')
}
}
}
// //will remove all paths where go/bin is found
// osal.profile_path_add_remove(paths2delete:"go/bin")!
// Execute a shell command and handle errors
fn execute_command(command string, operation string) ! {
result := os.execute(command)
if result.exit_code != 0 {
return error('Failed ${operation}: ${result.output}')
}
osal.rm('
podman
conmon
buildah
skopeo
runc
/var/lib/containers
/var/lib/podman
/var/lib/buildah
/tmp/podman
/tmp/conmon
')!
}

View File

@@ -1,8 +1,12 @@
module podman
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 freeflowuniverse.herolib.ui.console
import time
__global (
podman_global map[string]&PodmanInstaller
@@ -14,13 +18,43 @@ __global (
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
name string
}
pub fn get(args_ ArgsGet) !&PodmanInstaller {
return &PodmanInstaller{}
}
@[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: 'podman.')!
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 podman.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action podman.install')
install()!
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -55,7 +89,7 @@ pub mut:
pub fn (mut self PodmanInstaller) install(args InstallArgs) ! {
switch(self.name)
if args.reset || !installed()! {
if args.reset || (!installed()!) {
install()!
}
}
@@ -69,3 +103,10 @@ pub fn (mut self PodmanInstaller) destroy() ! {
pub fn switch(name string) {
podman_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,10 +1,38 @@
module podman
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import os
pub const version = '4.9.3'
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
@[heap]
pub struct PodmanInstaller {
pub mut:
name string = 'default'
}
// your checking & initialization code if needed
fn obj_init(mycfg_ PodmanInstaller) !PodmanInstaller {
mut mycfg := mycfg_
return mycfg
}
// called before start if done
fn configure() ! {
// mut installer := get()!
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj PodmanInstaller) !string {
return encoderhero.encode[PodmanInstaller](obj)!
}
pub fn heroscript_loads(heroscript string) !PodmanInstaller {
mut obj := encoderhero.decode[PodmanInstaller](heroscript)!
return obj
}

View File

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

View File

@@ -0,0 +1,11 @@
!!hero_code.generate_installer
name:'traefik'
classname:'TraefikServer'
singleton:1 //there can only be 1 object in the globals, is called 'default'
templates:1 //are there templates for the installer
default:1 //can we create a default when the factory is used
title:''
supported_platforms:'' //osx, ... (empty means all)
reset:0 // regenerate all, dangerous !!!
startupmanager:1 //managed by a startup manager, default true

Some files were not shown because too many files have changed in this diff Show More