refactor: Update cryptpad installer code

- Use installer.kube_client for Kubernetes operations
- Remove redundant startupmanager calls
- Simplify `delete_resource` command
- Add default values for installer name and hostname
- Refactor `get` function to use new arguments correctly
- Remove commented out example code and unused imports
- Change the factory file<REQUIRED> to load the default instance name
- Update the README file of the installer

Co-authored-by: peternahaaat <peternashaaat@gmail.com>
This commit is contained in:
Mahmoud-Emad
2025-11-02 13:34:44 +02:00
parent 86549480b5
commit 44c8793074
14 changed files with 229 additions and 1081 deletions

View File

@@ -6,14 +6,18 @@ import incubaid.herolib.installers.k8s.cryptpad
// 1. Create a new installer instance with a specific hostname. // 1. Create a new installer instance with a specific hostname.
// Replace 'mycryptpad' with your desired hostname. // Replace 'mycryptpad' with your desired hostname.
mut installer := cryptpad.new(hostname: 'omda')! mut installer := cryptpad.get(
name: 'kristof'
create: true
)!
// 2. Install CryptPad. // 2. Install CryptPad.
// This will generate the necessary Kubernetes YAML files and apply them to your cluster. // This will generate the necessary Kubernetes YAML files and apply them to your cluster.
installer.install()! // installer.install()!
// cryptpad.delete()!
println('CryptPad installation started.') // println('CryptPad installation started.')
println('You can access it at https://${installer.hostname}.gent01.grid.tf') // println('You can access it at https://${installer.hostname}.gent01.grid.tf')
// 3. To destroy the deployment, you can run the following: // 3. To destroy the deployment, you can run the following:
// installer.destroy()! installer.destroy()!

View File

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

View File

