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  `