Merge branch 'master' of ssh://git.mvl.sh/vleeuwenmenno/dotfiles
This commit is contained in:
@@ -5,4 +5,7 @@ mennos-desktop ansible_connection=local
|
||||
[servers]
|
||||
mennos-vps ansible_connection=local
|
||||
mennos-server ansible_connection=local
|
||||
mennos-rtlsdr-pc ansible_connection=local
|
||||
mennos-rtlsdr-pc ansible_connection=local
|
||||
|
||||
[wsl]
|
||||
mennos-desktopw ansible_connection=local
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
- name: Configure all hosts
|
||||
hosts: all
|
||||
handlers:
|
||||
- name: Import handler tasks
|
||||
ansible.builtin.import_tasks: handlers/main.yml
|
||||
- name: Import handler tasks
|
||||
ansible.builtin.import_tasks: handlers/main.yml
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: Include global tasks
|
||||
ansible.builtin.import_tasks: tasks/global/global.yml
|
||||
- name: Include global tasks
|
||||
ansible.builtin.import_tasks: tasks/global/global.yml
|
||||
|
||||
- name: Include workstation tasks
|
||||
ansible.builtin.import_tasks: tasks/workstations/workstation.yml
|
||||
when: inventory_hostname in ['mennos-laptop', 'mennos-desktop']
|
||||
- name: Include workstation tasks
|
||||
ansible.builtin.import_tasks: tasks/workstations/workstation.yml
|
||||
when: inventory_hostname in ['mennos-laptop', 'mennos-desktop']
|
||||
|
||||
- name: Include server tasks
|
||||
ansible.builtin.import_tasks: tasks/servers/server.yml
|
||||
when: inventory_hostname in ['mennos-vps', 'mennos-server', 'mennos-rtlsdr-pc']
|
||||
- name: Include server tasks
|
||||
ansible.builtin.import_tasks: tasks/servers/server.yml
|
||||
when: inventory_hostname in ['mennos-vps', 'mennos-server', 'mennos-rtlsdr-pc', 'mennos-desktopw']
|
||||
|
||||
@@ -30,10 +30,10 @@ type LoggingConfig struct {
|
||||
|
||||
// SmartAlias represents a smart SSH alias configuration
|
||||
type SmartAlias struct {
|
||||
Primary string `yaml:"primary"` // SSH config host to use when local
|
||||
Fallback string `yaml:"fallback"` // SSH config host to use when remote
|
||||
Primary string `yaml:"primary"` // SSH config host to use when local
|
||||
Fallback string `yaml:"fallback"` // SSH config host to use when remote
|
||||
CheckHost string `yaml:"check_host"` // IP to ping for connectivity test
|
||||
Timeout string `yaml:"timeout"` // Ping timeout (default: "2s")
|
||||
Timeout string `yaml:"timeout"` // Ping timeout (default: "2s")
|
||||
}
|
||||
|
||||
// TunnelDefinition represents a tunnel configuration
|
||||
@@ -47,36 +47,39 @@ type TunnelDefinition struct {
|
||||
|
||||
// TunnelState represents runtime state of an active tunnel
|
||||
type TunnelState struct {
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"` // "config" or "adhoc"
|
||||
Type string `json:"type"` // local, remote, dynamic
|
||||
LocalPort int `json:"local_port"`
|
||||
RemoteHost string `json:"remote_host"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
SSHHost string `json:"ssh_host"`
|
||||
SSHHostResolved string `json:"ssh_host_resolved"` // After smart alias resolution
|
||||
PID int `json:"pid"`
|
||||
Status string `json:"status"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
CommandLine string `json:"command_line"`
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"` // "config" or "adhoc"
|
||||
Type string `json:"type"` // local, remote, dynamic
|
||||
LocalPort int `json:"local_port"`
|
||||
RemoteHost string `json:"remote_host"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
SSHHost string `json:"ssh_host"`
|
||||
SSHHostResolved string `json:"ssh_host_resolved"` // After smart alias resolution
|
||||
PID int `json:"pid"`
|
||||
Status string `json:"status"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
CommandLine string `json:"command_line"`
|
||||
}
|
||||
|
||||
// Config represents the YAML configuration structure
|
||||
type Config struct {
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
SmartAliases map[string]SmartAlias `yaml:"smart_aliases"`
|
||||
Tunnels map[string]TunnelDefinition `yaml:"tunnels"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
SmartAliases map[string]SmartAlias `yaml:"smart_aliases"`
|
||||
Tunnels map[string]TunnelDefinition `yaml:"tunnels"`
|
||||
}
|
||||
|
||||
const (
|
||||
realSSHPath = "/usr/bin/ssh"
|
||||
defaultSSHPath = "/usr/bin/ssh"
|
||||
wslSSHPath = "ssh.exe"
|
||||
wslDetectPath = "/mnt/c/Windows/System32/cmd.exe"
|
||||
)
|
||||
|
||||
var (
|
||||
configDir string
|
||||
tunnelsDir string
|
||||
config *Config
|
||||
configDir string
|
||||
tunnelsDir string
|
||||
config *Config
|
||||
sshPath string // Will be set based on WSL2 detection
|
||||
|
||||
// Global flags
|
||||
tunnelMode bool
|
||||
@@ -92,10 +95,10 @@ var (
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ssh",
|
||||
Short: "Smart SSH utility with tunnel management",
|
||||
Long: "A transparent SSH wrapper that provides smart alias resolution and background tunnel management",
|
||||
Run: handleSSH,
|
||||
Use: "ssh",
|
||||
Short: "Smart SSH utility with tunnel management",
|
||||
Long: "A transparent SSH wrapper that provides smart alias resolution and background tunnel management",
|
||||
Run: handleSSH,
|
||||
DisableFlagParsing: true,
|
||||
}
|
||||
|
||||
@@ -103,13 +106,16 @@ var tunnelCmd = &cobra.Command{
|
||||
Use: "tunnel [tunnel-name]",
|
||||
Short: "Manage background SSH tunnels",
|
||||
Long: "Create, list, and manage persistent SSH tunnels in the background",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
handleTunnelManual(append([]string{"--tunnel"}, args...))
|
||||
},
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Detect and set SSH path based on environment (WSL2 vs native Linux)
|
||||
sshPath = detectSSHPath()
|
||||
|
||||
// Initialize config directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -141,6 +147,13 @@ func init() {
|
||||
// Initialize logging
|
||||
initLogging(config.Logging)
|
||||
|
||||
// Log SSH path detection (after logging is initialized)
|
||||
if isWSL2() {
|
||||
log.Debug().Str("ssh_path", sshPath).Msg("WSL2 detected, using Windows SSH")
|
||||
} else {
|
||||
log.Debug().Str("ssh_path", sshPath).Msg("Native Linux environment, using Linux SSH")
|
||||
}
|
||||
|
||||
// Global flags
|
||||
rootCmd.PersistentFlags().BoolVarP(&tunnelMode, "tunnel", "T", false, "Enable tunnel mode")
|
||||
rootCmd.Flags().BoolVarP(&tunnelOpen, "open", "O", false, "Open a tunnel")
|
||||
@@ -169,6 +182,22 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// detectSSHPath determines the correct SSH binary path based on the environment
|
||||
func detectSSHPath() string {
|
||||
if isWSL2() {
|
||||
// In WSL2, use Windows SSH
|
||||
return wslSSHPath
|
||||
}
|
||||
// Default to Linux SSH
|
||||
return defaultSSHPath
|
||||
}
|
||||
|
||||
// isWSL2 checks if we're running in WSL2 by looking for Windows System32
|
||||
func isWSL2() bool {
|
||||
_, err := os.Stat(wslDetectPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Check if this is a tunnel command first
|
||||
args := os.Args[1:]
|
||||
@@ -563,7 +592,7 @@ func openTunnel(name string) error {
|
||||
log.Debug().Strs("command", cmdArgs).Msg("Starting SSH tunnel")
|
||||
|
||||
// Start SSH process
|
||||
cmd := exec.Command(realSSHPath, cmdArgs[1:]...)
|
||||
cmd := exec.Command(sshPath, cmdArgs[1:]...)
|
||||
|
||||
// Capture stderr to see any SSH errors
|
||||
var stderr bytes.Buffer
|
||||
@@ -708,7 +737,9 @@ func createAdhocTunnel() (TunnelDefinition, error) {
|
||||
}
|
||||
|
||||
func buildSSHCommand(tunnel TunnelDefinition, sshHost string) []string {
|
||||
args := []string{"ssh", "-f", "-N"}
|
||||
// Use the detected SSH path basename for the command
|
||||
sshBinary := filepath.Base(sshPath)
|
||||
args := []string{sshBinary, "-f", "-N"}
|
||||
|
||||
switch tunnel.Type {
|
||||
case "local":
|
||||
@@ -1056,18 +1087,37 @@ func findSSHProcessByPort(port int) int {
|
||||
|
||||
// executeRealSSH executes the real SSH binary with given arguments
|
||||
func executeRealSSH(args []string) {
|
||||
// Check if real SSH exists
|
||||
if _, err := os.Stat(realSSHPath); os.IsNotExist(err) {
|
||||
log.Error().Str("path", realSSHPath).Msg("Real SSH binary not found")
|
||||
fmt.Fprintf(os.Stderr, "Error: Real SSH binary not found at %s\n", realSSHPath)
|
||||
log.Debug().Str("ssh_path", sshPath).Strs("args", args).Msg("Executing real SSH")
|
||||
|
||||
// In WSL2, we need to use exec.Command instead of syscall.Exec for Windows binaries
|
||||
if isWSL2() {
|
||||
cmd := exec.Command(sshPath, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(exitErr.ExitCode())
|
||||
}
|
||||
log.Error().Err(err).Msg("Failed to execute SSH")
|
||||
fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// For native Linux, check if SSH exists
|
||||
if _, err := os.Stat(sshPath); os.IsNotExist(err) {
|
||||
log.Error().Str("path", sshPath).Msg("Real SSH binary not found")
|
||||
fmt.Fprintf(os.Stderr, "Error: Real SSH binary not found at %s\n", sshPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Debug().Str("ssh_path", realSSHPath).Strs("args", args).Msg("Executing real SSH")
|
||||
|
||||
// Execute the real SSH binary
|
||||
// Using syscall.Exec to replace current process (like exec in shell)
|
||||
err := syscall.Exec(realSSHPath, append([]string{"ssh"}, args...), os.Environ())
|
||||
// Execute the real SSH binary using syscall.Exec (Linux only)
|
||||
// This replaces the current process (like exec in shell)
|
||||
err := syscall.Exec(sshPath, append([]string{"ssh"}, args...), os.Environ())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to execute SSH")
|
||||
fmt.Fprintf(os.Stderr, "Error executing SSH: %v\n", err)
|
||||
|
||||
@@ -1,161 +1,163 @@
|
||||
---
|
||||
- name: Server setup
|
||||
block:
|
||||
- name: Ensure openssh-server is installed on Arch-based systems
|
||||
ansible.builtin.package:
|
||||
name: openssh
|
||||
state: present
|
||||
when: ansible_pkg_mgr == 'pacman'
|
||||
- name: Ensure openssh-server is installed on Arch-based systems
|
||||
ansible.builtin.package:
|
||||
name: openssh
|
||||
state: present
|
||||
when: ansible_pkg_mgr == 'pacman' and 'microsoft-standard-WSL2' not in ansible_kernel
|
||||
become: true
|
||||
|
||||
- name: Ensure openssh-server is installed on non-Arch systems
|
||||
ansible.builtin.package:
|
||||
name: openssh-server
|
||||
state: present
|
||||
when: ansible_pkg_mgr != 'pacman'
|
||||
- name: Ensure openssh-server is installed on non-Arch systems
|
||||
ansible.builtin.package:
|
||||
name: openssh-server
|
||||
state: present
|
||||
when: ansible_pkg_mgr != 'pacman' and 'microsoft-standard-WSL2' not in ansible_kernel
|
||||
become: true
|
||||
|
||||
- name: Ensure Borg is installed on Arch-based systems
|
||||
ansible.builtin.package:
|
||||
name: borg
|
||||
state: present
|
||||
become: true
|
||||
when: ansible_pkg_mgr == 'pacman'
|
||||
- name: Ensure Borg is installed on Arch-based systems
|
||||
ansible.builtin.package:
|
||||
name: borg
|
||||
state: present
|
||||
become: true
|
||||
when: ansible_pkg_mgr == 'pacman'
|
||||
|
||||
- name: Ensure Borg is installed on Debian/Ubuntu systems
|
||||
ansible.builtin.package:
|
||||
name: borgbackup
|
||||
state: present
|
||||
become: true
|
||||
when: ansible_pkg_mgr != 'pacman'
|
||||
- name: Ensure Borg is installed on Debian/Ubuntu systems
|
||||
ansible.builtin.package:
|
||||
name: borgbackup
|
||||
state: present
|
||||
become: true
|
||||
when: ansible_pkg_mgr != 'pacman'
|
||||
|
||||
- name: Include JuiceFS tasks
|
||||
ansible.builtin.include_tasks: juicefs.yml
|
||||
tags:
|
||||
- juicefs
|
||||
- name: Include JuiceFS tasks
|
||||
ansible.builtin.include_tasks: juicefs.yml
|
||||
tags:
|
||||
- juicefs
|
||||
|
||||
- name: Include Dynamic DNS tasks
|
||||
ansible.builtin.include_tasks: dynamic-dns.yml
|
||||
tags:
|
||||
- dynamic-dns
|
||||
- name: Include Dynamic DNS tasks
|
||||
ansible.builtin.include_tasks: dynamic-dns.yml
|
||||
tags:
|
||||
- dynamic-dns
|
||||
|
||||
- name: Include Borg Backup tasks
|
||||
ansible.builtin.include_tasks: borg-backup.yml
|
||||
tags:
|
||||
- borg-backup
|
||||
- name: Include Borg Backup tasks
|
||||
ansible.builtin.include_tasks: borg-backup.yml
|
||||
tags:
|
||||
- borg-backup
|
||||
|
||||
- name: Include Borg Local Sync tasks
|
||||
ansible.builtin.include_tasks: borg-local-sync.yml
|
||||
tags:
|
||||
- borg-local-sync
|
||||
- name: Include Borg Local Sync tasks
|
||||
ansible.builtin.include_tasks: borg-local-sync.yml
|
||||
tags:
|
||||
- borg-local-sync
|
||||
|
||||
- name: System performance optimizations
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item.name }}"
|
||||
value: "{{ item.value }}"
|
||||
state: present
|
||||
reload: true
|
||||
become: true
|
||||
loop:
|
||||
- { name: "fs.file-max", value: "2097152" } # Max open files for the entire system
|
||||
- { name: "vm.max_map_count", value: "16777216" } # Max memory map areas a process can have
|
||||
- { name: "vm.swappiness", value: "10" } # Controls how aggressively the kernel swaps out memory
|
||||
- { name: "vm.vfs_cache_pressure", value: "50" } # Controls kernel's tendency to reclaim memory for directory/inode caches
|
||||
- { name: "net.core.somaxconn", value: "65535" } # Max pending connections for a listening socket
|
||||
- { name: "net.core.netdev_max_backlog", value: "65535" } # Max packets queued on network interface input
|
||||
- { name: "net.ipv4.tcp_fin_timeout", value: "30" } # How long sockets stay in FIN-WAIT-2 state
|
||||
- { name: "net.ipv4.tcp_tw_reuse", value: "1" } # Allows reusing TIME_WAIT sockets for new outgoing connections
|
||||
- name: System performance optimizations
|
||||
ansible.posix.sysctl:
|
||||
name: "{{ item.name }}"
|
||||
value: "{{ item.value }}"
|
||||
state: present
|
||||
reload: true
|
||||
become: true
|
||||
loop:
|
||||
- { name: "fs.file-max", value: "2097152" } # Max open files for the entire system
|
||||
- { name: "vm.max_map_count", value: "16777216" } # Max memory map areas a process can have
|
||||
- { name: "vm.swappiness", value: "10" } # Controls how aggressively the kernel swaps out memory
|
||||
- { name: "vm.vfs_cache_pressure", value: "50" } # Controls kernel's tendency to reclaim memory for directory/inode caches
|
||||
- { name: "net.core.somaxconn", value: "65535" } # Max pending connections for a listening socket
|
||||
- { name: "net.core.netdev_max_backlog", value: "65535" } # Max packets queued on network interface input
|
||||
- { name: "net.ipv4.tcp_fin_timeout", value: "30" } # How long sockets stay in FIN-WAIT-2 state
|
||||
- { name: "net.ipv4.tcp_tw_reuse", value: "1" } # Allows reusing TIME_WAIT sockets for new outgoing connections
|
||||
|
||||
- name: Include service tasks
|
||||
ansible.builtin.include_tasks: "services/{{ item.name }}/{{ item.name }}.yml"
|
||||
loop: "{{ services | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list if specific_service is not defined else services | selectattr('name', 'equalto', specific_service) | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
tags:
|
||||
- services
|
||||
- always
|
||||
- name: Include service tasks
|
||||
ansible.builtin.include_tasks: "services/{{ item.name }}/{{ item.name }}.yml"
|
||||
loop: "{{ services | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list if specific_service is not defined else services | selectattr('name', 'equalto', specific_service) | selectattr('enabled', 'equalto', true) | selectattr('hosts', 'contains', inventory_hostname) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
tags:
|
||||
- services
|
||||
- always
|
||||
|
||||
vars:
|
||||
services:
|
||||
- name: dashy
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: gitea
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: factorio
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: dozzle
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: beszel
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: caddy
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: golink
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: immich
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: plex
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: tautulli
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: downloaders
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: wireguard
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: nextcloud
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: cloudreve
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: echoip
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: arr-stack
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: home-assistant
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: privatebin
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: unifi-network-application
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: avorion
|
||||
enabled: false
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: sathub
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
services:
|
||||
- name: dashy
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: gitea
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: factorio
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: dozzle
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: beszel
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: caddy
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: golink
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: immich
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: plex
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: tautulli
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: downloaders
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: wireguard
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: nextcloud
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: cloudreve
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: echoip
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: arr-stack
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: home-assistant
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: privatebin
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: unifi-network-application
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: avorion
|
||||
enabled: false
|
||||
hosts:
|
||||
- mennos-server
|
||||
- name: sathub
|
||||
enabled: true
|
||||
hosts:
|
||||
- mennos-server
|
||||
|
||||
Reference in New Issue
Block a user