Update Makefile and inventory configurations for improved task execution and organization

- Refactor Makefile to enhance command structure, including clearer descriptions and usage examples for targets related to development, inventory, and monitoring tasks.
- Update inventory files to ensure correct host configurations and user settings, including adjustments to ansible_user for specific hosts.
- Modify group_vars to streamline Tailscale configuration and ensure proper handling of authentication keys.

These changes improve the clarity and usability of the Makefile and inventory setup, facilitating smoother operations across the infrastructure.
This commit is contained in:
ilia 2025-10-09 21:24:45 -04:00
parent e05b3aa0d5
commit 579f0709ce
34 changed files with 3308 additions and 320 deletions

View File

@ -1,4 +1,4 @@
.PHONY: help bootstrap lint test check apply dev local clean status tailscale tailscale-check tailscale-dev tailscale-status create-vault create-vm monitoring
.PHONY: help bootstrap lint test check dev datascience inventory inventory-all local clean status tailscale tailscale-check tailscale-dev tailscale-status create-vault create-vm monitoring
.DEFAULT_GOAL := help
## Colors for output
@ -226,10 +226,6 @@ check-local: ## Dry-run the local playbook
@echo "$(YELLOW)Running dry-run on localhost...$(RESET)"
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_LOCAL) --check --diff -K
apply: auto-fallback ## Run the development playbook on all dev hosts
@echo "$(YELLOW)Applying development playbook...$(RESET)"
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV)
site: ## Run the complete site playbook
@echo "$(YELLOW)Running complete site deployment...$(RESET)"
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SITE)
@ -248,6 +244,42 @@ endif
@echo "$(YELLOW)Running on host: $(HOST)$(RESET)"
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST)
# Data science role
datascience: ## Install data science stack (usage: make datascience HOST=server01)
ifndef HOST
@echo "$(RED)Error: HOST parameter required$(RESET)"
@echo "Usage: make datascience HOST=server01"
@exit 1
endif
@echo "$(YELLOW)Installing data science stack on: $(HOST)$(RESET)"
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST) --tags datascience
# Inventory system
inventory: ## Show installed software on host (usage: make inventory HOST=dev01)
ifndef HOST
@echo "$(RED)Error: HOST parameter required$(RESET)"
@echo "Usage: make inventory HOST=dev01"
@exit 1
endif
@echo "$(BOLD)Software Inventory for: $(HOST)$(RESET)"
@echo ""
@ip=$$(ansible-inventory --list | jq -r "._meta.hostvars.$(HOST).ansible_host // empty" 2>/dev/null); \
user=$$(ansible-inventory --list | jq -r "._meta.hostvars.$(HOST).ansible_user // empty" 2>/dev/null); \
if [ -n "$$ip" ] && [ "$$ip" != "null" ] && [ -n "$$user" ] && [ "$$user" != "null" ]; then \
scp -q scripts/inventory.sh $$user@$$ip:/tmp/inventory.sh 2>/dev/null && \
ssh $$user@$$ip 'bash /tmp/inventory.sh; rm /tmp/inventory.sh' 2>/dev/null; \
else \
echo "$(RED)Could not determine IP or user for $(HOST)$(RESET)"; \
fi
inventory-all: ## Show installed software on all hosts
@echo "$(BOLD)Software Inventory - All Hosts$(RESET)\n"
@for host in $$(ansible all --list-hosts 2>/dev/null | grep -v "hosts" | tr -d ' '); do \
echo "$(YELLOW)=== $$host ===$(RESET)"; \
ansible $$host -m script -a "scripts/inventory.sh" 2>/dev/null | sed -n '/CHANGED/,$$p' | tail -n +3 || echo "$(RED)Failed to connect$(RESET)"; \
echo ""; \
done
# Tag-based execution
security: ## Run only security-related roles
@echo "$(YELLOW)Running security roles...$(RESET)"

View File

@ -1,32 +0,0 @@
---
# Common variables for all hosts
timezone: America/Toronto
locale: en_US.UTF-8
ansible_python_interpreter: /usr/bin/python3
# Debug settings
ansible_debug_output: false
# Security settings
fail2ban_bantime: 3600
fail2ban_findtime: 600
fail2ban_maxretry: 3
# Maintenance settings
maintenance_default_serial: "100%" # Default serial execution for maintenance
maintenance_reboot_timeout: 300 # Reboot timeout in seconds
maintenance_pre_reboot_delay: 5 # Delay before reboot in seconds
# Global variables for all hosts
# Tailscale configuration
# Store your actual auth key in vault_tailscale_auth_key using ansible-vault
# Example: ansible-vault create group_vars/all/vault.yml
# vault_tailscale_auth_key: "tskey-auth-your-actual-key-here"
# Default Tailscale settings - these tell the playbook to use your vault key
tailscale_auth_key: "{{ vault_tailscale_auth_key | default('') }}"
tailscale_accept_routes: true
tailscale_accept_dns: true
tailscale_ssh: true
tailscale_hostname: "{{ inventory_hostname }}"

View File

@ -28,5 +28,5 @@ maintenance_pre_reboot_delay: 5 # Delay before reboot in seconds
tailscale_auth_key: "{{ vault_tailscale_auth_key | default('') }}"
tailscale_accept_routes: true
tailscale_accept_dns: true
tailscale_ssh: true
tailscale_ssh: false
tailscale_hostname: "{{ inventory_hostname }}"

View File

@ -0,0 +1,31 @@
ansible_become_password: root
ansible_python_interpreter: /usr/bin/python3
# Shell configuration
# ansible_user (root) is configured by default
# Add additional users here if needed:
shell_additional_users:
- devuser01
- devuser02
- dev
# Data Science configuration (datascience role)
install_conda: true
conda_install_path: "/root/anaconda3"
install_jupyter: true
jupyter_port: 8888
jupyter_allow_remote: true
jupyter_bind_all_interfaces: true
# R configuration
install_r: true
# Cursor IDE configuration
install_cursor_extensions: true
# Cursor extension groups to enable
install_python: true # Python development
install_jupyter: true # Jupyter notebooks
install_r: true # R language support
install_docs: true # Markdown/documentation

View File

@ -13,13 +13,13 @@ portainerVM ansible_host=10.0.30.69 ansible_user=ladmin
homepageVM ansible_host=10.0.30.12 ansible_user=homepage
[vaultwarden]
vaultwardenVM ansible_host=100.100.19.11 ansible_host_fallback=10.0.10.142 ansible_user=ladmin
vaultwardenVM ansible_host=100.100.19.11 ansible_host_fallback=10.0.10.142 ansible_user=root
[dev]
dev01 ansible_host=10.0.30.105 ansible_user=ladmin
bottom ansible_host=10.0.10.156 ansible_user=beast
debianDesktopVM ansible_host=10.0.10.206 ansible_user=user skip_reboot=true
devGPU ansible_host=10.0.30.63 ansible_user=root
[ansible]
ansibleVM ansible_host=10.0.10.157 ansible_user=master
@ -28,8 +28,8 @@ ansibleVM ansible_host=10.0.10.157 ansible_user=master
tailscaleVM ansible_host=100.66.218.53 ansible_user=ladmin
[services]
caddy ansible_host=100.117.106.18 ansible_host_fallback=10.0.10.50 ansible_user=ladmin
jellyfin ansible_host=100.104.109.45 ansible_host_fallback=10.0.10.232 ansible_user=user
caddy ansible_host=100.117.106.18 ansible_host_fallback=10.0.10.50 ansible_user=root
jellyfin ansible_host=100.104.109.45 ansible_host_fallback=10.0.10.232 ansible_user=root
listmonk ansible_host=100.73.190.115 ansible_host_fallback=10.0.10.149 ansible_user=ladmin
slack ansible_host=100.110.190.69 ansible_host_fallback=10.0.10.154 ansible_user=ladmin

View File

@ -11,10 +11,10 @@
- {role: ssh, tags: ['ssh', 'security']}
- {role: shell, tags: ['shell']}
- {role: development, tags: ['development', 'dev']}
- {role: datascience, tags: ['datascience', 'conda', 'jupyter', 'r']}
- {role: docker, tags: ['docker']}
- {role: applications, tags: ['applications', 'apps']}
- {role: snap, tags: ['snap', 'apps']}
- {role: tailscale, tags: ['tailscale', 'vpn']}
# - {role: tailscale, tags: ['tailscale', 'vpn']}
- {role: monitoring, tags: ['monitoring']}
pre_tasks:

