Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

nushell/modules/ssh.nu

Raw
use ./lib.nu *

# Generate a new ed25519 SSH private key
export def ssh-keygen-ed [
    email: string # Identifier for the COMMENT section of the key
] {
  require-binary ssh-keygen
  ssh-keygen -t ed25519 -C $email
}

# Generate public key from private key
export def ssh-keygen-pub [
  key: path # path to private key
] {
  require-binary ssh-keygen
  ssh-keygen -y -f $key | save --raw $"($key + '.pub')"
}

export extern "main" [
    destination?: string@"nu-complete ssh-host"
    -4            # Forces ssh to use IPv4 addresses only.
    -6            # Forces ssh to use IPv6 addresses only.
    -A            # Enables forwarding of connections from an authentication agent such as ssh-agent(1).
    -a            # Disables forwarding of the authentication agent connection.
    -B: string    # bind_interface
    -b: string    # bind_address
    -C            # Requests compression of all data
    -c: string    # cipher_spec
    -D            # [bind_address:]port
    -E: string    # log_file
    -e            # escape_char
    -F: string    # configfile
    -f            # Requests ssh to go to background just before command execution.
    -G            # Causes ssh to print its configuration after evaluating Host and Match blocks and exit.
    -g            # Allows remote hosts to connect to local forwarded ports
    -I: string    # pkcs11
    -i: string    # identity_file
    -J: string    # destination
    -K            # Enables GSSAPI-based authentication and forwarding(delegation) of GSSAPI credentials to the server.
    -k            # Disables forwarding (delegation) of GSSAPI credentials to the server.
    -L: string    # [bind_address:]port:host:hostport / [bind_address:]port:remote_socket / local_socket:host:hostport / local_socket:remote_socket
    -l: string    # login_name
    -M            # Places the ssh client into “master” mode for connection sharing.
    -m: string    # mac_spec
    -N            # Do not execute a remote command.
    -n            # Redirects stdin from /dev/null (5) for details.
    -O: string    # ctl_cmd
    -o: string    # option
]

module ssh-completion-utils {
    def extract-host []: list<string> -> record<names: list<string>, addr: string> {
        # Host is a list of lines, like:
        # ╭───┬──────────────────────────────╮
        # │ 0 │ Host quanweb                 │
        # │ 1 │     User quan                │
        # │ 2 │     Hostname quan.hoabinh.vn │
        # │ 3 │     # ProxyJump farmb-omz    │
        # │ 4 │                              │
        # ╰───┴──────────────────────────────╯
        let host = $in
        let first_line = try { $host | first | str trim } catch { null }
        # Don't accept blocks like "Host *"
        if ($first_line | is-empty) or '*' in $first_line {
            null
        } else {
            let names = $first_line | split row -r '\s+' | reject 0
            # May not contain hostname
            match ($host | slice 1.. | find -n -ir '^\s*Hostname\s') {
                [] => null,
                $addr => { names: $names, addr: ($addr | str trim | split row -n 2 -r '\s+' | get 1) }
            }
        }
    }

    # Process a SSH config file
    export def process []: string -> record<hosts: table<names: list<string>, addr: string>, includes: list<string>> {
        let lines = $in | lines
        # Get 'Include' lines
        let include_lines = $lines | find -n -ir '^Include\s' | str trim | each { $in | split row -n 2 -r '\s+' | get 1 | str trim -c '"'}
        # Find "Host" blocks
        let marks = $lines | enumerate | find -n -ir '^Host\s'
        let mark_indices = $marks | get index | append ($lines | length)
        let hosts = $mark_indices | window 2 | each {|w| $lines | slice $w.0..<($w.1) }
        {
            hosts: ($hosts | each { $in | extract-host }),
            includes: $include_lines
        }
    }

}

def "nu-complete ssh-host" [] {
    let files = [
        '/etc/ssh/ssh_config',
        '~/.ssh/config'
    ] | where {|file| $file | path exists }

    use ssh-completion-utils process

    let first_result = $files | par-each {|file|
        let folder = $file | path expand | path dirname
        mut r = $file | open --raw | process
        $r.includes = $r.includes | each {|f| $folder | path join $f }
        $r
    } | reduce {|it| merge deep $it --strategy=append }

    let $includes: list<string> = $first_result.includes | each {|f|
        if '*' in $f {
            glob $f
        } else if ($f | path exists) {
            [$f]
        } else []
    } | flatten

    # Process include files
    let included_hosts = (if ($includes | is-empty) { [] } else {
        let second_result = $includes | par-each {|p| $p | open --raw | process } | reduce {|it| merge deep $it --strategy=append }
        $second_result.hosts
    })

    let hosts = $first_result.hosts ++ $included_hosts
    $hosts | each { {value: $in.names, description: $in.addr } } | flatten
}