Lecture 5

Job control
  1. From what we have seen, we can use some ps aux | grep commands to get our jobs’ pids and then kill them, but there are better ways to do it. Start a sleep 10000 job in a terminal, background it with Ctrl-Z and continue its execution with bg. Now use pgrep to find its pid and pkill to kill it without ever typing the pid itself. (Hint: use the -af flags).
    pgrep -af sleep

    Terminal output:

    12345 sleep 1000
    pkill -f sleep
  2. Say you don’t want to start a process until another completes. How would you go about it? In this exercise, our limiting process will always be sleep 60 &. One way to achieve this is to use the wait command. Try launching the sleep command and having an ls wait until the background process finishes.
    sleep 60 &
    wait $! | ls

    $! refers to the id of the last job. You can change it with the id of another job if you like or use %1 to refer to the first job on the list.

    However, this strategy will fail if we start in a different bash session, since wait only works for child processes. One feature we did not discuss in the notes is that the kill command’s exit status will be zero on success and nonzero otherwise. kill -0 does not send a signal but will give a nonzero exit status if the process does not exist. Write a bash function called pidwait that takes a pid and waits until the given process completes. You should use sleep to avoid wasting CPU unnecessarily.

    pidwait

    pidwait() {
        local pid="$1"
    
        # Check if a PID was provided
        if [ -z "$pid" ]; then
            echo "Usage: pidwait "
            return 1
        fi
    
        # Loop until the process is no longer running
        while kill -0 "$pid" 2>/dev/null; do
            sleep 1  # Sleep for 1 second to avoid busy waiting
        done
    
        echo "Process $pid has completed."
    }
    
    
Terminal Multiplexer
  1. Follow this tmux tutorial and then learn how to do some basic customizations following these steps.
Aliases
  1. Create an alias dc that resolves to cd for when you type it wrongly.

    .zshrc

    alias dc='cd'
    
  2. Run history | awk '{$1="";print substr($0,2)}' | sort | uniq -c | sort -n | tail -n 10 to get your top 10 most used commands and consider writing shorter aliases for them. Note: this works for Bash; if you’re using ZSH, use history 1 instead of just history.
Dotfiles
  1. Create a folder for your dotfiles and set up version control.
    You can create a folder in your root directory:
    mkdir ~/dotfiles
    Then you can use Git to set up version control.
    You will see more details on how to use git in the next lecture but if you don't know the basics, here is how to do it:

    First you have to install git if you don't already have it.
    sudo apt-get install git
    Then, you can create a Github account and create a dotfiles repository.
    After that, download the remote repository on you local machine using:
    git pull [url of your dotfiles repository]
    Copy your dotfiles to your dotfiles directory.
    When you are ready to save the changes you made, add them to the staging area and make a commit:
    git add -A
    git commit -m 'my first commit!'

    -A adds all files to the staging area, but you can specify each file to add instead
    -m '[message]' Specifies the message associated with the commit

  2. Add a configuration for at least one program, e.g. your shell, with some customization (to start off, it can be something as simple as customizing your shell prompt by setting $PS1).
    You can look at my own dotfiles for some simple configuration example. ChatGPT is also a good tool to find some commonly used configuration.
  3. Set up a method to install your dotfiles quickly (and without manual effort) on a new machine. This can be as simple as a shell script that calls ln -s for each file, or you could use a specialized utility.

    machine_init.sh

    #!/bin/bash
    
    # Install Dotfiles
    files="zshrc profile tmux.conf vimrc"
    
    for file in $files; do 
    	ln -sf ~/dotfiles/$file ~/.$file
    
    done
    
    You can also setup a script to install zsh, Oh My Zsh! and Zsh plugins

    machine_init.sh

    #Install Zsh and oh-my-zsh
    
    #Zsh
    if ! command -v zsh >/dev/null 2>&1; then
        echo "Zsh not found. Installing Zsh..."
        sudo apt update && sudo apt install -y zsh
    else
        echo "Zsh is already installed."
    fi
    
    #Oh-my-Zsh
    if [ ! -d "$HOME/.oh-my-zsh" ]; then
        echo "Installing Oh My Zsh..."
        # Run the Oh My Zsh installation script
        RUNZSH=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
    else
        echo "Oh My Zsh is already installed."
    fi
    
    # Install zsh plugins
    
    # Define the Oh My Zsh custom plugin directory
    ZSH_CUSTOM="$HOME/.oh-my-zsh/custom"
    PLUGIN_DIR="$ZSH_CUSTOM/plugins"
    AUTOSUGGESTIONS_REPO="https://github.com/zsh-users/zsh-autosuggestions"
    SYNTAX_HIGHLIGHTING_REPO="https://github.com/zsh-users/zsh-syntax-highlighting"
    FAST_SYNTAX_HIGHLIGHTING_REPO="https://github.com/zdharma-continuum/fast-syntax-highlighting"
    AUTOCOMPLETE_REPO="https://github.com/marlonrichert/zsh-autocomplete"
    
    # Create custom plugins directory if it doesn't exist
    mkdir -p "$PLUGIN_DIR"
    
    # Function to install a plugin
    install_plugin() {
    	local repo_url=$1
    	local plugin_name=$2
    	local plugin_path="$PLUGIN_DIR/$plugin_name"
    
    	echo "Installing $plugin_name..."
    	git clone "$repo_url" "$plugin_path"
    }
    
    # Install each plugin
    install_plugin "$AUTOSUGGESTIONS_REPO" "zsh-autosuggestions"
    install_plugin "$SYNTAX_HIGHLIGHTING_REPO" "zsh-syntax-highlighting"
    install_plugin "$FAST_SYNTAX_HIGHLIGHTING_REPO" "fast-syntax-highlighting"
    install_plugin "$AUTOCOMPLETE_REPO" "zsh-autocomplete"
    
    echo "Zsh, Oh My Zsh, and Zsh plugins installed!"
    
    # Switch shell to zsh
    if [ "$SHELL" != "$(which zsh)" ]; then
        echo "Changing default shell to Zsh..."
        chsh -s $(which zsh)
    else
        echo "Default shell is already Zsh."
    fi
    
    echo "Installation complete!"

    You should change the ZSH_CUSTOM variable to $HOME/.zsh/custom if you do not use oh my zsh.

  4. Test your installation script on a fresh virtual machine.
  5. Migrate all of your current tool configurations to your dotfiles repository.
  6. Publish your dotfiles on GitHub.
    Since we already created the repository in a previous question, all we have to do is
    git push [URL]