View File

@ -13,8 +13,7 @@
- {role: development, tags: ['development', 'dev']}
- {role: docker, tags: ['docker']}
- {role: applications, tags: ['applications', 'apps']}
- {role: snap, tags: ['snap', 'apps']}
- {role: tailscale, tags: ['tailscale', 'vpn']}
# - {role: tailscale, tags: ['tailscale', 'vpn']}
- {role: monitoring, tags: ['monitoring']}
pre_tasks:

View File

@ -1,6 +1,6 @@
---
# Playbook: shell.yml
# Purpose: Configure shell environment (zsh, oh-my-zsh, plugins) on all hosts
# Purpose: Configure shell environment (zsh, oh-my-zsh, plugins)
# Targets: all hosts
# Tags: shell
# Usage: make shell-all
@ -9,6 +9,8 @@
hosts: all
become: true
strategy: free
ignore_errors: true
ignore_unreachable: true
roles:
- {role: shell, tags: ['shell']}

View File

@ -1,38 +1,64 @@
Role Name
=========
# Role: base
A brief description of the role goes here.
## Description
Installs core system packages and utilities required by all other roles. This is the foundation role that should be applied to all managed hosts.
Requirements
------------
## Requirements
- Ansible 2.9+
- Debian/Ubuntu systems
- Root or sudo access
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
## Installed Packages
Role Variables
--------------
### Base Utilities
- curl, wget - Download tools
- unzip - Archive extraction
- xclip - Clipboard utility
- tree - Directory visualization
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
### Network & Admin Tools
- net-tools - Network configuration
- ufw - Uncomplicated Firewall
- mailutils - Email utilities
Dependencies
------------
### Modern CLI Tools
- jq - JSON processor
- yq - YAML processor (from apt or GitHub binary)
- ripgrep - Fast text search
- fd-find - Fast file finder
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
## Configuration Tasks
- Sets system timezone (default: UTC)
- Configures system locale (default: en_US.UTF-8)
- Creates Ansible temporary directory with proper permissions
- Creates fd symlink for Ubuntu compatibility
Example Playbook
----------------
## Variables
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
| Variable | Default | Description |
|----------|---------|-------------|
| `timezone` | `UTC` | System timezone |
| `locale` | `en_US.UTF-8` | System locale |
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
## Dependencies
None - this is the foundation role.
License
-------
## Example Playbook
BSD
```yaml
- hosts: all
roles:
- role: base
timezone: America/Toronto
locale: en_US.UTF-8
```
Author Information
------------------
## Tags
- `base`: All base tasks
- `security`: Security-related tasks
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
## Notes
- This role should be applied before all other roles
- yq is installed from apt if available, otherwise from GitHub binary
- fd-find is symlinked to fd for compatibility across distributions
- UFW firewall is installed but not enabled (handled by SSH role)

View File

@ -27,11 +27,27 @@
- fd-find
state: present
- name: Install modern tools via snap
community.general.snap:
name:
- yq
- name: Install yq YAML processor
ansible.builtin.apt:
name: yq
state: present
update_cache: false
failed_when: false
register: yq_apt_install
- name: Install yq from binary if apt fails
when: yq_apt_install.failed or yq_apt_install is not succeeded
block:
- name: Download yq binary
ansible.builtin.get_url:
url: https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
dest: /usr/local/bin/yq
mode: '0755'
register: yq_download
- name: Verify yq installation
ansible.builtin.command: yq --version
changed_when: false
- name: Create fd symlink (Ubuntu uses fd-find)
ansible.builtin.file:

185
roles/datascience/README.md Normal file
View File

@ -0,0 +1,185 @@
# Role: datascience
## Description
Installs and configures a complete data science environment including Anaconda/Conda, Jupyter Notebook, and R statistical computing language. This role is optional and separate from general development tools for faster deployments when data science capabilities are not needed.
## Requirements
- Ansible 2.9+
- Debian/Ubuntu systems
- At least 5GB free disk space (Anaconda is ~700MB)
- Root or sudo access
## Installed Components
### Anaconda/Conda
- Full Anaconda3 distribution (latest version)
- Python data science packages (pandas, numpy, matplotlib, scikit-learn)
- Conda package manager
- Initialized for bash (zsh initialization via shell role)
### Jupyter Notebook
- Jupyter Notebook server
- IPython kernel
- JupyterLab interface
- Systemd service for automatic startup
- Web-based access on configurable port
### R Language
- R base and development packages
- R recommended packages
- CRAN repository configuration
- IRkernel for Jupyter integration
- Common R packages
## Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `install_conda` | `false` | Install Anaconda/Conda |
| `conda_install_path` | `{{ ansible_env.HOME }}/anaconda3` | Conda installation directory |
| `install_jupyter` | `false` | Install Jupyter Notebook (requires conda) |
| `jupyter_port` | `8888` | Jupyter web server port |
| `jupyter_bind_all_interfaces` | `true` | Listen on all network interfaces |
| `jupyter_allow_remote` | `true` | Allow remote connections |
| `install_r` | `false` | Install R language |
| `r_packages` | `[r-base, r-base-dev, r-recommended]` | R packages to install |
## Dependencies
- `base` role (for core utilities)
## Example Playbook
### Full Data Science Stack
```yaml
- hosts: datascience_servers
roles:
- role: datascience
install_conda: true
install_jupyter: true
install_r: true
```
### Conda + Jupyter Only
```yaml
- hosts: jupyter_servers
roles:
- role: datascience
install_conda: true
install_jupyter: true
install_r: false
```
### R Only (no Python)
```yaml
- hosts: r_servers
roles:
- role: datascience
install_conda: false
install_r: true
```
## Usage
### Installation
```bash
# Install full data science stack
make datascience HOST=server01
# Install on specific host with custom vars
ansible-playbook playbooks/development.yml --limit server01 --tags datascience \
-e "install_conda=true install_jupyter=true install_r=true"
```
### Post-Installation
#### Set Jupyter Password
```bash
jupyter notebook password
```
#### Access Jupyter
```bash
# Local
http://localhost:8888
# Remote
http://your-server-ip:8888
```
#### Verify Installations
```bash
conda --version
jupyter --version
R --version
```
## Tags
- `datascience`: All data science tasks
- `conda`: Conda/Anaconda installation only
- `jupyter`: Jupyter Notebook installation only
- `r`, `rstats`: R language installation only
## Services
### Jupyter Notebook Service
- **Service name**: `jupyter-notebook.service`
- **Start**: `systemctl start jupyter-notebook`
- **Status**: `systemctl status jupyter-notebook`
- **Logs**: `journalctl -u jupyter-notebook`
## Performance Notes
### Installation Times
- **Anaconda**: 5-10 minutes (700MB download)
- **Jupyter**: 2-5 minutes (if Anaconda already installed)
- **R**: 10-30 minutes (compilation of packages)
### Disk Space
- **Anaconda**: ~2.5GB after installation
- **R + packages**: ~500MB
- **Total**: ~3GB for full stack
## Security Considerations
1. **Jupyter Password**: Always set a password after installation
2. **Firewall**: Ensure port 8888 (or custom port) is properly firewalled
3. **HTTPS**: Consider using HTTPS for remote access
4. **User Isolation**: Jupyter runs as the ansible user by default
## Troubleshooting
### Conda Not in PATH
```bash
# Add to .bashrc or .zshrc
export PATH="$HOME/anaconda3/bin:$PATH"
```
### Jupyter Service Won't Start
```bash
# Check logs
journalctl -u jupyter-notebook -n 50
# Verify conda is accessible
/root/anaconda3/bin/jupyter --version
```
### R Package Installation Fails
```bash
# Install manually in R
R
> install.packages("IRkernel")
```
## Integration with Other Roles
- **Shell Role**: Provides zsh with conda integration
- **Monitoring**: btop/htop for resource monitoring
- **Docker**: Can run Jupyter in containers alternatively
## Notes
- Anaconda installer is cleaned up after installation
- Conda init for zsh is handled by the shell role
- IRkernel is automatically installed if both Jupyter and R are enabled
- R packages are compiled during installation (can be slow)
- Jupyter service starts on boot automatically

