diff --git a/examples/virt/hetzner/hetzner_example b/examples/virt/hetzner/hetzner_example new file mode 100755 index 00000000..397ac6b2 Binary files /dev/null and b/examples/virt/hetzner/hetzner_example differ diff --git a/examples/virt/hetzner/hetzner_example.vsh b/examples/virt/hetzner/hetzner_example.vsh index aa36722c..8b8ebc96 100755 --- a/examples/virt/hetzner/hetzner_example.vsh +++ b/examples/virt/hetzner/hetzner_example.vsh @@ -1,4 +1,4 @@ -#!/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.hetznermanager import freeflowuniverse.herolib.ui.console @@ -10,6 +10,10 @@ import os import freeflowuniverse.herolib.core.playcmds +user:=os.environ()['HETZNER_USER'] or { + println('HETZNER_USER not set') + exit(1) +} passwd:=os.environ()['HETZNER_PASSWORD'] or { println('HETZNER_PASSWORD not set') exit(1) @@ -19,7 +23,7 @@ playcmds.run( heroscript: ' !!hetznermanager.configure name:"main" - user:"operations@threefold.io" + user:"${user}" whitelist:"2111181, 2392178" password:"${passwd}" ' @@ -29,27 +33,29 @@ console.print_header('Hetzner Test.') mut cl := hetznermanager.get(name:'main')! -for i in 0 .. 5 { - println('test cache, first time slow then fast') - cl.servers_list()! -} +// for i in 0 .. 5 { +// println('test cache, first time slow then fast') +// } // println(cl.servers_list()!) -// mut serverinfo := cl.server_info_get(name: 'kristof2')! +mut serverinfo := cl.server_info_get(name: 'kristof2')! -// println(serverinfo) +println(serverinfo) -// // cl.server_reset(name:"kristof2",wait:true)! +// cl.server_reset(name:"kristof2",wait:true)! -// // cl.server_rescue(name:"kristof2",wait:true)! +cl.server_rescue(name:"kristof2",wait:true, hero_install:true,sshkey_name:"kristof")! -// console.print_header('SSH login') -// mut b := builder.new()! -// mut n := b.node_new(ipaddr: serverinfo.server_ip)! +mut ks:=cl.keys_get()! +println(ks) -// // n.hero_install()! -// // n.hero_compile_debug()! +console.print_header('SSH login') +mut b := builder.new()! +mut n := b.node_new(ipaddr: serverinfo.server_ip)! + +//this will put hero in debug mode on the system +n.hero_install(compile:true)! + +// n.shell("")! -// // mut ks:=cl.keys_get()! -// // println(ks) diff --git a/lib/builder/bootstrapper.v b/lib/builder/bootstrapper.v index cdf81f16..9d078cbb 100644 --- a/lib/builder/bootstrapper.v +++ b/lib/builder/bootstrapper.v @@ -5,14 +5,11 @@ import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.ui import v.embed_file const heropath_ = os.dir(@FILE) + '/../' pub struct BootStrapper { -pub mut: - embedded_files map[string]embed_file.EmbedFileData @[skip; str: skip] } @[params] @@ -24,18 +21,9 @@ pub mut: debug bool } -fn (mut bs BootStrapper) load() { - panic('not implemented') - - // TODO: check how to install hero. maybe once we have releases, we could just download the binary - // bs.embedded_files['install_base.sh'] = $embed_file('../../scripts/install_base.sh') - // bs.embedded_files['install_hero.sh'] = $embed_file('../../scripts/install_hero.sh') -} - // to use do something like: export NODES="195.192.213.3" . pub fn bootstrapper() BootStrapper { mut bs := BootStrapper{} - bs.load() return bs } @@ -49,51 +37,7 @@ pub fn (mut bs BootStrapper) run(args_ BootstrapperArgs) ! { name := '${args.name}_${counter}' mut n := b.node_new(ipaddr: a, name: name)! n.hero_install()! - n.hero_install()! - } -} - -pub fn (mut node Node) upgrade() ! { - mut bs := bootstrapper() - install_base_content_ := bs.embedded_files['install_base.sh'] or { panic('bug') } - install_base_content := install_base_content_.to_string() - cmd := '${install_base_content}\n' - node.exec_cmd( - cmd: cmd - period: 48 * 3600 - reset: false - description: 'upgrade operating system packages' - )! -} - -pub fn (mut node Node) hero_install() ! { - console.print_debug('install hero') - mut bs := bootstrapper() - install_hero_content_ := bs.embedded_files['install_hero.sh'] or { panic('bug') } - install_hero_content := install_hero_content_.to_string() - if node.platform == .osx { - // we have no choice then to do it interactive - myenv := node.environ_get()! - homedir := myenv['HOME'] or { return error("can't find HOME in env") } - node.exec_silent('mkdir -p ${homedir}/hero/bin')! - node.file_write('${homedir}/hero/bin/install.sh', install_hero_content)! - node.exec_silent('chmod +x ${homedir}/hero/bin/install.sh')! - node.exec_interactive('${homedir}/hero/bin/install.sh')! - } else if node.platform == .ubuntu { - myenv := node.environ_get()! - homedir := myenv['HOME'] or { return error("can't find HOME in env") } - node.exec_silent('mkdir -p ${homedir}/hero/bin')! - node.file_write('${homedir}/hero/bin/install.sh', install_hero_content)! - node.exec_silent('chmod +x ${homedir}/hero/bin/install.sh')! - node.exec_interactive('${homedir}/hero/bin/install.sh')! - } -} - -pub fn (mut node Node) dagu_install() ! { - console.print_debug('install dagu') - if !osal.cmd_exists('dagu') { - _ = bootstrapper() - node.exec_silent('curl -L https://raw.githubusercontent.com/yohamta/dagu/main/scripts/downloader.sh | bash')! + // n.hero_install()! } } @@ -101,47 +45,32 @@ pub fn (mut node Node) dagu_install() ! { pub struct HeroInstallArgs { pub mut: reset bool + compile bool + v_analyzer bool + debug bool //will go in shell } -// pub fn (mut node Node) hero_install(args HeroInstallArgs) ! { -// mut bs := bootstrapper() -// install_base_content_ := bs.embedded_files['install_base.sh'] or { panic('bug') } -// install_base_content := install_base_content_.to_string() +pub fn (mut node Node) hero_install(args HeroInstallArgs) ! { + console.print_debug('install hero') + mut bs := bootstrapper() -// if args.reset { -// console.clear() -// console.print_debug('') -// console.print_stderr('will remove: .vmodules, hero lib code and ~/hero') -// console.print_debug('') -// mut myui := ui.new()! -// toinstall := myui.ask_yesno( -// question: 'Ok to reset?' -// default: true -// )! -// if !toinstall { -// exit(1) -// } -// os.rmdir_all('${os.home_dir()}/.vmodules')! -// os.rmdir_all('${os.home_dir()}/hero')! -// os.rmdir_all('${os.home_dir()}/code/github/freeflowuniverse/herolib')! -// os.rmdir_all('${os.home_dir()}/code/github/freeflowuniverse/webcomponents')! -// } + myenv := node.environ_get()! + homedir := myenv['HOME'] or { return error("can't find HOME in env") } -// cmd := ' -// ${install_base_content} - -// rm -f /usr/local/bin/hero -// freeflow_dev_env_install - -// ~/code/github/freeflowuniverse/herolib/install.sh - -// echo HERO, V, CRYSTAL ALL INSTALL OK -// echo WE ARE READY TO HERO... - -// ' -// console.print_debug('executing cmd ${cmd}') -// node.exec_cmd(cmd: cmd)! -// } + mut todo := []string{} + if ! args.compile { + todo << "curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_herolib.sh > /tmp/install.sh" + todo << "bash /tmp/install.sh" + }else{ + todo << "curl 'https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_v.sh' > /tmp/install_v.sh" + if args.v_analyzer { + todo << "bash /tmp/install_v.sh --analyzer --herolib " + }else{ + todo << "bash /tmp/install_v.sh --herolib " + } + } + node.exec_interactive(todo.join('\n'))! +} @[params] pub struct HeroUpdateArgs { diff --git a/lib/builder/executor_ssh.v b/lib/builder/executor_ssh.v index 8a526d4d..1e795b99 100644 --- a/lib/builder/executor_ssh.v +++ b/lib/builder/executor_ssh.v @@ -21,7 +21,6 @@ pub mut: fn (mut executor ExecutorSSH) init() ! { if !executor.initialized { - $dbg; mut addr := executor.ipaddr.addr if addr == '' { addr = 'localhost' @@ -53,7 +52,7 @@ pub fn (mut executor ExecutorSSH) exec(args_ ExecArgs) !string { if executor.ipaddr.port > 10 { port = '-p ${executor.ipaddr.port}' } - args.cmd = 'ssh -o StrictHostKeyChecking=no ${executor.user}@${executor.ipaddr.addr} ${port} "${args.cmd}"' + args.cmd = 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${executor.user}@${executor.ipaddr.addr} ${port} "${args.cmd}"' res := osal.exec(cmd: args.cmd, stdout: args.stdout, debug: executor.debug)! return res.output } @@ -64,7 +63,8 @@ pub fn (mut executor ExecutorSSH) exec_interactive(args_ ExecArgs) ! { if executor.ipaddr.port > 10 { port = '-p ${executor.ipaddr.port}' } - args.cmd = 'ssh -tt -o StrictHostKeyChecking=no ${executor.user}@${executor.ipaddr.addr} ${port} "${args.cmd}"' + args.cmd = 'ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${executor.user}@${executor.ipaddr.addr} ${port} "${args.cmd}"' + console.print_debug(args.cmd) osal.execute_interactive(args.cmd)! } diff --git a/lib/core/httpconnection/connection_methods.v b/lib/core/httpconnection/connection_methods.v index b4944732..1d7c71f4 100644 --- a/lib/core/httpconnection/connection_methods.v +++ b/lib/core/httpconnection/connection_methods.v @@ -51,8 +51,13 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result { mut from_cache := false // used to know if result came from cache mut req := req_ - is_cacheable := h.is_cacheable(req) - // console.print_debug("is cacheable: ${is_cacheable}") + // println("Sending request: ${req}") + + mut is_cacheable := h.is_cacheable(req) + if req.debug { + //in debug mode should not cache + is_cacheable = false + } // 1 - Check cache if enabled try to get result from cache if is_cacheable { @@ -90,14 +95,14 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result { new_req.header.set(http.CommonHeader.content_type, 'multipart/form-data') } } - + if req.debug { - console.print_debug("----") - console.print_debug(" ${url} ${req.method} url:${url}\n${req.data} \n${new_req.header}") - console.print_debug("----") console.print_debug('http request:\n${new_req.str()}') } - for _ in 0 .. h.retry { + for counter in 0 .. h.retry { + if req.debug { + console.print_debug("request attempt:${counter}") + } response = new_req.do() or { err_message = 'Cannot send request:${req}\nerror:${err}' // console.print_debug(err_message) @@ -106,8 +111,9 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result { break } if req.debug { + console.print_debug("request done") console.print_debug(response.str()) - } + } if response.status_code == 0 { return error(err_message) } @@ -124,8 +130,6 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result { h.cache_invalidate(req)! } - $dbg; - // 5 - Return result return result } @@ -186,10 +190,9 @@ pub fn (mut h HTTPConnection) get_json(req Request) !string { // Get Request with json data and return response as string pub fn (mut h HTTPConnection) get(req_ Request) !string { mut req := req_ - req.debug req.method = .get result := h.send(req)! - if !result.is_ok() { + if !result.is_ok() { return error('Could not get ${req}\result:\n${result}') } return result.data diff --git a/lib/core/httpconnection/request.v b/lib/core/httpconnection/request.v index c2ee227b..95837f44 100644 --- a/lib/core/httpconnection/request.v +++ b/lib/core/httpconnection/request.v @@ -20,6 +20,6 @@ pub mut: header ?Header dict_key string // if the return is a dict, then will take the element out of the dict with the key and process further list_dict_key string // if the output is a list of dicts, then will process each element of the list to take the val with key out of that dict - debug bool = true + debug bool dataformat DataFormat } diff --git a/lib/osal/core/net.v b/lib/osal/core/net.v index 82044b56..9dfc6b22 100644 --- a/lib/osal/core/net.v +++ b/lib/osal/core/net.v @@ -4,6 +4,7 @@ import net import time import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core + import os pub enum PingResult { @@ -141,3 +142,82 @@ pub fn is_ip_on_local_interface(public_ip string) !bool { } return false } + + +//will give error if ssh test did not work +pub fn ssh_check(args TcpPortTestArgs) ! { + errmsg, res := ssh_testrun_internal(args)! + if res != .ok{ + return error(errmsg) + } +} + +pub enum SSHResult { + ok + ping // timeout from ping + tcpport // means we don't know the hostname its a dns issue + ssh +} + + +pub fn ssh_test(args TcpPortTestArgs) !SSHResult { + _, res := ssh_testrun_internal(args)! + return res +} + +//will give error if ssh test did not work +pub fn ssh_wait(args TcpPortTestArgs) ! { + + start_time := time.now().unix_milli() + mut run_time := 0.0 + for true { + run_time = time.now().unix_milli() + + errmsg, res := ssh_testrun_internal(args)! + console.print_debug(errmsg) + + if run_time > start_time + args.timeout { + return error(errmsg) + } + + if res == .ok{ + return + } + } + +} + + +fn ssh_testrun_internal(args TcpPortTestArgs) !(string,SSHResult) { + cmd:=' + ssh -o BatchMode=yes -o ConnectTimeout=3 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q "${args.address}" exit + if [ $? -eq 0 ]; then + echo "OK: SSH works" + exit 0 + fi + timeout 1 nc -z "${args.address}" 22 >/dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "ERROR: SSH failed but port ${args.port} open" + exit 1 + fi + ping -c1 -W2 "${args.address}" >/dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "ERROR: SSH & port test failed, host reachable by ping" + exit 2 + fi + echo "ERROR: Host unreachable, over ping and ssh" + exit 3 + ' + // console.print_debug('ssh test cmd: ${cmd}') + res:=exec(cmd:cmd,ignore_error:true,stdout:false,debug:false)! + // console.print_debug('ssh test result: ${res}') + if res.exit_code == 0 { + return res.output, SSHResult.ok + } else if res.exit_code == 1 { + return res.output, SSHResult.tcpport + } else if res.exit_code == 2 { + return res.output, SSHResult.ping + } else { + return res.output, SSHResult.ssh + } +} \ No newline at end of file diff --git a/lib/virt/hetznermanager/hetznermanager_model.v b/lib/virt/hetznermanager/hetznermanager_model.v index 64683ac3..654c5ee9 100644 --- a/lib/virt/hetznermanager/hetznermanager_model.v +++ b/lib/virt/hetznermanager/hetznermanager_model.v @@ -26,7 +26,6 @@ pub fn (mut h HetznerManager) connection() !&httpconnection.HTTPConnection { retry: 3 )! c2.basic_auth(h.user, h.password) - println(c2) return c2 } diff --git a/lib/virt/hetznermanager/rescue.v b/lib/virt/hetznermanager/rescue.v index 770460ef..f57bd12a 100644 --- a/lib/virt/hetznermanager/rescue.v +++ b/lib/virt/hetznermanager/rescue.v @@ -5,6 +5,7 @@ import time import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.builder +import os // ///////////////////////////RESCUE @@ -27,21 +28,32 @@ pub mut: name string wait bool = true hero_install bool - sshkey_name string + sshkey_name string @[required] reset bool // ask to do reset/rescue even if its already in that state } // put server in rescue mode, if sshkey_name not specified then will use the first one in the list -pub fn (mut h HetznerManager) server_rescue(args ServerRescueArgs) !ServerInfoDetailed { +pub fn (mut h HetznerManager) server_rescue(args_ ServerRescueArgs) !ServerInfoDetailed { + mut args := args_ mut serverinfo := h.server_info_get(id: args.id, name: args.name)! console.print_header('server ${serverinfo.server_name} goes into rescue mode') + if serverinfo.rescue && ! args.reset { + if osal.ssh_test(address: serverinfo.server_ip, port: 22)! == .ok { + + console.print_debug('server ${serverinfo.server_name} is in rescue mode') + } + serverinfo.rescue = false + } // only do it if its not in rescue yet if serverinfo.rescue == false || args.reset { - mut key := h.keys_get()![0] - if args.sshkey_name == '' { - key = h.key_get(args.sshkey_name)! + + mut keyfps := []string{} + if args.sshkey_name != '' { + keyfps << h.key_get(args.sshkey_name)!.fingerprint + }else{ + keyfps = h.keys_get()!.map(it.fingerprint) } mut conn := h.connection()! @@ -49,7 +61,7 @@ pub fn (mut h HetznerManager) server_rescue(args ServerRescueArgs) !ServerInfoDe prefix: 'boot/${serverinfo.server_number}/rescue' params: { 'os': 'linux' - 'authorized_key': key.fingerprint + 'authorized_key': keyfps[0] } dict_key: 'rescue' dataformat: .urlencoded @@ -58,28 +70,20 @@ pub fn (mut h HetznerManager) server_rescue(args ServerRescueArgs) !ServerInfoDe console.print_debug('hetzner rescue\n${rescue}') h.server_reset(id: args.id, name: args.name, wait: args.wait)! + + os.execute_opt("ssh-keygen -R ${serverinfo.server_ip}")! + } + + if args.hero_install{ + args.wait = true } if args.wait { - // now we should check if ssh is responding - // next will do that check mut b := builder.new()! mut n := b.node_new(ipaddr: serverinfo.server_ip)! - println(n) - $dbg; - // builder.executor_new(ipaddr: serverinfo.server_ip, checkconnect: 60)! - } - - if args.hero_install { - mut b := builder.new()! - mut n := b.node_new(ipaddr: serverinfo.server_ip)! - n.hero_install()! - } - - if args.hero_install { - mut b := builder.new()! - mut n := b.node_new(ipaddr: serverinfo.server_ip)! - n.hero_install()! + if args.hero_install { + n.hero_install()! + } } mut serverinfo2 := h.server_info_get(id: args.id, name: args.name)! diff --git a/lib/virt/hetznermanager/reset.v b/lib/virt/hetznermanager/reset.v index 945d196a..26a474e2 100644 --- a/lib/virt/hetznermanager/reset.v +++ b/lib/virt/hetznermanager/reset.v @@ -45,6 +45,8 @@ pub fn (mut h HetznerManager) server_reset(args ServerRebootArgs) !ResetInfo { dataformat: .urlencoded // dict_key:'reset' )! + println(o) + console.print_debug('server ${serverinfo.server_name} reset done.') // now need to wait till it goes off if serveractive { for { @@ -63,10 +65,8 @@ pub fn (mut h HetznerManager) server_reset(args ServerRebootArgs) !ResetInfo { for { time.sleep(1000 * time.millisecond) console.print_debug('wait for ${serverinfo.server_name}') - if osal.ping(address: serverinfo.server_ip)! == .ok { - console.print_debug('ping ok') - osal.tcp_port_test(address: serverinfo.server_ip, port: 22, timeout: 3000) - console.print_debug('ssh tcp port ok') + if osal.ssh_test(address: serverinfo.server_ip)! == .ok { + console.print_debug('ssh test ok') console.print_header('server is rebooted: ${serverinfo.server_name}') break } diff --git a/lib/virt/hetznermanager/serverinfo.v b/lib/virt/hetznermanager/serverinfo.v index 4adf0510..b83808c8 100644 --- a/lib/virt/hetznermanager/serverinfo.v +++ b/lib/virt/hetznermanager/serverinfo.v @@ -50,6 +50,7 @@ pub fn (mut h HetznerManager) servers_list() ![]ServerInfo { method: .get prefix: 'server' list_dict_key: 'server' + debug: false )! }