...
This commit is contained in:
@@ -10,5 +10,11 @@ if !t.is_running()! {
|
|||||||
if t.session_exist('main') {
|
if t.session_exist('main') {
|
||||||
t.session_delete('main')!
|
t.session_delete('main')!
|
||||||
}
|
}
|
||||||
t.window_new(name: 'test', cmd: 'mc', reset: true)!
|
// Create session first, then create window
|
||||||
|
mut session := t.session_create(name: 'main')!
|
||||||
|
session.window_new(name: 'test', cmd: 'mc', reset: true)!
|
||||||
|
|
||||||
|
// Or use the convenience method
|
||||||
|
// t.window_new(session_name: 'main', name: 'test', cmd: 'mc', reset: true)!
|
||||||
|
|
||||||
println(t)
|
println(t)
|
||||||
|
|||||||
29
lib/osal/linux/factory.v
Normal file
29
lib/osal/linux/factory.v
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
module linux
|
||||||
|
|
||||||
|
// import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
import freeflowuniverse.herolib.core.texttools
|
||||||
|
// import freeflowuniverse.herolib.screen
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
// import freeflowuniverse.herolib.ui.console
|
||||||
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
|
||||||
|
@[heap]
|
||||||
|
pub struct LinuxFactory {
|
||||||
|
pub mut:
|
||||||
|
username string
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct LinuxNewArgs {
|
||||||
|
pub:
|
||||||
|
username string
|
||||||
|
}
|
||||||
|
|
||||||
|
// return screen instance
|
||||||
|
pub fn new(args LinuxNewArgs) !LinuxFactory {
|
||||||
|
mut t := LinuxFactory{
|
||||||
|
username: args.username
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
107
lib/osal/linux/templates/user_add.sh
Normal file
107
lib/osal/linux/templates/user_add.sh
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "❌ Must be run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- ask for username ---
|
||||||
|
read -rp "Enter username to create: " NEWUSER
|
||||||
|
|
||||||
|
# --- ask for SSH public key ---
|
||||||
|
read -rp "Enter SSH public key (or path to pubkey file): " PUBKEYINPUT
|
||||||
|
if [ -f "$PUBKEYINPUT" ]; then
|
||||||
|
PUBKEY="$(cat "$PUBKEYINPUT")"
|
||||||
|
else
|
||||||
|
PUBKEY="$PUBKEYINPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- ensure user exists ---
|
||||||
|
if id "$NEWUSER" >/dev/null 2>&1; then
|
||||||
|
echo "✅ User $NEWUSER already exists"
|
||||||
|
else
|
||||||
|
echo "➕ Creating user $NEWUSER"
|
||||||
|
useradd -m -s /bin/bash "$NEWUSER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
USERHOME=$(eval echo "~$NEWUSER")
|
||||||
|
|
||||||
|
# --- setup SSH authorized_keys ---
|
||||||
|
mkdir -p "$USERHOME/.ssh"
|
||||||
|
chmod 700 "$USERHOME/.ssh"
|
||||||
|
echo "$PUBKEY" > "$USERHOME/.ssh/authorized_keys"
|
||||||
|
chmod 600 "$USERHOME/.ssh/authorized_keys"
|
||||||
|
chown -R "$NEWUSER":"$NEWUSER" "$USERHOME/.ssh"
|
||||||
|
echo "✅ SSH key installed for $NEWUSER"
|
||||||
|
|
||||||
|
# --- ensure ourworld group exists ---
|
||||||
|
if getent group ourworld >/dev/null 2>&1; then
|
||||||
|
echo "✅ Group 'ourworld' exists"
|
||||||
|
else
|
||||||
|
echo "➕ Creating group 'ourworld'"
|
||||||
|
groupadd ourworld
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- add user to group ---
|
||||||
|
if id -nG "$NEWUSER" | grep -qw ourworld; then
|
||||||
|
echo "✅ $NEWUSER already in 'ourworld'"
|
||||||
|
else
|
||||||
|
usermod -aG ourworld "$NEWUSER"
|
||||||
|
echo "✅ Added $NEWUSER to 'ourworld' group"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- setup /code ---
|
||||||
|
mkdir -p /code
|
||||||
|
chown root:ourworld /code
|
||||||
|
chmod 2775 /code # rwx for user+group, SGID bit so new files inherit group
|
||||||
|
echo "✅ /code prepared (group=ourworld, rwx for group, SGID bit set)"
|
||||||
|
|
||||||
|
# --- create login helper script for gpg-agent ---
|
||||||
|
PROFILE_SCRIPT="$USERHOME/.profile_gpgagent"
|
||||||
|
cat > "$PROFILE_SCRIPT" <<'EOF'
|
||||||
|
# Auto-start gpg-agent with SSH support if not running
|
||||||
|
mkdir -p "$HOME/.gnupg"
|
||||||
|
chmod 700 "$HOME/.gnupg"
|
||||||
|
|
||||||
|
# Always overwrite gpg-agent.conf with required config
|
||||||
|
cat > "$HOME/.gnupg/gpg-agent.conf" <<CONF
|
||||||
|
enable-ssh-support
|
||||||
|
default-cache-ttl 7200
|
||||||
|
max-cache-ttl 7200
|
||||||
|
CONF
|
||||||
|
|
||||||
|
# Kill old agent if any (so config is applied)
|
||||||
|
gpgconf --kill gpg-agent 2>/dev/null || true
|
||||||
|
|
||||||
|
# Launch gpg-agent
|
||||||
|
gpgconf --launch gpg-agent
|
||||||
|
|
||||||
|
# Export socket path so ssh-add works
|
||||||
|
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
|
||||||
|
|
||||||
|
# Load all private keys found in ~/.ssh
|
||||||
|
if [ -d "$HOME/.ssh" ]; then
|
||||||
|
for KEY in "$HOME"/.ssh/*; do
|
||||||
|
if [ -f "$KEY" ] && grep -q "PRIVATE KEY" "$KEY" 2>/dev/null; then
|
||||||
|
ssh-add "$KEY" >/dev/null 2>&1 && echo "🔑 Loaded key: $KEY"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For interactive shells
|
||||||
|
if [[ $- == *i* ]]; then
|
||||||
|
echo "🔑 GPG Agent ready at \$SSH_AUTH_SOCK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chown "$NEWUSER":"$NEWUSER" "$PROFILE_SCRIPT"
|
||||||
|
chmod 644 "$PROFILE_SCRIPT"
|
||||||
|
|
||||||
|
# --- source it on login ---
|
||||||
|
if ! grep -q ".profile_gpgagent" "$USERHOME/.bashrc"; then
|
||||||
|
echo "[ -f ~/.profile_gpgagent ] && source ~/.profile_gpgagent" >> "$USERHOME/.bashrc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🎉 Setup complete for user $NEWUSER"
|
||||||
29
lib/osal/linux/user_mgmt.v
Normal file
29
lib/osal/linux/user_mgmt.v
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
module linux
|
||||||
|
|
||||||
|
// import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
import freeflowuniverse.herolib.core.texttools
|
||||||
|
// import freeflowuniverse.herolib.screen
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
// import freeflowuniverse.herolib.ui.console
|
||||||
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
|
||||||
|
@[heap]
|
||||||
|
pub struct LinuxFactory {
|
||||||
|
pub mut:
|
||||||
|
username string
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct LinuxNewArgs {
|
||||||
|
pub:
|
||||||
|
username string
|
||||||
|
}
|
||||||
|
|
||||||
|
// return screen instance
|
||||||
|
pub fn new(args LinuxNewArgs) !LinuxFactory {
|
||||||
|
mut t := LinuxFactory{
|
||||||
|
username: args.username
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
module tmux
|
module tmux
|
||||||
|
|
||||||
import freeflowuniverse.herolib.osal.core as osal
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
import freeflowuniverse.herolib.core.texttools
|
||||||
// import freeflowuniverse.herolib.session
|
// import freeflowuniverse.herolib.session
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
@@ -98,6 +99,33 @@ pub fn new(args TmuxNewArgs) !Tmux {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct WindowNewArgs {
|
||||||
|
pub mut:
|
||||||
|
session_name string = 'main'
|
||||||
|
name string
|
||||||
|
cmd string
|
||||||
|
env map[string]string
|
||||||
|
reset bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut t Tmux) window_new(args WindowNewArgs) !&Window {
|
||||||
|
// Get or create session
|
||||||
|
mut session := if t.session_exist(args.session_name) {
|
||||||
|
t.session_get(args.session_name)!
|
||||||
|
} else {
|
||||||
|
t.session_create(name: args.session_name)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create window in session
|
||||||
|
return session.window_new(
|
||||||
|
name: args.name
|
||||||
|
cmd: args.cmd
|
||||||
|
env: args.env
|
||||||
|
reset: args.reset
|
||||||
|
)!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn (mut t Tmux) stop() ! {
|
pub fn (mut t Tmux) stop() ! {
|
||||||
$if debug {
|
$if debug {
|
||||||
@@ -128,19 +156,6 @@ pub fn (mut t Tmux) start() ! {
|
|||||||
t.scan()!
|
t.scan()!
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProcessStatus {
|
|
||||||
running
|
|
||||||
finished_ok
|
|
||||||
finished_error
|
|
||||||
not_found
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LogEntry {
|
|
||||||
pub mut:
|
|
||||||
content string
|
|
||||||
timestamp time.Time
|
|
||||||
offset int
|
|
||||||
}
|
|
||||||
|
|
||||||
// print list of tmux sessions
|
// print list of tmux sessions
|
||||||
pub fn (mut t Tmux) list_print() {
|
pub fn (mut t Tmux) list_print() {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
module tmux
|
module tmux
|
||||||
|
|
||||||
import freeflowuniverse.herolib.osal.core as osal
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
|
import freeflowuniverse.herolib.data.ourtime
|
||||||
|
import time
|
||||||
// import freeflowuniverse.herolib.session
|
// import freeflowuniverse.herolib.session
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
|
||||||
@[heap]
|
@[heap]
|
||||||
@@ -48,14 +49,14 @@ pub fn (mut p Pane) stats() !ProcessStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct LogEntry {
|
pub struct TMuxLogEntry {
|
||||||
pub mut:
|
pub mut:
|
||||||
content string
|
content string
|
||||||
timestamp time.Time
|
timestamp time.Time
|
||||||
offset int
|
offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut p Pane) logs_get_new(reset bool) ![]LogEntry {
|
pub fn (mut p Pane) logs_get_new(reset bool) ![]TMuxLogEntry {
|
||||||
|
|
||||||
if reset{
|
if reset{
|
||||||
p.last_output_offset = 0
|
p.last_output_offset = 0
|
||||||
@@ -64,15 +65,15 @@ pub fn (mut p Pane) logs_get_new(reset bool) ![]LogEntry {
|
|||||||
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
|
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
|
||||||
result := osal.execute_silent(cmd) or {
|
result := osal.execute_silent(cmd) or {
|
||||||
return error('Cannot capture pane output: ${err}')
|
return error('Cannot capture pane output: ${err}')
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := result.split_into_lines()
|
lines := result.split_into_lines()
|
||||||
mut entries := []LogEntry{}
|
mut entries := []TMuxLogEntry{}
|
||||||
|
|
||||||
mut i:= 0
|
mut i:= 0
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if line.trim_space() != '' {
|
if line.trim_space() != '' {
|
||||||
entries << LogEntry{
|
entries << TMuxLogEntry{
|
||||||
content: line
|
content: line
|
||||||
timestamp: time.now()
|
timestamp: time.now()
|
||||||
offset: p.last_output_offset + i + 1
|
offset: p.last_output_offset + i + 1
|
||||||
@@ -83,7 +84,7 @@ pub fn (mut p Pane) logs_get_new(reset bool) ![]LogEntry {
|
|||||||
if entries.len > 0 {
|
if entries.len > 0 {
|
||||||
p.last_output_offset = entries.last().offset
|
p.last_output_offset = entries.last().offset
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (mut p Pane) exit_status() !ProcessStatus {
|
pub fn (mut p Pane) exit_status() !ProcessStatus {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module tmux
|
|||||||
import freeflowuniverse.herolib.osal.core as osal
|
import freeflowuniverse.herolib.osal.core as osal
|
||||||
import freeflowuniverse.herolib.core.texttools
|
import freeflowuniverse.herolib.core.texttools
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
|
import time
|
||||||
|
|
||||||
fn (mut t Tmux) scan_add(line string) !&Pane {
|
fn (mut t Tmux) scan_add(line string) !&Pane {
|
||||||
// Parse the line to get session, window, and pane info
|
// Parse the line to get session, window, and pane info
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ pub mut:
|
|||||||
pub struct WindowArgs {
|
pub struct WindowArgs {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string
|
name string
|
||||||
|
cmd string
|
||||||
|
env map[string]string
|
||||||
reset bool
|
reset bool
|
||||||
}
|
}
|
||||||
@[params]
|
@[params]
|
||||||
@@ -27,6 +29,25 @@ pub mut:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn (mut s Session) create() ! {
|
||||||
|
// Check if session already exists
|
||||||
|
cmd_check := "tmux has-session -t ${s.name}"
|
||||||
|
check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or {
|
||||||
|
// Session doesn't exist, this is expected
|
||||||
|
osal.Job{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_result.exit_code == 0 {
|
||||||
|
return error('duplicate session: ${s.name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new session
|
||||||
|
cmd := "tmux new-session -d -s ${s.name}"
|
||||||
|
osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or {
|
||||||
|
return error("Can't create session ${s.name}: ${err}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//load info from reality
|
//load info from reality
|
||||||
pub fn (mut s Session) scan() ! {
|
pub fn (mut s Session) scan() ! {
|
||||||
// Get current windows from tmux for this session
|
// Get current windows from tmux for this session
|
||||||
@@ -115,21 +136,10 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window {
|
|||||||
}
|
}
|
||||||
s.windows << &w
|
s.windows << &w
|
||||||
|
|
||||||
res_opt := "-P -F '#\{window_id\}'"
|
// Create the window with the specified command
|
||||||
cmd := "tmux new-session ${res_opt} -d -s ${s.name} 'sh'"
|
w.create(args.cmd)!
|
||||||
window_id_ := osal.execute_silent(cmd) or {
|
|
||||||
return error("Can't create tmux session ${s.name} \n${cmd}\n${err}")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd3 := 'tmux set-option remain-on-exit on'
|
|
||||||
osal.execute_silent(cmd3) or { return error("Can't execute ${cmd3}\n${err}") }
|
|
||||||
|
|
||||||
window_id := window_id_.trim(' \n')
|
|
||||||
cmd2 := "tmux rename-window -t ${window_id} 'notused'"
|
|
||||||
osal.execute_silent(cmd2) or {
|
|
||||||
return error("Can't rename window ${window_id} to notused \n${cmd2}\n${err}")
|
|
||||||
}
|
|
||||||
s.scan()!
|
s.scan()!
|
||||||
|
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user