View File

@ -0,0 +1,20 @@
---
# Data Science role defaults
# Conda/Anaconda configuration
install_conda: false
conda_install_path: "{{ ansible_env.HOME }}/anaconda3"
# Jupyter Notebook configuration
install_jupyter: false
jupyter_port: 8888
jupyter_allow_remote: true
jupyter_bind_all_interfaces: true
# R language configuration
install_r: false
r_packages:
- r-base
- r-base-dev
- r-recommended

View File

@ -0,0 +1,8 @@
---
- name: Restart jupyter-notebook
ansible.builtin.systemd:
name: jupyter-notebook
state: restarted
daemon_reload: true
become: true

View File

@ -0,0 +1,4 @@
---
dependencies:
- role: base

View File

@ -0,0 +1,203 @@
---
# Conda/Anaconda installation
- name: Conda installation block
when: install_conda | default(false) | bool
tags: ['conda']
block:
- name: Check if conda is installed
ansible.builtin.stat:
path: "{{ conda_install_path }}/bin/conda"
register: conda_installed
- name: Download Anaconda installer
ansible.builtin.get_url:
url: https://repo.anaconda.com/archive/Anaconda3-2024.10-1-Linux-x86_64.sh
dest: /tmp/anaconda_installer.sh
mode: '0755'
when: not conda_installed.stat.exists
- name: Install Anaconda
ansible.builtin.shell: |
bash /tmp/anaconda_installer.sh -b -p {{ conda_install_path }}
args:
creates: "{{ conda_install_path }}/bin/conda"
when: not conda_installed.stat.exists
- name: Initialize conda for bash
ansible.builtin.shell: |
{{ conda_install_path }}/bin/conda init bash
args:
creates: "{{ ansible_env.HOME }}/.bashrc"
when: not conda_installed.stat.exists
failed_when: false
# Note: conda init zsh is skipped because conda initialization
# is already included in the custom .zshrc deployed by the shell role
# This prevents conda from overwriting our custom .zshrc configuration
- name: Clean up Anaconda installer
ansible.builtin.file:
path: /tmp/anaconda_installer.sh
state: absent
- name: Verify conda installation
ansible.builtin.command: "{{ conda_install_path }}/bin/conda --version"
register: conda_version
changed_when: false
- name: Display conda version
ansible.builtin.debug:
msg: "Conda version installed: {{ conda_version.stdout if conda_version.stdout is defined else 'Not checked in dry-run mode' }}"
# Jupyter Notebook installation
- name: Jupyter Notebook installation block
tags: ['jupyter']
when:
- install_conda | default(false) | bool
- install_jupyter | default(false) | bool
block:
- name: Check if Jupyter is installed
ansible.builtin.command: "{{ conda_install_path }}/bin/conda list jupyter"
register: jupyter_installed
changed_when: false
failed_when: false
- name: Install Jupyter Notebook and common packages via conda
ansible.builtin.shell: |
{{ conda_install_path }}/bin/conda install -y jupyter notebook ipython pandas numpy matplotlib scikit-learn
when: jupyter_installed.rc != 0 or 'jupyter' not in jupyter_installed.stdout
changed_when: true
- name: Create Jupyter config directory
ansible.builtin.file:
path: "{{ ansible_env.HOME }}/.jupyter"
state: directory
mode: '0755'
- name: Configure Jupyter Notebook
ansible.builtin.copy:
content: |
# Jupyter Notebook Configuration
c.NotebookApp.ip = '{{ "0.0.0.0" if jupyter_bind_all_interfaces | default(true) | bool else "localhost" }}'
c.NotebookApp.port = {{ jupyter_port | default(8888) }}
c.NotebookApp.open_browser = False
c.NotebookApp.allow_root = True
# Note: For security, set a password with: jupyter notebook password
dest: "{{ ansible_env.HOME }}/.jupyter/jupyter_notebook_config.py"
mode: '0644'
- name: Create systemd service for Jupyter Notebook
ansible.builtin.copy:
content: |
[Unit]
Description=Jupyter Notebook Server
After=network.target
[Service]
Type=simple
User={{ ansible_user_id }}
WorkingDirectory={{ ansible_env.HOME }}
ExecStart={{ conda_install_path }}/bin/jupyter notebook
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/jupyter-notebook.service
mode: '0644'
become: true
- name: Enable and start Jupyter Notebook service
ansible.builtin.systemd:
name: jupyter-notebook
enabled: true
state: started
daemon_reload: true
become: true
- name: Verify Jupyter installation
ansible.builtin.command: "{{ conda_install_path }}/bin/jupyter --version"
register: jupyter_version
changed_when: false
- name: Display Jupyter installation info
ansible.builtin.debug:
msg:
- "Jupyter version: {{ jupyter_version.stdout if jupyter_version.stdout is defined else 'Not checked in dry-run mode' }}"
- "Access Jupyter at: http://{{ ansible_host }}:{{ jupyter_port | default(8888) }}"
- "Set a password with: jupyter notebook password"
# R language installation
- name: R language installation block
when: install_r | default(false) | bool
tags: ['r', 'rstats']
block:
- name: Install R dependencies
ansible.builtin.apt:
name:
- dirmngr
- gnupg
- apt-transport-https
- ca-certificates
- software-properties-common
state: present
update_cache: false
become: true
- name: Fetch CRAN GPG key from keyserver
ansible.builtin.shell: |
gpg --keyserver keyserver.ubuntu.com --recv-key '95C0FAF38DB3CCAD0C080A7BDC78B2DDEABC47B7'
gpg --armor --export '95C0FAF38DB3CCAD0C080A7BDC78B2DDEABC47B7' | tee /etc/apt/trusted.gpg.d/cran_debian_key.asc
args:
creates: /etc/apt/trusted.gpg.d/cran_debian_key.asc
become: true
- name: Add CRAN repository
ansible.builtin.apt_repository:
repo: "deb https://cloud.r-project.org/bin/linux/debian {{ ansible_distribution_release }}-cran40/"
state: present
filename: cran
update_cache: false
become: true
- name: Update apt cache after adding CRAN
ansible.builtin.apt:
update_cache: true
become: true
retries: 2
delay: 2
- name: Install R packages
ansible.builtin.apt:
name: "{{ r_packages }}"
state: present
become: true
- name: Install common R packages via R (non-interactive)
ansible.builtin.shell: >
R --quiet --no-save -e "install.packages(c('IRkernel'), repos='https://cloud.r-project.org', Ncpus=4)"
environment:
R_LIBS_USER: "/usr/local/lib/R/site-library"
register: r_packages_install
changed_when: "'DONE' in r_packages_install.stdout or r_packages_install.rc == 0"
failed_when: false
async: 3600
poll: 30
- name: Install IRkernel for Jupyter
ansible.builtin.command: >
R -e "IRkernel::installspec(user = TRUE)"
when: install_jupyter | default(false) | bool
register: irkernel_install
changed_when: "'✔' in irkernel_install.stdout or 'successfully' in irkernel_install.stdout.lower()"
failed_when: false
- name: Verify R installation
ansible.builtin.command: R --version
register: r_version
changed_when: false
- name: Display R version
ansible.builtin.debug:
msg: "R version installed: {{ r_version.stdout_lines[0] if r_version.stdout_lines | length > 0 else 'Not checked in dry-run mode' }}"

View File