Remote Machines

    Install a Linux virtual machine (or use an already existing one) for this exercise. If you are not familiar with virtual machines check out this tutorial for installing one.

  1. Go to ~/.ssh/ and check if you have a pair of SSH keys there. If not, generate them with ssh-keygen -o -a 100 -t ed25519. It is recommended that you use a password and use ssh-agent , more info here.
  2. Edit .ssh/config to have an entry as follows

    Host vm
    	User username_goes_here
    	HostName ip_goes_here
    	IdentityFile ~/.ssh/id_ed25519
    	LocalForward 9999 localhost:8888
  3. Use ssh-copy-id vm to copy your ssh key to the server.
  4. Start a webserver in your VM by executing python -m http.server 8888. Access the VM webserver by navigating to http://localhost:9999 in your machine.
    After starting the server on the vm you should connect your host to the vm with:
    SSH -L 9999:localhost:8888 vm
    After that you can naviguate (on a browser) to http:localhost:9999

    You might have to use python3 instead of python
    You should create an alias for python3 is that's the case.

  5. Edit your SSH server config by doing sudo vim /etc/ssh/sshd_config and disable password authentication by editing the value of PasswordAuthentication. Disable root login by editing the value of PermitRootLogin. Restart the ssh service with sudo service sshd restart. Try sshing in again.

    sshd_config

    PasswordAuthentification no
    PermitRootLogin no

    Disabling password authentification forces users to connect using ssh keys. Disabling root logins prevents users from connecting directly as the root. Both increase security.

  6. (Challenge) Install mosh in the VM and establish a connection. Then disconnect the network adapter of the server/VM. Can mosh properly recover from it?
    Install mosh (on both machines):
    sudo apt-get install mosh
    Then, connect to your vm using mosh:
    mosh vm

    Disconnect your vm from virtual box and reconnect it again.
    As you can see mosh successfully recovers from it instead of crashing.

  7. (Challenge) Look into what the -N and -f flags do in ssh and figure out a command to achieve background port forwarding.

    The -N flag tells SSH not to execute any remote commands. It's used when you only want to establish a connection for port forwarding or tunneling, without running any shell or commands on the remote host.

    The -f flag tells SSH to go into the background just before executing the command. When paired with -N, it allows SSH to start the connection, set up the port forwarding, and then run in the background, leaving your terminal free.

    To put it all together:
    ssh -f -N -L 9999:localhost:8888 vm