...
This commit is contained in:
@@ -10,5 +10,11 @@ if !t.is_running()! {
|
||||
if t.session_exist('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)
|
||||
|
||||
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
|
||||
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
// import freeflowuniverse.herolib.session
|
||||
import os
|
||||
import time
|
||||
@@ -98,6 +99,33 @@ pub fn new(args TmuxNewArgs) !Tmux {
|
||||
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() ! {
|
||||
$if debug {
|
||||
@@ -128,19 +156,6 @@ pub fn (mut t Tmux) start() ! {
|
||||
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
|
||||
pub fn (mut t Tmux) list_print() {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
module tmux
|
||||
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import time
|
||||
// import freeflowuniverse.herolib.session
|
||||
import os
|
||||
import time
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@[heap]
|
||||
@@ -48,14 +49,14 @@ pub fn (mut p Pane) stats() !ProcessStats {
|
||||
}
|
||||
|
||||
|
||||
pub struct LogEntry {
|
||||
pub struct TMuxLogEntry {
|
||||
pub mut:
|
||||
content string
|
||||
timestamp time.Time
|
||||
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{
|
||||
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'
|
||||
result := osal.execute_silent(cmd) or {
|
||||
return error('Cannot capture pane output: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
lines := result.split_into_lines()
|
||||
mut entries := []LogEntry{}
|
||||
mut entries := []TMuxLogEntry{}
|
||||
|
||||
mut i:= 0
|
||||
for line in lines {
|
||||
if line.trim_space() != '' {
|
||||
entries << LogEntry{
|
||||
entries << TMuxLogEntry{
|
||||
content: line
|
||||
timestamp: time.now()
|
||||
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 {
|
||||
p.last_output_offset = entries.last().offset
|
||||
}
|
||||
return entries
|
||||
return entries
|
||||
}
|
||||
|
||||
pub fn (mut p Pane) exit_status() !ProcessStatus {
|
||||
|
||||
@@ -3,6 +3,7 @@ module tmux
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import time
|
||||
|
||||
fn (mut t Tmux) scan_add(line string) !&Pane {
|
||||
// Parse the line to get session, window, and pane info
|
||||
|
||||
@@ -17,6 +17,8 @@ pub mut:
|
||||
pub struct WindowArgs {
|
||||
pub mut:
|
||||
name string
|
||||
cmd string
|
||||
env map[string]string
|
||||
reset bool
|
||||
}
|
||||
@[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
|
||||
pub fn (mut s Session) scan() ! {
|
||||
// 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
|
||||
|
||||
res_opt := "-P -F '#\{window_id\}'"
|
||||
cmd := "tmux new-session ${res_opt} -d -s ${s.name} 'sh'"
|
||||
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}")
|
||||
}
|
||||
// Create the window with the specified command
|
||||
w.create(args.cmd)!
|
||||
s.scan()!
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user