@ -1,38 +1,249 @@
Role Name
=========
# Role: development
A brief description of the role goes here.
## Description
Installs core development tools and utilities for software development. This role provides a lightweight foundation for coding without heavy data science dependencies.
Requirements
------------
**For data science tools (Anaconda, Jupyter, R), see the `datascience` role.**
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
## Requirements
- Ansible 2.9+
- Debian/Ubuntu systems
- Root or sudo access
Role Variables
--------------
## Installed Components
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
### Development Tools
- **git**: Version control system
- **build-essential**: C/C++ compilation tools (gcc, g++, make)
- **python3**: Python 3 interpreter
- **python3-pip**: Python package manager
Dependencies
------------
### Node.js
- **Node.js 22.x**: Latest LTS from NodeSource
- **npm**: Node package manager (included with Node.js)
- Configured from official NodeSource repository
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
### Code Editors
- **Cursor IDE**: AI-powered code editor (AppImage)
- Installed to `/usr/local/bin/cursor`
- Latest stable version from cursor.com
Example Playbook
----------------
## Variables
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
### Core Settings
| Variable | Default | Description |
|----------|---------|-------------|
| `install_cursor` | `true` | Install Cursor IDE |
| `install_cursor_extensions` | `false` | Install Cursor extensions |
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
### Extension Groups
Enable specific extension groups based on your development needs:
License
-------
| Variable | Default | Extensions Included |
|----------|---------|-------------------|
| `install_python` | `false` | Python, Pylance, Black, isort, Flake8, Ruff |
| `install_jupyter` | `false` | Jupyter notebooks, keybindings, renderers |
| `install_web` | `false` | Prettier, ESLint, Tailwind, Vue, Svelte |
| `install_playwright` | `false` | Playwright testing framework |
| `install_devops` | `false` | Go, Rust, YAML, Docker, Ansible |
| `install_r` | `false` | R language support and pack development |
| `install_docs` | `false` | Markdown tools and linter |
BSD
### Base Extensions (Always Installed)
When `install_cursor_extensions: true`, these are always installed:
- ErrorLens (better error highlighting)
- GitLens (Git supercharged)
- Git Graph (visualization)
- Code Spell Checker
- EditorConfig support
- Material Icon Theme
- GitHub Copilot (if licensed)
- Copilot Chat
Author Information
------------------
## Dependencies
- `base` role (for core utilities)
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
## Example Playbook
### Basic Installation
```yaml
- hosts: developers
roles:
- role: development
```
### Python Data Science Machine
```yaml
- hosts: datascience
roles:
- role: development
vars:
install_cursor_extensions: true
install_python: true
install_jupyter: true
install_docs: true
```
### Web Development Machine
```yaml
- hosts: webdevs
roles:
- role: development
vars:
install_cursor_extensions: true
install_web: true
install_playwright: true
install_docs: true
```
### Full Stack with DevOps
```yaml
- hosts: fullstack
roles:
- role: development
vars:
install_cursor_extensions: true
install_python: true
install_web: true
install_devops: true
install_docs: true
```
### Custom Extension List
You can also override the extension list completely in `host_vars`:
```yaml
# host_vars/myhost.yml
install_cursor_extensions: true
cursor_extensions:
- ms-python.python
- golang.go
- hashicorp.terraform
# ... your custom list
```
### With Cursor disabled
```yaml
- hosts: servers
roles:
- role: development
install_cursor: false
```
## Usage
```bash
# Install on specific host
make dev HOST=dev01
# Install only development tools (skip other roles)
ansible-playbook playbooks/development.yml --limit dev01 --tags development
```
## Tags
- `development`, `dev`: All development tasks
- `cursor`, `ide`: Cursor IDE installation only
## Post-Installation
### Verify Installations
```bash
git --version
node --version
npm --version
python3 --version
cursor --version
```
### Node.js Usage
```bash
# Install packages globally
npm install -g <package>
# Check Node.js version
node --version # Should show v22.x
```
### Cursor IDE Usage
```bash
# Launch Cursor (if using X11/Wayland)
cursor
# For root users, use the aliased version from .zshrc:
cursor # Automatically adds --no-sandbox flags
```
## Performance Notes
### Installation Time
- **Base packages**: 1-2 minutes
- **Node.js**: 1-2 minutes
- **Cursor IDE**: 2-5 minutes (~200MB download)
- **Total**: ~5-10 minutes
### Disk Space
- **Node.js + npm**: ~100MB
- **Cursor IDE**: ~200MB
- **Build tools**: ~50MB
- **Total**: ~350MB
## Integration
### With Data Science Role
```yaml
- hosts: datascience_workstation
roles:
- role: development # Core dev tools
- role: datascience # Anaconda, Jupyter, R
```
### With Docker
```yaml
- hosts: fullstack_dev
roles:
- role: development
- role: docker
```
## Troubleshooting
### Node.js Version Issues
If Node.js doesn't upgrade to v22:
```bash
# Check current version
node --version
# Force reinstall
apt-get remove nodejs
# Re-run playbook
```
### Cursor Won't Launch
For root users, use the alias that adds required flags:
```bash
# Check alias in .zshrc
alias cursor="cursor --no-sandbox --disable-gpu-sandbox..."
```
## Notes
- Node.js 22 is the current LTS version
- NodeSource repository is configured for automatic updates
- Cursor IDE is installed as AppImage for easy updates
- Build tools (gcc, make) are essential for npm native modules
- Python 3 is included for development scripts
- All installations are idempotent (safe to re-run)
## Comparison with Data Science Role
| Component | Development Role | Data Science Role |
|-----------|------------------|-------------------|
| Git | ✅ | - |
| Node.js | ✅ | - |
| Build Tools | ✅ | - |
| Cursor IDE | ✅ | - |
| Anaconda | ❌ | ✅ |
| Jupyter | ❌ | ✅ |
| R Language | ❌ | ✅ |
| Install Time | ~10 min | ~30-60 min |
| Disk Space | ~350MB | ~3GB |
**Recommendation**: Use `development` role for general coding. Add `datascience` role only when needed for data analysis/ML work.

View File

@ -1,2 +1,87 @@
---
# defaults file for development
# Development role defaults
# Node.js is installed by default from NodeSource
# No additional configuration needed
# Cursor IDE - lightweight IDE installation
install_cursor: true
install_cursor_extensions: false
# Base Cursor extensions (always good to have)
cursor_extensions_base:
- usernamehw.errorlens # Better error highlighting
- eamodio.gitlens # Git supercharged
- mhutchie.git-graph # Git graph visualization
- streetsidesoftware.code-spell-checker # Spell checker
- EditorConfig.EditorConfig # EditorConfig support
- PKief.material-icon-theme # Better file icons
# Python/Data Science extensions
cursor_extensions_python:
- ms-python.python # Python language support
- ms-python.vscode-pylance # Python IntelliSense
- ms-python.black-formatter # Black formatter
- ms-python.isort # Import sorter
- ms-python.flake8 # Linter
- charliermarsh.ruff # Fast Python linter
# Jupyter/Data Science extensions
cursor_extensions_jupyter:
- ms-toolsai.jupyter # Jupyter notebooks
- ms-toolsai.jupyter-keymap # Jupyter keybindings
- ms-toolsai.jupyter-renderers # Jupyter renderers
# Web Development extensions
cursor_extensions_web:
- esbenp.prettier-vscode # Code formatter
- dbaeumer.vscode-eslint # ESLint
- bradlc.vscode-tailwindcss # Tailwind CSS
- vue.volar # Vue 3
- svelte.svelte-vscode # Svelte
# Testing extensions
cursor_extensions_testing:
- ms-playwright.playwright # Playwright testing
# Systems/DevOps extensions
cursor_extensions_devops:
- golang.go # Go language
- rust-lang.rust-analyzer # Rust language
- redhat.vscode-yaml # YAML support
- ms-azuretools.vscode-docker # Docker support
- redhat.ansible # Ansible support
# R language extensions
cursor_extensions_r:
- REditorSupport.r # R language support
- Ikuyadeu.r-pack # R package development
# Markdown/Documentation extensions
cursor_extensions_docs:
- yzhang.markdown-all-in-one # Markdown tools
- DavidAnson.vscode-markdownlint # Markdown linter
# Default combined list (customize per host in host_vars)
cursor_extensions: >-
{{
[
cursor_extensions_base,
(cursor_extensions_python if install_python | default(false) else []),
(cursor_extensions_jupyter if install_jupyter | default(false) else []),
(cursor_extensions_web if install_web | default(false) else []),
(cursor_extensions_testing if install_playwright | default(false) else []),
(cursor_extensions_devops if install_devops | default(false) else []),
(cursor_extensions_r if install_r | default(false) else []),
(cursor_extensions_docs if install_docs | default(false) else [])
] | flatten
}}
# Feature flags to enable extension groups
install_python: false
install_jupyter: false
install_web: false
install_playwright: false
install_devops: false
install_r: false
install_docs: false