@@ -1,187 +0,0 @@
module cryptpad
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
import incubaid.herolib.core.texttools
import incubaid.herolib.core.pathlib
import incubaid.herolib.osal.systemd
import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist
import incubaid.herolib.installers.lang.golang
import incubaid.herolib.installers.lang.rust
import incubaid.herolib.installers.lang.python
import os
fn startupcmd() ![]startupmanager.ZProcessNewArgs {
mut installer := get()!
mut res := []startupmanager.ZProcessNewArgs{}
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// res << startupmanager.ZProcessNewArgs{
// name: 'cryptpad'
// cmd: 'cryptpad server'
// env: {
// 'HOME': '/root'
// }
// }
return res
}
fn running() !bool {
mut installer := get()!
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// this checks health of cryptpad
// 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: 'cryptpad', 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('cryptpad is answering.')
return false
}
fn start_pre() ! {
}
fn start_post() ! {
}
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 {
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// res := os.execute('${osal.profile_path_source_and()!} cryptpad 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 cryptpad version.\n${res.output}")
// }
// if texttools.version(version) == texttools.version(r[0]) {
// 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: 'cryptpad'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/cryptpad'
// )!
}
fn install() ! {
console.print_header('install cryptpad')
// THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// mut url := ''
// if core.is_linux_arm()! {
// url = 'https://github.com/cryptpad-dev/cryptpad/releases/download/v${version}/cryptpad_${version}_linux_arm64.tar.gz'
// } else if core.is_linux_intel()! {
// url = 'https://github.com/cryptpad-dev/cryptpad/releases/download/v${version}/cryptpad_${version}_linux_amd64.tar.gz'
// } else if core.is_osx_arm()! {
// url = 'https://github.com/cryptpad-dev/cryptpad/releases/download/v${version}/cryptpad_${version}_darwin_arm64.tar.gz'
// } else if osal.is_osx_intel()! {
// url = 'https://github.com/cryptpad-dev/cryptpad/releases/download/v${version}/cryptpad_${version}_darwin_amd64.tar.gz'
// } else {
// return error('unsported platform')
// }
// mut dest := osal.download(
// url: url
// minsize_kb: 9000
// expand_dir: '/tmp/cryptpad'
// )!
// //dest.moveup_single_subdir()!
// mut binpath := dest.file_get('cryptpad')!
// osal.cmd_add(
// cmdname: 'cryptpad'
// source: binpath.path
// )!
}
fn build() ! {
// url := 'https://github.com/threefoldtech/cryptpad'
// 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 cryptpad')
// gitpath := gittools.get_repo(coderoot: '/tmp/builder', url: url, reset: true, pull: true)!
// cmd := '
// cd ${gitpath}
// source ~/.cargo/env
// exit 1 #todo
// '
// osal.execute_stdout(cmd)!
//
// //now copy to the default bin path
// mut binpath := dest.file_get('...')!
// adds it to path
// osal.cmd_add(
// cmdname: 'griddriver2'
// source: binpath.path
// )!
}
fn destroy() ! {
// mut systemdfactory := systemd.new()!
// systemdfactory.destroy("zinit")!
// 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,312 +0,0 @@
module cryptpad
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
import incubaid.herolib.osal.startupmanager
import time
__global (
cryptpad_global map[string]&CryptPad
cryptpad_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) !&CryptPad {
mut obj := CryptPad{
name: args.name
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&CryptPad {
mut context := base.context()!
cryptpad_default = args.name
if args.fromdb || args.name !in cryptpad_global {
mut r := context.redis()!
if r.hexists('context:cryptpad', args.name)! {
data := r.hget('context:cryptpad', args.name)!
if data.len == 0 {
print_backtrace()
return error('CryptPad with name: ${args.name} does not exist, prob bug.')
}
mut obj := json.decode(CryptPad, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("CryptPad with name '${args.name}' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return cryptpad_global[args.name] or {
print_backtrace()
return error('could not get config for cryptpad with name:${args.name}')
}
}
// register the config for the future
pub fn set(o CryptPad) ! {
mut o2 := set_in_mem(o)!
cryptpad_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:cryptpad', 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:cryptpad', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:cryptpad', 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) ![]&CryptPad {
mut res := []&CryptPad{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
cryptpad_global = map[string]&CryptPad{}
cryptpad_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:cryptpad')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in cryptpad_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o CryptPad) !CryptPad {
mut o2 := obj_init(o)!
cryptpad_global[o2.name] = &o2
cryptpad_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'cryptpad.') {
return
}
mut install_actions := plbook.find(filter: 'cryptpad.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
}
}
mut other_actions := plbook.find(filter: 'cryptpad.')!
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')
if other_action.name == 'destroy' || reset {
console.print_debug('install action cryptpad.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action cryptpad.install')
install()!
}
}
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut cryptpad_obj := get(name: name)!
console.print_debug('action object:\n${cryptpad_obj}')
if other_action.name == 'start' {
console.print_debug('install action cryptpad.${other_action.name}')
cryptpad_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action cryptpad.${other_action.name}')
cryptpad_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action cryptpad.${other_action.name}')
cryptpad_obj.restart()!
}
}
other_action.done = true
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: cryptpad' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: cryptpad' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: cryptpad' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: cryptpad' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized
pub fn (mut self CryptPad) reload() ! {
switch(self.name)
self = obj_init(self)!
}
pub fn (mut self CryptPad) start() ! {
switch(self.name)
if self.running()! {
return
}
console.print_header('installer: cryptpad start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: cryptpad starting 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('cryptpad did not install properly.')
}
pub fn (mut self CryptPad) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self CryptPad) 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 CryptPad) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self CryptPad) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
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 CryptPad) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self CryptPad) build() ! {
switch(self.name)
build()!
}
pub fn (mut self CryptPad) destroy() ! {
switch(self.name)
self.stop() or {}
destroy()!
}
// switch instance to be used for cryptpad
pub fn switch(name string) {
cryptpad_default = name
}

View File

