This commit is contained in:
2025-08-28 09:17:10 +02:00
parent 049f2316bd
commit c10a7f2e7b
19 changed files with 553 additions and 497 deletions

View File

@@ -1,40 +1,55 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.virt.hetzner
import freeflowuniverse.herolib.virt.hetznermanager
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.builder
import time
import os
console.print_header('Hetzner login.')
import freeflowuniverse.herolib.core.playcmds
// USE IF YOU WANT TO CONFIGURE THE HETZNER, ONLY DO THIS ONCE
// hetzner.configure("test")!
mut cl := hetzner.get('test')!
passwd:=os.environ()['HETZNER_PASSWORD'] or {
println('HETZNER_PASSWORD not set')
exit(1)
}
playcmds.run(
heroscript: '
!!hetznermanager.configure
name:"main"
user:"operations@threefold.io"
whitelist:"2111181, 2392178"
password:"${passwd}"
'
)!
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()!
}
println(cl.servers_list()!)
// 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)!
console.print_header('SSH login')
mut b := builder.new()!
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
// console.print_header('SSH login')
// mut b := builder.new()!
// mut n := b.node_new(ipaddr: serverinfo.server_ip)!
// n.hero_install()!
// n.hero_compile_debug()!
// // n.hero_install()!
// // n.hero_compile_debug()!
// mut ks:=cl.keys_get()!
// println(ks)
// // mut ks:=cl.keys_get()!
// // println(ks)

View File