View File

@ -30,7 +30,7 @@
fi
register: nodesource_repo_check
failed_when: false
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- name: Check if NodeSource GPG key exists and is correct
ansible.builtin.shell: |
@ -45,7 +45,7 @@
fi
register: nodesource_key_check
failed_when: false
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- name: Remove incorrect NodeSource repository
ansible.builtin.file:
@ -53,7 +53,7 @@
state: absent
become: true
when:
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- nodesource_repo_check.stdout == "wrong_config"
- name: Remove incorrect NodeSource key
@ -62,7 +62,7 @@
state: absent
become: true
when:
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- nodesource_key_check.stdout == "wrong_key"
- name: Create keyrings directory
@ -72,7 +72,7 @@
mode: '0755'
become: true
when:
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- nodesource_key_check.stdout in ["not_exists", "wrong_key"]
- name: Add NodeSource GPG key only if needed
@ -83,7 +83,7 @@
force: true
become: true
when:
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- nodesource_key_check.stdout in ["not_exists", "wrong_key"]
- name: Add NodeSource repository only if needed
@ -93,7 +93,7 @@
update_cache: false
become: true
when:
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- nodesource_repo_check.stdout in ["not_exists", "wrong_config"]
- name: Install Node.js 22 from NodeSource
@ -101,7 +101,7 @@
name: nodejs
state: present
become: true
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v2')
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
- name: Verify Node.js installation
ansible.builtin.command: node --version
@ -110,4 +110,93 @@
- name: Display Node.js version
ansible.builtin.debug:
msg: "Node.js version installed: {{ final_node_version.stdout }}"
msg: "Node.js version installed: {{ final_node_version.stdout if final_node_version.stdout is defined else 'Not checked in dry-run mode' }}"
# Cursor IDE installation (using AppImage)
# Downloads the latest version from cursor.com API
- name: Install Cursor IDE block
tags: ['cursor', 'ide']
block:
- name: Install libfuse2 dependency for AppImage
ansible.builtin.apt:
name: libfuse2
state: present
update_cache: false
become: true
when: ansible_os_family == "Debian"
- name: Check if Cursor is already installed at /usr/local/bin
ansible.builtin.stat:
path: /usr/local/bin/cursor
register: cursor_bin_check
- name: Get Cursor download URL from API and download AppImage
ansible.builtin.shell: |
DOWNLOAD_URL=$(curl -sL "https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable" | grep -o '"downloadUrl":"[^"]*' | cut -d'"' -f4)
wget --timeout=60 --tries=3 -O /tmp/cursor.AppImage "$DOWNLOAD_URL"
args:
creates: /tmp/cursor.AppImage
when: not cursor_bin_check.stat.exists
register: cursor_download
retries: 2
delay: 5
until: cursor_download.rc == 0
- name: Make Cursor AppImage executable
ansible.builtin.file:
path: /tmp/cursor.AppImage
mode: '0755'
when:
- not cursor_bin_check.stat.exists
- cursor_download is defined
- cursor_download.rc is defined
- cursor_download.rc == 0
- name: Install Cursor to /usr/local/bin
ansible.builtin.copy:
src: /tmp/cursor.AppImage
dest: /usr/local/bin/cursor
mode: '0755'
remote_src: true
when:
- not cursor_bin_check.stat.exists
- cursor_download is defined
- cursor_download.rc is defined
- cursor_download.rc == 0
become: true
- name: Clean up Cursor download
ansible.builtin.file:
path: /tmp/cursor.AppImage
state: absent
when:
- cursor_download is defined
- cursor_download.rc is defined
- cursor_download.rc == 0
- name: Display Cursor installation status
ansible.builtin.debug:
msg: "{{ 'Cursor already installed' if cursor_bin_check.stat.exists else ('Cursor installed successfully' if (cursor_download is defined and cursor_download.rc is defined and cursor_download.rc == 0) else 'Cursor installation failed - download manually from cursor.com') }}"
# Cursor extensions installation
- name: Install Cursor extensions block
when:
- install_cursor | default(true) | bool
- install_cursor_extensions | default(false) | bool
- cursor_extensions is defined
- cursor_extensions | length > 0
tags: ['cursor', 'extensions']
block:
- name: Install Cursor extensions
ansible.builtin.shell: |
cursor --install-extension {{ item }} --force --user-data-dir={{ ansible_env.HOME }}/.cursor-root 2>/dev/null || true
loop: "{{ cursor_extensions }}"
register: cursor_ext_install
changed_when: "'successfully installed' in cursor_ext_install.stdout.lower()"
failed_when: false
become: true
become_user: "{{ ansible_user }}"
- name: Display Cursor extensions status
ansible.builtin.debug:
msg: "Installed {{ cursor_extensions | length }} Cursor extensions"

View File

@ -9,7 +9,8 @@ Installs comprehensive system monitoring tools and custom monitoring scripts for
- Sufficient disk space for logs
## Installed Tools
- **htop/btop**: Interactive process viewers
- **htop**: Interactive process viewer
- **btop**: Modern system monitor (from apt or binary)
- **iotop**: I/O monitoring
- **nethogs**: Network usage per process
- **iftop**: Network bandwidth monitoring
@ -57,5 +58,5 @@ Installs comprehensive system monitoring tools and custom monitoring scripts for
## Notes
- Creates monitoring user directories
- Configures fail2ban with custom rules
- Installs both CLI and snap-based tools
- btop installed from apt on Debian 12+, or from GitHub binary on older versions
- Custom scripts require manual execution

View File

@ -1,5 +1,5 @@
---
# Monitoring role defaults
monitoring_install_snap_tools: true
monitoring_install_btop: true
monitoring_enable_sysstat: true
monitoring_create_scripts: true

View File

@ -21,12 +21,56 @@
- atop
state: present
- name: Install modern monitoring tools via snap
community.general.snap:
name:
- btop
- bandwhich
- name: Check if btop is available in apt
ansible.builtin.command: apt-cache policy btop
register: btop_apt_check
changed_when: false
failed_when: false
- name: Install btop from apt if available (Debian 12+)
ansible.builtin.apt:
name: btop
state: present
update_cache: false
when:
- btop_apt_check.rc == 0
- "'Candidate:' in btop_apt_check.stdout"
- "'(none)' not in btop_apt_check.stdout"
failed_when: false
- name: Install btop from binary if apt not available
when: btop_apt_check.rc != 0 or "(none)" in btop_apt_check.stdout
block:
- name: Download btop binary
ansible.builtin.get_url:
url: https://github.com/aristocratos/btop/releases/latest/download/btop-x86_64-linux-musl.tbz
dest: /tmp/btop.tbz
mode: '0644'
failed_when: false
- name: Extract btop
ansible.builtin.unarchive:
src: /tmp/btop.tbz
dest: /tmp/
remote_src: true
failed_when: false
- name: Install btop binary
ansible.builtin.copy:
src: /tmp/btop/bin/btop
dest: /usr/local/bin/btop
mode: '0755'
remote_src: true
failed_when: false
- name: Clean up btop download
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /tmp/btop.tbz
- /tmp/btop
failed_when: false
- name: Configure fail2ban
ansible.builtin.template:

View File

