From 731580991450356247c081f239fef0469e1ae7f8 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Mon, 10 Mar 2025 19:05:18 +0100 Subject: [PATCH] feat: add SSH login information and dotfiles status check to hello.py; include OpenSSH server tasks in Ansible configuration --- .bashrc | 3 + bin/actions/hello.py | 128 ++++++++++++++++++ config/ansible/tasks/global/global.yml | 6 + .../ansible/tasks/global/openssh-server.yml | 22 +++ .../templates/sshd_config.j2} | 30 ++-- config/home-manager/flake.lock | 6 +- 6 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 config/ansible/tasks/global/openssh-server.yml rename config/{ssh/sshd_config => ansible/templates/sshd_config.j2} (86%) diff --git a/.bashrc b/.bashrc index 5ecfc1b..4a2b062 100644 --- a/.bashrc +++ b/.bashrc @@ -82,6 +82,9 @@ export NIXPKGS_ALLOW_UNFREE=1 # Allow insecure nixpkgs export NIXPKGS_ALLOW_INSECURE=1 +# 1Password SSH Agent +export SSH_AUTH_SOCK=$HOME/.1password/agent.sock + # Set DOTF_HOSTNAME to the hostname from .hostname file # If this file doesn't exist, use mennos-unknown-hostname export DOTF_HOSTNAME="mennos-unknown-hostname" diff --git a/bin/actions/hello.py b/bin/actions/hello.py index d279ada..4707f9c 100755 --- a/bin/actions/hello.py +++ b/bin/actions/hello.py @@ -2,11 +2,129 @@ import os import sys +import subprocess +from datetime import datetime # Import helper functions sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin")) from helpers.functions import printfe, logo, _rainbow_color +def get_last_ssh_login(): + """Get information about the last SSH login""" + try: + # Using lastlog to get the last login information + result = subprocess.run(['lastlog2', '-u', os.environ.get("USER", "")], + capture_output=True, text=True) + if result.returncode == 0: + lines = result.stdout.strip().split('\n') + if len(lines) >= 2: # Header line + data line + # Parse the last login line for SSH connections + parts = lines[1].split() + if len(parts) >= 7 and 'ssh' in ' '.join(parts).lower(): + time_str = ' '.join(parts[3:6]) + ip = parts[-1] + return f"Last SSH login: {time_str} from {ip}" + return None + except Exception: + return None + +def check_dotfiles_status(): + """Check if the dotfiles repository is dirty""" + dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles")) + try: + if not os.path.isdir(os.path.join(dotfiles_path, ".git")): + return None + + # Check for git status details + status = { + 'is_dirty': False, + 'untracked': 0, + 'modified': 0, + 'staged': 0, + 'commit_hash': '', + 'unpushed': 0 + } + + # Get status of files + result = subprocess.run(['git', 'status', '--porcelain'], + cwd=dotfiles_path, + capture_output=True, text=True) + + if result.stdout.strip(): + status['is_dirty'] = True + for line in result.stdout.splitlines(): + if line.startswith('??'): + status['untracked'] += 1 + if line.startswith(' M') or line.startswith('MM'): + status['modified'] += 1 + if line.startswith('M ') or line.startswith('A '): + status['staged'] += 1 + + # Get current commit hash + result = subprocess.run(['git', 'rev-parse', '--short', 'HEAD'], + cwd=dotfiles_path, + capture_output=True, text=True) + if result.returncode == 0: + status['commit_hash'] = result.stdout.strip() + + # Count unpushed commits + # Fix: Remove capture_output and set stdout explicitly + result = subprocess.run(['git', 'log', '--oneline', '@{u}..'], + cwd=dotfiles_path, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True) + if result.returncode == 0: + status['unpushed'] = len(result.stdout.splitlines()) + + return status + except Exception as e: + print(f"Error checking dotfiles status: {str(e)}") + return None + +def get_condensed_status(): + """Generate a condensed status line for trash and git status""" + status_parts = [] + + # Define ANSI color codes + RED = "\033[31m" + YELLOW = "\033[33m" + GREEN = "\033[32m" + BLUE = "\033[34m" + RESET = "\033[0m" + + # Check trash status + trash_path = os.path.expanduser("~/.local/share/Trash/files") + try: + if os.path.exists(trash_path): + items = os.listdir(trash_path) + count = len(items) + if count > 0: + status_parts.append(f"[!] {count} file(s) in trash") + except Exception: + pass + + # Check dotfiles status + dotfiles_status = check_dotfiles_status() + if dotfiles_status is not None: + if dotfiles_status['is_dirty']: + status_parts.append(f"{YELLOW}dotfiles is dirty{RESET}") + status_parts.append(f"{RED}[{dotfiles_status['untracked']}] untracked{RESET}") + status_parts.append(f"{YELLOW}[{dotfiles_status['modified']}] modified{RESET}") + status_parts.append(f"{GREEN}[{dotfiles_status['staged']}] staged{RESET}") + + if dotfiles_status['commit_hash']: + status_parts.append(f"[{BLUE}{dotfiles_status['commit_hash']}{RESET}]") + + if dotfiles_status['unpushed'] > 0: + status_parts.append(f"[!] You have {dotfiles_status['unpushed']} commit(s) to push") + else: + status_parts.append("Unable to check dotfiles status") + + if status_parts: + return " - ".join(status_parts) + return None + def welcome(): """Display welcome message with hostname and username""" print() @@ -20,6 +138,16 @@ def welcome(): print("\033[36m] as [", end="") print(_rainbow_color(username), end="") print("\033[36m]\033[0m") + + # Display last SSH login info if available + ssh_login = get_last_ssh_login() + if ssh_login: + print(f"\033[33m{ssh_login}\033[0m") + + # Display condensed status line + condensed_status = get_condensed_status() + if condensed_status: + print(f"\033[33m{condensed_status}\033[0m") def main(): logo(continue_after=True) diff --git a/config/ansible/tasks/global/global.yml b/config/ansible/tasks/global/global.yml index 05350c0..b6b8d3e 100644 --- a/config/ansible/tasks/global/global.yml +++ b/config/ansible/tasks/global/global.yml @@ -22,6 +22,10 @@ ansible.builtin.import_tasks: tasks/global/ollama.yml become: true +- name: Include OpenSSH Server tasks + ansible.builtin.import_tasks: tasks/global/openssh-server.yml + become: true + - name: Ensure common packages are installed ansible.builtin.package: name: @@ -31,9 +35,11 @@ - trash-cli - curl - wget + # Python is used for the dotfiles CLI tools - python3 - python3-pip - python3-venv + - lastlog2 # Used for displaying last login information state: present become: true diff --git a/config/ansible/tasks/global/openssh-server.yml b/config/ansible/tasks/global/openssh-server.yml new file mode 100644 index 0000000..0f9e500 --- /dev/null +++ b/config/ansible/tasks/global/openssh-server.yml @@ -0,0 +1,22 @@ +--- +- name: Ensure openssh-server is installed + ansible.builtin.package: + name: openssh-server + state: present + +- name: Ensure SSH server configuration is proper + ansible.builtin.template: + src: templates/sshd_config.j2 + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: '0644' + validate: '/usr/sbin/sshd -t -f %s' + notify: Restart SSH service + register: ssh_config + +- name: Ensure SSH service is enabled and running + ansible.builtin.service: + name: ssh + state: started + enabled: true diff --git a/config/ssh/sshd_config b/config/ansible/templates/sshd_config.j2 similarity index 86% rename from config/ssh/sshd_config rename to config/ansible/templates/sshd_config.j2 index 5fd1531..2a9ffee 100644 --- a/config/ssh/sshd_config +++ b/config/ansible/templates/sshd_config.j2 @@ -1,4 +1,3 @@ - # This is the sshd server system-wide configuration file. See # sshd_config(5) for more information. @@ -11,6 +10,15 @@ Include /etc/ssh/sshd_config.d/*.conf +# When systemd socket activation is used (the default), the socket +# configuration must be re-generated after changing Port, AddressFamily, or +# ListenAddress. +# +# For changes to take effect, run: +# +# systemctl daemon-reload +# systemctl restart ssh.socket +# Port 400 #AddressFamily any #ListenAddress 0.0.0.0 @@ -25,12 +33,12 @@ Port 400 # Logging #SyslogFacility AUTH -#LogLevel INFO +LogLevel INFO # Authentication: #LoginGraceTime 2m -PermitRootLogin prohibit-password +PermitRootLogin no #StrictModes yes #MaxAuthTries 6 #MaxSessions 10 @@ -54,8 +62,8 @@ PubkeyAuthentication yes #IgnoreRhosts yes # To disable tunneled clear text passwords, change to no here! -PasswordAuthentication yes -PermitEmptyPasswords no +PasswordAuthentication no +#PermitEmptyPasswords no # Change to yes to enable challenge-response passwords (beware issues with # some PAM modules and threads) @@ -84,8 +92,8 @@ KbdInteractiveAuthentication no # and KbdInteractiveAuthentication to 'no'. UsePAM yes -AllowAgentForwarding yes -AllowTcpForwarding yes +#AllowAgentForwarding yes +#AllowTcpForwarding yes #GatewayPorts no X11Forwarding yes #X11DisplayOffset 10 @@ -96,8 +104,8 @@ PrintMotd no #TCPKeepAlive yes #PermitUserEnvironment no #Compression delayed -#ClientAliveInterval 0 -#ClientAliveCountMax 3 +ClientAliveInterval 300 +ClientAliveCountMax 2 #UseDNS no #PidFile /run/sshd.pid #MaxStartups 10:30:100 @@ -116,7 +124,7 @@ Subsystem sftp /usr/lib/openssh/sftp-server # Example of overriding settings on a per-user basis #Match User anoncvs -# X11Forwarding no -# AllowTcpForwarding no +X11Forwarding yes +AllowTcpForwarding yes # PermitTTY no # ForceCommand cvs server diff --git a/config/home-manager/flake.lock b/config/home-manager/flake.lock index 46f9607..a3134eb 100644 --- a/config/home-manager/flake.lock +++ b/config/home-manager/flake.lock @@ -39,11 +39,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1741379970, - "narHash": "sha256-Wh7esNh7G24qYleLvgOSY/7HlDUzWaL/n4qzlBePpiw=", + "lastModified": 1741513245, + "narHash": "sha256-7rTAMNTY1xoBwz0h7ZMtEcd8LELk9R5TzBPoHuhNSCk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "36fd87baa9083f34f7f5027900b62ee6d09b1f2f", + "rev": "e3e32b642a31e6714ec1b712de8c91a3352ce7e1", "type": "github" }, "original": {