@@ -1,52 +0,0 @@
module cryptpad
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import os
pub const version = '0.0.0'
const singleton = false
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 CryptPad {
pub mut:
name string = 'default'
domain string
domain_sandbox string
configpath string //can be left empty, will be set to default path (is kubernetes config file)
cryptpad_configpath string //can be left empty, will be set to default path
masters []string //list of master servers for kubernetes
domainname string //can be 1 name e.g. mycryptpad or it can be a fully qualified domain name e.g. mycryptpad.mycompany.com
}
// your checking & initialization code if needed
fn obj_init(mycfg_ CryptPad) !CryptPad {
mut mycfg := mycfg_
if mycfg.domain == '' {
return error('CryptPad client "${mycfg.name}" missing domain')
}
if mycfg.configpath == '' {
mycfg.configpath = '${os.home_dir()}/.apps/cryptpad/${mycfg.name}/config.yaml'
}
//call kubernetes client to get master nodes and put them in
mycfg.masters = []string{} //TODO get from kubernetes
return mycfg
}
// called before start if done
fn configure() ! {
mut installer := get()!
mut mycode := $tmpl('templates/main.yaml')
mut path := pathlib.get_file(path: cfg.configpath, create: true)!
path.write(mycode)!
console.print_debug(mycode)
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_loads(heroscript string) !CryptPad {
mut obj := encoderhero.decode[CryptPad](heroscript)!
return obj
}

View File

@@ -1,44 +0,0 @@
# cryptpad
To get started
```v
import incubaid.herolib.installers.something.cryptpad as cryptpad_installer
heroscript:="
!!cryptpad.configure name:'test'
password: '1234'
port: 7701
!!cryptpad.start name:'test' reset:1
"
cryptpad_installer.play(heroscript=heroscript)!
//or we can call the default and do a start with reset
//mut installer:= cryptpad_installer.get()!
//installer.start(reset:true)!
```
## example heroscript
```hero
!!cryptpad.configure
homedir: '/home/user/cryptpad'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
```

View File

@@ -1,127 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: collab
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cryptpad-config
namespace: collab
data:
config.js: |
module.exports = {
httpUnsafeOrigin: 'https://cryptpadp2.gent01.grid.tf',
httpSafeOrigin: 'https://cryptpads2.gent01.grid.tf',
httpAddress: '0.0.0.0',
httpPort: 3000,
websocketPort: 3003,
websocketPath: '/cryptpad_websocket',
blockPath: './block',
blobPath: './blob',
dataPath: './data',
filePath: './datastore',
logToStdout: true,
logLevel: 'info',
};
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cryptpad
namespace: collab
spec:
replicas: 1
selector:
matchLabels: { app: cryptpad }
template:
metadata:
labels: { app: cryptpad }
spec:
initContainers:
- name: fix-perms
image: busybox:1.36
command: ["/bin/sh","-lc"]
args:
- >
chown -R 4001:4001
/cryptpad/blob /cryptpad/block /cryptpad/data /cryptpad/datastore /cryptpad/customize || true
volumeMounts:
- { name: blob, mountPath: /cryptpad/blob }
- { name: block, mountPath: /cryptpad/block }
- { name: data, mountPath: /cryptpad/data }
- { name: files, mountPath: /cryptpad/datastore }
- { name: customize, mountPath: /cryptpad/customize }
containers:
- name: cryptpad
image: cryptpad/cryptpad:latest
ports:
- { name: http, containerPort: 3000 }
- { name: ws, containerPort: 3003 }
env:
- { name: CPAD_CONF, value: "/cryptpad/config/config.js" }
- { name: CPAD_MAIN_DOMAIN, value: "https://cryptpadp2.gent01.grid.tf" }
- { name: CPAD_SANDBOX_DOMAIN, value: "https://cryptpads2.gent01.grid.tf" }
- { name: CPAD_INSTALL_ONLYOFFICE, value: "no" }
readinessProbe:
httpGet: { path: /, port: 3000 }
initialDelaySeconds: 20
periodSeconds: 10
volumeMounts:
- { name: cfg, mountPath: /cryptpad/config/config.js, subPath: config.js, readOnly: true }
- { name: blob, mountPath: /cryptpad/blob }
- { name: block, mountPath: /cryptpad/block }
- { name: data, mountPath: /cryptpad/data }
- { name: files, mountPath: /cryptpad/datastore }
- { name: customize, mountPath: /cryptpad/customize }
volumes:
- name: cfg
configMap:
name: cryptpad-config
items: [{ key: config.js, path: config.js }]
- { name: blob, emptyDir: {} }
- { name: block, emptyDir: {} }
- { name: data, emptyDir: {} }
- { name: files, emptyDir: {} }
- { name: customize, emptyDir: {} }
---
apiVersion: v1
kind: Service
metadata:
name: cryptpad
namespace: collab
spec:
selector: { app: cryptpad }
ports:
- { name: http, port: 3000, targetPort: 3000 }
- { name: ws, port: 3003, targetPort: 3003 }
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cryptpad
namespace: collab
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: cryptpadp2.gent01.grid.tf
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: cryptpad, port: { number: 3000 } } }
- path: /cryptpad_websocket
pathType: Prefix
backend: { service: { name: cryptpad, port: { number: 3003 } } }
- host: cryptpads2.gent01.grid.tf
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: cryptpad, port: { number: 3000 } } }
- path: /cryptpad_websocket
pathType: Prefix
backend: { service: { name: cryptpad, port: { number: 3003 } } }

View File

@@ -1,28 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: collab
---
apiVersion: ingress.grid.tf/v1
kind: TFGW
metadata:
name: cryptpad-main
namespace: collab
spec:
hostname: "cryptpadp2"
backends:
- "http://[49a:bfc3:59bf:872c:ff0f:d25f:751c:5106]:80"
- "http://[53c:609e:6488:9ddd:ff0f:2ffc:ac2a:94a6]:80"
- "http://[58e:ca1d:ce3d:355c:ff0f:a065:a40a:a08c]:80"
---
apiVersion: ingress.grid.tf/v1
kind: TFGW
metadata:
name: cryptpad-sandbox
namespace: collab
spec:
hostname: "cryptpads2"
backends:
- "http://[49a:bfc3:59bf:872c:ff0f:d25f:751c:5106]:80"
- "http://[53c:609e:6488:9ddd:ff0f:2ffc:ac2a:94a6]:80"
- "http://[58e:ca1d:ce3d:355c:ff0f:a065:a40a:a08c]:80"