@ -1,46 +1,202 @@
# Role: shell
### Role: shell
## Description
Configures modern shell environment with Zsh, Oh My Zsh, and Powerlevel10k theme for enhanced terminal experience.
Configures modern shell environment with zsh, Oh My Zsh, Powerlevel10k theme, and useful plugins. Can be configured for multiple users on the same host.
## Requirements
- Ansible 2.9+
- Debian/Ubuntu target systems
- Internet access for downloading themes and plugins
- Debian/Ubuntu systems
- Users must exist before running this role
## Features
- Installs and configures Zsh as default shell
- Sets up Oh My Zsh framework
- Installs Powerlevel10k theme
- Adds useful plugins: syntax highlighting, autosuggestions
- Configures tmux and fzf for enhanced productivity
## Installed Components
### Shell Environment
- **zsh**: Z shell
- **Oh My Zsh**: Zsh configuration framework
- **Powerlevel10k**: Modern, feature-rich theme
- **tmux**: Terminal multiplexer
- **fzf**: Fuzzy finder
### Zsh Plugins
- **zsh-syntax-highlighting**: Syntax highlighting for commands
- **zsh-autosuggestions**: Fish-like autosuggestions
### Configuration Files
- `.zshrc`: Custom zsh configuration with conda support
- `.p10k.zsh`: Powerlevel10k theme configuration
## Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `shell_user` | `{{ ansible_user }}` | User to configure shell for |
| `shell_install_ohmyzsh` | `true` | Whether to install Oh My Zsh |
| `shell_install_p10k` | `true` | Whether to install Powerlevel10k theme |
| `shell_users` | `[ansible_user]` | List of users to configure zsh for |
| `zsh_plugins` | See defaults/main.yml | List of zsh plugins to install |
## Dependencies
- `base` role (for basic packages)
None
## Example Playbook
### Configure Only Ansible User (Default)
```yaml
- hosts: dev
- hosts: servers
roles:
- { role: shell, shell_user: "myuser" }
- role: shell
```
## Tags
- `shell`: All shell configuration tasks
- `zsh`: Zsh installation and configuration
- `ohmyzsh`: Oh My Zsh framework setup
- `theme`: Theme and plugin configuration
### Configure Multiple Users
```yaml
- hosts: servers
roles:
- role: shell
shell_users:
- root
- ladmin
- beast
- user
```
### Host-Specific Configuration
In `host_vars/server01.yml`:
```yaml
shell_users:
- root
- myuser
- developer
```
## Usage
### Default (Ansible User Only)
```bash
make shell HOST=dev01
```
### Configure Additional Users
Add to host_vars file:
```yaml
# host_vars/devGPU.yml
shell_users:
- root
- beast
- ladmin
```
Then run:
```bash
make dev HOST=devGPU --tags shell
```
## Post-Installation
### For Each Configured User
The shell configuration is immediately active. Users can:
1. **Customize Powerlevel10k**:
```bash
p10k configure
```
2. **Reload Configuration**:
```bash
source ~/.zshrc
```
3. **View Available Aliases**:
```bash
alias # List all aliases
```
## Features
### Custom Aliases
The `.zshrc` includes aliases for:
- Git operations (`gs`, `ga`, `gc`, etc.)
- Docker (`dps`, `dex`, `dlogs`)
- System (`ll`, `la`, `update`, `sysinfo`)
- Networking (`ports`, `myip`)
- Development (`serve`, `mkcd`)
- Data Science (`jup`, `conda-list`, `r-studio`)
### Conda Integration
If Anaconda is installed (via datascience role), conda is automatically initialized in zsh.
### Root User Support
Includes special aliases for root users (IDEs with `--no-sandbox` flags).
## Notes
- Changes default shell to zsh for the target user
- Downloads themes and plugins from GitHub
- Requires logout/login to see changes
- Zsh is set as the default shell for all configured users
- Oh My Zsh is installed in each user's home directory
- The role is idempotent - safe to run multiple times
- Existing `.zshrc` files are overwritten
- Users must log out and back in for shell changes to take effect
- The role skips users that don't exist on the system
## Troubleshooting
### User Not Found
If a user in `shell_users` doesn't exist:
```
User username not found, skipping shell configuration
```
Solution: Ensure the user exists or remove from `shell_users` list.
### Oh My Zsh Installation Fails
Check user has a valid home directory and write permissions.
### Powerlevel10k Not Loading
Verify the theme is cloned:
```bash
ls ~/.oh-my-zsh/custom/themes/powerlevel10k
```
### Conda Not Initialized
The datascience role must be run to install Anaconda. The shell role only adds the initialization code to `.zshrc`.
## Integration
### With User Role
The user role should run before the shell role to ensure users exist:
```yaml
roles:
- user
- shell
```
### With Data Science Role
Conda initialization is included in `.zshrc`. Run datascience role after shell:
```yaml
roles:
- shell
- datascience
```
## Security Considerations
- The `.zshrc` is deployed from a template - review before deploying to production
- Root aliases include `--no-sandbox` flags for IDEs (required for root but less secure)
- Consider limiting which users get shell configuration on production servers
## Performance
- Installation time: ~2-3 minutes per user
- Disk space: ~10MB per user (Oh My Zsh + plugins + theme)
- First shell launch: ~1-2 seconds (Powerlevel10k initialization)
- Subsequent launches: <0.5 seconds
## Customization
### Adding Custom Aliases
Edit `roles/shell/files/.zshrc` and add your aliases.
### Adding More Plugins
Update `roles/shell/defaults/main.yml`:
```yaml
zsh_plugins:
- name: "my-plugin"
repo: "https://github.com/user/my-plugin.git"
```
### Custom Theme
Replace Powerlevel10k in tasks if desired, or users can run `p10k configure` to customize.

View File

@ -1,2 +1,23 @@
---
# defaults file for shell
# Shell role defaults
# List of users to configure zsh for
# By default, only configure the ansible user
# Override in host_vars to add more users
shell_users:
- "{{ ansible_user | default(ansible_user_id) }}"
# Additional users to configure (appended to shell_users)
# Use this to add users WITHOUT having to repeat ansible_user
# Example in host_vars:
# shell_additional_users:
# - beast
# - ladmin
shell_additional_users: []
# Zsh plugins to install
zsh_plugins:
- name: "zsh-syntax-highlighting"
repo: "https://github.com/zsh-users/zsh-syntax-highlighting.git"
- name: "zsh-autosuggestions"
repo: "https://github.com/zsh-users/zsh-autosuggestions.git"

1719
roles/shell/files/.p10k.zsh Normal file

File diff suppressed because it is too large Load Diff

View File

@ -132,6 +132,7 @@ alias dc="cd ~/Documents/code"
alias df="df -h" # disk usage human readable
alias du="du -h" # directory usage human readable
alias free="free -h" # memory usage human readable
alias sysinfo="/usr/local/bin/monitoring/sysinfo 2>/dev/null || echo 'sysinfo script not found'"
# Process management
alias ps="ps aux"
@ -143,6 +144,11 @@ alias ports="ss -tulpn" # open ports
# Network information
alias myip="curl -s http://ipecho.net/plain; echo"
alias localip="ip route get 1.2.3.4 | awk '{print $7}'"
alias netinfo="/usr/local/bin/monitoring/netinfo 2>/dev/null || echo 'netinfo script not found'"
# Software inventory - show what's installed on this system
alias showapps="$HOME/.local/bin/showapps"
alias apps="showapps"
# Python
alias py="python3"
@ -159,6 +165,10 @@ alias dcb="docker-compose build"
alias dps="docker ps"
alias di="docker images"
# IDE - suppress root warnings
alias code="code --no-sandbox --user-data-dir=/root/.vscode-root"
alias cursor="cursor --no-sandbox --disable-gpu-sandbox --appimage-extract-and-run --user-data-dir=/root/.cursor-root"
# Date and time
alias now="date +'%Y-%m-%d %H:%M:%S'"
alias today="date +'%Y-%m-%d'"
@ -175,3 +185,20 @@ alias own="sudo chown -R $USER:$USER"
alias nfresh="rm -rf node_modules/ package-lock.json && npm install"
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
if [ -f "$HOME/anaconda3/bin/conda" ]; then
__conda_setup="$('$HOME/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "$HOME/anaconda3/etc/profile.d/conda.sh" ]; then
. "$HOME/anaconda3/etc/profile.d/conda.sh"
else
export PATH="$HOME/anaconda3/bin:$PATH"
fi
fi
unset __conda_setup
fi
# <<< conda initialize <<<

View File

