github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/runner/debug/client.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package debug 5 6 import ( 7 "encoding/base64" 8 "strings" 9 10 goyaml "gopkg.in/yaml.v2" 11 ) 12 13 type hookArgs struct { 14 Hooks []string `yaml:"hooks,omitempty"` 15 DebugAt string `yaml:"debug-at,omitempty"` 16 } 17 18 // ClientScript returns a bash script suitable for executing 19 // on the unit system to intercept matching hooks or actions via tmux shell. 20 func ClientScript(c *HooksContext, match []string, debugAt string) string { 21 // If any argument is "*", then the client is interested in all. 22 for _, m := range match { 23 if m == "*" { 24 match = nil 25 break 26 } 27 } 28 29 s := strings.Replace(debugHooksClientScript, "{unit_name}", c.Unit, -1) 30 s = strings.Replace(s, "{tmux_conf}", tmuxConf, 1) 31 s = strings.Replace(s, "{entry_flock}", c.ClientFileLock(), -1) 32 s = strings.Replace(s, "{exit_flock}", c.ClientExitFileLock(), -1) 33 34 base64Args := Base64HookArgs(match, debugAt) 35 s = strings.Replace(s, "{hook_args}", base64Args, 1) 36 return s 37 } 38 39 // Base64HookArgs returns the encoded arguments for defining debug-hook behavior. 40 // This is a base64 encoded yaml blob containing serialized arguments. 41 func Base64HookArgs(match []string, debugAt string) string { 42 yamlArgs := encodeArgs(match, debugAt) 43 return base64.StdEncoding.EncodeToString(yamlArgs) 44 } 45 46 func encodeArgs(args []string, debugAt string) []byte { 47 // Marshal to YAML, then encode in base64 to avoid shell escapes. 48 yamlArgs, err := goyaml.Marshal(hookArgs{Hooks: args, DebugAt: debugAt}) 49 if err != nil { 50 // This should not happen: we're in full control. 51 panic(err) 52 } 53 return yamlArgs 54 } 55 56 const debugHooksClientScript = `#!/bin/bash 57 ( 58 cleanup_on_exit() 59 { 60 echo "Cleaning up the debug session" 61 tmux kill-session -t {unit_name}; 62 } 63 trap cleanup_on_exit EXIT 64 65 # Lock the juju-<unit>-debug lockfile. 66 flock -n 8 || ( 67 echo "Found existing debug sessions, attempting to reconnect" 2>&1 68 exec tmux attach-session -t {unit_name} 69 exit $? 70 ) 71 ( 72 # Close the inherited lock FD, or tmux will keep it open. 73 exec 8>&- 74 75 # Write out the debug-hooks args. 76 echo "{hook_args}" | base64 -d > {entry_flock} 77 78 # Lock the juju-<unit>-debug-exit lockfile. 79 flock -n 9 || exit 1 80 81 # Wait for tmux to be installed. 82 while [ ! -f /usr/bin/tmux ]; do 83 sleep 1 84 done 85 86 if [ ! -f ~/.tmux.conf ]; then 87 if [ -f /usr/share/byobu/profiles/tmux ]; then 88 # Use byobu/tmux profile for familiar keybindings and branding 89 echo "source-file /usr/share/byobu/profiles/tmux" > ~/.tmux.conf 90 else 91 # Otherwise, use the legacy juju/tmux configuration 92 cat > ~/.tmux.conf <<END 93 {tmux_conf} 94 END 95 fi 96 fi 97 98 ( 99 # Close the inherited lock FD, or tmux will keep it open. 100 exec 9>&- 101 # Since we just use byobu tmux configs without byobu-tmux, we need 102 # to export this to prevent the TERM being set to empty string. 103 export BYOBU_TERM=$TERM 104 if ! tmux has-session -t {unit_name}; then 105 tmux new-session -d -s {unit_name} 106 fi 107 client_count=$(tmux list-clients | wc -l) 108 if [ $client_count -ge 1 ]; then 109 session_name={unit_name}"-"$client_count 110 tmux new-session -d -t {unit_name} -s $session_name 111 exec tmux attach-session -t $session_name \; set-option destroy-unattached 112 else 113 exec tmux attach-session -t {unit_name} 114 fi 115 ) 116 ) 9>{exit_flock} 117 ) 8>{entry_flock} 118 exit $? 119 ` 120 121 const tmuxConf = ` 122 # Status bar 123 set-option -g status-bg black 124 set-option -g status-fg white 125 126 set-window-option -g window-status-current-bg red 127 set-window-option -g window-status-current-attr bright 128 129 set-option -g status-right '' 130 131 # Panes 132 set-option -g pane-border-fg white 133 set-option -g pane-active-border-fg white 134 135 # Monitor activity on windows 136 set-window-option -g monitor-activity on 137 138 # Screen bindings, since people are more familiar with that. 139 set-option -g prefix C-a 140 bind C-a last-window 141 bind a send-key C-a 142 143 bind | split-window -h 144 bind - split-window -v 145 146 # Fix CTRL-PGUP/PGDOWN for vim 147 set-window-option -g xterm-keys on 148 149 # Prevent ESC key from adding delay and breaking Vim's ESC > arrow key 150 set-option -s escape-time 0 151 `