View File

@@ -3,7 +3,7 @@
classname:'CryptpadServer' classname:'CryptpadServer'
singleton:0 //there can only be 1 object in the globals, is called 'default' singleton:0 //there can only be 1 object in the globals, is called 'default'
templates:1 //are there templates for the installer templates:1 //are there templates for the installer
default:1 //can we create a default when the factory is used default:0 //can we create a default when the factory is used
title:'' title:''
supported_platforms:'' //osx, ... (empty means all) supported_platforms:'' //osx, ... (empty means all)
reset:0 // regenerate all, dangerous !!! reset:0 // regenerate all, dangerous !!!

View File

@@ -0,0 +1,72 @@
# CryptPad Kubernetes Installer
A Kubernetes installer for CryptPad with TFGrid Gateway integration.
## Quick Start
```v
import incubaid.herolib.installers.k8s.cryptpad
// Create and install CryptPad
mut installer := cryptpad.get(
name: 'mycryptpad'
create: true
)!
installer.install()!
```
to change the hostname and the namespace, you can override the default values:
```v
mut installer := cryptpad.get(
name: 'mycryptpad'
create: true
)!
installer.hostname = 'customhostname'
installer.namespace = 'customnamespace'
installer.install()!
```
## Usage
### Create an Instance
```v
mut installer := cryptpad.get(
name: 'mycryptpad' // Unique name for this instance
create: true // Create if doesn't exist
)!
```
The instance name will be used as:
- Kubernetes namespace name
- Hostname prefix (e.g., `mycryptpad.gent01.grid.tf`)
### Install
```v
installer.install()!
```
This will:
1. Generate Kubernetes YAML files for CryptPad and TFGrid Gateway
2. Apply them to your k3s cluster
3. Wait for deployment to be ready
### Destroy
```v
installer.destroy()!
```
Removes all CryptPad resources from the cluster.
## Requirements
- kubectl installed and configured
- k3s cluster running
- Redis server running (for configuration storage)

View File