@ -0,0 +1,54 @@
#!/bin/bash
# Show installed software on this system
# Part of the Ansible shell role
BOLD='\033[1m'
GREEN='\033[32m'
YELLOW='\033[33m'
RED='\033[31m'
RESET='\033[0m'
echo -e "${BOLD}=== Installed Software ===${RESET}\n"
echo -e "${YELLOW}Development:${RESET}"
for tool in git node python3 docker; do
if command -v $tool >/dev/null 2>&1; then
version=$(case $tool in
node) node --version 2>/dev/null ;;
python3) python3 --version 2>/dev/null | awk '{print $2}' ;;
git) git --version 2>/dev/null | awk '{print $3}' ;;
docker) docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' ;;
esac)
printf " ${GREEN}${RESET} %-15s %s\n" "$tool" "$version"
fi
done
echo -e "\n${YELLOW}Data Science:${RESET}"
for tool in conda jupyter R; do
if command -v $tool >/dev/null 2>&1; then
version=$(case $tool in
conda) conda --version 2>/dev/null | awk '{print $2}' ;;
jupyter) echo "installed" ;;
R) R --version 2>/dev/null | head -1 | awk '{print $3}' ;;
esac)
printf " ${GREEN}${RESET} %-15s %s\n" "$tool" "$version"
fi
done
echo -e "\n${YELLOW}Editors:${RESET}"
for tool in cursor code vim nvim nano; do
command -v $tool >/dev/null 2>&1 && printf " ${GREEN}${RESET} %s\n" "$tool"
done
echo -e "\n${YELLOW}CLI Tools:${RESET}"
for tool in tmux fzf htop btop jq yq rg fd; do
command -v $tool >/dev/null 2>&1 && printf " ${GREEN}${RESET} %s\n" "$tool"
done
echo -e "\n${YELLOW}Shell:${RESET}"
echo -e " ${GREEN}${RESET} current: $SHELL"
[ -d "$HOME/.oh-my-zsh" ] && echo -e " ${GREEN}${RESET} oh-my-zsh"
[ -d "$HOME/.oh-my-zsh/custom/themes/powerlevel10k" ] && echo -e " ${GREEN}${RESET} powerlevel10k"
echo ""

View File

@ -0,0 +1,104 @@
---
# Configure shell for a single user
# Variable: current_user - the username to configure
- name: "Get user information: {{ current_user }}"
ansible.builtin.getent:
database: passwd
key: "{{ current_user }}"
register: user_info
failed_when: false
- name: "Set user home directory: {{ current_user }}"
ansible.builtin.set_fact:
user_home: "{{ user_info.ansible_facts.getent_passwd[current_user][4] }}"
when: user_info.ansible_facts.getent_passwd[current_user] is defined
- name: Skip if user not found
ansible.builtin.debug:
msg: "User {{ current_user }} not found, skipping shell configuration"
when: user_info.ansible_facts.getent_passwd[current_user] is not defined
- name: Configure shell environment
when: user_info.ansible_facts.getent_passwd[current_user] is defined
block:
- name: "Set zsh as default shell: {{ current_user }}"
ansible.builtin.user:
name: "{{ current_user }}"
shell: /usr/bin/zsh
become: true
- name: "Install Oh My Zsh: {{ current_user }}"
become: true
become_user: "{{ current_user }}"
ansible.builtin.shell: sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)" "" --unattended
args:
creates: "{{ user_home }}/.oh-my-zsh"
- name: "Clone Powerlevel10k theme: {{ current_user }}"
ansible.builtin.git:
repo: https://github.com/romkatv/powerlevel10k.git
dest: "{{ user_home }}/.oh-my-zsh/custom/themes/powerlevel10k"
version: master
depth: 1
update: false
become: true
become_user: "{{ current_user }}"
- name: "Install zsh plugins: {{ current_user }}"
ansible.builtin.git:
repo: "{{ item.repo }}"
dest: "{{ user_home }}/.oh-my-zsh/custom/plugins/{{ item.name }}"
version: master
depth: 1
update: false
become: true
become_user: "{{ current_user }}"
loop: "{{ zsh_plugins }}"
- name: "Deploy .zshrc: {{ current_user }}"
ansible.builtin.copy:
src: files/.zshrc
dest: "{{ user_home }}/.zshrc"
owner: "{{ current_user }}"
group: "{{ current_user }}"
mode: '0644'
become: true
- name: "Deploy Powerlevel10k configuration: {{ current_user }}"
ansible.builtin.copy:
src: files/.p10k.zsh
dest: "{{ user_home }}/.p10k.zsh"
owner: "{{ current_user }}"
group: "{{ current_user }}"
mode: '0644'
become: true
- name: "Ensure .local/bin directory exists: {{ current_user }}"
ansible.builtin.file:
path: "{{ user_home }}/.local/bin"
state: directory
owner: "{{ current_user }}"
group: "{{ current_user }}"
mode: '0755'
become: true
- name: "Deploy showapps script: {{ current_user }}"
ansible.builtin.copy:
src: files/showapps.sh
dest: "{{ user_home }}/.local/bin/showapps"
owner: "{{ current_user }}"
group: "{{ current_user }}"
mode: '0755'
become: true
- name: "Display post-installation instructions: {{ current_user }}"
ansible.builtin.debug:
msg:
- "=== Shell Configuration Complete for {{ current_user }} ==="
- "NOTE: Zsh has been set as the default shell."
- "To activate immediately, choose one of:"
- " 1. Log out and back in (recommended)"
- " 2. Run: exec zsh"
- " 3. Or simply run: zsh"
- "=========================================="

View File

@ -1,8 +1,4 @@
---
- name: Set target user variable
ansible.builtin.set_fact:
shell_target_user: "{{ ansible_user | default(ansible_user_id) }}"
- name: Install shell packages
ansible.builtin.apt:
name:
@ -10,46 +6,17 @@
- tmux
- fzf
state: present
- name: Install zsh plugins
ansible.builtin.git:
repo: "{{ item.repo }}"
dest: "/home/{{ shell_target_user }}/.oh-my-zsh/custom/plugins/{{ item.name }}"
version: master
depth: 1
update: false
become: true
become_user: "{{ shell_target_user }}"
loop:
- {name: "zsh-syntax-highlighting", repo: "https://github.com/zsh-users/zsh-syntax-highlighting.git"}
- {name: "zsh-autosuggestions", repo: "https://github.com/zsh-users/zsh-autosuggestions.git"}
- name: Set zsh as default shell for user
ansible.builtin.user:
name: "{{ shell_target_user }}"
shell: /usr/bin/zsh
# Merge shell_users and shell_additional_users
- name: Build complete user list
ansible.builtin.set_fact:
shell_all_users: "{{ (shell_users | default([ansible_user])) + (shell_additional_users | default([])) | unique }}"
- name: Install Oh My Zsh for user
become: true
become_user: "{{ shell_target_user }}"
ansible.builtin.shell: sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)" "" --unattended
args:
creates: "/home/{{ shell_target_user }}/.oh-my-zsh"
- name: Clone Powerlevel10k theme
ansible.builtin.git:
repo: https://github.com/romkatv/powerlevel10k.git
dest: "/home/{{ shell_target_user }}/.oh-my-zsh/custom/themes/powerlevel10k"
version: master
depth: 1
update: false
become: true
become_user: "{{ shell_target_user }}"
- name: Deploy .zshrc for user
ansible.builtin.copy:
src: files/.zshrc
dest: "/home/{{ shell_target_user }}/.zshrc"
owner: "{{ shell_target_user }}"
group: "{{ shell_target_user }}"
mode: '0644'
# Configure shell for each user
- name: Configure shell for each user
ansible.builtin.include_tasks: configure_user_shell.yml
loop: "{{ shell_all_users }}"
loop_control:
loop_var: current_user
when: shell_all_users is defined and shell_all_users | length > 0

View File

@ -1,47 +0,0 @@
# Role: snap
## Description
Installs and configures Snap package manager and essential snap applications for development.
## Requirements
- Ansible 2.9+
- Ubuntu/Debian systems
- Internet access for snap store
## Installed Snap Applications
- **Visual Studio Code**: Popular code editor
- **Cursor**: AI-powered code editor
- **yq**: YAML processor
- **btop**: Modern system monitor
- **bandwhich**: Network utilization monitor
## Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `snap_install_vscode` | `true` | Install VS Code via snap |
| `snap_install_cursor` | `true` | Install Cursor editor |
| `snap_install_yq` | `true` | Install yq YAML processor |
| `snap_install_btop` | `true` | Install btop system monitor |
## Dependencies
- `base` role (for system packages)
## Example Playbook
```yaml
- hosts: dev
roles:
- { role: snap, snap_install_cursor: false }
```
## Tags
- `snap`: All snap-related tasks
- `apps`: Application installations
- `editors`: Code editor installations
## Notes
- Enables universe repository for snap support
- Waits for snap system to be ready before installing
- Some snaps may require additional permissions
- Classic confinement used for development tools