@@ -21,15 +21,7 @@ pub mut:
fn (mut executor ExecutorSSH) init() ! {
if !executor.initialized {
// if executor.ipaddr.port == 0 {
// return error('port cannot be 0.\n${executor}')
// }
// TODO: need to call code from SSHAGENT do not reimplement here, not nicely done
os.execute('pgrep -x ssh-agent || eval `ssh-agent -s`')
if executor.sshkey != '' {
osal.exec(cmd: 'ssh-add ${executor.sshkey}')!
}
$dbg;
mut addr := executor.ipaddr.addr
if addr == '' {
addr = 'localhost'

View File

@@ -153,10 +153,11 @@ pub fn play(mut plbook PlayBook) ! {
mut install_actions := plbook.find(filter: '${args.name}.configure')!
if install_actions.len > 0 {
@if args.hasconfig
for install_action in install_actions {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
@else
return error("can't configure ${args.name}, because no configuration allowed for this installer.")
@@ -164,7 +165,7 @@ pub fn play(mut plbook PlayBook) ! {
}
@if args.cat == .installer
mut other_actions := plbook.find(filter: '${args.name}.')!
for other_action in other_actions {
for mut other_action in other_actions {
if other_action.name in ["destroy","install","build"]{
mut p := other_action.params
reset:=p.get_default_false("reset")
@@ -198,6 +199,7 @@ pub fn play(mut plbook PlayBook) ! {
}
}
@end
other_action.done = true
}
@end
}

View File

@@ -71,11 +71,6 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result {
}
url := h.url(req)
// println("----")
// println(url)
// println(req.data)
// println("----")
mut new_req := http.new_request(req.method, url, req.data)
// joining the header from the HTTPConnection with the one from Request
new_req.header = h.header()
@@ -97,6 +92,9 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result {
}
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 {
@@ -126,6 +124,8 @@ pub fn (mut h HTTPConnection) send(req_ Request) !Result {
h.cache_invalidate(req)!
}
$dbg;
// 5 - Return result
return result
}
@@ -189,6 +189,9 @@ pub fn (mut h HTTPConnection) get(req_ Request) !string {
req.debug
req.method = .get
result := h.send(req)!
if !result.is_ok() {
return error('Could not get ${req}\result:\n${result}')
}
return result.data
}
@@ -197,6 +200,9 @@ pub fn (mut h HTTPConnection) delete(req_ Request) !string {
mut req := req_
req.method = .delete
result := h.send(req)!
if !result.is_ok() {
return error('Could not delete ${req}\result:\n${result}')
}
return result.data
}
@@ -207,5 +213,6 @@ pub fn (mut h HTTPConnection) post_multi_part(req Request, form http.PostMultipa
header.set(http.CommonHeader.content_type, 'multipart/form-data')
req_form.header = header
url := h.url(req)
//TODO: should that not be on line with above? seems to be other codepath.
return http.post_multipart_form(url, req_form)!
}

View File

@@ -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
debug bool = true
dataformat DataFormat
}

View File

@@ -5,6 +5,7 @@ import freeflowuniverse.herolib.data.doctree
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.threefold.incatokens
import freeflowuniverse.herolib.web.site
import freeflowuniverse.herolib.virt.hetznermanager
import freeflowuniverse.herolib.web.docusaurus
import freeflowuniverse.herolib.clients.openai
import freeflowuniverse.herolib.clients.giteaclient
@@ -53,6 +54,7 @@ pub fn run(args_ PlayArgs) ! {
incatokens.play(mut plbook)!
docusaurus.play(mut plbook)!
hetznermanager.play(mut plbook)!
giteaclient.play(mut plbook)!

View File

@@ -6,6 +6,10 @@ import freeflowuniverse.herolib.core.pathlib
import os
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'incatokens.') {
return
}
console.print_header('INCA Token Simulation')
// Collect all configurations first

View File

@@ -1,274 +0,0 @@
module hetzner
import freeflowuniverse.herolib.core.texttools
import time
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.builder
/////////////////////////// LIST
pub struct ServerInfo {
pub mut:
server_ip string
server_ipv6_net string
server_number int
server_name string
product string
dc string
traffic string
status string
cancelled bool
paid_until string
ip []string
subnet []Subnet
}
pub struct ServerInfoDetailed {
ServerInfo
pub mut:
reset bool
rescue bool
vnc bool
windows bool
plesk bool
cpanel bool
wol bool
hot_swap bool
// linked_storagebox int
}
pub struct Subnet {
pub mut:
ip string
mask string
}
pub fn (mut h HetznerManager) servers_list() ![]ServerInfo {
mut conn := h.connection()!
return conn.get_json_list_generic[ServerInfo](
method: .get
prefix: 'server'
list_dict_key: 'server'
)!
}
// ///////////////////////////GETID
pub struct ServerGetArgs {
pub mut:
id int
name string
}
pub fn (mut h HetznerManager) server_info_get(args_ ServerGetArgs) !ServerInfoDetailed {
mut args := args_
args.name = texttools.name_fix(args.name)
l := h.servers_list()!
mut res := []ServerInfo{}
for item in l {
if args.id > 0 && item.server_number != args.id {
continue
}
server_name := texttools.name_fix(item.server_name)
if args.name.len > 0 && server_name != args.name {
continue
}
res << item
}
if res.len > 1 {
return error("Found too many servers with: '${args}'")
}
if res.len == 0 {
return error("couldn't find server with: '${args}'")
}
mut conn := h.connection()!
return conn.get_json_generic[ServerInfoDetailed](
method: .get
prefix: 'server/${res[0].server_number}'
dict_key: 'server'
cache_disable: true
)!
}
// ///////////////////////////RESCUE
pub struct RescueInfo {
pub mut:
server_ip string
server_ipv6_net string
server_number int
os string
arch int
active bool
password string
authorized_key []string
host_key []string
}
pub struct ServerRescueArgs {
pub mut:
id int
name string
wait bool = true
hero_install bool
hero_install bool
sshkey_name string
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 {
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
console.print_header('server ${serverinfo.server_name} goes into rescue mode')
// 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 conn := h.connection()!
rescue := conn.post_json_generic[RescueInfo](
prefix: 'boot/${serverinfo.server_number}/rescue'
params: {
'os': 'linux'
'authorized_key': key.fingerprint
}
dict_key: 'rescue'
dataformat: .urlencoded
)!
console.print_debug('hetzner rescue\n${rescue}')
h.server_reset(id: args.id, name: args.name, wait: args.wait)!
}
if args.wait {
// now we should check if ssh is responding
// next will do that check
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()!
}
mut serverinfo2 := h.server_info_get(id: args.id, name: args.name)!
return serverinfo2
}
pub fn (mut h HetznerManager) server_rescue_node(args ServerRescueArgs) !&builder.Node {
mut serverinfo := h.server_rescue(args)!
mut b := builder.new()!
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
return n
}
// /////////////////////////////////////RESET
struct ResetInfo {
server_ip string
server_ipv6_net string
server_number int
operating_status string
}
pub struct ServerRebootArgs {
pub mut:
id int
name string
wait bool = true
}
pub fn (mut h HetznerManager) server_reset(args ServerRebootArgs) !ResetInfo {
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
console.print_header('server ${serverinfo.server_name} goes for reset')
mut serveractive := false
if osal.ping(address: serverinfo.server_ip)! == .ok {
serveractive = true
console.print_debug('server ${serverinfo.server_name} is active')
} else {
console.print_debug('server ${serverinfo.server_name} is down')
}
mut conn := h.connection()!
o := conn.post_json_generic[ResetInfo](
prefix: 'reset/${serverinfo.server_number}'
params: {
'type': 'hw'
}
dataformat: .urlencoded
// dict_key:'reset'
)!
// now need to wait till it goes off
if serveractive {
for {
console.print_debug('wait for server ${serverinfo.server_name} to go down.')
if osal.ping(address: serverinfo.server_ip) != .ok {
console.print_debug('server ${serverinfo.server_name} is now down, now waitig for reboot.')
break
}
time.sleep(1000 * time.millisecond)
}
}
mut x := 0
if args.wait {
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')
console.print_header('server is rebooted: ${serverinfo.server_name}')
break
}
x += 1
if x > 60 * 5 {
// 5 min
return error('Could not reboot server ${serverinfo.server_name} in 5 min')
}
}
}
return o
}
// /////////////////////////////////////BOOT
// struct BootRoot {
// boot Boot
// }
// struct Boot {
// rescue RescueInfo
// }
// pub fn (mut h HetznerManager) server_boot(id int) !RescueInfo {
// mut conn := h.connection()!
// boot := conn.get_json[BootRoot](prefix: 'boot/${id}')!
// return boot.boot.rescue
// }

View File

@@ -1,102 +0,0 @@
module hetzner
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
__global (
hetzner_global map[string]&HetznerManager
hetzner_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) !&HetznerManager {
mut context := base.context()!
mut args := args_get(args_)
mut obj := HetznerManager{
name: args.name
}
if args.name !in hetzner_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('hetzner', args.name)!
mut obj_ := heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return hetzner_global[args.name] or {
println(hetzner_global)
// bug if we get here because should be in globals
panic('could not get config for hetzner with name, is bug:${args.name}')
}
}
// register the config for the future
pub fn set(o HetznerManager) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('hetzner', 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('hetzner', args.name)
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('hetzner', args.name)!
if args.name in hetzner_global {
// del hetzner_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o HetznerManager) ! {
mut o2 := obj_init(o)!
hetzner_global[o.name] = &o2
hetzner_default = o.name
}
pub fn play(mut plbook PlayBook) ! {
mut install_actions := plbook.find(filter: 'hetzner.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 hetzner
pub fn switch(name string) {
hetzner_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -1,67 +0,0 @@
module hetzner
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.core.httpconnection
pub const version = '1.14.3'
const singleton = false
const default = true
pub fn heroscript_default() !string {
heroscript := "
!!hetzner.configure
name:'default'
url:'https://robot-ws.your-server.de'
user:''
password:''
whitelist:''
"
return heroscript
}
// THIS IS THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct HetznerManager {
pub mut:
name string = 'default'
description string
baseurl string
whitelist string // comma separated list of servers we whitelist to work on
user string
password string
conn ?&httpconnection.HTTPConnection
}
fn cfg_play(p paramsparser.Params) !HetznerManager {
mut mycfg := HetznerManager{
name: p.get_default('name', 'default')!
baseurl: p.get_default('url', 'https://robot-ws.your-server.de')!
// TODO: whitelist
user: p.get('user')!
password: p.get('password')!
}
return mycfg
}
fn obj_init(obj_ HetznerManager) !HetznerManager {
// never call get here, only thing we can do here is work on object itself
mut obj := obj_
// Initialize connection with caching enabled
return obj
}
pub fn (mut h HetznerManager) connection() !&httpconnection.HTTPConnection {
mut c := h.conn or {
mut c2 := httpconnection.new(
name: 'hetzner_${h.name}'
url: h.baseurl
cache: true
retry: 3
)!
c2.basic_auth(h.user, h.password)
c2
}
return c
}

View File

@@ -1,6 +1,6 @@
!!hero_code.generate_client
name:'hetzner'
name:'hetznermanager'
classname:'HetznerManager'
singleton:0
default:1

View File

@@ -1,4 +1,4 @@
module hetzner
module hetznermanager
import freeflowuniverse.herolib.core.texttools

View File

@@ -0,0 +1,137 @@
module hetznermanager
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
import json
__global (
hetznermanager_global map[string]&HetznerManager
hetznermanager_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&HetznerManager {
mut obj := HetznerManager{
name: args.name
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&HetznerManager {
mut context := base.context()!
hetznermanager_default = args.name
if args.fromdb || args.name !in hetznermanager_global {
mut r := context.redis()!
if r.hexists('context:hetznermanager', args.name)! {
data := r.hget('context:hetznermanager', args.name)!
if data.len == 0 {
return error('HetznerManager with name: hetznermanager does not exist, prob bug.')
}
mut obj := json.decode(HetznerManager, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
return error("HetznerManager with name 'hetznermanager' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return hetznermanager_global[args.name] or {
return error('could not get config for hetznermanager with name:hetznermanager')
}
}
// register the config for the future
pub fn set(o HetznerManager) ! {
mut o2 := set_in_mem(o)!
hetznermanager_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:hetznermanager', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:hetznermanager', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:hetznermanager', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&HetznerManager {
mut res := []&HetznerManager{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
hetznermanager_global = map[string]&HetznerManager{}
hetznermanager_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:hetznermanager')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in hetznermanager_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o HetznerManager) !HetznerManager {
mut o2 := obj_init(o)!
hetznermanager_global[o2.name] = &o2
hetznermanager_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'hetznermanager.') {
return
}
mut install_actions := plbook.find(filter: 'hetznermanager.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
}
}
// switch instance to be used for hetznermanager
pub fn switch(name string) {
hetznermanager_default = name
}

View File

@@ -0,0 +1,41 @@
module hetznermanager
import freeflowuniverse.herolib.core.httpconnection
import freeflowuniverse.herolib.data.encoderhero
pub const version = '0.0.0'
const singleton = false
const default = true
@[heap]
pub struct HetznerManager {
pub mut:
name string = 'default'
description string
baseurl string = 'https://robot-ws.your-server.de'
whitelist []string // comma separated list of servers we whitelist to work on
user string
password string
}
pub fn (mut h HetznerManager) connection() !&httpconnection.HTTPConnection {
mut c2 := httpconnection.new(
name: 'hetzner_${h.name}'
url: h.baseurl
cache: true
retry: 3
)!
c2.basic_auth(h.user, h.password)
println(c2)
return c2
}
fn obj_init(mycfg_ HetznerManager) !HetznerManager {
mut mycfg := mycfg_
return mycfg
}
pub fn heroscript_loads(heroscript string) !HetznerManager {
mut obj := encoderhero.decode[HetznerManager](heroscript)!
return obj
}

View File

@@ -1,4 +1,4 @@
module hetzner
module hetznermanager
// TODO: couldn't get ssh lib to work

View File

@@ -4,22 +4,28 @@ This module provides a V client for interacting with Hetzner's Robot API, allowi
## Setup
1. Create an account on [Hetzner Robot](https://robot.hetzner.com/preferences/index)
1. Create an account on [Hetzner Robot](https://robot.hetznermanager.com/preferences/index)
2. Configure the client using heroscript:
3.
```v
import freeflowuniverse.herolib.virt.hetzner
heroscript := "
!!hetzner.configure
name:'my_instance'
url:'https://robot-ws.your-server.de'
user:'your-username'
password:'your-password'
whitelist:'' // comma separated list of servers to whitelist
"
import freeflowuniverse.herolib.core.playcmds
passwd:=os.environ()['HETZNER_PASSWORD'] or {
println('HETZNER_PASSWORD not set')
exit(1)
}
playcmds.run(
heroscript: '
!!hetznermanager.configure
name:"main"
user:"operations@threefold.io"
whitelist:"2111181, 2392178"
password:"${passwd}"
'
)!
// Apply the configuration (only needs to be done once)
hetzner.play(heroscript: heroscript)!
```
## Usage
@@ -27,7 +33,7 @@ hetzner.play(heroscript: heroscript)!
### Initialize Client
```v
// Get a configured client instance
mut cl := hetzner.get(name: 'my_instance')!
mut cl := hetznermanager.get(name: 'main')!
```
### Configuration Notes
@@ -40,7 +46,7 @@ mut cl := hetzner.get(name: 'my_instance')!
### Examples
> see examples/virt/hetzner
> see examples/virt/hetznermanager
### Available Operations
@@ -100,12 +106,12 @@ Here's a complete example showing common operations:
```v
#!/usr/bin/env -S v run
import freeflowuniverse.herolib.virt.hetzner
import freeflowuniverse.herolib.virt.hetznermanager
import freeflowuniverse.herolib.ui.console
fn main() {
// Get client instance
mut cl := hetzner.get('my_instance')!
mut cl := hetznermanager.get('my_instance')!
// List all servers
servers := cl.servers_list()!
@@ -141,4 +147,4 @@ fn main() {
- Server operations that include `wait: true` will monitor the server until the operation completes
- Reset operations with `wait: true` will timeout after 5 minutes if the server doesn't come back online
- The module automatically manages SSH keys for rescue mode operations
- API description is on https://robot.hetzner.com/doc/webservice/en.html#preface
- API description is on https://robot.hetznermanager.com/doc/webservice/en.html#preface

View File

@@ -0,0 +1,97 @@
module hetznermanager
import freeflowuniverse.herolib.core.texttools
import time
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.builder
// ///////////////////////////RESCUE
pub struct RescueInfo {
pub mut:
server_ip string
server_ipv6_net string
server_number int
os string
arch int
active bool
password string
authorized_key []string
host_key []string
}
pub struct ServerRescueArgs {
pub mut:
id int
name string
wait bool = true
hero_install bool
sshkey_name string
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 {
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
console.print_header('server ${serverinfo.server_name} goes into rescue mode')
// 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 conn := h.connection()!
rescue := conn.post_json_generic[RescueInfo](
prefix: 'boot/${serverinfo.server_number}/rescue'
params: {
'os': 'linux'
'authorized_key': key.fingerprint
}
dict_key: 'rescue'
dataformat: .urlencoded
)!
console.print_debug('hetzner rescue\n${rescue}')
h.server_reset(id: args.id, name: args.name, wait: args.wait)!
}
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()!
}
mut serverinfo2 := h.server_info_get(id: args.id, name: args.name)!
return serverinfo2
}
pub fn (mut h HetznerManager) server_rescue_node(args ServerRescueArgs) !&builder.Node {
mut serverinfo := h.server_rescue(args)!
mut b := builder.new()!
mut n := b.node_new(ipaddr: serverinfo.server_ip)!
return n
}

View File

@@ -0,0 +1,98 @@
module hetznermanager
import freeflowuniverse.herolib.core.texttools
import time
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.builder
// /////////////////////////////////////RESET
struct ResetInfo {
server_ip string
server_ipv6_net string
server_number int
operating_status string
}
@[params]
pub struct ServerRebootArgs {
pub mut:
id int
name string
wait bool = true
}
pub fn (mut h HetznerManager) server_reset(args ServerRebootArgs) !ResetInfo {
mut serverinfo := h.server_info_get(id: args.id, name: args.name)!
console.print_header('server ${serverinfo.server_name} goes for reset')
mut serveractive := false
if osal.ping(address: serverinfo.server_ip)! == .ok {
serveractive = true
console.print_debug('server ${serverinfo.server_name} is active')
} else {
console.print_debug('server ${serverinfo.server_name} is down')
}
mut conn := h.connection()!
o := conn.post_json_generic[ResetInfo](
prefix: 'reset/${serverinfo.server_number}'
params: {
'type': 'hw'
}
dataformat: .urlencoded
// dict_key:'reset'
)!
// now need to wait till it goes off
if serveractive {
for {
console.print_debug('wait for server ${serverinfo.server_name} to go down.')
pingresult := osal.ping(address: serverinfo.server_ip)!
if pingresult != .ok {
console.print_debug('server ${serverinfo.server_name} is now down, now waitig for reboot.')
break
}
time.sleep(1000 * time.millisecond)
}
}
mut x := 0
if args.wait {
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')
console.print_header('server is rebooted: ${serverinfo.server_name}')
break
}
x += 1
if x > 60 * 5 {
// 5 min
return error('Could not reboot server ${serverinfo.server_name} in 5 min')
}
}
}
return o
}
// /////////////////////////////////////BOOT
// struct BootRoot {
// boot Boot
// }
// struct Boot {
// rescue RescueInfo
// }
// pub fn (mut h HetznerManager) server_boot(id int) !RescueInfo {
// mut conn := h.connection()!
// boot := conn.get_json[BootRoot](prefix: 'boot/${id}')!
// return boot.boot.rescue
// }

View File

@@ -0,0 +1,98 @@
module hetznermanager
import freeflowuniverse.herolib.core.texttools
import time
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.builder
/////////////////////////// LIST
pub struct ServerInfo {
pub mut:
server_ip string
server_ipv6_net string
server_number int
server_name string
product string
dc string
traffic string
status string
cancelled bool
paid_until string
ip []string
subnet []Subnet
}
pub struct ServerInfoDetailed {
ServerInfo
pub mut:
reset bool
rescue bool
vnc bool
windows bool
plesk bool
cpanel bool
wol bool
hot_swap bool
// linked_storagebox int
}
pub struct Subnet {
pub mut:
ip string
mask string
}
pub fn (mut h HetznerManager) servers_list() ![]ServerInfo {
mut conn := h.connection()!
return conn.get_json_list_generic[ServerInfo](
method: .get
prefix: 'server'
list_dict_key: 'server'
)!
}
// ///////////////////////////GETID
pub struct ServerGetArgs {
pub mut:
id int
name string
}
pub fn (mut h HetznerManager) server_info_get(args_ ServerGetArgs) !ServerInfoDetailed {
mut args := args_
args.name = texttools.name_fix(args.name)
l := h.servers_list()!
mut res := []ServerInfo{}
for item in l {
if args.id > 0 && item.server_number != args.id {
continue
}
server_name := texttools.name_fix(item.server_name)
if args.name.len > 0 && server_name != args.name {
continue
}
res << item
}
if res.len > 1 {
return error("Found too many servers with: '${args}'")
}
if res.len == 0 {
return error("couldn't find server with: '${args}'")
}
mut conn := h.connection()!
return conn.get_json_generic[ServerInfoDetailed](
method: .get
prefix: 'server/${res[0].server_number}'
dict_key: 'server'
cache_disable: true
)!
}