@@ -2,38 +2,18 @@ module cryptpad
import incubaid.herolib.osal.core as osal import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
import incubaid.herolib.osal.startupmanager
import incubaid.herolib.installers.ulist import incubaid.herolib.installers.ulist
import incubaid.herolib.virt.kubernetes
import os
import strings
import time import time
const max_deployment_retries = 30 const max_deployment_retries = 30
const deployment_check_interval_seconds = 2 const deployment_check_interval_seconds = 2
fn startupcmd() ![]startupmanager.ZProcessNewArgs { //////////////////// following actions are not specific to instance of the object
// We don't have a long-running process to manage with startupmanager for this installer,
// but we'll keep the function for consistency.
return []startupmanager.ZProcessNewArgs{}
}
fn kubectl_installed() ! { // checks if a certain version or above is installed
// Check if kubectl command exists fn installed() !bool {
if !osal.cmd_exists('kubectl') {
return error('kubectl is not installed. Please install it to continue.')
}
// Check if kubectl is configured to connect to a cluster
mut k8s := kubernetes.get()!
if !k8s.test_connection()! {
return error('kubectl is not configured to connect to a Kubernetes cluster. Please check your kubeconfig.')
}
}
fn running() !bool {
installer := get()! installer := get()!
mut k8s := kubernetes.get()! mut k8s := installer.kube_client
// Try to get the cryptpad deployment // Try to get the cryptpad deployment
deployments := k8s.get_deployments(installer.namespace) or { deployments := k8s.get_deployments(installer.namespace) or {
@@ -51,59 +31,18 @@ fn running() !bool {
return false return false
} }
fn start_pre() ! {
}
fn start_post() ! {
}
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 {
return running()
}
// get the Upload List of the files // get the Upload List of the files
fn ulist_get() !ulist.UList { 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{} return ulist.UList{}
} }
// uploads to S3 server if configured
fn upload() ! { fn upload() ! {
// Not needed for this installer. // installers.upload(
} // cmdname: 'cryptpad'
// source: '${gitpath}/target/x86_64-unknown-linux-musl/release/cryptpad'
fn get_master_node_ips() ![]string { // )!
mut master_ips := []string{}
mut k8s := kubernetes.get()!
// Get all nodes using the kubernetes client
nodes := k8s.get_nodes()!
// Extract IPv6 internal IPs from all nodes (dual-stack support)
for node in nodes {
// Check all internal IPs (not just the first one) for IPv6 addresses
for ip in node.internal_ips {
if ip.len > 0 && ip.contains(':') {
master_ips << ip
}
}
}
return master_ips
}
struct ConfigValues {
pub mut:
hostname string
backends string
namespace string
} }
fn install() ! { fn install() ! {
@@ -111,46 +50,14 @@ fn install() ! {
// Get installer config to access namespace // Get installer config to access namespace
installer := get()! installer := get()!
if installer.hostname == '' { mut k8s := installer.kube_client
return error('hostname is empty') configure()!
}
// Configure kubernetes client with the correct namespace
mut k8s := kubernetes.get()!
k8s.config.namespace = installer.namespace
// 1. Check for dependencies. // 1. Check for dependencies.
console.print_info('Checking for kubectl...') console.print_info('Checking for kubectl...')
kubectl_installed()! kubectl_installed()!
console.print_info('kubectl is installed and configured.') console.print_info('kubectl is installed and configured.')
// 2. Get Kubernetes master node IPs.
console.print_info('Getting Kubernetes master node IPs...')
master_ips := get_master_node_ips()!
console.print_info('Master node IPs: ${master_ips}')
// 3. Generate YAML files from templates.
console.print_info('Generating YAML files from templates...')
mut backends_str_builder := strings.new_builder(100)
for ip in master_ips {
backends_str_builder.writeln(' - "http://[${ip}]:80"')
}
config_values := ConfigValues{
hostname: installer.hostname
backends: backends_str_builder.str()
namespace: installer.namespace
}
// Write to tfgw file
temp := $tmpl('./templates/tfgw-cryptpad.yaml')
os.write_file('/tmp/tfgw-cryptpad.yaml', temp)!
// write to cryptpad yaml file
temp2 := $tmpl('./templates/cryptpad.yaml')
os.write_file('/tmp/cryptpad.yaml', temp2)!
console.print_info('YAML files generated successfully.')
// 4. Apply the YAML files using kubernetes client // 4. Apply the YAML files using kubernetes client
console.print_info('Applying Gateway YAML file to the cluster...') console.print_info('Applying Gateway YAML file to the cluster...')
res1 := k8s.apply_yaml('/tmp/tfgw-cryptpad.yaml')! res1 := k8s.apply_yaml('/tmp/tfgw-cryptpad.yaml')!
@@ -175,7 +82,7 @@ fn install() ! {
console.print_info('Verifying deployment status...') console.print_info('Verifying deployment status...')
mut is_running := false mut is_running := false
for i in 0 .. max_deployment_retries { for i in 0 .. max_deployment_retries {
if running()! { if installed()! {
is_running = true is_running = true
break break
} }
@@ -199,10 +106,11 @@ pub mut:
retry int = 30 retry int = 30
} }
// Function for verifying the generating of of the FQDN using tfgw crd // Function for verifying the generating of of the FQDN using tfgw crd
fn verify_tfgw_deployment(args VerifyTfgwDeployment) ! { fn verify_tfgw_deployment(args VerifyTfgwDeployment) ! {
console.print_info('Verifying TFGW deployment for ${args.tfgw_name}...') console.print_info('Verifying TFGW deployment for ${args.tfgw_name}...')
mut k8s := kubernetes.get()! installer := get()!
mut k8s := installer.kube_client
mut is_fqdn_generated := false mut is_fqdn_generated := false
for i in 0 .. args.retry { for i in 0 .. args.retry {
@@ -240,17 +148,42 @@ fn verify_tfgw_deployment(args VerifyTfgwDeployment) ! {
fn destroy() ! { fn destroy() ! {
console.print_header('Destroying CryptPad...') console.print_header('Destroying CryptPad...')
installer := get()! installer := get()!
mut k8s := kubernetes.get()! mut k8s := installer.kube_client
console.print_debug('Attempting to delete namespace: ${installer.namespace}')
// Delete the namespace using kubernetes client // Delete the namespace using kubernetes client
result := k8s.delete_resource('namespace', installer.namespace, '') or { result := k8s.delete_resource('namespace', installer.namespace, '') or {
console.print_stderr('Failed to delete namespace ${installer.namespace}: ${err}') console.print_stderr('Failed to delete namespace ${installer.namespace}: ${err}')
return return error('Failed to delete namespace ${installer.namespace}: ${err}')
} }
console.print_debug('Delete command completed. Exit code: ${result.exit_code}, Success: ${result.success}')
if !result.success { if !result.success {
console.print_stderr('Failed to delete namespace ${installer.namespace}: ${result.stderr}') // Namespace not found is OK - it means it's already deleted
if result.stderr.contains('NotFound') {
console.print_info('Namespace ${installer.namespace} does not exist (already deleted).')
} else {
console.print_stderr('Failed to delete namespace ${installer.namespace}: ${result.stderr}')
return error('Failed to delete namespace ${installer.namespace}: ${result.stderr}')
}
} else { } else {
console.print_info('Namespace ${installer.namespace} deleted.') console.print_info('Namespace ${installer.namespace} deleted successfully.')
}
}
fn kubectl_installed() ! {
// Check if kubectl command exists
if !osal.cmd_exists('kubectl') {
return error('kubectl is not installed. Please install it to continue.')
}
// Check if kubectl is configured to connect to a cluster
installer := get()!
mut k8s := installer.kube_client
if !k8s.test_connection()! {
return error('kubectl is not configured to connect to a Kubernetes cluster. Please check your kubeconfig.')
} }
} }

View File

@@ -4,8 +4,6 @@ import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook } import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
import json import json
import incubaid.herolib.osal.startupmanager
import time
__global ( __global (
cryptpad_global map[string]&CryptpadServer cryptpad_global map[string]&CryptpadServer
@@ -17,26 +15,25 @@ __global (
@[params] @[params]
pub struct ArgsGet { pub struct ArgsGet {
pub mut: pub mut:
name string = 'default' name string = 'cryptpad'
fromdb bool // will load from filesystem fromdb bool // will load from filesystem
create bool // default will not create if not exist create bool // default will not create if not exist
hostname string
namespace string
} }
pub fn new(args ArgsGet) !&CryptpadServer { pub fn new(args ArgsGet) !&CryptpadServer {
mut obj := CryptpadServer{ mut obj := CryptpadServer{
name: args.name name: args.name
hostname: args.hostname
namespace: args.namespace
} }
set(obj)! set(obj)!
return get(name: args.name)! return get(name: args.name)!
} }
pub fn get(args ArgsGet) !&CryptpadServer { pub fn get(args_ ArgsGet) !&CryptpadServer {
mut args := args_
mut context := base.context()! mut context := base.context()!
cryptpad_default = args.name if args.name == 'cryptpad' && cryptpad_default != '' {
args.name = cryptpad_default
}
if args.fromdb || args.name !in cryptpad_global { if args.fromdb || args.name !in cryptpad_global {
mut r := context.redis()! mut r := context.redis()!
if r.hexists('context:cryptpad', args.name)! { if r.hexists('context:cryptpad', args.name)! {
@@ -55,12 +52,17 @@ pub fn get(args ArgsGet) !&CryptpadServer {
return error("CryptpadServer with name '${args.name}' does not exist") return error("CryptpadServer with name '${args.name}' does not exist")
} }
} }
return get(name: args.name)! // no longer from db nor create return get(
name: args.name
fromdb: args.fromdb
create: args.create
)! // no longer from db nor create
} }
return cryptpad_global[args.name] or { result := cryptpad_global[args.name] or {
print_backtrace() print_backtrace()
return error('could not get config for cryptpad with name:${args.name}') return error('could not get config for cryptpad with name:${args.name}')
} }
return result
} }
// register the config for the future // register the config for the future
@@ -69,7 +71,8 @@ pub fn set(o CryptpadServer) ! {
cryptpad_default = o2.name cryptpad_default = o2.name
mut context := base.context()! mut context := base.context()!
mut r := context.redis()! mut r := context.redis()!
r.hset('context:cryptpad', o2.name, json.encode(o2))! encoded := json.encode(o2)
r.hset('context:cryptpad', o2.name, encoded)!
} }
// does the config exists? // does the config exists?
@@ -152,25 +155,6 @@ pub fn play(mut plbook PlayBook) ! {
install()! install()!
} }
} }
if other_action.name in ['start', 'stop', 'restart'] {
mut p := other_action.params
name := p.get('name')!
mut cryptpad_obj := get(name: name)!
console.print_debug('action object:\n${cryptpad_obj}')
if other_action.name == 'start' {
console.print_debug('install action cryptpad.${other_action.name}')
cryptpad_obj.start()!
}
if other_action.name == 'stop' {
console.print_debug('install action cryptpad.${other_action.name}')
cryptpad_obj.stop()!
}
if other_action.name == 'restart' {
console.print_debug('install action cryptpad.${other_action.name}')
cryptpad_obj.restart()!
}
}
other_action.done = true other_action.done = true
} }
} }
@@ -179,118 +163,18 @@ pub fn play(mut plbook PlayBook) ! {
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.screen {
console.print_debug("installer: cryptpad' startupmanager get screen")
return startupmanager.get(.screen)!
}
.zinit {
console.print_debug("installer: cryptpad' startupmanager get zinit")
return startupmanager.get(.zinit)!
}
.systemd {
console.print_debug("installer: cryptpad' startupmanager get systemd")
return startupmanager.get(.systemd)!
}
else {
console.print_debug("installer: cryptpad' startupmanager get auto")
return startupmanager.get(.auto)!
}
}
}
// load from disk and make sure is properly intialized // load from disk and make sure is properly intialized
pub fn (mut self CryptpadServer) reload() ! { pub fn (mut self CryptpadServer) reload() ! {
switch(self.name)
self = obj_init(self)! self = obj_init(self)!
} }
pub fn (mut self CryptpadServer) start() ! {
if self.running()! {
return
}
console.print_header('installer: cryptpad start')
if !installed()! {
install()!
}
configure()!
start_pre()!
for zprocess in startupcmd()! {
mut sm := startupmanager_get(zprocess.startuptype)!
console.print_debug('installer: cryptpad starting 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('cryptpad did not install properly.')
}
@[params] @[params]
pub struct InstallArgs { pub struct InstallArgs {
pub mut: pub mut:
reset bool reset bool
} }
pub fn (mut self CryptpadServer) install_start(args InstallArgs) ! {
switch(self.name)
self.install(args)!
self.start()!
}
pub fn (mut self CryptpadServer) 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 CryptpadServer) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self CryptpadServer) running() !bool {
switch(self.name)
// walk over the generic processes, if not running return
for zprocess in startupcmd()! {
if zprocess.startuptype != .screen {
mut sm := startupmanager_get(zprocess.startuptype)!
r := sm.running(zprocess.name)!
if r == false {
return false
}
}
}
return running()!
}
pub fn (mut self CryptpadServer) install(args InstallArgs) ! { pub fn (mut self CryptpadServer) install(args InstallArgs) ! {
switch(self.name) switch(self.name)
if args.reset || (!installed()!) { if args.reset || (!installed()!) {
@@ -300,10 +184,10 @@ pub fn (mut self CryptpadServer) install(args InstallArgs) ! {
pub fn (mut self CryptpadServer) destroy() ! { pub fn (mut self CryptpadServer) destroy() ! {
switch(self.name) switch(self.name)
self.stop() or {}
destroy()! destroy()!
} }
// switch instance to be used for cryptpad // switch instance to be used for cryptpad
pub fn switch(name string) { pub fn switch(name string) {
cryptpad_default = name
} }

View File

@@ -1,46 +1,103 @@
module cryptpad module cryptpad
import incubaid.herolib.data.encoderhero
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
import incubaid.herolib.data.encoderhero
import incubaid.herolib.virt.kubernetes
import incubaid.herolib.core.pathlib
import strings
pub const version = '1.0.0' pub const version = '0.0.0'
const singleton = true const singleton = false
const default = true const default = false
struct ConfigValues {
pub mut:
hostname string // The CryptPad hostname
backends string // The backends for the TFGW
namespace string // The namespace for the CryptPad deployment
}
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap] @[heap]
pub struct CryptpadServer { pub struct CryptpadServer {
pub mut: pub mut:
name string = 'default' name string = 'cryptpad'
hostname string hostname string
namespace string = 'collab' namespace string
cryptpad_path string = '/tmp/cryptpad.yaml'
tfgw_cryptpad_path string = '/tmp/tfgw-cryptpad.yaml'
kube_client kubernetes.KubeClient @[skip]
} }
// your checking & initialization code if needed // your checking & initialization code if needed
fn obj_init(mycfg_ CryptpadServer) !CryptpadServer { fn obj_init(mycfg_ CryptpadServer) !CryptpadServer {
mut mycfg := mycfg_ mut mycfg := mycfg_
if mycfg.hostname == '' {
return error('hostname cannot be empty')
}
if mycfg.namespace == '' { if mycfg.namespace == '' {
mycfg.namespace = 'collab' mycfg.namespace = mycfg.name
} }
if mycfg.hostname == '' {
mycfg.hostname = mycfg.name
}
mycfg.kube_client = kubernetes.get(create: true)!
mycfg.kube_client.config.namespace = mycfg.namespace
return mycfg return mycfg
} }
// called before start if done // called before start if done
fn configure() ! { fn configure() ! {
// We will implement the configuration logic here, mut installer := get()!
// like generating the yaml files from templates.
console.print_debug('configuring cryptpad...') master_ips := get_master_node_ips()!
console.print_info('Master node IPs: ${master_ips}')
mut backends_str_builder := strings.new_builder(100)
for ip in master_ips {
backends_str_builder.writeln(' - "http://[${ip}]:80"')
}
config_values := ConfigValues{
hostname: installer.hostname
backends: backends_str_builder.str()
namespace: installer.namespace
}
console.print_info('Generating YAML files from templates...')
temp := $tmpl('./templates/tfgw-cryptpad.yaml')
mut temp_path := pathlib.get_file(path: installer.tfgw_cryptpad_path, create: true)!
temp_path.write(temp)!
temp2 := $tmpl('./templates/cryptpad.yaml')
mut temp_path2 := pathlib.get_file(path: installer.cryptpad_path, create: true)!
temp_path2.write(temp2)!
console.print_info('YAML files generated successfully.')
}
// Get Kubernetes master node IPs
fn get_master_node_ips() ![]string {
mut master_ips := []string{}
installer := get()!
// Get all nodes using the kubernetes client
mut k8s := installer.kube_client
nodes := k8s.get_nodes()!
// Extract IPv6 internal IPs from all nodes (dual-stack support)
for node in nodes {
// Check all internal IPs (not just the first one) for IPv6 addresses
for ip in node.internal_ips {
if ip.len > 0 && ip.contains(':') {
master_ips << ip
}
}
}
return master_ips
} }
/////////////NORMALLY NO NEED TO TOUCH /////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj CryptpadServer) !string {
return encoderhero.encode[CryptpadServer](obj)!
}
pub fn heroscript_loads(heroscript string) !CryptpadServer { pub fn heroscript_loads(heroscript string) !CryptpadServer {
mut obj := encoderhero.decode[CryptpadServer](heroscript)! mut obj := encoderhero.decode[CryptpadServer](heroscript)!
return obj return obj

View File

@@ -3,6 +3,7 @@ module kubernetes
import incubaid.herolib.osal.core as osal import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console import incubaid.herolib.ui.console
import json import json
import os
@[params] @[params]
pub struct KubectlExecArgs { pub struct KubectlExecArgs {
@@ -46,55 +47,13 @@ pub fn (mut k KubeClient) kubectl_exec(args KubectlExecArgs) !KubectlResult {
} }
cmd += ' ${args.command}' cmd += ' ${args.command}'
result := os.execute(cmd)
console.print_debug('executing: ${cmd}') return KubectlResult{
exit_code: result.exit_code
// Check if this is a command that might produce large output stdout: result.output
is_large_output := args.command.contains('get nodes') || args.command.contains('get pods') stderr: result.output // os.execute combines stdout and stderr
|| args.command.contains('get deployments') || args.command.contains('get services') success: result.exit_code == 0
if is_large_output {
// Use exec_fast for large outputs (avoids 8KB buffer limit in osal.exec)
// exec_fast uses os.execute which doesn't have the pipe buffer limitation
result_output := osal.exec_fast(
cmd: cmd
ignore_error: true
) or { return error('Failed to execute kubectl command: ${err}') }
// Check if command succeeded by looking for error messages
if result_output.contains('Error from server') || result_output.contains('error:')
|| result_output.contains('Unable to connect') {
return KubectlResult{
exit_code: 1
stdout: result_output
stderr: result_output
success: false
}
}
return KubectlResult{
exit_code: 0
stdout: result_output
stderr: ''
success: result_output.len > 0
}
} else {
// Use regular exec for normal commands (supports timeout and proper error handling)
// Note: stdout must be true to prevent process from hanging when output buffer fills
job := osal.exec(
cmd: cmd
timeout: args.timeout
retry: args.retry
raise_error: false
stdout: true
)!
return KubectlResult{
exit_code: job.exit_code
stdout: job.output
stderr: job.error
success: job.exit_code == 0
}
} }
} }
@@ -401,7 +360,8 @@ pub fn (mut k KubeClient) apply_yaml(yaml_path string) !KubectlResult {
// Delete resource // Delete resource
pub fn (mut k KubeClient) delete_resource(kind string, name string, namespace string) !KubectlResult { pub fn (mut k KubeClient) delete_resource(kind string, name string, namespace string) !KubectlResult {
result := k.kubectl_exec(command: 'delete ${kind} ${name} -n ${namespace}')! mut cmd := 'delete ${kind} ${name}'
result := k.kubectl_exec(command: cmd)!
return result return result
} }