View File

@ -1 +0,0 @@
---

View File

@ -1,5 +0,0 @@
---
- name: Restart snapd
ansible.builtin.systemd:
name: snapd
state: restarted

View File

@ -1,78 +0,0 @@
---
- name: Enable 'universe' repo (Ubuntu and Mint only)
ansible.builtin.command: add-apt-repository universe
when: ansible_facts['distribution'] in ["Ubuntu", "Linux Mint"]
changed_when: false
failed_when: false
- name: Remove Mint's nosnap.pref block (Mint only)
ansible.builtin.file:
path: /etc/apt/preferences.d/nosnap.pref
state: absent
when: ansible_facts['distribution'] == "Linux Mint"
- name: Install Snap daemon
ansible.builtin.apt:
name: snapd
state: present
when: ansible_facts['os_family'] == "Debian"
notify: restart snapd
- name: Enable snapd socket on Debian
ansible.builtin.systemd:
name: snapd.socket
enabled: true
state: started
when: ansible_facts['distribution'] == "Debian"
- name: Force Ansible to reload facts
ansible.builtin.setup:
- name: Wait for snapd to be ready
ansible.builtin.command: snap wait system seed.loaded
register: snap_wait_result
until: snap_wait_result.rc == 0
retries: 10
delay: 5
when: ansible_facts['os_family'] == "Debian"
failed_when: false
changed_when: false
- name: Check if snap is working
ansible.builtin.command: snap list
register: snap_check
when: ansible_facts['os_family'] == "Debian"
failed_when: false
changed_when: false
- name: Install VSCode IDE (Snap, all distros)
community.general.snap:
name: code
classic: true
state: present
when:
- ansible_facts['os_family'] == "Debian"
- snap_check is defined
- snap_check.rc == 0
failed_when: false
- name: Install Cursor (Snap, all distros)
community.general.snap:
name: cursor
state: present
when:
- ansible_facts['os_family'] == "Debian"
- snap_check is defined
- snap_check.rc == 0
failed_when: false
- name: Display snap installation status
ansible.builtin.debug:
msg: |
Snap check result: {{ snap_check.rc if snap_check is defined else 'not defined' }}
VSCode and Cursor installation may have failed if snap is not properly configured.
You may need to manually install these applications or troubleshoot snap on this host.
when:
- ansible_facts['os_family'] == "Debian"
- snap_check is defined
- snap_check.rc != 0

View File

@ -2,14 +2,14 @@
# SSH server configuration
ssh_port: 22
ssh_listen_addresses: ['0.0.0.0']
ssh_permit_root_login: 'no'
ssh_password_authentication: 'no'
ssh_permit_root_login: 'yes'
ssh_password_authentication: 'yes'
ssh_pubkey_authentication: 'yes'
ssh_max_auth_tries: 3
ssh_client_alive_interval: 300
ssh_max_sessions: 10
ssh_allowed_users: [] # Restrict to specific users
ssh_allowed_groups: ['sudo', 'ssh']
ssh_allowed_groups: ['sudo']
# Security hardening
ssh_kex_algorithms:

147
scripts/inventory.sh Executable file
View File

@ -0,0 +1,147 @@
#!/bin/bash
# Dynamic inventory script - lists all installed tools and services
# Can be run on any managed host
# Colors
BOLD='\033[1m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
RED='\033[31m'
RESET='\033[0m'
echo -e "${BOLD}=== Installed Software Inventory ===${RESET}\n"
# Function to check if command exists
cmd_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to get version
get_version() {
if cmd_exists "$1"; then
case "$1" in
node) node --version 2>/dev/null ;;
python3) python3 --version 2>/dev/null | awk '{print $2}' ;;
git) git --version 2>/dev/null | awk '{print $3}' ;;
docker) docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' ;;
conda) conda --version 2>/dev/null | awk '{print $2}' ;;
jupyter) jupyter --version 2>/dev/null | head -1 | awk '{print $3}' ;;
R) R --version 2>/dev/null | head -1 | awk '{print $3}' ;;
yq) yq --version 2>/dev/null | awk '{print $NF}' ;;
btop) btop --version 2>/dev/null | head -1 | awk '{print $3}' ;;
cursor) echo "installed" ;;
*) echo "unknown" ;;
esac
fi
}
# Development Tools
echo -e "${YELLOW}Development Tools:${RESET}"
for tool in git node python3; do
version=$(get_version $tool)
if [ -n "$version" ]; then
printf " ${GREEN}${RESET} %-15s %s\n" "$tool" "$version"
else
printf " ${RED}${RESET} %-15s %s\n" "$tool" "not installed"
fi
done
# Data Science
echo -e "\n${YELLOW}Data Science Stack:${RESET}"
for tool in conda jupyter R; do
version=$(get_version $tool)
if [ -n "$version" ]; then
printf " ${GREEN}${RESET} %-15s %s\n" "$tool" "$version"
else
printf " ${RED}${RESET} %-15s %s\n" "$tool" "not installed"
fi
done
# Check Jupyter service
if systemctl is-active --quiet jupyter-notebook 2>/dev/null; then
echo -e " ${GREEN}${RESET} Jupyter service running"
else
echo -e " ${RED}${RESET} Jupyter service not running"
fi
# IDEs & Editors
echo -e "\n${YELLOW}IDEs & Editors:${RESET}"
for tool in cursor code; do
if cmd_exists $tool; then
printf " ${GREEN}${RESET} %-15s installed\n" "$tool"
else
printf " ${RED}${RESET} %-15s not installed\n" "$tool"
fi
done
# Container Platform
echo -e "\n${YELLOW}Container Platform:${RESET}"
version=$(get_version docker)
if [ -n "$version" ]; then
printf " ${GREEN}${RESET} %-15s %s\n" "docker" "$version"
if cmd_exists docker-compose; then
compose_version=$(docker-compose --version 2>/dev/null | awk '{print $NF}')
printf " ${GREEN}${RESET} %-15s %s\n" "docker-compose" "$compose_version"
fi
else
printf " ${RED}${RESET} %-15s not installed\n" "docker"
fi
# Monitoring Tools
echo -e "\n${YELLOW}Monitoring Tools:${RESET}"
for tool in htop btop iotop nethogs; do
if cmd_exists $tool; then
version=$(get_version $tool)
if [ "$version" = "unknown" ] || [ -z "$version" ]; then
printf " ${GREEN}${RESET} %-15s installed\n" "$tool"
else
printf " ${GREEN}${RESET} %-15s %s\n" "$tool" "$version"
fi
else
printf " ${RED}${RESET} %-15s not installed\n" "$tool"
fi
done
# CLI Utilities
echo -e "\n${YELLOW}CLI Utilities:${RESET}"
for tool in jq yq ripgrep fd; do
if cmd_exists $tool; then
version=$(get_version $tool)
if [ "$version" = "unknown" ] || [ -z "$version" ]; then
printf " ${GREEN}${RESET} %-15s installed\n" "$tool"
else
printf " ${GREEN}${RESET} %-15s %s\n" "$tool" "$version"
fi
else
printf " ${RED}${RESET} %-15s not installed\n" "$tool"
fi
done
# Shell
echo -e "\n${YELLOW}Shell Environment:${RESET}"
current_shell=$(basename "$SHELL")
printf " ${GREEN}${RESET} %-15s %s\n" "current shell" "$current_shell"
if [ -d "$HOME/.oh-my-zsh" ]; then
printf " ${GREEN}${RESET} %-15s installed\n" "oh-my-zsh"
fi
if [ -d "$HOME/.oh-my-zsh/custom/themes/powerlevel10k" ]; then
printf " ${GREEN}${RESET} %-15s installed\n" "powerlevel10k"
fi
# Services
echo -e "\n${YELLOW}System Services:${RESET}"
for service in docker tailscaled fail2ban sshd; do
if systemctl is-active --quiet $service 2>/dev/null; then
printf " ${GREEN}${RESET} %-15s running\n" "$service"
elif systemctl list-unit-files | grep -q "^$service"; then
printf " ${BLUE}${RESET} %-15s installed (not running)\n" "$service"
else
printf " ${RED}${RESET} %-15s not installed\n" "$service"
fi
done
echo ""