refactor-servers-workstations-shell-monitoring #4
@ -1,7 +1,14 @@
|
||||
# Ansible Lint Configuration
|
||||
---
|
||||
# Exclude patterns
|
||||
# ansible-lint configuration
|
||||
#
|
||||
# We exclude inventory host/group vars because many contain vault-encrypted content
|
||||
# that cannot be parsed without vault secrets in CI/dev environments.
|
||||
exclude_paths:
|
||||
- inventories/production/host_vars/
|
||||
- inventories/production/group_vars/all/vault.yml
|
||||
- inventories/production/group_vars/all/vault.example.yml
|
||||
|
||||
# Exclude patterns
|
||||
- .cache/
|
||||
- .github/
|
||||
- .gitea/
|
||||
|
||||
33
.cursor/rules/project-rules.mdc
Normal file
33
.cursor/rules/project-rules.mdc
Normal file
@ -0,0 +1,33 @@
|
||||
## Project rules (Ansible infrastructure repo)
|
||||
|
||||
### Canonical documentation
|
||||
|
||||
- Start here: `project-docs/index.md`
|
||||
- Architecture: `project-docs/architecture.md`
|
||||
- Standards: `project-docs/standards.md`
|
||||
- Workflow: `project-docs/workflow.md`
|
||||
- Decisions: `project-docs/decisions.md`
|
||||
|
||||
### Repo structure (high level)
|
||||
|
||||
- **Inventory**: `inventories/production/`
|
||||
- **Playbooks**: `playbooks/`
|
||||
- `playbooks/servers.yml`: server baseline
|
||||
- `playbooks/workstations.yml`: workstation baseline + desktop apps on `desktop` group only
|
||||
- `playbooks/app/*`: Proxmox app-project suite
|
||||
- **Roles**: `roles/*` (standard Ansible role layout)
|
||||
|
||||
### Key standards to follow
|
||||
|
||||
- **YAML**: 2-space indentation; tasks must have `name:`
|
||||
- **Modules**: prefer native modules; use FQCN (e.g., `ansible.builtin.*`, `community.general.*`)
|
||||
- **Idempotency**: no “always-changed” shell tasks; use `changed_when:` / `creates:` / `removes:`
|
||||
- **Secrets**: never commit plaintext; use Ansible Vault with `vault_`-prefixed vars
|
||||
- **Makefile-first**: prefer `make ...` targets over raw `ansible-playbook`
|
||||
|
||||
### Architectural decisions (must not regress)
|
||||
|
||||
- Editor/IDE installation is **out of scope** for Ansible roles/playbooks.
|
||||
- Monitoring is split: `monitoring_server` vs `monitoring_desktop`.
|
||||
- Desktop applications run only for `desktop` group (via workstations playbook).
|
||||
|
||||
@ -62,10 +62,8 @@ jobs:
|
||||
|
||||
lint-and-test:
|
||||
needs: skip-ci-check
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1'
|
||||
runs-on: ubuntu-latest
|
||||
# Skip push events for non-master branches (they'll be covered by PR events)
|
||||
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
||||
container:
|
||||
image: node:20-bullseye
|
||||
steps:
|
||||
@ -84,10 +82,8 @@ jobs:
|
||||
|
||||
ansible-validation:
|
||||
needs: skip-ci-check
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1'
|
||||
runs-on: ubuntu-latest
|
||||
# Skip push events for non-master branches (they'll be covered by PR events)
|
||||
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/master'
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
steps:
|
||||
@ -100,6 +96,30 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure CI Ansible (no vault, localhost inventory)
|
||||
run: |
|
||||
set -e
|
||||
cat > /tmp/ci-inventory.ini <<'EOF'
|
||||
[all]
|
||||
localhost ansible_connection=local
|
||||
EOF
|
||||
|
||||
cat > /tmp/ci-ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
inventory = /tmp/ci-inventory.ini
|
||||
roles_path = /workspace/ilia/ansible/roles
|
||||
host_key_checking = False
|
||||
stdout_callback = yaml
|
||||
bin_ansible_callbacks = True
|
||||
retry_files_enabled = False
|
||||
interpreter_python = auto_silent
|
||||
forks = 10
|
||||
pipelining = True
|
||||
EOF
|
||||
|
||||
echo "ANSIBLE_CONFIG=/tmp/ci-ansible.cfg" >> "$GITHUB_ENV"
|
||||
echo "ANSIBLE_INVENTORY=/tmp/ci-inventory.ini" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Python and dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y python3 python3-pip
|
||||
@ -107,6 +127,10 @@ jobs:
|
||||
- name: Install Ansible and linting tools
|
||||
run: pip3 install --no-cache-dir ansible ansible-lint yamllint
|
||||
|
||||
- name: Install Ansible collections
|
||||
run: |
|
||||
ansible-galaxy collection install -r collections/requirements.yml
|
||||
|
||||
- name: Validate YAML syntax
|
||||
run: |
|
||||
echo "Checking YAML syntax..."
|
||||
@ -115,10 +139,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run ansible-lint
|
||||
run: |
|
||||
# Skip vault-encrypted files and playbooks that require vault passwords
|
||||
ansible-lint --skip-list vault,internal-error || true
|
||||
continue-on-error: true
|
||||
run: ansible-lint
|
||||
|
||||
secret-scanning:
|
||||
needs: skip-ci-check
|
||||
@ -154,8 +175,45 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Scan dependencies
|
||||
run: trivy fs --scanners vuln,secret --exit-code 0 .
|
||||
- name: Show dependency manifests (debug)
|
||||
run: |
|
||||
set -e
|
||||
echo "Repo root:"
|
||||
ls -la
|
||||
echo ""
|
||||
echo "Common dependency manifests:"
|
||||
ls -la package.json package-lock.json requirements.txt pyproject.toml poetry.lock Pipfile Pipfile.lock 2>/dev/null || true
|
||||
echo ""
|
||||
echo "Count of lock/manifests found:"
|
||||
find . -maxdepth 3 -type f \( \
|
||||
-name "package-lock.json" -o \
|
||||
-name "pnpm-lock.yaml" -o \
|
||||
-name "yarn.lock" -o \
|
||||
-name "requirements.txt" -o \
|
||||
-name "pyproject.toml" -o \
|
||||
-name "poetry.lock" -o \
|
||||
-name "Pipfile.lock" \
|
||||
\) | wc -l
|
||||
|
||||
- name: Dependency vulnerability scan (Trivy)
|
||||
run: |
|
||||
trivy fs \
|
||||
--scanners vuln \
|
||||
--severity HIGH,CRITICAL \
|
||||
--ignore-unfixed \
|
||||
--timeout 10m \
|
||||
--skip-dirs .git,node_modules \
|
||||
--exit-code 0 \
|
||||
.
|
||||
|
||||
- name: Secret scan (Trivy)
|
||||
run: |
|
||||
trivy fs \
|
||||
--scanners secret \
|
||||
--timeout 10m \
|
||||
--skip-dirs .git,node_modules \
|
||||
--exit-code 0 \
|
||||
.
|
||||
|
||||
sast-scan:
|
||||
needs: skip-ci-check
|
||||
@ -198,11 +256,12 @@ jobs:
|
||||
- name: Check npm licenses
|
||||
run: |
|
||||
if [ -f "package.json" ]; then
|
||||
license-checker --onlyAllow 'MIT;Apache-2.0;BSD-3-Clause;ISC;BSD-2-Clause' || true
|
||||
npm ci
|
||||
# Exclude the repo itself (private=true packages are treated as UNLICENSED by license-checker).
|
||||
license-checker --excludePrivatePackages --onlyAllow 'MIT;Apache-2.0;BSD-3-Clause;ISC;BSD-2-Clause;Python-2.0;BlueOak-1.0.0;0BSD'
|
||||
else
|
||||
echo "No package.json found, skipping license check"
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
vault-check:
|
||||
needs: skip-ci-check
|
||||
@ -230,7 +289,12 @@ jobs:
|
||||
- name: Validate vault files are encrypted
|
||||
run: |
|
||||
echo "Checking for Ansible Vault files..."
|
||||
vault_files=$(find . -name "*vault*.yml" -o -name "*vault*.yaml" | grep -v ".git" | grep -v ".example" || true)
|
||||
# Intentionally skip *.example files: they are plaintext templates.
|
||||
# Only treat conventional vault files as "must be encrypted":
|
||||
# - vault.yml / vault.yaml
|
||||
# - vault_*.yml / vault_*.yaml
|
||||
# Avoid false-positives like host_vars/vaultwardenVM.yml (host name contains "vault").
|
||||
vault_files=$(find . \( -name "vault.yml" -o -name "vault.yaml" -o -name "vault_*.yml" -o -name "vault_*.yaml" \) | grep -v ".git" | grep -v ".example" || true)
|
||||
if [ -z "$vault_files" ]; then
|
||||
echo "No vault files found"
|
||||
exit 0
|
||||
@ -239,7 +303,8 @@ jobs:
|
||||
for vault_file in $vault_files; do
|
||||
echo "Checking $vault_file..."
|
||||
# Check if file starts with ANSIBLE_VAULT header (doesn't require password)
|
||||
if head -n 1 "$vault_file" | grep -q "^\$ANSIBLE_VAULT"; then
|
||||
# Some vault files may start with '---' (YAML document start) on line 1.
|
||||
if head -n 5 "$vault_file" | grep -q "^\$ANSIBLE_VAULT"; then
|
||||
echo "✓ $vault_file is properly encrypted (has vault header)"
|
||||
else
|
||||
echo "✗ ERROR: $vault_file does not have ANSIBLE_VAULT header - may be unencrypted!"
|
||||
@ -268,6 +333,48 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure CI Ansible (no vault, localhost inventory)
|
||||
run: |
|
||||
set -e
|
||||
cat > /tmp/ci-inventory.ini <<'EOF'
|
||||
[dev]
|
||||
localhost ansible_connection=local
|
||||
|
||||
[desktop]
|
||||
localhost ansible_connection=local
|
||||
|
||||
[services]
|
||||
localhost ansible_connection=local
|
||||
|
||||
[qa]
|
||||
localhost ansible_connection=local
|
||||
|
||||
[ansible]
|
||||
localhost ansible_connection=local
|
||||
|
||||
[tailscale]
|
||||
localhost ansible_connection=local
|
||||
|
||||
[local]
|
||||
localhost ansible_connection=local
|
||||
EOF
|
||||
|
||||
cat > /tmp/ci-ansible.cfg <<'EOF'
|
||||
[defaults]
|
||||
inventory = /tmp/ci-inventory.ini
|
||||
roles_path = /workspace/ilia/ansible/roles
|
||||
host_key_checking = False
|
||||
stdout_callback = yaml
|
||||
bin_ansible_callbacks = True
|
||||
retry_files_enabled = False
|
||||
interpreter_python = auto_silent
|
||||
forks = 10
|
||||
pipelining = True
|
||||
EOF
|
||||
|
||||
echo "ANSIBLE_CONFIG=/tmp/ci-ansible.cfg" >> "$GITHUB_ENV"
|
||||
echo "ANSIBLE_INVENTORY=/tmp/ci-inventory.ini" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Python and dependencies
|
||||
run: |
|
||||
apt-get update && apt-get install -y python3 python3-pip
|
||||
@ -275,29 +382,33 @@ jobs:
|
||||
- name: Install Ansible
|
||||
run: pip3 install --no-cache-dir ansible
|
||||
|
||||
- name: Dry-run playbooks
|
||||
- name: Install Ansible collections
|
||||
run: |
|
||||
echo "Running dry-run tests on playbooks..."
|
||||
ansible-galaxy collection install -r collections/requirements.yml
|
||||
|
||||
- name: Validate playbooks (CI inventory, no vault)
|
||||
run: |
|
||||
set -e
|
||||
echo "Validating playbooks against a CI-only localhost inventory (no vault required)..."
|
||||
failed=0
|
||||
for playbook in playbooks/*.yml; do
|
||||
if [ -f "$playbook" ]; then
|
||||
for playbook in playbooks/*.yml site.yml configure_app.yml provision_vms.yml; do
|
||||
[ -f "$playbook" ] || continue
|
||||
echo "Testing $playbook..."
|
||||
if ansible-playbook "$playbook" --syntax-check --list-tasks > /dev/null 2>&1; then
|
||||
echo "✓ $playbook syntax is valid"
|
||||
if ansible-playbook -i /tmp/ci-inventory.ini "$playbook" --syntax-check --list-tasks; then
|
||||
echo "✓ $playbook validated (syntax-check + list-tasks)"
|
||||
else
|
||||
echo "✗ $playbook has syntax errors"
|
||||
echo "✗ $playbook failed validation (syntax-check/list-tasks)"
|
||||
failed=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $failed -eq 1 ]; then
|
||||
echo "❌ Some playbooks have syntax errors!"
|
||||
echo "Note: This may be expected if playbooks require inventory/vault, but syntax errors should still be fixed."
|
||||
echo "❌ Some playbooks failed CI validation."
|
||||
echo "This should not require production inventory or vault secrets."
|
||||
exit 1
|
||||
else
|
||||
echo "✅ All playbooks passed syntax check"
|
||||
echo "✅ All playbooks passed CI validation"
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
container-scan:
|
||||
needs: skip-ci-check
|
||||
@ -373,7 +484,7 @@ jobs:
|
||||
|
||||
sonar-analysis:
|
||||
needs: skip-ci-check
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1'
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
@ -381,6 +492,12 @@ jobs:
|
||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
steps:
|
||||
- name: Install Node.js for checkout action
|
||||
run: |
|
||||
apt-get update && apt-get install -y curl
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt-get install -y nodejs
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@ -467,9 +584,8 @@ jobs:
|
||||
run: |
|
||||
echo "Checking SonarQube connectivity..."
|
||||
if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then
|
||||
echo "❌ ERROR: SONAR_HOST_URL or SONAR_TOKEN secrets are not set!"
|
||||
echo "Please configure them in: Repository Settings → Actions → Secrets"
|
||||
exit 1
|
||||
echo "⚠️ Skipping SonarQube analysis: SONAR_HOST_URL or SONAR_TOKEN secrets are not set."
|
||||
exit 0
|
||||
fi
|
||||
echo "✓ Secrets are configured"
|
||||
echo "SonarQube URL: ${SONAR_HOST_URL}"
|
||||
@ -477,12 +593,17 @@ jobs:
|
||||
if curl -f -s -o /dev/null -w "%{http_code}" "${SONAR_HOST_URL}/api/system/status" | grep -q "200"; then
|
||||
echo "✓ SonarQube server is reachable"
|
||||
else
|
||||
echo "⚠️ Warning: Could not verify SonarQube server connectivity"
|
||||
echo "⚠️ Warning: Could not verify SonarQube server connectivity (continuing anyway)"
|
||||
fi
|
||||
|
||||
- name: Run SonarScanner
|
||||
run: |
|
||||
echo "Starting SonarQube analysis..."
|
||||
if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then
|
||||
echo "Skipping SonarQube analysis: secrets not set."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! sonar-scanner \
|
||||
-Dsonar.projectKey=ansible \
|
||||
-Dsonar.sources=. \
|
||||
@ -503,7 +624,8 @@ jobs:
|
||||
echo " → Grant this permission in SonarQube user settings"
|
||||
echo ""
|
||||
echo "Check SonarQube logs for more details."
|
||||
exit 1
|
||||
# Do not fail CI on Sonar auth/project setup issues.
|
||||
exit 0
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
31
Makefile
31
Makefile
@ -1,4 +1,4 @@
|
||||
.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
|
||||
.PHONY: help bootstrap lint test check dev datascience inventory inventory-all local servers workstations clean status tailscale tailscale-check tailscale-dev tailscale-status create-vault create-vm monitoring
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
## Colors for output
|
||||
@ -13,6 +13,8 @@ RESET := \033[0m
|
||||
PLAYBOOK_SITE := playbooks/site.yml
|
||||
PLAYBOOK_DEV := playbooks/development.yml
|
||||
PLAYBOOK_LOCAL := playbooks/local.yml
|
||||
PLAYBOOK_SERVERS := playbooks/servers.yml
|
||||
PLAYBOOK_WORKSTATIONS := playbooks/workstations.yml
|
||||
PLAYBOOK_MAINTENANCE := playbooks/maintenance.yml
|
||||
PLAYBOOK_TAILSCALE := playbooks/tailscale.yml
|
||||
PLAYBOOK_PROXMOX := playbooks/infrastructure/proxmox-vm.yml
|
||||
@ -33,7 +35,8 @@ ANSIBLE_ARGS := --vault-password-file ~/.ansible-vault-pass
|
||||
|
||||
## Auto-detect current host to exclude from remote operations
|
||||
CURRENT_IP := $(shell hostname -I | awk '{print $$1}')
|
||||
CURRENT_HOST := $(shell ansible-inventory --list | jq -r '._meta.hostvars | to_entries[] | select(.value.ansible_host == "$(CURRENT_IP)") | .key' 2>/dev/null | head -1)
|
||||
# NOTE: inventory parsing may require vault secrets. Keep this best-effort and silent in CI.
|
||||
CURRENT_HOST := $(shell ansible-inventory --list --vault-password-file ~/.ansible-vault-pass 2>/dev/null | jq -r '._meta.hostvars | to_entries[] | select(.value.ansible_host == "$(CURRENT_IP)") | .key' 2>/dev/null | head -1)
|
||||
EXCLUDE_CURRENT := $(if $(CURRENT_HOST),--limit '!$(CURRENT_HOST)',)
|
||||
|
||||
help: ## Show this help message
|
||||
@ -251,6 +254,28 @@ local: ## Run the local playbook on localhost
|
||||
@echo "$(YELLOW)Applying local playbook...$(RESET)"
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_LOCAL) -K
|
||||
|
||||
servers: ## Run baseline server playbook (usage: make servers [GROUP=services] [HOST=host1])
|
||||
@echo "$(YELLOW)Applying server baseline...$(RESET)"
|
||||
@EXTRA=""; \
|
||||
if [ -n "$(HOST)" ]; then \
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SERVERS) --limit $(HOST); \
|
||||
elif [ -n "$(GROUP)" ]; then \
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SERVERS) -e target_group=$(GROUP); \
|
||||
else \
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SERVERS); \
|
||||
fi
|
||||
|
||||
workstations: ## Run workstation baseline (usage: make workstations [GROUP=dev] [HOST=dev01])
|
||||
@echo "$(YELLOW)Applying workstation baseline...$(RESET)"
|
||||
@EXTRA=""; \
|
||||
if [ -n "$(HOST)" ]; then \
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS) --limit $(HOST); \
|
||||
elif [ -n "$(GROUP)" ]; then \
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS) -e target_group=$(GROUP); \
|
||||
else \
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS); \
|
||||
fi
|
||||
|
||||
# Host-specific targets
|
||||
dev: ## Run on specific host (usage: make dev HOST=dev01 [SUDO=true] [SSH_PASS=true])
|
||||
ifndef HOST
|
||||
@ -398,7 +423,7 @@ shell-all: ## Configure shell on all hosts (usage: make shell-all)
|
||||
|
||||
apps: ## Install applications only
|
||||
@echo "$(YELLOW)Installing applications...$(RESET)"
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags apps
|
||||
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_WORKSTATIONS) --tags apps
|
||||
|
||||
# Connectivity targets
|
||||
ping: auto-fallback ## Ping hosts with colored output (usage: make ping [GROUP=dev] [HOST=dev01])
|
||||
|
||||
@ -78,3 +78,4 @@ ansible/
|
||||
|
||||
- **Guides**: `docs/guides/`
|
||||
- **Reference**: `docs/reference/`
|
||||
- **Project docs (architecture/standards/workflow)**: `project-docs/index.md`
|
||||
@ -5,5 +5,3 @@
|
||||
|
||||
- name: Configure app project guests
|
||||
import_playbook: playbooks/app/configure_app.yml
|
||||
|
||||
|
||||
|
||||
28
docs/guides/custom-roles.md
Normal file
28
docs/guides/custom-roles.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Custom roles guide
|
||||
|
||||
This repo is designed to be extended by adding new roles under `roles/`.
|
||||
|
||||
## Role structure
|
||||
|
||||
Follow the standard Ansible role layout:
|
||||
|
||||
```
|
||||
roles/<role_name>/
|
||||
├── defaults/main.yml
|
||||
├── handlers/main.yml
|
||||
├── tasks/main.yml
|
||||
├── templates/
|
||||
├── files/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Where to wire a new role
|
||||
|
||||
- Add it to the relevant playbook under `playbooks/` (or create a new playbook if it’s a major concern).
|
||||
- Prefer tagging the role at inclusion time so `make <feature>` targets can use `--tags`.
|
||||
|
||||
## Standards (canonical)
|
||||
|
||||
- `project-docs/standards.md`
|
||||
- `project-docs/decisions.md` (add an ADR entry for significant changes)
|
||||
|
||||
22
docs/guides/monitoring.md
Normal file
22
docs/guides/monitoring.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Monitoring guide
|
||||
|
||||
Monitoring is split by host type:
|
||||
|
||||
- **Servers**: `roles/monitoring_server/` (includes `fail2ban`, sysstat tooling)
|
||||
- **Desktops/workstations**: `roles/monitoring_desktop/` (desktop-oriented tooling)
|
||||
|
||||
## Run monitoring only
|
||||
|
||||
```bash
|
||||
# Dry-run
|
||||
make monitoring CHECK=true
|
||||
|
||||
# Apply
|
||||
make monitoring
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Desktop apps are installed only on the `desktop` group via `playbooks/workstations.yml`.
|
||||
- If you need packet analysis tools, keep them opt-in (see `docs/reference/applications.md`).
|
||||
|
||||
31
docs/guides/security.md
Normal file
31
docs/guides/security.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Security hardening guide
|
||||
|
||||
This repo’s “security” work is primarily implemented via roles and inventory defaults.
|
||||
|
||||
## What runs where
|
||||
|
||||
- **SSH hardening + firewall**: `roles/ssh/`
|
||||
- **Baseline packages/security utilities**: `roles/base/`
|
||||
- **Monitoring + intrusion prevention (servers)**: `roles/monitoring_server/` (includes `fail2ban`)
|
||||
- **Secrets**: Ansible Vault in `inventories/production/group_vars/all/vault.yml`
|
||||
|
||||
## Recommended flow
|
||||
|
||||
```bash
|
||||
# Dry-run first
|
||||
make check
|
||||
|
||||
# Apply only security-tagged roles
|
||||
make security
|
||||
```
|
||||
|
||||
## Secrets / Vault
|
||||
|
||||
Use vault for anything sensitive:
|
||||
|
||||
- Guide: `docs/guides/vault.md`
|
||||
|
||||
## Canonical standards
|
||||
|
||||
- `project-docs/standards.md`
|
||||
|
||||
13
docs/project-docs.md
Normal file
13
docs/project-docs.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Project docs (canonical)
|
||||
|
||||
Canonical project documentation lives in `project-docs/` (repo root):
|
||||
|
||||
- Index: `project-docs/index.md`
|
||||
- Overview: `project-docs/overview.md`
|
||||
- Architecture: `project-docs/architecture.md`
|
||||
- Standards: `project-docs/standards.md`
|
||||
- Workflow: `project-docs/workflow.md`
|
||||
- Decisions (ADRs): `project-docs/decisions.md`
|
||||
|
||||
This file exists so users browsing `docs/` can quickly find the canonical project documentation.
|
||||
|
||||
@ -6,7 +6,7 @@ Complete inventory of applications and tools deployed by Ansible playbooks.
|
||||
|
||||
### 🔧 System Tools
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| curl | Command line HTTP client | apt | base |
|
||||
| wget | Network downloader | apt | base |
|
||||
| unzip | Archive extraction | apt | base |
|
||||
@ -20,7 +20,7 @@ Complete inventory of applications and tools deployed by Ansible playbooks.
|
||||
|
||||
### 🔒 Security Tools
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| ufw | Uncomplicated Firewall | apt | ssh |
|
||||
| fail2ban | Intrusion prevention | apt | monitoring |
|
||||
| openssh-server | SSH daemon | apt | ssh |
|
||||
@ -31,7 +31,7 @@ Complete inventory of applications and tools deployed by Ansible playbooks.
|
||||
|
||||
### 💻 Development Tools
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| git | Version control | apt | development |
|
||||
| nodejs | JavaScript runtime | apt | development |
|
||||
| npm | Node package manager | apt | development |
|
||||
@ -41,7 +41,7 @@ Complete inventory of applications and tools deployed by Ansible playbooks.
|
||||
|
||||
### 🐳 Container Platform
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| docker-ce | Docker Community Edition | docker | docker |
|
||||
| docker-ce-cli | Docker CLI | docker | docker |
|
||||
| containerd.io | Container runtime | docker | docker |
|
||||
@ -50,18 +50,15 @@ Complete inventory of applications and tools deployed by Ansible playbooks.
|
||||
|
||||
### 🖥️ Shell Environment
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| zsh | Z shell | apt | shell |
|
||||
| tmux | Terminal multiplexer | apt | shell |
|
||||
| fzf | Fuzzy finder | apt | shell |
|
||||
| oh-my-zsh | Zsh framework | git | shell |
|
||||
| powerlevel10k | Zsh theme | git | shell |
|
||||
| zsh-syntax-highlighting | Syntax highlighting | git | shell |
|
||||
| zsh-autosuggestions | Command suggestions | git | shell |
|
||||
| zsh aliases | Minimal alias set (sourced from ~/.zshrc) | file | shell |
|
||||
|
||||
### 📊 Monitoring Tools
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| htop | Process viewer | apt | monitoring |
|
||||
| btop | Modern system monitor | snap | monitoring |
|
||||
| iotop | I/O monitor | apt | monitoring |
|
||||
@ -77,23 +74,41 @@ Complete inventory of applications and tools deployed by Ansible playbooks.
|
||||
|
||||
### 🌐 Network Tools
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| tailscale | Mesh VPN client | tailscale | tailscale |
|
||||
| tailscaled | Tailscale daemon | tailscale | tailscale |
|
||||
|
||||
### 🖱️ Desktop Applications
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| brave-browser | Privacy-focused browser | brave | applications |
|
||||
| libreoffice | Office suite | apt | applications |
|
||||
| --------- | ------------- | -------- | ------ |
|
||||
| copyq | Clipboard manager (history/search) | apt | applications |
|
||||
| evince | PDF viewer | apt | applications |
|
||||
| redshift | Blue light filter | apt | applications |
|
||||
|
||||
### 📝 Code Editors
|
||||
| Package | Description | Source | Role |
|
||||
|---------|-------------|--------|------|
|
||||
| code | Visual Studio Code | snap | snap |
|
||||
| cursor | AI-powered editor | snap | snap |
|
||||
## Nice-to-have apps (not installed by default)
|
||||
|
||||
These are good add-ons depending on how you use your workstations. Keep them opt-in to avoid bloating baseline installs.
|
||||
|
||||
### Desktop / UX
|
||||
|
||||
- **flameshot**: screenshots + annotation
|
||||
- **keepassxc**: local password manager (or use your preferred)
|
||||
- **syncthing**: peer-to-peer file sync (if you want self-hosted sync)
|
||||
- **remmina**: RDP/VNC client
|
||||
- **mpv**: lightweight media player
|
||||
|
||||
### Developer workstation helpers
|
||||
|
||||
- **direnv**: per-project env var loading
|
||||
- **shellcheck**: shell script linting
|
||||
- **jq** / **yq**: JSON/YAML CLI tooling (already in base here, but listing for completeness)
|
||||
- **ripgrep** / **fd-find**: fast search/find (already in base here)
|
||||
|
||||
### Networking / diagnostics
|
||||
|
||||
- **wireshark** (GUI) or **wireshark-common**: packet analysis (only if you need it)
|
||||
- **iperf3**: bandwidth testing
|
||||
- **dnsutils**: dig/nslookup tools
|
||||
|
||||
## Installation by Playbook
|
||||
|
||||
@ -103,7 +118,6 @@ Installs all roles for development machines:
|
||||
- Development environment
|
||||
- Docker platform
|
||||
- Shell configuration
|
||||
- Desktop applications
|
||||
- Monitoring tools
|
||||
- Tailscale VPN
|
||||
|
||||
@ -112,7 +126,11 @@ Installs for local machine management:
|
||||
- Core system tools
|
||||
- Shell environment
|
||||
- Development basics
|
||||
- Selected applications
|
||||
|
||||
### `playbooks/workstations.yml`
|
||||
Installs baseline for `dev:desktop:local`, and installs desktop apps only for the `desktop` group:
|
||||
- Workstation baseline (dev + desktop + local)
|
||||
- Desktop applications (desktop group only)
|
||||
|
||||
### `playbooks/maintenance.yml`
|
||||
Maintains existing installations:
|
||||
@ -130,18 +148,17 @@ Maintains existing installations:
|
||||
## Package Sources
|
||||
|
||||
| Source | Description | Configuration |
|
||||
|--------|-------------|---------------|
|
||||
| -------- | ------------- | --------------- |
|
||||
| apt | Debian/Ubuntu packages | System default |
|
||||
| snap | Snap packages | snapd daemon |
|
||||
| docker | Docker repository | Docker GPG key + repo |
|
||||
| tailscale | Tailscale repository | Tailscale GPG key + repo |
|
||||
| brave | Brave browser repository | Brave GPG key + repo |
|
||||
| git | Git repositories | Direct clone |
|
||||
|
||||
## Services Enabled
|
||||
|
||||
| Service | Description | Management |
|
||||
|---------|-------------|------------|
|
||||
| --------- | ------------- | ------------ |
|
||||
| docker | Container runtime | systemctl |
|
||||
| tailscaled | VPN daemon | systemctl |
|
||||
| ufw | Firewall | systemctl |
|
||||
|
||||
@ -1,263 +1,10 @@
|
||||
# Architecture Overview
|
||||
# Architecture (canonical doc moved)
|
||||
|
||||
Technical architecture and design of the Ansible infrastructure management system.
|
||||
The canonical architecture document is now:
|
||||
|
||||
## System Architecture
|
||||
- `project-docs/architecture.md`
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Control Machine |
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Ansible │ │ Makefile │ │ Vault │ │
|
||||
│ │ Engine │ │ Automation │ │ Secrets │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────┬───────────────────────────────────────┘
|
||||
│ SSH + Tailscale VPN
|
||||
┌─────────────────┴──────────────────────────────┐
|
||||
│ │
|
||||
┌───▼────────┐ ┌──────────────┐ ┌─────────────────▼────────┐
|
||||
│ Dev │ │ Service │ │ Infrastructure │
|
||||
│ Machines │ │ VMs │ │ VMs │
|
||||
├────────────┤ ├──────────────┤ ├──────────────────────────┤
|
||||
│ • dev01 │ │ • giteaVM │ │ • Proxmox Controller │
|
||||
│ • bottom │ │ • portainerVM│ │ • Future VMs │
|
||||
│ • desktop │ │ • homepageVM │ │ │
|
||||
└────────────┘ └──────────────┘ └──────────────────────────┘
|
||||
```
|
||||
|
||||
## Network Topology
|
||||
|
||||
### Physical Network
|
||||
- **LAN**: 192.168.1.0/24 (typical home/office network)
|
||||
- **Proxmox Host**: Hypervisor for VM management
|
||||
- **Physical Machines**: Direct network access
|
||||
|
||||
### Tailscale Overlay Network
|
||||
- **Mesh VPN**: Secure peer-to-peer connections
|
||||
- **100.x.x.x**: Tailscale IP range
|
||||
- **Zero-config**: Automatic NAT traversal
|
||||
- **End-to-end encryption**: WireGuard protocol
|
||||
|
||||
## Host Groups
|
||||
|
||||
### Development (`dev`)
|
||||
**Purpose**: Developer workstations and environments
|
||||
- **Hosts**: dev01, bottom, debianDesktopVM
|
||||
- **OS**: Debian/Ubuntu
|
||||
- **Roles**: Full development stack
|
||||
|
||||
### Services
|
||||
**Purpose**: Self-hosted services and applications
|
||||
|
||||
#### Gitea (`gitea`)
|
||||
- **Host**: giteaVM
|
||||
- **OS**: Alpine Linux (lightweight)
|
||||
- **Service**: Git repository hosting
|
||||
|
||||
#### Portainer (`portainer`)
|
||||
- **Host**: portainerVM
|
||||
- **OS**: Alpine Linux
|
||||
- **Service**: Container management UI
|
||||
|
||||
#### Homepage (`homepage`)
|
||||
- **Host**: homepageVM
|
||||
- **OS**: Debian
|
||||
- **Service**: Service dashboard
|
||||
|
||||
### Infrastructure (`ansible`)
|
||||
**Purpose**: Ansible control and automation
|
||||
- **Host**: Ansible controller VM
|
||||
- **OS**: Ubuntu Server
|
||||
- **Service**: Infrastructure automation
|
||||
|
||||
### Local (`local`)
|
||||
**Purpose**: Local machine management
|
||||
- **Host**: localhost
|
||||
- **Connection**: Local (no SSH)
|
||||
|
||||
## Playbook Architecture
|
||||
|
||||
### Core Playbooks
|
||||
|
||||
```yaml
|
||||
playbooks/development.yml # Development environment setup
|
||||
├── roles/maintenance # System updates
|
||||
├── roles/base # Core packages
|
||||
├── roles/ssh # SSH hardening
|
||||
├── roles/user # User management
|
||||
├── roles/development # Dev tools
|
||||
├── roles/shell # Shell config
|
||||
├── roles/docker # Container platform
|
||||
├── roles/applications # Desktop apps
|
||||
├── roles/snap # Snap packages
|
||||
├── roles/tailscale # VPN setup
|
||||
├── roles/monitoring # Monitoring tools
|
||||
|
||||
playbooks/local.yml # Local machine
|
||||
├── roles/base
|
||||
├── roles/shell
|
||||
├── roles/development
|
||||
└── roles/tailscale
|
||||
|
||||
playbooks/maintenance.yml # System maintenance
|
||||
└── roles/maintenance
|
||||
|
||||
playbooks/tailscale.yml # VPN deployment
|
||||
└── roles/tailscale
|
||||
|
||||
playbooks/infrastructure/proxmox-vm.yml # KVM VM provisioning (controller VM, etc.)
|
||||
└── roles/proxmox_vm
|
||||
|
||||
playbooks/app/site.yml # Proxmox app stack (LXC-first)
|
||||
├── playbooks/app/provision_vms.yml # Proxmox API provisioning (LXC/KVM)
|
||||
└── playbooks/app/configure_app.yml # Guest OS + app configuration over SSH
|
||||
```
|
||||
|
||||
### Role Dependencies
|
||||
|
||||
```
|
||||
base
|
||||
├── Required by: all other roles
|
||||
├── Provides: core utilities, security tools
|
||||
└── Dependencies: none
|
||||
|
||||
ssh
|
||||
├── Required by: secure access
|
||||
├── Provides: hardened SSH, firewall
|
||||
└── Dependencies: base
|
||||
|
||||
user
|
||||
├── Required by: system access
|
||||
├── Provides: user accounts, sudo
|
||||
└── Dependencies: base
|
||||
|
||||
development
|
||||
├── Required by: coding tasks
|
||||
├── Provides: git, nodejs, python
|
||||
└── Dependencies: base
|
||||
|
||||
docker
|
||||
├── Required by: containerization
|
||||
├── Provides: Docker CE, compose
|
||||
└── Dependencies: base
|
||||
|
||||
tailscale
|
||||
├── Required by: secure networking
|
||||
├── Provides: mesh VPN
|
||||
└── Dependencies: base
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Configuration Management
|
||||
1. **Variables** → `inventories/production/group_vars/all/main.yml`
|
||||
2. **Secrets** → `inventories/production/group_vars/all/vault.yml` (encrypted)
|
||||
3. **Host Config** → `inventories/production/host_vars/<hostname>.yml`
|
||||
4. **Role Defaults** → roles/*/defaults/main.yml
|
||||
5. **Tasks** → roles/*/tasks/main.yml
|
||||
6. **Templates** → roles/*/templates/*.j2
|
||||
7. **Handlers** → roles/*/handlers/main.yml
|
||||
|
||||
### Execution Flow
|
||||
```
|
||||
make command
|
||||
↓
|
||||
Makefile target
|
||||
↓
|
||||
ansible-playbook
|
||||
↓
|
||||
Inventory + Variables
|
||||
↓
|
||||
Role execution
|
||||
↓
|
||||
Task processing
|
||||
↓
|
||||
Handler notification
|
||||
↓
|
||||
Result reporting
|
||||
```
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Defense in Depth
|
||||
|
||||
#### Network Layer
|
||||
- **Tailscale VPN**: Encrypted mesh network
|
||||
- **UFW Firewall**: Default deny, explicit allow
|
||||
- **SSH Hardening**: Key-only, rate limiting
|
||||
|
||||
#### Application Layer
|
||||
- **Fail2ban**: Intrusion prevention
|
||||
- **Package signing**: GPG verification
|
||||
- **Service isolation**: Docker containers
|
||||
|
||||
#### Data Layer
|
||||
- **Ansible Vault**: Encrypted secrets
|
||||
- **SSH Keys**: Ed25519 cryptography
|
||||
|
||||
### Access Control
|
||||
|
||||
```
|
||||
User → SSH Key → Jump Host → Tailscale → Target Host
|
||||
↓ ↓ ↓ ↓
|
||||
Ed25519 Bastion WireGuard Firewall
|
||||
Encryption Rules
|
||||
```
|
||||
|
||||
## Storage Architecture
|
||||
|
||||
|
||||
### Configuration Storage
|
||||
```
|
||||
/etc/ # System configuration
|
||||
/opt/ # Application data
|
||||
/usr/local/ # Custom scripts
|
||||
/var/log/ # Logs and audit trails
|
||||
```
|
||||
|
||||
## Monitoring Architecture
|
||||
|
||||
### System Monitoring
|
||||
- **btop/htop**: Process monitoring
|
||||
- **iotop**: I/O monitoring
|
||||
- **nethogs**: Network per-process
|
||||
- **Custom dashboards**: sysinfo, netinfo
|
||||
|
||||
### Log Management
|
||||
- **logwatch**: Daily summaries
|
||||
- **journald**: System logs
|
||||
- **fail2ban**: Security logs
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
### Horizontal Scaling
|
||||
- Add hosts to inventory groups
|
||||
- Parallel execution with ansible forks
|
||||
- Role reusability across environments
|
||||
|
||||
### Vertical Scaling
|
||||
- Proxmox VM resource adjustment
|
||||
- Docker resource limits
|
||||
- Service-specific tuning
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Technologies
|
||||
- **Ansible**: 2.9+ (Configuration management)
|
||||
- **Python**: 3.x (Ansible runtime)
|
||||
- **Jinja2**: Templating engine
|
||||
- **YAML**: Configuration format
|
||||
|
||||
### Target Platforms
|
||||
- **Debian**: 11+ (Bullseye, Bookworm)
|
||||
- **Ubuntu**: 20.04+ (Focal, Jammy, Noble)
|
||||
- **Alpine**: 3.x (Lightweight containers)
|
||||
|
||||
### Service Technologies
|
||||
- **Docker**: Container runtime
|
||||
- **Tailscale**: Mesh VPN
|
||||
- **SystemD**: Service management
|
||||
- **UFW**: Firewall management
|
||||
This `docs/reference/architecture.md` file is kept as a pointer to avoid maintaining two competing sources of truth.
|
||||
|
||||
## Best Practices
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### Setup & Testing
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `help` | Show all available commands | `make help` |
|
||||
| `bootstrap` | Install required collections and dependencies | `make bootstrap` |
|
||||
| `test` | Run all tests (lint + syntax check) | `make test` |
|
||||
@ -17,7 +17,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### Deployment
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `apply` | Run development playbook on all dev hosts | `make apply` |
|
||||
| `local` | Run local playbook on localhost | `make local` |
|
||||
| `dev` | Run on specific host | `make dev HOST=dev01` |
|
||||
@ -27,7 +27,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### System Maintenance
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `maintenance` | Run system maintenance | `make maintenance [GROUP=dev] [HOST=dev01]` |
|
||||
| | | Options: `SERIAL=1 CHECK=true VERBOSE=true` |
|
||||
| `maintenance-dev` | Run maintenance on dev group | `make maintenance-dev` |
|
||||
@ -37,7 +37,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### Security & Networking
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `security` | Run only security-related roles | `make security` |
|
||||
| `tailscale` | Install Tailscale on all machines | `make tailscale` |
|
||||
| `tailscale-check` | Check Tailscale installation (dry-run) | `make tailscale-check` |
|
||||
@ -46,7 +46,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### Applications & Tools
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `docker` | Install/configure Docker only | `make docker` |
|
||||
| `shell` | Configure shell only | `make shell` |
|
||||
| `apps` | Install applications only | `make apps` |
|
||||
@ -56,9 +56,9 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### VM & Host Management
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `create-vm` | Create Ansible controller VM on Proxmox | `make create-vm` |
|
||||
| `proxmox-info` | Show Proxmox guest info (LXC/VM) | `make proxmox-info [PROJECT=projectA] [ALL=true] [TYPE=lxc\|qemu\|all]` |
|
||||
| `proxmox-info` | Show Proxmox guest info (LXC/VM) | `make proxmox-info [PROJECT=projectA] [ALL=true] [TYPE=lxc/qemu/all]` |
|
||||
| `app-provision` | Provision app project guests on Proxmox | `make app-provision PROJECT=projectA` |
|
||||
| `app-configure` | Configure OS + app on project guests | `make app-configure PROJECT=projectA` |
|
||||
| `app` | Provision + configure app project guests | `make app PROJECT=projectA` |
|
||||
@ -69,7 +69,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### SSH & Vault Management
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `copy-ssh-key` | Copy SSH key to specific host | `make copy-ssh-key HOST=giteaVM` |
|
||||
| `create-vault` | Create encrypted vault file | `make create-vault` |
|
||||
| `edit-vault` | Edit encrypted host vars | `make edit-vault HOST=dev01` |
|
||||
@ -79,7 +79,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
|
||||
### Debugging & Cleanup
|
||||
| Command | Description | Usage |
|
||||
|---------|-------------|-------|
|
||||
| --------- | ------------- | ------- |
|
||||
| `debug` | Run with debug output enabled | `make debug` |
|
||||
| `verbose` | Run with verbose output | `make verbose` |
|
||||
| `clean` | Clean up ansible artifacts | `make clean` |
|
||||
@ -90,7 +90,7 @@ Complete reference for all available `make` commands in the Ansible project.
|
||||
Many commands accept these optional variables:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| ---------- | ------------- | --------- |
|
||||
| `HOST` | Target specific host | `HOST=dev01` |
|
||||
| `GROUP` | Target specific group | `GROUP=dev` |
|
||||
| `CHECK` | Run in check mode (dry-run) | `CHECK=true` |
|
||||
@ -170,7 +170,7 @@ make debug
|
||||
The Makefile respects these environment variables:
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| ---------- | ------------- | --------- |
|
||||
| `ANSIBLE_CONFIG` | Ansible configuration file | `./ansible.cfg` |
|
||||
| `ANSIBLE_VAULT_PASSWORD_FILE` | Vault password file | `~/.ansible-vault-pass` |
|
||||
| `ANSIBLE_HOST_KEY_CHECKING` | SSH host key checking | `False` |
|
||||
|
||||
21
docs/reference/network.md
Normal file
21
docs/reference/network.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Network reference
|
||||
|
||||
## Overview
|
||||
|
||||
This repo manages hosts reachable over your LAN and optionally over a Tailscale overlay network.
|
||||
|
||||
## Physical network
|
||||
|
||||
- Typical LAN: `192.168.1.0/24` (adjust for your environment)
|
||||
- Inventory host addressing is defined in `inventories/production/hosts`
|
||||
|
||||
## Tailscale overlay
|
||||
|
||||
- Tailscale provides a mesh VPN (WireGuard-based) with `100.x.y.z` addresses.
|
||||
- The repo installs/configures it via `playbooks/tailscale.yml` + `roles/tailscale/`.
|
||||
|
||||
## References
|
||||
|
||||
- Tailscale guide: `docs/guides/tailscale.md`
|
||||
- Canonical architecture: `project-docs/architecture.md`
|
||||
|
||||
281
docs/reference/playbooks-and-tags.md
Normal file
281
docs/reference/playbooks-and-tags.md
Normal file
@ -0,0 +1,281 @@
|
||||
# Playbooks & Tags Map
|
||||
|
||||
This repo is organized around playbooks in `playbooks/` (plus a few thin wrapper playbooks in the repo root).
|
||||
|
||||
This reference gives you:
|
||||
- **Execution paths**: where each playbook “goes” (imports → roles → included tasks).
|
||||
- **Tag paths**: what each tag actually selects, including Makefile shortcuts.
|
||||
|
||||
---
|
||||
|
||||
## Playbook entrypoints (paths)
|
||||
|
||||
### `site.yml` (wrapper)
|
||||
|
||||
`site.yml` is a wrapper that delegates to `playbooks/site.yml`.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[site.yml] --> B[playbooks/site.yml]
|
||||
```
|
||||
|
||||
### `playbooks/site.yml` (dispatcher)
|
||||
|
||||
This is a pure dispatcher: it imports other playbooks and assigns **top-level tags** per import.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S[playbooks/site.yml] -->|tags: maintenance| M[playbooks/maintenance.yml]
|
||||
S -->|tags: development| D[playbooks/development.yml]
|
||||
S -->|tags: tailscale| T[playbooks/tailscale.yml]
|
||||
S -->|tags: app| A[playbooks/app/site.yml]
|
||||
```
|
||||
|
||||
### `playbooks/maintenance.yml`
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/maintenance.yml] --> R[role: maintenance]
|
||||
```
|
||||
|
||||
- **Notes**:
|
||||
- `pre_tasks`/`post_tasks` in the playbook are **untagged**; if you run with
|
||||
`--tags maintenance`, only the `maintenance` role runs (the untagged pre/post tasks are skipped).
|
||||
|
||||
### `playbooks/development.yml`
|
||||
|
||||
Targets: `hosts: dev`
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/development.yml] --> R1[role: maintenance]
|
||||
P --> R2[role: base]
|
||||
P --> R3[role: user]
|
||||
P --> R4[role: ssh]
|
||||
P --> R5[role: shell]
|
||||
P --> R6[role: development]
|
||||
P --> R7[role: datascience]
|
||||
P --> R8[role: docker]
|
||||
P --> R9[role: monitoring_desktop]
|
||||
|
||||
%% role-internal paths that matter
|
||||
R5 --> S1[roles/shell/tasks/main.yml]
|
||||
S1 --> S2[include_tasks: roles/shell/tasks/configure_user_shell.yml]
|
||||
|
||||
R8 --> D1[roles/docker/tasks/main.yml]
|
||||
D1 --> D2[include_tasks: roles/docker/tasks/setup_gpg_key.yml]
|
||||
D1 --> D3[include_tasks: roles/docker/tasks/setup_repo_*.yml]
|
||||
```
|
||||
|
||||
- **Notes**:
|
||||
- `pre_tasks` is **untagged**; if you run with tag filters, that apt-cache update is skipped unless you include
|
||||
`--tags all` or also run untagged tasks (Ansible has `--skip-tags`/`--tags` behavior to be aware of).
|
||||
|
||||
### `playbooks/local.yml`
|
||||
|
||||
Targets: `hosts: localhost`, `connection: local`
|
||||
|
||||
This is basically the same role stack as `playbooks/development.yml` (minus `datascience`), but applied locally.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/local.yml] --> R1[role: maintenance]
|
||||
P --> R2[role: base]
|
||||
P --> R3[role: user]
|
||||
P --> R4[role: ssh]
|
||||
P --> R5[role: shell]
|
||||
P --> R6[role: development]
|
||||
P --> R7[role: docker]
|
||||
P --> R8[role: monitoring_desktop]
|
||||
```
|
||||
|
||||
### `playbooks/servers.yml`
|
||||
|
||||
Targets by default: `services:qa:ansible:tailscale` (override via `-e target_group=...`).
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/servers.yml] --> R1[role: maintenance]
|
||||
P --> R2[role: base]
|
||||
P --> R3[role: user]
|
||||
P --> R4[role: ssh]
|
||||
P --> R5[role: shell]
|
||||
P --> R6[role: docker]
|
||||
P --> R7[role: monitoring_server]
|
||||
```
|
||||
|
||||
### `playbooks/workstations.yml`
|
||||
|
||||
Two plays:
|
||||
- Workstation baseline for `dev:desktop:local`
|
||||
- Desktop applications only for the `desktop` group
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
W[playbooks/workstations.yml] --> B1[play: workstation baseline]
|
||||
B1 --> R1[role: maintenance]
|
||||
B1 --> R2[role: base]
|
||||
B1 --> R3[role: user]
|
||||
B1 --> R4[role: ssh]
|
||||
B1 --> R5[role: shell]
|
||||
B1 --> R6[role: development]
|
||||
B1 --> R7[role: datascience]
|
||||
B1 --> R8[role: docker]
|
||||
B1 --> R9[role: monitoring_desktop]
|
||||
|
||||
W --> B2[play: desktop apps]
|
||||
B2 --> A1[role: applications]
|
||||
```
|
||||
|
||||
### `playbooks/shell.yml`
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/shell.yml] --> R[role: shell]
|
||||
R --> S1[roles/shell/tasks/main.yml]
|
||||
S1 --> S2[include_tasks: roles/shell/tasks/configure_user_shell.yml]
|
||||
```
|
||||
|
||||
### `playbooks/tailscale.yml`
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/tailscale.yml] --> R[role: tailscale]
|
||||
R --> T1[roles/tailscale/tasks/main.yml]
|
||||
T1 -->|Debian| T2[include_tasks: roles/tailscale/tasks/debian.yml]
|
||||
T1 -->|Alpine| T3[include_tasks: roles/tailscale/tasks/alpine.yml]
|
||||
```
|
||||
|
||||
### `playbooks/infrastructure/proxmox-vm.yml`
|
||||
|
||||
Creates an Ansible controller VM on Proxmox (local connection) via `role: proxmox_vm`.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/infrastructure/proxmox-vm.yml] --> R[role: proxmox_vm]
|
||||
R --> M1[roles/proxmox_vm/tasks/main.yml]
|
||||
M1 -->|proxmox_guest_type=lxc| L1[include_tasks: roles/proxmox_vm/tasks/lxc.yml]
|
||||
M1 -->|else| K1[include_tasks: roles/proxmox_vm/tasks/kvm.yml]
|
||||
```
|
||||
|
||||
### App project suite (`playbooks/app/*`)
|
||||
|
||||
#### `playbooks/app/site.yml` (app dispatcher)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S[playbooks/app/site.yml] -->|tags: app,provision| P[playbooks/app/provision_vms.yml]
|
||||
S -->|tags: app,configure| C[playbooks/app/configure_app.yml]
|
||||
```
|
||||
|
||||
#### `playbooks/app/provision_vms.yml` (provision app guests)
|
||||
|
||||
High-level loop: `project_key` → `env_item` → provision guest → add to dynamic inventory groups.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P[playbooks/app/provision_vms.yml] --> T1[include_tasks: playbooks/app/provision_one_guest.yml]
|
||||
T1 --> T2[include_tasks: playbooks/app/provision_one_env.yml]
|
||||
T2 --> R[include_role: proxmox_vm]
|
||||
R --> M1[roles/proxmox_vm/tasks/main.yml]
|
||||
M1 --> L1[roles/proxmox_vm/tasks/lxc.yml]
|
||||
|
||||
T2 --> H[add_host groups]
|
||||
H --> G1[app_all]
|
||||
H --> G2[app_${project}_all]
|
||||
H --> G3[app_${project}_${env}]
|
||||
```
|
||||
|
||||
#### `playbooks/app/configure_app.yml` (configure app guests)
|
||||
|
||||
Two phases:
|
||||
1) **localhost** builds a dynamic inventory from `app_projects` (static IPs)
|
||||
2) **app_all** (or `app_${project}_all`) configures each host
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[play: localhost build inventory] --> H[add_host groups]
|
||||
H --> G1[app_all / app_${project}_*]
|
||||
|
||||
B[play: app_all configure] --> OS[include_role: base_os]
|
||||
B --> POTE[include_role: pote (only when app_project == 'pote')]
|
||||
B --> APP[include_role: app_setup (when app_project != 'pote')]
|
||||
```
|
||||
|
||||
#### `playbooks/app/proxmox_info.yml`
|
||||
|
||||
Single local play that queries Proxmox and prints a filtered summary.
|
||||
|
||||
#### `playbooks/app/ssh_client_config.yml`
|
||||
|
||||
Single local play that optionally manages `~/.ssh/config` (gated by `manage_ssh_config`).
|
||||
|
||||
---
|
||||
|
||||
## Tags map (what each tag hits)
|
||||
|
||||
### Top-level dispatcher tags (`playbooks/site.yml`)
|
||||
|
||||
- **maintenance**: runs `playbooks/maintenance.yml`
|
||||
- **development**: runs `playbooks/development.yml`
|
||||
- **tailscale**: runs `playbooks/tailscale.yml`
|
||||
- **app**: runs `playbooks/app/site.yml` (and therefore app provision + configure)
|
||||
|
||||
### Dev/local role tags (`playbooks/development.yml`, `playbooks/local.yml`)
|
||||
|
||||
These playbooks tag roles directly:
|
||||
- **maintenance** → `role: maintenance`
|
||||
- **base** → `role: base`
|
||||
- **security** → `role: base` + `role: ssh` (both are tagged `security` at role-inclusion)
|
||||
- **user** → `role: user`
|
||||
- **ssh** → `role: ssh`
|
||||
- **shell** → `role: shell`
|
||||
- **development** / **dev** → `role: development`
|
||||
- **docker** → `role: docker`
|
||||
- **monitoring** → `role: monitoring_desktop`
|
||||
- **datascience** / **conda** / **jupyter** / **r** → `role: datascience` (development playbook only)
|
||||
- **tailscale** / **vpn** → (currently commented out in dev/local)
|
||||
|
||||
### Workstation + desktop apps tags (`playbooks/workstations.yml`)
|
||||
|
||||
- **apps** / **applications** → `role: applications` (desktop group only)
|
||||
|
||||
### App suite tags
|
||||
|
||||
From `playbooks/app/site.yml` imports:
|
||||
- **app**: everything in the app suite
|
||||
- **provision**: `playbooks/app/provision_vms.yml` only
|
||||
- **configure**: `playbooks/app/configure_app.yml` only
|
||||
|
||||
Standalone app playbook tags (so `--tags ...` works when running them directly):
|
||||
- `playbooks/app/provision_vms.yml`: **app**, **provision**
|
||||
- `playbooks/app/configure_app.yml`: **app**, **configure**
|
||||
- `playbooks/app/proxmox_info.yml`: **app**, **proxmox**, **info**
|
||||
- `playbooks/app/ssh_client_config.yml`: **app**, **ssh-config**
|
||||
|
||||
### Role-internal tags (task/block level)
|
||||
|
||||
These are tags inside role task files (useful for targeting parts of a role even if the role itself isn’t included with that tag):
|
||||
- `roles/datascience/tasks/main.yml`:
|
||||
- **conda**
|
||||
- **jupyter**
|
||||
- **r**, **rstats**
|
||||
|
||||
### Makefile tag shortcuts
|
||||
|
||||
Make targets that apply `--tags`:
|
||||
- `make datascience HOST=...` → `--tags datascience`
|
||||
- `make security` → `--tags security`
|
||||
- `make docker` → `--tags docker`
|
||||
- `make shell` → `--tags shell`
|
||||
- `make apps` → `--tags apps`
|
||||
- `make monitoring` → `--tags monitoring`
|
||||
|
||||
---
|
||||
|
||||
## Tag-filtering gotchas (important)
|
||||
|
||||
- If you run with `--tags X`, **untagged** `pre_tasks`/`tasks`/`post_tasks` in a playbook are skipped.
|
||||
- Example: `playbooks/maintenance.yml` has untagged `pre_tasks` and `post_tasks`.
|
||||
`--tags maintenance` runs only the `maintenance` role, not the surrounding reporting steps.
|
||||
|
||||
28
docs/reference/security.md
Normal file
28
docs/reference/security.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Security reference
|
||||
|
||||
## Overview
|
||||
|
||||
Security in this repo is implemented via:
|
||||
- hardened SSH + firewall defaults (`roles/ssh/`)
|
||||
- baseline system configuration (`roles/base/`)
|
||||
- monitoring/intrusion prevention on servers (`roles/monitoring_server/`)
|
||||
- secrets handled via Ansible Vault (`inventories/production/group_vars/all/vault.yml`)
|
||||
|
||||
## Recommended execution
|
||||
|
||||
```bash
|
||||
# Dry-run first
|
||||
make check
|
||||
|
||||
# Apply security-tagged tasks
|
||||
make security
|
||||
```
|
||||
|
||||
## Vault
|
||||
|
||||
- Vault guide: `docs/guides/vault.md`
|
||||
|
||||
## Canonical standards
|
||||
|
||||
- `project-docs/standards.md`
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
---
|
||||
ansible_become_password: "{{ vault_devgpu_become_password }}"
|
||||
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
# Shell configuration
|
||||
@ -22,10 +20,4 @@ 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_docs: true # Markdown/documentation
|
||||
# IDE/editor tooling is intentionally not managed by Ansible in this repo.
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
---
|
||||
vault_devgpu_become_password: root
|
||||
316
package-lock.json
generated
316
package-lock.json
generated
@ -10,7 +10,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"markdown-link-check": "^3.13.7",
|
||||
"markdownlint-cli2": "^0.18.1"
|
||||
"markdownlint-cli2": "^0.20.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
@ -56,61 +56,61 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/dom": {
|
||||
"version": "1.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
|
||||
"integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz",
|
||||
"integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/url": "1.0.4",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
"@oozcitak/infra": "^2.0.2",
|
||||
"@oozcitak/url": "^3.0.0",
|
||||
"@oozcitak/util": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
"node": ">=20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/infra": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
|
||||
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz",
|
||||
"integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/util": "8.3.8"
|
||||
"@oozcitak/util": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
"node": ">=20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/url": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
|
||||
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz",
|
||||
"integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
"@oozcitak/infra": "^2.0.2",
|
||||
"@oozcitak/util": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
"node": ">=20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/util": {
|
||||
"version": "8.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
|
||||
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz",
|
||||
"integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
"node": ">=20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/merge-streams": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
|
||||
"integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
|
||||
"integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -168,6 +168,19 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@ -196,9 +209,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-ftp": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
|
||||
"integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
|
||||
"integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -316,13 +329,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
|
||||
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
|
||||
"version": "14.0.2",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
|
||||
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
@ -596,9 +609,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@ -618,6 +631,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-uri": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
|
||||
@ -647,21 +673,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globby": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz",
|
||||
"integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==",
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-15.0.0.tgz",
|
||||
"integrity": "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sindresorhus/merge-streams": "^2.1.0",
|
||||
"@sindresorhus/merge-streams": "^4.0.0",
|
||||
"fast-glob": "^3.3.3",
|
||||
"ignore": "^7.0.3",
|
||||
"ignore": "^7.0.5",
|
||||
"path-type": "^6.0.0",
|
||||
"slash": "^5.1.0",
|
||||
"unicorn-magic": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@ -762,9 +788,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
|
||||
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -866,9 +892,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-relative-url": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-4.0.0.tgz",
|
||||
"integrity": "sha512-PkzoL1qKAYXNFct5IKdKRH/iBQou/oCC85QhXj6WKtUQBliZ4Yfd3Zk27RHu9KQG8r6zgvAA2AQKC9p+rqTszg==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-4.1.0.tgz",
|
||||
"integrity": "sha512-vhIXKasjAuxS7n+sdv7pJQykEAgS+YU8VBQOENXwo/VZpOHDgBBsIbHo7zFKaWBjYWF4qxERdhbPRRtFAeJKfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -882,9 +908,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -902,9 +928,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.22",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
|
||||
"integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==",
|
||||
"version": "0.16.27",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.27.tgz",
|
||||
"integrity": "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
@ -929,17 +955,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/link-check": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/link-check/-/link-check-5.4.0.tgz",
|
||||
"integrity": "sha512-0Pf4xBVUnwJdbDgpBlhHNmWDtbVjHTpIFs+JaBuIsC9PKRxjv4KMGCO2Gc8lkVnqMf9B/yaNY+9zmMlO5MyToQ==",
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmjs.org/link-check/-/link-check-5.5.1.tgz",
|
||||
"integrity": "sha512-GrtE4Zp/FBduvElmad375NrPeMYnKwNt9rH/TDG/rbQbHL0QVC4S/cEPVKZ0CkhXlVuiK+/5flGpRxQzoLbjEA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-relative-url": "^4.0.0",
|
||||
"is-relative-url": "^4.1.0",
|
||||
"ms": "^2.1.3",
|
||||
"needle": "^3.3.1",
|
||||
"node-email-verifier": "^2.0.0",
|
||||
"proxy-agent": "^6.4.0"
|
||||
"node-email-verifier": "^3.4.1",
|
||||
"proxy-agent": "^6.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
@ -981,41 +1007,41 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-link-check": {
|
||||
"version": "3.13.7",
|
||||
"resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.13.7.tgz",
|
||||
"integrity": "sha512-Btn3HU8s2Uyh1ZfzmyZEkp64zp2+RAjwfQt1u4swq2Xa6w37OW0T2inQZrkSNVxDSa2jSN2YYhw/JkAp5jF1PQ==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.14.2.tgz",
|
||||
"integrity": "sha512-DPJ+itd3D5fcfXD5s1i53lugH0Z/h80kkQxlYCBh8tFwEZGhyVgDcLl0rnKlWssAVDAmSmcbePpHpMEY+JcMMQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"async": "^3.2.6",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^13.1.0",
|
||||
"link-check": "^5.4.0",
|
||||
"markdown-link-extractor": "^4.0.2",
|
||||
"chalk": "^5.6.2",
|
||||
"commander": "^14.0.2",
|
||||
"link-check": "^5.5.1",
|
||||
"markdown-link-extractor": "^4.0.3",
|
||||
"needle": "^3.3.1",
|
||||
"progress": "^2.0.3",
|
||||
"proxy-agent": "^6.4.0",
|
||||
"xmlbuilder2": "^3.1.1"
|
||||
"proxy-agent": "^6.5.0",
|
||||
"xmlbuilder2": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-link-check": "markdown-link-check"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-link-extractor": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-4.0.2.tgz",
|
||||
"integrity": "sha512-5cUOu4Vwx1wenJgxaudsJ8xwLUMN7747yDJX3V/L7+gi3e4MsCm7w5nbrDQQy8nEfnl4r5NV3pDXMAjhGXYXAw==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-4.0.3.tgz",
|
||||
"integrity": "sha512-aEltJiQ4/oC0h6Jbw/uuATGSHZPkcH8DIunNH1A0e+GSFkvZ6BbBkdvBTVfIV8r6HapCU3yTd0eFdi3ZeM1eAQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"html-link-extractor": "^1.0.5",
|
||||
"marked": "^12.0.1"
|
||||
"marked": "^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint": {
|
||||
"version": "0.38.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz",
|
||||
"integrity": "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==",
|
||||
"version": "0.40.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz",
|
||||
"integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1026,7 +1052,8 @@
|
||||
"micromark-extension-gfm-footnote": "2.1.0",
|
||||
"micromark-extension-gfm-table": "2.1.1",
|
||||
"micromark-extension-math": "3.1.0",
|
||||
"micromark-util-types": "2.0.2"
|
||||
"micromark-util-types": "2.0.2",
|
||||
"string-width": "8.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@ -1036,18 +1063,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint-cli2": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.18.1.tgz",
|
||||
"integrity": "sha512-/4Osri9QFGCZOCTkfA8qJF+XGjKYERSHkXzxSyS1hd3ZERJGjvsUao2h4wdnvpHp6Tu2Jh/bPHM0FE9JJza6ng==",
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.20.0.tgz",
|
||||
"integrity": "sha512-esPk+8Qvx/f0bzI7YelUeZp+jCtFOk3KjZ7s9iBQZ6HlymSXoTtWGiIRZP05/9Oy2ehIoIjenVwndxGtxOIJYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"globby": "14.1.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"globby": "15.0.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"markdown-it": "14.1.0",
|
||||
"markdownlint": "0.38.0",
|
||||
"markdownlint-cli2-formatter-default": "0.0.5",
|
||||
"markdownlint": "0.40.0",
|
||||
"markdownlint-cli2-formatter-default": "0.0.6",
|
||||
"micromatch": "4.0.8"
|
||||
},
|
||||
"bin": {
|
||||
@ -1061,9 +1088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/markdownlint-cli2-formatter-default": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.5.tgz",
|
||||
"integrity": "sha512-4XKTwQ5m1+Txo2kuQ3Jgpo/KmnG+X90dWt4acufg6HVGadTUG5hzHF/wssp9b5MBYOMCnZ9RMPaU//uHsszF8Q==",
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz",
|
||||
"integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@ -1074,16 +1101,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "12.0.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz",
|
||||
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==",
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz",
|
||||
"integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
@ -1688,17 +1715,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-email-verifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-email-verifier/-/node-email-verifier-2.0.0.tgz",
|
||||
"integrity": "sha512-AHcppjOH2KT0mxakrxFMOMjV/gOVMRpYvnJUkNfgF9oJ3INdVmqcMFJ5TlM8elpTPwt6A7bSp1IMnnWcxGom/Q==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/node-email-verifier/-/node-email-verifier-3.4.1.tgz",
|
||||
"integrity": "sha512-69JMeWgEUrCji+dOLULirdSoosRxgAq2y+imfmHHBGvgTwyTKqvm65Ls3+W30DCIWMrYj5kKVb/DHTQDK7OVwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3",
|
||||
"validator": "^13.11.0"
|
||||
"validator": "^13.15.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
@ -1958,11 +1985,11 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
|
||||
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "5.1.0",
|
||||
@ -2029,12 +2056,38 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"node_modules/string-width": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
|
||||
"integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
@ -2064,9 +2117,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.15.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz",
|
||||
"integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz",
|
||||
"integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -2087,9 +2140,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.15",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
|
||||
"version": "13.15.26",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz",
|
||||
"integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -2100,6 +2153,7 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2120,43 +2174,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
||||
"integrity": "sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz",
|
||||
"integrity": "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/dom": "1.15.10",
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8",
|
||||
"js-yaml": "3.14.1"
|
||||
"@oozcitak/dom": "^2.0.2",
|
||||
"@oozcitak/infra": "^2.0.2",
|
||||
"@oozcitak/util": "^10.0.0",
|
||||
"js-yaml": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
"node": ">=20.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"markdown-link-check": "^3.13.7",
|
||||
"markdownlint-cli2": "^0.18.1"
|
||||
"markdownlint-cli2": "^0.20.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tags: ['app', 'configure']
|
||||
vars:
|
||||
selected_projects: >-
|
||||
{{
|
||||
@ -61,6 +62,7 @@
|
||||
}}
|
||||
become: true
|
||||
gather_facts: true
|
||||
tags: ['app', 'configure']
|
||||
|
||||
tasks:
|
||||
- name: Build project/env effective variables
|
||||
@ -115,20 +117,19 @@
|
||||
app_owner: "{{ project_def.os_user | default(appuser_name) }}"
|
||||
app_group: "{{ project_def.os_user | default(appuser_name) }}"
|
||||
|
||||
app_backend_port: "{{ project_def.backend_port | default(app_backend_port) }}"
|
||||
app_frontend_port: "{{ project_def.frontend_port | default(app_frontend_port) }}"
|
||||
# Use different variable names to avoid self-referential recursion
|
||||
app_backend_port: "{{ project_def.backend_port | default(3001) }}"
|
||||
app_frontend_port: "{{ project_def.frontend_port | default(3000) }}"
|
||||
app_enable_backend: "{{ project_def.components.backend | default(true) }}"
|
||||
app_enable_frontend: "{{ project_def.components.frontend | default(true) }}"
|
||||
|
||||
app_backend_install_cmd: "{{ project_def.deploy.backend_install_cmd | default(app_backend_install_cmd) }}"
|
||||
app_backend_migrate_cmd: "{{ project_def.deploy.backend_migrate_cmd | default(app_backend_migrate_cmd) }}"
|
||||
app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default(app_backend_start_cmd) }}"
|
||||
app_backend_install_cmd: "{{ project_def.deploy.backend_install_cmd | default('npm ci') }}"
|
||||
app_backend_migrate_cmd: "{{ project_def.deploy.backend_migrate_cmd | default('npm run migrate') }}"
|
||||
app_backend_start_cmd: "{{ project_def.deploy.backend_start_cmd | default('npm start') }}"
|
||||
|
||||
app_frontend_install_cmd: "{{ project_def.deploy.frontend_install_cmd | default(app_frontend_install_cmd) }}"
|
||||
app_frontend_build_cmd: "{{ project_def.deploy.frontend_build_cmd | default(app_frontend_build_cmd) }}"
|
||||
app_frontend_start_cmd: "{{ project_def.deploy.frontend_start_cmd | default(app_frontend_start_cmd) }}"
|
||||
app_frontend_install_cmd: "{{ project_def.deploy.frontend_install_cmd | default('npm ci') }}"
|
||||
app_frontend_build_cmd: "{{ project_def.deploy.frontend_build_cmd | default('npm run build') }}"
|
||||
app_frontend_start_cmd: "{{ project_def.deploy.frontend_start_cmd | default('npm start') }}"
|
||||
|
||||
app_env_vars: "{{ env_def.env_vars | default({}) }}"
|
||||
when: app_project != 'pote'
|
||||
|
||||
|
||||
|
||||
@ -233,3 +233,5 @@
|
||||
ansible_user: root
|
||||
app_project: "{{ project_key }}"
|
||||
app_env: "{{ env_name }}"
|
||||
|
||||
# EOF
|
||||
|
||||
@ -19,3 +19,5 @@
|
||||
loop: "{{ project_def.envs | dict2items }}"
|
||||
loop_control:
|
||||
loop_var: env_item
|
||||
|
||||
# EOF
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tags: ['app', 'provision']
|
||||
vars:
|
||||
selected_projects: >-
|
||||
{{
|
||||
@ -32,5 +33,3 @@
|
||||
loop: "{{ selected_projects }}"
|
||||
loop_control:
|
||||
loop_var: project_key
|
||||
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tags: ['app', 'proxmox', 'info']
|
||||
vars:
|
||||
selected_projects: >-
|
||||
{{
|
||||
@ -95,5 +96,3 @@
|
||||
{% for g in (filtered_guests | sort(attribute='vmid')) %}
|
||||
- vmid={{ g.vmid }} type={{ g.id.split('/')[0] if g.id is defined else 'unknown' }} name={{ g.name | default('') }} node={{ g.node | default('') }} status={{ g.status | default('') }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
@ -11,5 +11,3 @@
|
||||
- name: Configure guests
|
||||
import_playbook: configure_app.yml
|
||||
tags: ['app', 'configure']
|
||||
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
tags: ['app', 'ssh-config']
|
||||
vars:
|
||||
manage_ssh_config: "{{ manage_ssh_config | default(false) }}"
|
||||
ssh_config_path: "{{ lookup('ansible.builtin.env', 'HOME') + '/.ssh/config' }}"
|
||||
@ -47,5 +48,3 @@
|
||||
- app_projects[item.0] is defined
|
||||
- app_projects[item.0].envs[item.1] is defined
|
||||
- (app_projects[item.0].envs[item.1].ip | default('')) | length > 0
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
- name: Configure development environment
|
||||
hosts: dev
|
||||
become: true
|
||||
strategy: free
|
||||
|
||||
roles:
|
||||
- {role: timeshift, tags: ['timeshift', 'snapshot']} # Create snapshot before changes
|
||||
@ -10,44 +9,12 @@
|
||||
- {role: base, tags: ['base', 'security']}
|
||||
- {role: user, tags: ['user']}
|
||||
- {role: ssh, tags: ['ssh', 'security']}
|
||||
- {role: shell, tags: ['shell']}
|
||||
- {role: shell, tags: ['shell'], shell_mode: full, shell_set_default_shell: true}
|
||||
- {role: development, tags: ['development', 'dev']}
|
||||
- {role: datascience, tags: ['datascience', 'conda', 'jupyter', 'r']}
|
||||
- {role: docker, tags: ['docker']}
|
||||
- {role: applications, tags: ['applications', 'apps']}
|
||||
# - {role: tailscale, tags: ['tailscale', 'vpn']}
|
||||
- {role: monitoring, tags: ['monitoring']}
|
||||
|
||||
pre_tasks:
|
||||
- name: Remove NodeSource repository completely (fix GPG errors)
|
||||
ansible.builtin.shell: |
|
||||
# Remove NodeSource repository file
|
||||
rm -f /etc/apt/sources.list.d/nodesource.list
|
||||
# Remove NodeSource key file
|
||||
rm -f /etc/apt/keyrings/nodesource.gpg
|
||||
# Remove from sources.list if present
|
||||
sed -i '/nodesource/d' /etc/apt/sources.list 2>/dev/null || true
|
||||
# Remove any cached InRelease files
|
||||
rm -f /var/lib/apt/lists/*nodesource* 2>/dev/null || true
|
||||
rm -f /var/lib/apt/lists/partial/*nodesource* 2>/dev/null || true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Update apt cache (ignore NodeSource errors)
|
||||
ansible.builtin.shell: |
|
||||
apt-get update 2>&1 | grep -v "nodesource\|NO_PUBKEY.*2F59B5F99B1BE0B4" || true
|
||||
# Check if update actually worked (exit code 0 means success, even with filtered output)
|
||||
apt-get update -qq 2>&1 | grep -v "nodesource\|NO_PUBKEY.*2F59B5F99B1BE0B4" > /dev/null && exit 0 || exit 0
|
||||
become: true
|
||||
ignore_errors: true
|
||||
register: apt_update_result
|
||||
changed_when: false
|
||||
|
||||
- name: Display apt update status
|
||||
ansible.builtin.debug:
|
||||
msg: "Apt cache update: {{ 'Success' if apt_update_result is succeeded else 'Failed - continuing anyway' }}"
|
||||
when: ansible_debug_output | default(false) | bool
|
||||
- {role: monitoring_desktop, tags: ['monitoring']}
|
||||
|
||||
tasks:
|
||||
# Additional tasks can be added here if needed
|
||||
|
||||
@ -12,14 +12,8 @@
|
||||
- {role: shell, tags: ['shell']}
|
||||
- {role: development, tags: ['development', 'dev']}
|
||||
- {role: docker, tags: ['docker']}
|
||||
- {role: applications, tags: ['applications', 'apps']}
|
||||
# - {role: tailscale, tags: ['tailscale', 'vpn']}
|
||||
- {role: monitoring, tags: ['monitoring']}
|
||||
|
||||
pre_tasks:
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
- {role: monitoring_desktop, tags: ['monitoring']}
|
||||
|
||||
tasks:
|
||||
- name: Display completion message
|
||||
|
||||
@ -22,12 +22,6 @@
|
||||
Group: {{ group_names | join(', ') }}
|
||||
Skip reboot: {{ skip_reboot | default(false) | bool }}
|
||||
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
when: maintenance_update_cache | bool
|
||||
|
||||
roles:
|
||||
- {role: maintenance, tags: ['maintenance']}
|
||||
|
||||
|
||||
27
playbooks/servers.yml
Normal file
27
playbooks/servers.yml
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
# Playbook: servers.yml
|
||||
# Purpose: Baseline configuration for servers (no desktop apps, no IDE install)
|
||||
# Targets: services + qa + ansible + tailscale (override with -e target_group=...)
|
||||
# Tags: maintenance, base, security, user, ssh, shell, docker, monitoring
|
||||
# Usage:
|
||||
# ansible-playbook -i inventories/production playbooks/servers.yml
|
||||
# ansible-playbook -i inventories/production playbooks/servers.yml -e target_group=services
|
||||
# ansible-playbook -i inventories/production playbooks/servers.yml --limit jellyfin
|
||||
|
||||
- name: Configure servers baseline
|
||||
hosts: "{{ target_group | default('services:qa:ansible:tailscale') }}"
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- {role: maintenance, tags: ['maintenance']}
|
||||
- {role: base, tags: ['base', 'security']}
|
||||
- {role: user, tags: ['user']}
|
||||
- {role: ssh, tags: ['ssh', 'security']}
|
||||
- {role: shell, tags: ['shell']}
|
||||
- {role: docker, tags: ['docker']}
|
||||
- {role: monitoring_server, tags: ['monitoring']}
|
||||
|
||||
tasks:
|
||||
- name: Display completion message
|
||||
ansible.builtin.debug:
|
||||
msg: "Server baseline configuration completed successfully!"
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
# Playbook: shell.yml
|
||||
# Purpose: Configure shell environment (zsh, oh-my-zsh, plugins)
|
||||
# Purpose: Configure shell environment (minimal zsh + managed aliases)
|
||||
# Targets: all hosts
|
||||
# Tags: shell
|
||||
# Usage: make shell-all
|
||||
@ -8,46 +8,12 @@
|
||||
- name: Configure shell environment
|
||||
hosts: all
|
||||
become: true
|
||||
strategy: free
|
||||
ignore_errors: true
|
||||
ignore_unreachable: true
|
||||
|
||||
roles:
|
||||
- {role: shell, tags: ['shell']}
|
||||
|
||||
pre_tasks:
|
||||
- name: Check if NodeSource repository exists
|
||||
ansible.builtin.stat:
|
||||
path: /etc/apt/sources.list.d/nodesource.list
|
||||
register: nodesource_repo_file
|
||||
failed_when: false
|
||||
|
||||
- name: Check if NodeSource GPG key exists
|
||||
ansible.builtin.stat:
|
||||
path: /etc/apt/keyrings/nodesource.gpg
|
||||
register: nodesource_key_file
|
||||
failed_when: false
|
||||
|
||||
- name: Remove incorrectly configured NodeSource repository
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/sources.list.d/nodesource.list
|
||||
state: absent
|
||||
become: true
|
||||
when:
|
||||
- nodesource_repo_file.stat.exists
|
||||
- not (nodesource_key_file.stat.exists and nodesource_key_file.stat.size > 0)
|
||||
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
ignore_errors: true
|
||||
register: apt_update_result
|
||||
|
||||
- name: Display apt update status
|
||||
ansible.builtin.debug:
|
||||
msg: "Apt cache update: {{ 'Success' if apt_update_result is succeeded else 'Failed - continuing anyway' }}"
|
||||
when: ansible_debug_output | default(false) | bool
|
||||
|
||||
tasks:
|
||||
- name: Display completion message
|
||||
ansible.builtin.debug:
|
||||
|
||||
@ -9,12 +9,6 @@
|
||||
# Override here if needed or pass via: --extra-vars "tailscale_auth_key=your_key"
|
||||
tailscale_auth_key: "{{ vault_tailscale_auth_key | default('') }}"
|
||||
|
||||
pre_tasks:
|
||||
- name: Update package cache (Debian/Ubuntu)
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
roles:
|
||||
- {role: tailscale, tags: ['tailscale', 'vpn']}
|
||||
|
||||
|
||||
42
playbooks/workstations.yml
Normal file
42
playbooks/workstations.yml
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
# Playbook: workstations.yml
|
||||
# Purpose: Workstation baseline (dev boxes + desktops). Desktop apps are applied only to the `desktop` group.
|
||||
# Targets: dev + desktop + local (override with -e target_group=...)
|
||||
# Tags: maintenance, base, security, user, ssh, shell, development, dev, datascience, docker, monitoring, apps
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventories/production playbooks/workstations.yml
|
||||
# ansible-playbook -i inventories/production playbooks/workstations.yml -e target_group=dev
|
||||
# ansible-playbook -i inventories/production playbooks/workstations.yml --tags apps
|
||||
|
||||
- name: Configure workstation baseline
|
||||
hosts: "{{ target_group | default('dev:desktop:local') }}"
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- {role: maintenance, tags: ['maintenance']}
|
||||
- {role: base, tags: ['base', 'security']}
|
||||
- {role: user, tags: ['user']}
|
||||
- {role: ssh, tags: ['ssh', 'security']}
|
||||
- {role: shell, tags: ['shell'], shell_mode: full, shell_set_default_shell: true}
|
||||
- {role: development, tags: ['development', 'dev']}
|
||||
- {role: datascience, tags: ['datascience', 'conda', 'jupyter', 'r']}
|
||||
- {role: docker, tags: ['docker']}
|
||||
- {role: monitoring_desktop, tags: ['monitoring']}
|
||||
|
||||
tasks:
|
||||
- name: Display completion message
|
||||
ansible.builtin.debug:
|
||||
msg: "Workstation baseline configuration completed successfully!"
|
||||
|
||||
- name: Install desktop applications (desktop group only)
|
||||
hosts: desktop
|
||||
become: true
|
||||
|
||||
roles:
|
||||
- {role: applications, tags: ['applications', 'apps']}
|
||||
|
||||
tasks:
|
||||
- name: Display completion message
|
||||
ansible.builtin.debug:
|
||||
msg: "Desktop applications installed successfully!"
|
||||
63
project-docs/architecture.md
Normal file
63
project-docs/architecture.md
Normal file
@ -0,0 +1,63 @@
|
||||
## Architecture
|
||||
|
||||
### High-level map (modules and relationships)
|
||||
|
||||
- **Inventory**: `inventories/production/`
|
||||
- `hosts`: groups like `dev`, `desktop`, `services`, `qa`, `ansible`, `tailscale`, `local`
|
||||
- `group_vars/all/main.yml`: shared configuration (including `app_projects`)
|
||||
- `group_vars/all/vault.yml`: encrypted secrets (Ansible Vault)
|
||||
- `host_vars/*`: per-host overrides (some encrypted)
|
||||
|
||||
- **Playbooks**: `playbooks/`
|
||||
- `playbooks/site.yml`: dispatcher (imports other playbooks)
|
||||
- `playbooks/servers.yml`: baseline for servers (`services:qa:ansible:tailscale`)
|
||||
- `playbooks/workstations.yml`: baseline for `dev:desktop:local` + desktop apps for `desktop` group only
|
||||
- `playbooks/development.yml`: dev machines baseline (no desktop apps)
|
||||
- `playbooks/local.yml`: localhost baseline (no desktop apps)
|
||||
- `playbooks/app/*`: Proxmox app-project provisioning/configuration suite
|
||||
|
||||
- **Roles**: `roles/*`
|
||||
- Baseline/security: `base`, `user`, `ssh`
|
||||
- Dev tooling: `development`, `datascience`, `docker`
|
||||
- Shell: `shell` (minimal aliases-only)
|
||||
- Monitoring split:
|
||||
- `monitoring_server` (fail2ban + sysstat)
|
||||
- `monitoring_desktop` (desktop-oriented monitoring tooling)
|
||||
- Proxmox guests: `proxmox_vm`
|
||||
- App guest configuration: `base_os`, `app_setup`, `pote`
|
||||
|
||||
### Proxmox “app projects” flow (data model + execution)
|
||||
|
||||
- **Data model**: `app_projects` in `inventories/production/group_vars/all/main.yml`
|
||||
- Defines projects and per-env (`dev/qa/prod`) guest parameters (ip, branch, vmid, etc.)
|
||||
|
||||
- **Provision**: `playbooks/app/provision_vms.yml`
|
||||
- Loops `app_projects` → envs → calls `role: proxmox_vm` to create LXC guests
|
||||
- Adds dynamic inventory groups:
|
||||
- `app_all`
|
||||
- `app_<project>_all`
|
||||
- `app_<project>_<env>`
|
||||
|
||||
- **Configure**: `playbooks/app/configure_app.yml`
|
||||
- Builds a dynamic inventory from `app_projects` (so it can run standalone)
|
||||
- Applies:
|
||||
- `role: base_os` (baseline OS for app guests)
|
||||
- `role: app_setup` (deploy + systemd) or `role: pote` for the POTE project
|
||||
|
||||
### Boundaries
|
||||
|
||||
- **Inventory/vars** define desired state and credentials.
|
||||
- **Playbooks** define “what path to run” (role ordering, target groups, tags).
|
||||
- **Roles** implement actual host configuration (idempotent tasks, handlers).
|
||||
|
||||
### External dependencies
|
||||
|
||||
- **Ansible collections**: `collections/requirements.yml`
|
||||
- **Ansible Vault**: `inventories/production/group_vars/all/vault.yml`
|
||||
- **Proxmox API**: used by `community.proxmox.*` modules in provisioning
|
||||
|
||||
### References
|
||||
|
||||
- Playbook execution graphs and tags: `docs/reference/playbooks-and-tags.md`
|
||||
- Legacy pointer (do not update): `docs/reference/architecture.md` → `project-docs/architecture.md`
|
||||
|
||||
35
project-docs/decisions.md
Normal file
35
project-docs/decisions.md
Normal file
@ -0,0 +1,35 @@
|
||||
## Decisions (ADR-style)
|
||||
|
||||
### 2025-12-31 — Do not manage IDE/editor installs in Ansible
|
||||
|
||||
- **Context**: IDEs/editors are interactive, fast-moving, and often user-preference-driven.
|
||||
- **Decision**: Keep editor installation (Cursor, VS Code, etc.) out of Ansible roles/playbooks.
|
||||
- **Consequences**:
|
||||
- Faster, more stable provisioning runs
|
||||
- Less drift caused by UI tooling changes
|
||||
- Editor setup is handled separately (manual or via dedicated tooling)
|
||||
|
||||
### 2025-12-31 — Split monitoring into server vs workstation roles
|
||||
|
||||
- **Context**: Servers and workstations have different needs (e.g., fail2ban/sysstat are server-centric; wireshark-common is workstation-centric).
|
||||
- **Decision**: Create `monitoring_server` and `monitoring_desktop` roles and wire them into `servers.yml` / workstation playbooks.
|
||||
- **Consequences**:
|
||||
- Smaller install footprint on servers
|
||||
- Clearer intent and faster runs
|
||||
|
||||
### 2025-12-31 — Desktop applications are installed only on the `desktop` group
|
||||
|
||||
- **Context**: Desktop apps should not be installed on headless servers or dev VMs by default.
|
||||
- **Decision**: Run `role: applications` only in a `desktop`-scoped play (workstations playbook).
|
||||
- **Consequences**:
|
||||
- Reduced unnecessary package installs
|
||||
- Less attack surface and fewer updates on non-desktop hosts
|
||||
|
||||
### 2025-12-31 — Minimal shell role (aliases-only)
|
||||
|
||||
- **Context**: Oh-my-zsh/theme/plugin cloning is slow and overwriting `.zshrc` is risky.
|
||||
- **Decision**: `role: shell` now manages a small alias file and ensures it’s sourced; it does not overwrite `.zshrc`.
|
||||
- **Consequences**:
|
||||
- Much faster shell configuration
|
||||
- Safer for servers and multi-user systems
|
||||
|
||||
33
project-docs/index.md
Normal file
33
project-docs/index.md
Normal file
@ -0,0 +1,33 @@
|
||||
## Project docs index
|
||||
|
||||
Last updated: **2025-12-31**
|
||||
|
||||
### Documents
|
||||
|
||||
- **`project-docs/overview.md`** (updated 2025-12-31)
|
||||
High-level goals, scope, and primary users for this Ansible infrastructure repo.
|
||||
|
||||
- **`project-docs/architecture.md`** (updated 2025-12-31)
|
||||
Architecture map: inventories, playbooks, roles, and the Proxmox app-project flow.
|
||||
|
||||
- **`project-docs/standards.md`** (updated 2025-12-31)
|
||||
Conventions for Ansible YAML, role structure, naming, vault usage, and linting.
|
||||
|
||||
- **`project-docs/workflow.md`** (updated 2025-12-31)
|
||||
How to run common tasks via `Makefile`, how to lint/test, and how to apply safely.
|
||||
|
||||
- **`project-docs/decisions.md`** (updated 2025-12-31)
|
||||
Short ADR-style notes for important architectural decisions.
|
||||
|
||||
### Related docs (existing)
|
||||
|
||||
- **Playbooks/tags map**: `docs/reference/playbooks-and-tags.md`
|
||||
- **Applications inventory**: `docs/reference/applications.md`
|
||||
- **Makefile reference**: `docs/reference/makefile.md`
|
||||
- **Proxmox app project guides**:
|
||||
- `docs/guides/app_stack_proxmox.md`
|
||||
- `docs/guides/app_stack_execution_flow.md`
|
||||
|
||||
Legacy pointers:
|
||||
- `docs/reference/architecture.md` → `project-docs/architecture.md`
|
||||
|
||||
27
project-docs/overview.md
Normal file
27
project-docs/overview.md
Normal file
@ -0,0 +1,27 @@
|
||||
## Overview
|
||||
|
||||
This repository manages infrastructure automation using **Ansible** for:
|
||||
- Development machines (`dev`)
|
||||
- Desktop machines (`desktop`)
|
||||
- Service hosts (`services`, `qa`, `ansible`, `tailscale`)
|
||||
- Proxmox-managed guests for “app projects” (LXC-first, with a KVM path)
|
||||
|
||||
Primary entrypoint is the **Makefile** (`Makefile`) and playbooks under `playbooks/`.
|
||||
|
||||
### Goals
|
||||
|
||||
- **Predictable, repeatable provisioning** of hosts and Proxmox guests
|
||||
- **Safe defaults**: avoid destructive automation; prefer guardrails and idempotency
|
||||
- **Clear separation** between server vs workstation responsibilities
|
||||
- **Secrets handled via Ansible Vault** (never commit plaintext credentials)
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Automated decommission/destroy playbooks for infrastructure or guests
|
||||
- Managing interactive IDE/editor installs (kept out of Ansible by design)
|
||||
|
||||
### Target users
|
||||
|
||||
- You (and collaborators) operating a small homelab / Proxmox environment
|
||||
- Contributors extending roles/playbooks in a consistent style
|
||||
|
||||
49
project-docs/standards.md
Normal file
49
project-docs/standards.md
Normal file
@ -0,0 +1,49 @@
|
||||
## Standards
|
||||
|
||||
### Ansible + YAML conventions
|
||||
|
||||
- **Indentation**: 2 spaces (no tabs)
|
||||
- **Task naming**: every task should include a clear `name:`
|
||||
- **Play-level privilege**: prefer `become: true` at play level when most tasks need sudo
|
||||
- **Modules**:
|
||||
- Prefer native modules over `shell`/`command`
|
||||
- Use **fully qualified collection names** (FQCN), e.g. `ansible.builtin.apt`, `community.general.ufw`
|
||||
- **Handlers**: use handlers for restarts/reloads
|
||||
- **Idempotency**:
|
||||
- If `shell`/`command` is unavoidable, set `changed_when:` / `creates:` / `removes:` appropriately
|
||||
|
||||
### Role structure
|
||||
|
||||
Roles should follow:
|
||||
|
||||
```
|
||||
roles/<role_name>/
|
||||
├── defaults/main.yml
|
||||
├── handlers/main.yml
|
||||
├── tasks/main.yml
|
||||
├── templates/
|
||||
├── files/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Variable naming
|
||||
|
||||
- **snake_case** everywhere
|
||||
- Vault-backed variables are prefixed with **`vault_`**
|
||||
|
||||
### Secrets / Vault
|
||||
|
||||
- Never commit plaintext secrets.
|
||||
- Use Ansible Vault for credentials:
|
||||
- `inventories/production/group_vars/all/vault.yml` (encrypted)
|
||||
- Local vault password file is expected at `~/.ansible-vault-pass`.
|
||||
|
||||
### Makefile-first workflow
|
||||
|
||||
- Prefer `make ...` targets over direct `ansible-playbook` commands for consistency.
|
||||
|
||||
### Linting
|
||||
|
||||
- `ansible-lint` is the primary linter.
|
||||
- `.ansible-lint` excludes vault-containing inventory paths to keep linting deterministic without vault secrets.
|
||||
|
||||
86
project-docs/workflow.md
Normal file
86
project-docs/workflow.md
Normal file
@ -0,0 +1,86 @@
|
||||
## Workflow
|
||||
|
||||
### Setup
|
||||
|
||||
- Install dependencies (Python requirements, Node deps for docs, Ansible collections):
|
||||
|
||||
```bash
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
- Edit vault secrets:
|
||||
|
||||
```bash
|
||||
make edit-group-vault
|
||||
```
|
||||
|
||||
### Validate (safe, local)
|
||||
|
||||
- Syntax checks:
|
||||
|
||||
```bash
|
||||
make test-syntax
|
||||
```
|
||||
|
||||
- Lint:
|
||||
|
||||
```bash
|
||||
make lint
|
||||
```
|
||||
|
||||
### Common apply flows
|
||||
|
||||
- **Servers baseline** (services + qa + ansible + tailscale):
|
||||
|
||||
```bash
|
||||
make servers
|
||||
make servers GROUP=services
|
||||
make servers HOST=jellyfin
|
||||
```
|
||||
|
||||
- **Workstations baseline** (dev + desktop + local; desktop apps only on `desktop` group):
|
||||
|
||||
```bash
|
||||
make workstations
|
||||
make workstations GROUP=dev
|
||||
make apps
|
||||
```
|
||||
|
||||
### Proxmox app projects
|
||||
|
||||
End-to-end:
|
||||
|
||||
```bash
|
||||
make app PROJECT=projectA
|
||||
```
|
||||
|
||||
Provision only / configure only:
|
||||
|
||||
```bash
|
||||
make app-provision PROJECT=projectA
|
||||
make app-configure PROJECT=projectA
|
||||
```
|
||||
|
||||
Inspect Proxmox guests:
|
||||
|
||||
```bash
|
||||
make proxmox-info PROJECT=projectA
|
||||
make proxmox-info ALL=true
|
||||
make proxmox-info TYPE=lxc
|
||||
```
|
||||
|
||||
### Safety checks
|
||||
|
||||
- Prefer `--check --diff` first:
|
||||
|
||||
```bash
|
||||
make check
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
make debug
|
||||
make verbose
|
||||
```
|
||||
|
||||
@ -5,5 +5,3 @@
|
||||
|
||||
- name: Provision app project guests
|
||||
import_playbook: playbooks/app/provision_vms.yml
|
||||
|
||||
|
||||
|
||||
@ -37,4 +37,3 @@ app_frontend_start_cmd: "npm start"
|
||||
|
||||
# Arbitrary environment variables for the env file
|
||||
app_env_vars: {}
|
||||
|
||||
|
||||
@ -4,5 +4,3 @@
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
|
||||
|
||||
@ -80,5 +80,3 @@
|
||||
enabled: true
|
||||
state: started
|
||||
when: app_enable_frontend | bool
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Role: applications
|
||||
|
||||
## Description
|
||||
Installs desktop applications for development and productivity including browsers, office suites, and utilities.
|
||||
Installs a small set of desktop GUI applications (desktop group only via `playbooks/workstations.yml`).
|
||||
|
||||
## Requirements
|
||||
- Ansible 2.9+
|
||||
@ -9,8 +9,7 @@ Installs desktop applications for development and productivity including browser
|
||||
- Internet access for package downloads
|
||||
|
||||
## Installed Applications
|
||||
- **Brave Browser**: Privacy-focused web browser
|
||||
- **LibreOffice**: Complete office suite
|
||||
- **CopyQ**: Clipboard manager (history, search, scripting)
|
||||
- **Evince**: PDF document viewer
|
||||
- **Redshift**: Blue light filter for eye comfort
|
||||
|
||||
@ -18,10 +17,7 @@ Installs desktop applications for development and productivity including browser
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `applications_install_brave` | `true` | Install Brave browser |
|
||||
| `applications_install_libreoffice` | `true` | Install LibreOffice suite |
|
||||
| `applications_install_evince` | `true` | Install PDF viewer |
|
||||
| `applications_install_redshift` | `true` | Install blue light filter |
|
||||
| `applications_desktop_packages` | `['copyq','evince','redshift']` | Desktop packages to install |
|
||||
|
||||
## Dependencies
|
||||
- `base` role (for package management)
|
||||
@ -31,16 +27,13 @@ Installs desktop applications for development and productivity including browser
|
||||
```yaml
|
||||
- hosts: desktop
|
||||
roles:
|
||||
- { role: applications, applications_install_brave: false }
|
||||
- role: applications
|
||||
```
|
||||
|
||||
## Tags
|
||||
- `applications`: All application installations
|
||||
- `apps`: Alias for applications
|
||||
- `browser`: Browser installation only
|
||||
- `office`: Office suite installation only
|
||||
|
||||
## Notes
|
||||
- Adds external repositories for Brave browser
|
||||
- Requires desktop environment for GUI applications
|
||||
- Applications are installed system-wide
|
||||
@ -1 +1,7 @@
|
||||
---
|
||||
|
||||
# Desktop GUI applications to install (desktop group only via playbooks/workstations.yml)
|
||||
applications_desktop_packages:
|
||||
- copyq
|
||||
- evince
|
||||
- redshift
|
||||
|
||||
@ -1,136 +1,27 @@
|
||||
---
|
||||
- name: Remove NodeSource repository to prevent GPG errors
|
||||
ansible.builtin.shell: |
|
||||
# Remove NodeSource repository file
|
||||
rm -f /etc/apt/sources.list.d/nodesource.list
|
||||
# Remove NodeSource key file
|
||||
rm -f /etc/apt/keyrings/nodesource.gpg
|
||||
# Remove from sources.list if present
|
||||
sed -i '/nodesource/d' /etc/apt/sources.list 2>/dev/null || true
|
||||
# Remove any cached InRelease files
|
||||
rm -f /var/lib/apt/lists/*nodesource* 2>/dev/null || true
|
||||
rm -f /var/lib/apt/lists/partial/*nodesource* 2>/dev/null || true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Check if applications are already installed
|
||||
ansible.builtin.package_facts:
|
||||
manager: apt
|
||||
|
||||
- name: Check if Brave browser is installed
|
||||
ansible.builtin.command: brave-browser --version
|
||||
register: applications_brave_check
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Set installation conditions
|
||||
ansible.builtin.set_fact:
|
||||
applications_desktop_apps_needed: "{{ ['redshift', 'libreoffice', 'evince'] | difference(ansible_facts.packages.keys()) | length > 0 }}"
|
||||
applications_brave_needs_install: "{{ applications_brave_check.rc != 0 or 'brave-browser' not in ansible_facts.packages }}"
|
||||
|
||||
- name: Check if Brave GPG key exists and is correct
|
||||
ansible.builtin.shell: |
|
||||
if [ -f /usr/share/keyrings/brave-browser-archive-keyring.gpg ]; then
|
||||
if file /usr/share/keyrings/brave-browser-archive-keyring.gpg | grep -q "PGP"; then
|
||||
echo "correct_key"
|
||||
else
|
||||
echo "wrong_key"
|
||||
fi
|
||||
else
|
||||
echo "not_exists"
|
||||
fi
|
||||
register: brave_key_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
when: applications_brave_needs_install
|
||||
|
||||
- name: Check if Brave repository exists and is correct
|
||||
ansible.builtin.shell: |
|
||||
if [ -f /etc/apt/sources.list.d/brave-browser.list ]; then
|
||||
if grep -q "deb \[signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg\]" /etc/apt/sources.list.d/brave-browser.list; then
|
||||
echo "correct_config"
|
||||
else
|
||||
echo "wrong_config"
|
||||
fi
|
||||
else
|
||||
echo "not_exists"
|
||||
fi
|
||||
register: brave_repo_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
when: applications_brave_needs_install
|
||||
|
||||
- name: Clean up duplicate Brave repository files
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /etc/apt/sources.list.d/brave-browser.list
|
||||
- /etc/apt/sources.list.d/brave-browser-release.sources
|
||||
become: true
|
||||
failed_when: false
|
||||
when:
|
||||
- applications_brave_needs_install
|
||||
- brave_repo_check.stdout == "wrong_config"
|
||||
|
||||
- name: Remove incorrect Brave GPG key
|
||||
ansible.builtin.file:
|
||||
path: /usr/share/keyrings/brave-browser-archive-keyring.gpg
|
||||
state: absent
|
||||
become: true
|
||||
when:
|
||||
- applications_brave_needs_install
|
||||
- brave_key_check.stdout == "wrong_key"
|
||||
applications_desktop_apps_needed: >-
|
||||
{{
|
||||
(applications_desktop_packages | default([]))
|
||||
| difference(ansible_facts.packages.keys())
|
||||
| length > 0
|
||||
}}
|
||||
|
||||
- name: Install desktop applications
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- redshift
|
||||
- libreoffice
|
||||
- evince
|
||||
name: "{{ applications_desktop_packages }}"
|
||||
state: present
|
||||
when: applications_desktop_apps_needed
|
||||
|
||||
- name: Brave browser installation
|
||||
when: applications_brave_needs_install
|
||||
block:
|
||||
- name: Download Brave APT key only if needed
|
||||
ansible.builtin.get_url:
|
||||
url: https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
|
||||
dest: /usr/share/keyrings/brave-browser-archive-keyring.gpg
|
||||
mode: '0644'
|
||||
when: brave_key_check.stdout in ["not_exists", "wrong_key"]
|
||||
|
||||
- name: Add Brave repository only if needed
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main"
|
||||
filename: brave-browser
|
||||
state: present
|
||||
update_cache: false
|
||||
when: brave_repo_check.stdout in ["not_exists", "wrong_config"]
|
||||
|
||||
- name: Update apt cache after adding Brave repository (ignore NodeSource errors)
|
||||
ansible.builtin.shell: |
|
||||
apt-get update 2>&1 | grep -v "nodesource\|NO_PUBKEY.*2F59B5F99B1BE0B4" || true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
when: brave_repo_check.stdout in ["not_exists", "wrong_config"]
|
||||
|
||||
- name: Install Brave browser
|
||||
ansible.builtin.apt:
|
||||
name: brave-browser
|
||||
state: present
|
||||
|
||||
- name: Display application status
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Desktop apps needed: {{ applications_desktop_apps_needed }}"
|
||||
- "Brave needed: {{ applications_brave_needs_install }}"
|
||||
- "Redshift: {{ 'Installed' if 'redshift' in ansible_facts.packages else 'Missing' }}"
|
||||
- "LibreOffice: {{ 'Installed' if 'libreoffice' in ansible_facts.packages else 'Missing' }}"
|
||||
- "Evince: {{ 'Installed' if 'evince' in ansible_facts.packages else 'Missing' }}"
|
||||
- "Brave: {{ applications_brave_check.stdout if applications_brave_check.rc == 0 else 'Not installed' }}"
|
||||
when: ansible_debug_output | default(false) | bool
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
---
|
||||
- name: Update apt cache (shared baseline)
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: "{{ apt_cache_valid_time | default(3600) }}"
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Ensure Ansible remote_tmp directory exists with correct permissions
|
||||
ansible.builtin.file:
|
||||
path: /root/.ansible/tmp
|
||||
|
||||
@ -29,4 +29,3 @@ base_os_user_ssh_public_key: "{{ appuser_ssh_public_key | default('') }}"
|
||||
|
||||
# If true, create passwordless sudo for base_os_user.
|
||||
base_os_passwordless_sudo: true
|
||||
|
||||
|
||||
@ -4,5 +4,3 @@
|
||||
- name: Reload ufw
|
||||
ansible.builtin.command: ufw reload
|
||||
changed_when: false
|
||||
|
||||
|
||||
|
||||
@ -38,28 +38,26 @@
|
||||
when: base_os_passwordless_sudo | bool
|
||||
|
||||
- name: Ensure UFW allows SSH
|
||||
ansible.builtin.ufw:
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ base_os_allow_ssh_port }}"
|
||||
proto: tcp
|
||||
|
||||
- name: Ensure UFW allows backend port
|
||||
ansible.builtin.ufw:
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ base_os_backend_port }}"
|
||||
proto: tcp
|
||||
when: base_os_enable_backend | bool
|
||||
|
||||
- name: Ensure UFW allows frontend port
|
||||
ansible.builtin.ufw:
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ base_os_frontend_port }}"
|
||||
proto: tcp
|
||||
when: base_os_enable_frontend | bool
|
||||
|
||||
- name: Enable UFW (deny incoming by default)
|
||||
ansible.builtin.ufw:
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
policy: deny
|
||||
|
||||
|
||||
|
||||
@ -23,42 +23,12 @@ Installs core development tools and utilities for software development. This rol
|
||||
- **npm**: Node package manager (included with Node.js)
|
||||
- Configured from official NodeSource repository
|
||||
|
||||
### Code Editors
|
||||
- **Cursor IDE**: AI-powered code editor (AppImage)
|
||||
- Installed to `/usr/local/bin/cursor`
|
||||
- Latest stable version from cursor.com
|
||||
|
||||
## Variables
|
||||
|
||||
### Core Settings
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `install_cursor` | `true` | Install Cursor IDE |
|
||||
| `install_cursor_extensions` | `false` | Install Cursor extensions |
|
||||
|
||||
### Extension Groups
|
||||
Enable specific extension groups based on your development needs:
|
||||
|
||||
| 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 |
|
||||
|
||||
### 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
|
||||
| `development_packages` | See defaults | Base packages installed by the role |
|
||||
|
||||
## Dependencies
|
||||
- `base` role (for core utilities)
|
||||
@ -72,61 +42,17 @@ When `install_cursor_extensions: true`, these are always installed:
|
||||
- role: development
|
||||
```
|
||||
|
||||
### Python Data Science Machine
|
||||
### Customize packages
|
||||
```yaml
|
||||
- hosts: datascience
|
||||
- hosts: developers
|
||||
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
|
||||
development_packages:
|
||||
- git
|
||||
- build-essential
|
||||
- python3
|
||||
- python3-pip
|
||||
```
|
||||
|
||||
## Usage
|
||||
@ -141,7 +67,6 @@ ansible-playbook playbooks/development.yml --limit dev01 --tags development
|
||||
|
||||
## Tags
|
||||
- `development`, `dev`: All development tasks
|
||||
- `cursor`, `ide`: Cursor IDE installation only
|
||||
|
||||
## Post-Installation
|
||||
|
||||
@ -151,7 +76,6 @@ git --version
|
||||
node --version
|
||||
npm --version
|
||||
python3 --version
|
||||
cursor --version
|
||||
```
|
||||
|
||||
### Node.js Usage
|
||||
@ -163,28 +87,17 @@ npm install -g <package>
|
||||
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
|
||||
- **Total**: ~3-5 minutes
|
||||
|
||||
### Disk Space
|
||||
- **Node.js + npm**: ~100MB
|
||||
- **Cursor IDE**: ~200MB
|
||||
- **Build tools**: ~50MB
|
||||
- **Total**: ~350MB
|
||||
- **Total**: ~150MB
|
||||
|
||||
## Integration
|
||||
|
||||
@ -217,17 +130,9 @@ 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)
|
||||
@ -239,11 +144,10 @@ alias cursor="cursor --no-sandbox --disable-gpu-sandbox..."
|
||||
| Git | ✅ | - |
|
||||
| Node.js | ✅ | - |
|
||||
| Build Tools | ✅ | - |
|
||||
| Cursor IDE | ✅ | - |
|
||||
| Anaconda | ❌ | ✅ |
|
||||
| Jupyter | ❌ | ✅ |
|
||||
| R Language | ❌ | ✅ |
|
||||
| Install Time | ~10 min | ~30-60 min |
|
||||
| Disk Space | ~350MB | ~3GB |
|
||||
| Disk Space | ~150MB | ~3GB |
|
||||
|
||||
**Recommendation**: Use `development` role for general coding. Add `datascience` role only when needed for data analysis/ML work.
|
||||
|
||||
@ -1,87 +1,9 @@
|
||||
---
|
||||
# Development role defaults
|
||||
# Development role defaults (IDEs intentionally not managed here).
|
||||
|
||||
# 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
|
||||
# Base packages for a lightweight dev foundation.
|
||||
development_packages:
|
||||
- git
|
||||
- build-essential
|
||||
- python3
|
||||
- python3-pip
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
---
|
||||
- name: Install basic development packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
# Development tools
|
||||
- git
|
||||
# Build tools
|
||||
- build-essential
|
||||
- python3
|
||||
- python3-pip
|
||||
name: "{{ development_packages }}"
|
||||
state: present
|
||||
become: true
|
||||
|
||||
@ -17,58 +11,51 @@
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Remove NodeSource repository to fix GPG errors (always run first)
|
||||
ansible.builtin.shell: |
|
||||
# Remove NodeSource repository file to prevent GPG errors
|
||||
rm -f /etc/apt/sources.list.d/nodesource.list
|
||||
# Remove NodeSource key file
|
||||
rm -f /etc/apt/keyrings/nodesource.gpg
|
||||
# Clean apt cache to remove GPG errors
|
||||
apt-get update 2>&1 | grep -v "NO_PUBKEY\|nodesource\|W:" || true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
- name: Check NodeSource repository file presence
|
||||
ansible.builtin.stat:
|
||||
path: /etc/apt/sources.list.d/nodesource.list
|
||||
register: nodesource_list_stat
|
||||
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
|
||||
- name: Skip NodeSource setup if Node.js is already installed
|
||||
- name: Read NodeSource repository file
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/apt/sources.list.d/nodesource.list
|
||||
register: nodesource_list_slurp
|
||||
when:
|
||||
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
- nodesource_list_stat.stat.exists | default(false)
|
||||
|
||||
- name: Set NodeSource repository state
|
||||
ansible.builtin.set_fact:
|
||||
skip_nodesource: "{{ node_version_check.rc == 0 }}"
|
||||
nodesource_repo_state: >-
|
||||
{{
|
||||
'not_exists'
|
||||
if not (nodesource_list_stat.stat.exists | default(false))
|
||||
else (
|
||||
'correct_config'
|
||||
if (
|
||||
(nodesource_list_slurp.content | b64decode)
|
||||
is search('^deb \\[signed-by=/etc/apt/keyrings/nodesource\\.gpg\\] https://deb\\.nodesource\\.com/node_22\\.x nodistro main', multiline=True)
|
||||
)
|
||||
else 'wrong_config'
|
||||
)
|
||||
}}
|
||||
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
|
||||
- name: Check if NodeSource repository exists and is correct
|
||||
ansible.builtin.shell: |
|
||||
if [ -f /etc/apt/sources.list.d/nodesource.list ]; then
|
||||
if grep -q "deb \[signed-by=/etc/apt/keyrings/nodesource.gpg\] https://deb.nodesource.com/node_22.x nodistro main" /etc/apt/sources.list.d/nodesource.list; then
|
||||
echo "correct_config"
|
||||
else
|
||||
echo "wrong_config"
|
||||
fi
|
||||
else
|
||||
echo "not_exists"
|
||||
fi
|
||||
register: nodesource_repo_check
|
||||
failed_when: false
|
||||
changed_when: false # noqa command-instead-of-module
|
||||
- name: Check NodeSource GPG key presence
|
||||
ansible.builtin.stat:
|
||||
path: /etc/apt/keyrings/nodesource.gpg
|
||||
register: nodesource_key_stat
|
||||
when: node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
|
||||
- name: Remove incorrect NodeSource repository
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/sources.list.d/nodesource.list
|
||||
state: absent
|
||||
become: true
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (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: |
|
||||
if [ -f /etc/apt/keyrings/nodesource.gpg ]; then
|
||||
if file /etc/apt/keyrings/nodesource.gpg | grep -q "PGP"; then
|
||||
echo "correct_key"
|
||||
else
|
||||
echo "wrong_key"
|
||||
fi
|
||||
else
|
||||
echo "not_exists"
|
||||
fi
|
||||
register: nodesource_key_check
|
||||
failed_when: false
|
||||
changed_when: false # noqa command-instead-of-module
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22'))
|
||||
|
||||
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
- nodesource_repo_state == "wrong_config"
|
||||
|
||||
- name: Create keyrings directory
|
||||
ansible.builtin.file:
|
||||
@ -77,18 +64,13 @@
|
||||
mode: '0755'
|
||||
become: true
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22'))
|
||||
- nodesource_key_check is defined
|
||||
- nodesource_key_check.stdout is defined
|
||||
- nodesource_key_check.stdout in ["not_exists", "wrong_key"]
|
||||
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
- not (nodesource_key_stat.stat.exists | default(false))
|
||||
|
||||
- name: Import NodeSource GPG key into apt keyring
|
||||
ansible.builtin.shell: |
|
||||
# Ensure keyrings directory exists
|
||||
mkdir -p /etc/apt/keyrings
|
||||
# Remove any existing broken key
|
||||
rm -f /etc/apt/keyrings/nodesource.gpg
|
||||
# Download and convert key to binary format for signed-by
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
chmod 644 /etc/apt/keyrings/nodesource.gpg
|
||||
@ -97,13 +79,12 @@
|
||||
echo "ERROR: Key file is not valid PGP format"
|
||||
exit 1
|
||||
fi
|
||||
args:
|
||||
creates: /etc/apt/keyrings/nodesource.gpg
|
||||
become: true
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22'))
|
||||
- nodesource_key_check is defined
|
||||
- nodesource_key_check.stdout is defined
|
||||
- nodesource_key_check.stdout in ["not_exists", "wrong_key"]
|
||||
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
- not (nodesource_key_stat.stat.exists | default(false))
|
||||
|
||||
- name: Add NodeSource repository only if needed
|
||||
ansible.builtin.apt_repository:
|
||||
@ -112,23 +93,8 @@
|
||||
update_cache: false
|
||||
become: true
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22'))
|
||||
- nodesource_repo_check is defined
|
||||
- nodesource_repo_check.stdout is defined
|
||||
- nodesource_repo_check.stdout in ["not_exists", "wrong_config"]
|
||||
|
||||
- name: Update apt cache after adding NodeSource repository
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22'))
|
||||
- nodesource_repo_check is defined
|
||||
- nodesource_repo_check.stdout is defined
|
||||
- nodesource_repo_check.stdout in ["not_exists", "wrong_config"]
|
||||
- node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22')
|
||||
- nodesource_repo_state in ["not_exists", "wrong_config"]
|
||||
|
||||
- name: Install Node.js 22 from NodeSource
|
||||
ansible.builtin.apt:
|
||||
@ -136,7 +102,6 @@
|
||||
state: present
|
||||
become: true
|
||||
when:
|
||||
- not skip_nodesource | default(false)
|
||||
- (node_version_check.rc != 0 or not node_version_check.stdout.startswith('v22'))
|
||||
|
||||
- name: Verify Node.js installation
|
||||
@ -147,92 +112,3 @@
|
||||
- name: Display Node.js version
|
||||
ansible.builtin.debug:
|
||||
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"
|
||||
|
||||
@ -1,14 +1,4 @@
|
||||
---
|
||||
- name: Remove NodeSource repository to prevent GPG errors
|
||||
ansible.builtin.shell: |
|
||||
# Remove NodeSource repository file to prevent GPG errors during apt cache update
|
||||
rm -f /etc/apt/sources.list.d/nodesource.list
|
||||
# Remove NodeSource key file
|
||||
rm -f /etc/apt/keyrings/nodesource.gpg
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Debug distribution information
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
@ -21,7 +11,6 @@
|
||||
- name: Check if Docker is already installed
|
||||
ansible.builtin.command: docker --version
|
||||
register: docker_check
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
no_log: true
|
||||
|
||||
@ -29,21 +29,6 @@
|
||||
become: true
|
||||
when: docker_repo_check.stdout == "wrong_config"
|
||||
|
||||
- name: Remove NodeSource repository completely before adding Docker repo
|
||||
ansible.builtin.shell: |
|
||||
# Remove NodeSource repository file
|
||||
rm -f /etc/apt/sources.list.d/nodesource.list
|
||||
# Remove NodeSource key file
|
||||
rm -f /etc/apt/keyrings/nodesource.gpg
|
||||
# Remove from sources.list if present
|
||||
sed -i '/nodesource/d' /etc/apt/sources.list 2>/dev/null || true
|
||||
# Remove any cached InRelease files
|
||||
rm -f /var/lib/apt/lists/*nodesource* 2>/dev/null || true
|
||||
rm -f /var/lib/apt/lists/partial/*nodesource* 2>/dev/null || true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Add Docker repository for Linux Mint (using Ubuntu base) only if needed
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ docker_ubuntu_codename }} stable"
|
||||
@ -51,16 +36,8 @@
|
||||
update_cache: false
|
||||
when: docker_repo_check.stdout in ["not_exists", "wrong_config"]
|
||||
|
||||
- name: Update apt cache after adding Docker repository (ignore NodeSource errors)
|
||||
ansible.builtin.shell: |
|
||||
apt-get update 2>&1 | grep -v "nodesource\|NO_PUBKEY.*2F59B5F99B1BE0B4" || true
|
||||
# Verify update succeeded for non-nodesource repos
|
||||
if apt-get update 2>&1 | grep -q "E:"; then
|
||||
# If there are real errors (not just nodesource), fail
|
||||
if ! apt-get update 2>&1 | grep -q "nodesource"; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
- name: Update apt cache after adding Docker repository
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
become: true
|
||||
ignore_errors: true
|
||||
when: docker_repo_check.stdout in ["not_exists", "wrong_config"]
|
||||
|
||||
5
roles/monitoring_desktop/defaults/main.yml
Normal file
5
roles/monitoring_desktop/defaults/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
# Monitoring (desktop/workstation) role defaults
|
||||
monitoring_desktop_install_btop: true
|
||||
monitoring_desktop_install_wireshark_common: true
|
||||
monitoring_desktop_create_scripts: true
|
||||
131
roles/monitoring_desktop/tasks/main.yml
Normal file
131
roles/monitoring_desktop/tasks/main.yml
Normal file
@ -0,0 +1,131 @@
|
||||
---
|
||||
- name: Install monitoring packages (desktop/workstation)
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
# System monitoring
|
||||
- htop
|
||||
- iotop
|
||||
- nethogs
|
||||
- iftop
|
||||
- ncdu
|
||||
- dstat
|
||||
# Network monitoring
|
||||
- nmap
|
||||
- tcpdump
|
||||
# Performance monitoring
|
||||
- atop
|
||||
# Desktop extras
|
||||
- "{{ 'wireshark-common' if monitoring_desktop_install_wireshark_common | bool else omit }}"
|
||||
state: present
|
||||
|
||||
- name: Check if btop is available in apt
|
||||
ansible.builtin.command: apt-cache policy btop
|
||||
register: monitoring_desktop_btop_apt_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: monitoring_desktop_install_btop | bool
|
||||
|
||||
- name: Install btop from apt if available (Debian 12+)
|
||||
ansible.builtin.apt:
|
||||
name: btop
|
||||
state: present
|
||||
update_cache: false
|
||||
when:
|
||||
- monitoring_desktop_install_btop | bool
|
||||
- monitoring_desktop_btop_apt_check.rc == 0
|
||||
- "'Candidate:' in monitoring_desktop_btop_apt_check.stdout"
|
||||
- "'(none)' not in monitoring_desktop_btop_apt_check.stdout"
|
||||
failed_when: false
|
||||
|
||||
- name: Install btop from binary if apt not available
|
||||
when:
|
||||
- monitoring_desktop_install_btop | bool
|
||||
- monitoring_desktop_btop_apt_check.rc != 0 or "(none)" in monitoring_desktop_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: Create monitoring scripts directory
|
||||
ansible.builtin.file:
|
||||
path: /usr/local/bin/monitoring
|
||||
state: directory
|
||||
mode: '0755'
|
||||
when: monitoring_desktop_create_scripts | bool
|
||||
|
||||
- name: Deploy system monitoring script
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# System monitoring dashboard
|
||||
echo "=== System Overview ==="
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "Uptime: $(uptime -p)"
|
||||
echo "Load: $(uptime | awk -F'load average:' '{print $2}')"
|
||||
echo ""
|
||||
echo "=== Memory ==="
|
||||
free -h
|
||||
echo ""
|
||||
echo "=== Disk Usage ==="
|
||||
df -h / /home 2>/dev/null | grep -v tmpfs
|
||||
echo ""
|
||||
echo "=== Top Processes ==="
|
||||
ps aux --sort=-%cpu | head -6
|
||||
echo ""
|
||||
echo "=== Network Connections ==="
|
||||
ss -tuln | head -10
|
||||
echo ""
|
||||
if command -v tailscale >/dev/null; then
|
||||
echo "=== Tailscale Status ==="
|
||||
tailscale status --peers=false 2>/dev/null || echo "Not connected"
|
||||
fi
|
||||
dest: /usr/local/bin/monitoring/sysinfo
|
||||
mode: '0755'
|
||||
when: monitoring_desktop_create_scripts | bool
|
||||
|
||||
- name: Deploy network monitoring script
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# Network monitoring script
|
||||
echo "=== Network Interface Status ==="
|
||||
ip addr show | grep -E "(inet |state )" | grep -v 127.0.0.1
|
||||
echo ""
|
||||
echo "=== Route Table ==="
|
||||
ip route show
|
||||
echo ""
|
||||
echo "=== DNS Configuration ==="
|
||||
cat /etc/resolv.conf | grep nameserver
|
||||
echo ""
|
||||
echo "=== Open Ports ==="
|
||||
ss -tuln | grep LISTEN | sort
|
||||
dest: /usr/local/bin/monitoring/netinfo
|
||||
mode: '0755'
|
||||
when: monitoring_desktop_create_scripts | bool
|
||||
5
roles/monitoring_server/defaults/main.yml
Normal file
5
roles/monitoring_server/defaults/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
# Monitoring (server) role defaults
|
||||
monitoring_server_install_btop: true
|
||||
monitoring_server_enable_sysstat: true
|
||||
monitoring_server_create_scripts: true
|
||||
11
roles/monitoring_server/handlers/main.yml
Normal file
11
roles/monitoring_server/handlers/main.yml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: restart fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
|
||||
- name: restart sysstat
|
||||
ansible.builtin.systemd:
|
||||
name: sysstat
|
||||
state: restarted
|
||||
enabled: true
|
||||
148
roles/monitoring_server/tasks/main.yml
Normal file
148
roles/monitoring_server/tasks/main.yml
Normal file
@ -0,0 +1,148 @@
|
||||
---
|
||||
- name: Install monitoring packages (server)
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
# System monitoring
|
||||
- htop
|
||||
- iotop
|
||||
- nethogs
|
||||
- iftop
|
||||
- ncdu
|
||||
- dstat
|
||||
# Log monitoring / security
|
||||
- logwatch
|
||||
- fail2ban
|
||||
# Network monitoring
|
||||
- nmap
|
||||
- tcpdump
|
||||
# Performance monitoring
|
||||
- sysstat
|
||||
- atop
|
||||
state: present
|
||||
|
||||
- name: Check if btop is available in apt
|
||||
ansible.builtin.command: apt-cache policy btop
|
||||
register: monitoring_server_btop_apt_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: monitoring_server_install_btop | bool
|
||||
|
||||
- name: Install btop from apt if available (Debian 12+)
|
||||
ansible.builtin.apt:
|
||||
name: btop
|
||||
state: present
|
||||
update_cache: false
|
||||
when:
|
||||
- monitoring_server_install_btop | bool
|
||||
- monitoring_server_btop_apt_check.rc == 0
|
||||
- "'Candidate:' in monitoring_server_btop_apt_check.stdout"
|
||||
- "'(none)' not in monitoring_server_btop_apt_check.stdout"
|
||||
failed_when: false
|
||||
|
||||
- name: Install btop from binary if apt not available
|
||||
when:
|
||||
- monitoring_server_install_btop | bool
|
||||
- monitoring_server_btop_apt_check.rc != 0 or "(none)" in monitoring_server_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:
|
||||
src: jail.local.j2
|
||||
dest: /etc/fail2ban/jail.local
|
||||
mode: '0644'
|
||||
notify: restart fail2ban
|
||||
|
||||
- name: Enable sysstat data collection
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/default/sysstat
|
||||
regexp: '^ENABLED='
|
||||
line: 'ENABLED="true"'
|
||||
notify: restart sysstat
|
||||
when: monitoring_server_enable_sysstat | bool
|
||||
|
||||
- name: Create monitoring scripts directory
|
||||
ansible.builtin.file:
|
||||
path: /usr/local/bin/monitoring
|
||||
state: directory
|
||||
mode: '0755'
|
||||
when: monitoring_server_create_scripts | bool
|
||||
|
||||
- name: Deploy system monitoring script
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# System monitoring dashboard
|
||||
echo "=== System Overview ==="
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "Uptime: $(uptime -p)"
|
||||
echo "Load: $(uptime | awk -F'load average:' '{print $2}')"
|
||||
echo ""
|
||||
echo "=== Memory ==="
|
||||
free -h
|
||||
echo ""
|
||||
echo "=== Disk Usage ==="
|
||||
df -h / /home 2>/dev/null | grep -v tmpfs
|
||||
echo ""
|
||||
echo "=== Top Processes ==="
|
||||
ps aux --sort=-%cpu | head -6
|
||||
echo ""
|
||||
echo "=== Network Connections ==="
|
||||
ss -tuln | head -10
|
||||
echo ""
|
||||
if command -v tailscale >/dev/null; then
|
||||
echo "=== Tailscale Status ==="
|
||||
tailscale status --peers=false 2>/dev/null || echo "Not connected"
|
||||
fi
|
||||
dest: /usr/local/bin/monitoring/sysinfo
|
||||
mode: '0755'
|
||||
when: monitoring_server_create_scripts | bool
|
||||
|
||||
- name: Deploy network monitoring script
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
# Network monitoring script
|
||||
echo "=== Network Interface Status ==="
|
||||
ip addr show | grep -E "(inet |state )" | grep -v 127.0.0.1
|
||||
echo ""
|
||||
echo "=== Route Table ==="
|
||||
ip route show
|
||||
echo ""
|
||||
echo "=== DNS Configuration ==="
|
||||
cat /etc/resolv.conf | grep nameserver
|
||||
echo ""
|
||||
echo "=== Open Ports ==="
|
||||
ss -tuln | grep LISTEN | sort
|
||||
dest: /usr/local/bin/monitoring/netinfo
|
||||
mode: '0755'
|
||||
when: monitoring_server_create_scripts | bool
|
||||
34
roles/monitoring_server/templates/jail.local.j2
Normal file
34
roles/monitoring_server/templates/jail.local.j2
Normal file
@ -0,0 +1,34 @@
|
||||
[DEFAULT]
|
||||
# Ban hosts for 1 hour
|
||||
bantime = 3600
|
||||
# Check for repeated failures for 10 minutes
|
||||
findtime = 600
|
||||
# Allow 3 failures before banning
|
||||
maxretry = 3
|
||||
|
||||
# Email notifications (uncomment and configure if needed)
|
||||
destemail = idobkin@gmail.com
|
||||
sender = idobkin@gmail.com
|
||||
action = %(action_mwl)s
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = 3
|
||||
|
||||
[apache]
|
||||
enabled = false
|
||||
port = http,https
|
||||
filter = apache-auth
|
||||
logpath = /var/log/apache2/error.log
|
||||
maxretry = 3
|
||||
|
||||
[nginx-http-auth]
|
||||
enabled = false
|
||||
port = http,https
|
||||
filter = nginx-http-auth
|
||||
logpath = /var/log/nginx/error.log
|
||||
maxretry = 3
|
||||
|
||||
@ -78,5 +78,3 @@
|
||||
Storage: {{ vm_storage }}:{{ vm_disk_size }}
|
||||
Network: {{ vm_network_bridge }}
|
||||
Status: {{ vm_creation_result.msg | default('Created') }}
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
### Role: shell
|
||||
|
||||
## Description
|
||||
Configures modern shell environment with zsh, Oh My Zsh, Powerlevel10k theme, and useful plugins. Can be configured for multiple users on the same host.
|
||||
Configures shell in one of two modes:
|
||||
|
||||
- **minimal**: aliases-only (safe for servers; does not overwrite `~/.zshrc`)
|
||||
- **full**: installs Oh My Zsh + Powerlevel10k + plugins and deploys a managed `~/.zshrc` (intended for developer machines)
|
||||
|
||||
## Requirements
|
||||
- Ansible 2.9+
|
||||
@ -12,25 +15,23 @@ Configures modern shell environment with zsh, Oh My Zsh, Powerlevel10k theme, an
|
||||
|
||||
### 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
|
||||
- **oh-my-zsh / powerlevel10k**: only in `shell_mode=full`
|
||||
|
||||
### Configuration Files
|
||||
- `.zshrc`: Custom zsh configuration with conda support
|
||||
- `.p10k.zsh`: Powerlevel10k theme configuration
|
||||
- `~/.zsh_aliases_ansible`: Managed aliases file (sourced from `~/.zshrc`)
|
||||
- `~/.zshrc`: appended in `minimal` mode; fully managed in `full` mode
|
||||
- `~/.p10k.zsh`: only in `shell_mode=full`
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `shell_users` | `[ansible_user]` | List of users to configure zsh for |
|
||||
| `zsh_plugins` | See defaults/main.yml | List of zsh plugins to install |
|
||||
| `shell_packages` | `['zsh','tmux','fzf']` | Packages installed by the role |
|
||||
| `shell_mode` | `minimal` | `minimal` (aliases-only) or `full` (oh-my-zsh + p10k + managed zshrc) |
|
||||
| `shell_set_default_shell` | `false` | If true, set login shell to `/usr/bin/zsh` |
|
||||
|
||||
## Dependencies
|
||||
None
|
||||
@ -44,6 +45,16 @@ None
|
||||
- role: shell
|
||||
```
|
||||
|
||||
### Full Zsh for developer machines
|
||||
```yaml
|
||||
- hosts: dev
|
||||
roles:
|
||||
- role: shell
|
||||
vars:
|
||||
shell_mode: full
|
||||
shell_set_default_shell: true
|
||||
```
|
||||
|
||||
### Configure Multiple Users
|
||||
```yaml
|
||||
- hosts: servers
|
||||
@ -90,19 +101,14 @@ make dev HOST=devGPU --tags shell
|
||||
## Post-Installation
|
||||
|
||||
### For Each Configured User
|
||||
The shell configuration is immediately active. Users can:
|
||||
The aliases are immediately available in new shells. Users can:
|
||||
|
||||
1. **Customize Powerlevel10k**:
|
||||
```bash
|
||||
p10k configure
|
||||
```
|
||||
|
||||
2. **Reload Configuration**:
|
||||
1. **Reload Configuration**:
|
||||
```bash
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
3. **View Available Aliases**:
|
||||
2. **View Available Aliases**:
|
||||
```bash
|
||||
alias # List all aliases
|
||||
```
|
||||
@ -110,27 +116,12 @@ The shell configuration is immediately active. Users can:
|
||||
## 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).
|
||||
The role installs a small, server-safe alias set in `~/.zsh_aliases_ansible`.
|
||||
|
||||
## Notes
|
||||
|
||||
- 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
|
||||
- Existing `.zshrc` files are **not** overwritten
|
||||
- The role skips users that don't exist on the system
|
||||
|
||||
## Troubleshooting
|
||||
@ -143,16 +134,13 @@ 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.
|
||||
If `shell_mode=full`, ensure the host has outbound internet access to fetch the installer and clone git repos.
|
||||
|
||||
### Powerlevel10k Not Loading
|
||||
Verify the theme is cloned:
|
||||
```bash
|
||||
ls ~/.oh-my-zsh/custom/themes/powerlevel10k
|
||||
```
|
||||
Only applies to `shell_mode=full`. Verify `~/.p10k.zsh` exists and the theme repo is present under `~/.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`.
|
||||
`shell_mode=full` includes a minimal “initialize conda if present” block. Conda installation is still handled by the `datascience` role.
|
||||
|
||||
## Integration
|
||||
|
||||
@ -174,29 +162,21 @@ roles:
|
||||
|
||||
## 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)
|
||||
- Only an aliases file is managed; no remote scripts/themes are downloaded.
|
||||
- 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
|
||||
- Installation time: seconds per user (copy + lineinfile)
|
||||
- Disk space: negligible
|
||||
|
||||
## Customization
|
||||
|
||||
### Adding Custom Aliases
|
||||
Edit `roles/shell/files/.zshrc` and add your aliases.
|
||||
Edit `roles/shell/files/ansible_aliases.zsh` and re-run the role.
|
||||
|
||||
### Adding More Plugins
|
||||
Update `roles/shell/defaults/main.yml`:
|
||||
```yaml
|
||||
zsh_plugins:
|
||||
- name: "my-plugin"
|
||||
repo: "https://github.com/user/my-plugin.git"
|
||||
```
|
||||
Out of scope for this role (keep it fast/minimal).
|
||||
|
||||
### Custom Theme
|
||||
Replace Powerlevel10k in tasks if desired, or users can run `p10k configure` to customize.
|
||||
Out of scope for this role.
|
||||
|
||||
@ -15,7 +15,40 @@ shell_users:
|
||||
# - ladmin
|
||||
shell_additional_users: []
|
||||
|
||||
# Zsh plugins to install
|
||||
# Shell configuration mode:
|
||||
# - minimal: aliases-only (safe for servers; does not overwrite ~/.zshrc)
|
||||
# - full: install oh-my-zsh + powerlevel10k + plugins and deploy managed ~/.zshrc
|
||||
shell_mode: minimal
|
||||
|
||||
# Packages installed for all modes.
|
||||
shell_packages_common:
|
||||
- zsh
|
||||
- tmux
|
||||
- fzf
|
||||
|
||||
# Extra packages for full mode.
|
||||
shell_packages_full_extra:
|
||||
- git
|
||||
|
||||
# Effective package list.
|
||||
shell_packages: "{{ shell_packages_common + (shell_packages_full_extra if shell_mode == 'full' else []) }}"
|
||||
|
||||
# If true, change users' login shell to zsh.
|
||||
shell_set_default_shell: false
|
||||
|
||||
# Path (relative to the user's home) for the managed aliases file.
|
||||
shell_aliases_filename: ".zsh_aliases_ansible"
|
||||
|
||||
# Line added to ~/.zshrc to source the managed aliases file.
|
||||
shell_zshrc_source_line: '[ -f "$HOME/{{ shell_aliases_filename }}" ] && source "$HOME/{{ shell_aliases_filename }}"'
|
||||
|
||||
# Full mode settings
|
||||
shell_install_oh_my_zsh: "{{ shell_mode == 'full' }}"
|
||||
shell_install_powerlevel10k: "{{ shell_mode == 'full' }}"
|
||||
shell_install_plugins: "{{ shell_mode == 'full' }}"
|
||||
shell_deploy_managed_zshrc: "{{ shell_mode == 'full' }}"
|
||||
|
||||
# Zsh plugins cloned into oh-my-zsh custom plugins (full mode only).
|
||||
zsh_plugins:
|
||||
- name: "zsh-syntax-highlighting"
|
||||
repo: "https://github.com/zsh-users/zsh-syntax-highlighting.git"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,220 +0,0 @@
|
||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
|
||||
|
||||
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
|
||||
# Initialization code that may require console input (password prompts, [y/n]
|
||||
# confirmations, etc.) must go above this block; everything else may go below.
|
||||
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||
fi
|
||||
|
||||
# If you come from bash you might have to change your $PATH.
|
||||
# export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH
|
||||
|
||||
# Path to your Oh My Zsh installation.
|
||||
export ZSH="$HOME/.oh-my-zsh"
|
||||
|
||||
# Set name of the theme to load --- if set to "random", it will
|
||||
# load a random theme each time Oh My Zsh is loaded, in which case,
|
||||
# to know which specific one was loaded, run: echo $RANDOM_THEME
|
||||
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
|
||||
ZSH_THEME="powerlevel10k/powerlevel10k"
|
||||
|
||||
# Set list of themes to pick from when loading at random
|
||||
# Setting this variable when ZSH_THEME=random will cause zsh to load
|
||||
# a theme from this variable instead of looking in $ZSH/themes/
|
||||
# If set to an empty array, this variable will have no effect.
|
||||
# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" )
|
||||
|
||||
# Uncomment the following line to use case-sensitive completion.
|
||||
# CASE_SENSITIVE="true"
|
||||
|
||||
# Uncomment the following line to use hyphen-insensitive completion.
|
||||
# Case-sensitive completion must be off. _ and - will be interchangeable.
|
||||
# HYPHEN_INSENSITIVE="true"
|
||||
|
||||
# Uncomment one of the following lines to change the auto-update behavior
|
||||
# zstyle ':omz:update' mode disabled # disable automatic updates
|
||||
# zstyle ':omz:update' mode auto # update automatically without asking
|
||||
# zstyle ':omz:update' mode reminder # just remind me to update when it's time
|
||||
|
||||
# Uncomment the following line to change how often to auto-update (in days).
|
||||
# zstyle ':omz:update' frequency 13
|
||||
|
||||
# Uncomment the following line if pasting URLs and other text is messed up.
|
||||
# DISABLE_MAGIC_FUNCTIONS="true"
|
||||
|
||||
# Uncomment the following line to disable colors in ls.
|
||||
# DISABLE_LS_COLORS="true"
|
||||
|
||||
# Uncomment the following line to disable auto-setting terminal title.
|
||||
# DISABLE_AUTO_TITLE="true"
|
||||
|
||||
# Uncomment the following line to enable command auto-correction.
|
||||
# ENABLE_CORRECTION="true"
|
||||
|
||||
# Uncomment the following line to display red dots whilst waiting for completion.
|
||||
# You can also set it to another string to have that shown instead of the default red dots.
|
||||
# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f"
|
||||
# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765)
|
||||
# COMPLETION_WAITING_DOTS="true"
|
||||
|
||||
# Uncomment the following line if you want to disable marking untracked files
|
||||
# under VCS as dirty. This makes repository status check for large repositories
|
||||
# much, much faster.
|
||||
# DISABLE_UNTRACKED_FILES_DIRTY="true"
|
||||
|
||||
# Uncomment the following line if you want to change the command execution time
|
||||
# stamp shown in the history command output.
|
||||
# You can set one of the optional three formats:
|
||||
# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
|
||||
# or set a custom format using the strftime function format specifications,
|
||||
# see 'man strftime' for details.
|
||||
# HIST_STAMPS="mm/dd/yyyy"
|
||||
|
||||
# Would you like to use another custom folder than $ZSH/custom?
|
||||
# ZSH_CUSTOM=/path/to/new-custom-folder
|
||||
|
||||
# Which plugins would you like to load?
|
||||
# Standard plugins can be found in $ZSH/plugins/
|
||||
# Custom plugins may be added to $ZSH_CUSTOM/plugins/
|
||||
# Example format: plugins=(rails git textmate ruby lighthouse)
|
||||
# Add wisely, as too many plugins slow down shell startup.
|
||||
plugins=(git sudo z colored-man-pages fzf zsh-syntax-highlighting zsh-autosuggestions web-search copypath)
|
||||
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
|
||||
# User configuration
|
||||
|
||||
# export MANPATH="/usr/local/man:$MANPATH"
|
||||
|
||||
# You may need to manually set your language environment
|
||||
# export LANG=en_US.UTF-8
|
||||
|
||||
# Preferred editor for local and remote sessions
|
||||
# if [[ -n $SSH_CONNECTION ]]; then
|
||||
# export EDITOR='vim'
|
||||
# else
|
||||
# export EDITOR='nvim'
|
||||
# fi
|
||||
|
||||
# Compilation flags
|
||||
# export ARCHFLAGS="-arch $(uname -m)"
|
||||
|
||||
# Set personal aliases, overriding those provided by Oh My Zsh libs,
|
||||
# plugins, and themes. Aliases can be placed here, though Oh My Zsh
|
||||
# users are encouraged to define aliases within a top-level file in
|
||||
# the $ZSH_CUSTOM folder, with .zsh extension. Examples:
|
||||
# - $ZSH_CUSTOM/aliases.zsh
|
||||
# - $ZSH_CUSTOM/macos.zsh
|
||||
# For a full list of active aliases, run `alias`.
|
||||
#
|
||||
# Example aliases
|
||||
# alias zshconfig="mate ~/.zshrc"
|
||||
# alias ohmyzsh="mate ~/.oh-my-zsh"
|
||||
|
||||
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
|
||||
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
|
||||
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
|
||||
|
||||
alias reload="source ~/.zshrc && echo 'ZSH config reloaded from ~/.zshrc'"
|
||||
alias editrc="nano ~/.zshrc"
|
||||
alias c="clear"
|
||||
alias ls="ls --color=auto"
|
||||
|
||||
alias ..="cd .."
|
||||
alias ...="cd ../.."
|
||||
alias ....="cd ../../.."
|
||||
alias cd..="cd .."
|
||||
alias h="cd ~"
|
||||
alias dc="cd ~/Documents/code"
|
||||
|
||||
# System information
|
||||
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"
|
||||
alias cpu="lscpu"
|
||||
alias top="btop"
|
||||
alias mem="free -m"
|
||||
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"
|
||||
alias pip="pip3"
|
||||
alias venv="python3 -m venv"
|
||||
alias activate="source venv/bin/activate"
|
||||
|
||||
# Docker
|
||||
alias d="docker"
|
||||
alias dc="docker-compose"
|
||||
alias dcu="docker-compose up -d"
|
||||
alias dcd="docker-compose down"
|
||||
alias dcb="docker-compose build"
|
||||
alias dps="docker ps"
|
||||
alias di="docker images"
|
||||
|
||||
# Date and time
|
||||
alias now="date +'%Y-%m-%d %H:%M:%S'"
|
||||
alias today="date +'%Y-%m-%d'"
|
||||
|
||||
# Package management (Debian/Ubuntu)
|
||||
alias update="sudo apt update && sudo apt upgrade -y"
|
||||
alias install="sudo apt install"
|
||||
alias remove="sudo apt remove"
|
||||
alias search="apt search"
|
||||
|
||||
# Permissions and ownership
|
||||
alias chmox="chmod +x"
|
||||
alias own="sudo chown -R $USER:$USER"
|
||||
|
||||
alias nfresh="rm -rf node_modules/ package-lock.json && npm install"
|
||||
|
||||
# SSH aliases for Ansible hosts
|
||||
alias ssh-gitea="ssh gitea@10.0.30.169"
|
||||
alias ssh-portainer="ssh ladmin@10.0.30.69"
|
||||
alias ssh-homepage="ssh homepage@10.0.30.12"
|
||||
alias ssh-vaultwarden="ssh root@100.100.19.11"
|
||||
alias ssh-vaultwarden-fallback="ssh root@10.0.10.142"
|
||||
alias ssh-dev01="ssh ladmin@10.0.30.105"
|
||||
alias ssh-bottom="ssh beast@10.0.10.156"
|
||||
alias ssh-debian="ssh user@10.0.10.206"
|
||||
alias ssh-devGPU="ssh root@10.0.30.63"
|
||||
alias ssh-ansible="ssh master@10.0.10.157"
|
||||
alias ssh-tailscale="ssh ladmin@100.66.218.53"
|
||||
alias ssh-caddy="ssh root@100.117.106.18"
|
||||
alias ssh-caddy-fallback="ssh root@10.0.10.50"
|
||||
alias ssh-jellyfin="ssh root@100.104.109.45"
|
||||
alias ssh-jellyfin-fallback="ssh root@10.0.10.232"
|
||||
alias ssh-listmonk="ssh root@100.73.190.115"
|
||||
alias ssh-listmonk-fallback="ssh root@10.0.10.149"
|
||||
alias ssh-desktop="ssh beast@100.117.34.106"
|
||||
|
||||
# >>> 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 <<<
|
||||
|
||||
38
roles/shell/files/ansible_aliases.zsh
Normal file
38
roles/shell/files/ansible_aliases.zsh
Normal file
@ -0,0 +1,38 @@
|
||||
# Managed by Ansible (role: shell)
|
||||
#
|
||||
# This file is intentionally small and safe for servers.
|
||||
# It is sourced from ~/.zshrc by the shell role.
|
||||
|
||||
alias reload="source ~/.zshrc && echo 'ZSH config reloaded from ~/.zshrc'"
|
||||
alias editrc="nano ~/.zshrc"
|
||||
alias c="clear"
|
||||
alias ls="ls --color=auto"
|
||||
|
||||
alias ..="cd .."
|
||||
alias ...="cd ../.."
|
||||
alias ....="cd ../../.."
|
||||
alias cd..="cd .."
|
||||
alias h="cd ~"
|
||||
|
||||
# System information
|
||||
alias df="df -h"
|
||||
alias du="du -h"
|
||||
alias free="free -h"
|
||||
alias ports="ss -tulpn"
|
||||
alias myip="curl -s http://ipecho.net/plain; echo"
|
||||
|
||||
# Package management (Debian/Ubuntu)
|
||||
alias update="sudo apt update && sudo apt upgrade -y"
|
||||
alias install="sudo apt install"
|
||||
alias remove="sudo apt remove"
|
||||
alias search="apt search"
|
||||
|
||||
# Docker (if installed)
|
||||
alias d="docker"
|
||||
alias dps="docker ps"
|
||||
alias di="docker images"
|
||||
|
||||
# Python
|
||||
alias py="python3"
|
||||
alias pip="pip3"
|
||||
|
||||
19
roles/shell/files/p10k.zsh
Normal file
19
roles/shell/files/p10k.zsh
Normal file
@ -0,0 +1,19 @@
|
||||
# Minimal Powerlevel10k config (lean-ish). Users can customize later via `p10k configure`.
|
||||
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
|
||||
dir
|
||||
vcs
|
||||
newline
|
||||
prompt_char
|
||||
)
|
||||
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
|
||||
status
|
||||
command_execution_time
|
||||
background_jobs
|
||||
time
|
||||
)
|
||||
|
||||
typeset -g POWERLEVEL9K_MODE='nerdfont-complete'
|
||||
typeset -g POWERLEVEL9K_PROMPT_ON_NEWLINE=true
|
||||
|
||||
@ -36,7 +36,7 @@ for tool in conda jupyter R; do
|
||||
done
|
||||
|
||||
echo -e "\n${YELLOW}Editors:${RESET}"
|
||||
for tool in cursor code vim nvim nano; do
|
||||
for tool in vim nvim nano; do
|
||||
command -v $tool >/dev/null 2>&1 && printf " ${GREEN}✓${RESET} %s\n" "$tool"
|
||||
done
|
||||
|
||||
|
||||
42
roles/shell/files/zshrc.full
Normal file
42
roles/shell/files/zshrc.full
Normal file
@ -0,0 +1,42 @@
|
||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
|
||||
|
||||
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
|
||||
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||
fi
|
||||
|
||||
# Path to your Oh My Zsh installation.
|
||||
export ZSH="$HOME/.oh-my-zsh"
|
||||
|
||||
# Theme
|
||||
ZSH_THEME="powerlevel10k/powerlevel10k"
|
||||
|
||||
# Plugins
|
||||
plugins=(git sudo z colored-man-pages fzf zsh-syntax-highlighting zsh-autosuggestions web-search copypath)
|
||||
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
|
||||
# Powerlevel10k config
|
||||
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
|
||||
|
||||
# fzf integration (if installed by user)
|
||||
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
|
||||
|
||||
# Source Ansible-managed aliases (safe, small)
|
||||
[ -f "$HOME/.zsh_aliases_ansible" ] && source "$HOME/.zsh_aliases_ansible"
|
||||
|
||||
# Conda auto-init (only if conda exists)
|
||||
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
|
||||
|
||||
@ -22,11 +22,40 @@
|
||||
- name: Configure shell environment
|
||||
when: user_info.ansible_facts.getent_passwd[current_user] is defined
|
||||
block:
|
||||
- name: "Set zsh as default shell: {{ current_user }}"
|
||||
- name: "Optionally set zsh as default shell: {{ current_user }}"
|
||||
ansible.builtin.user:
|
||||
name: "{{ current_user }}"
|
||||
shell: /usr/bin/zsh
|
||||
become: true
|
||||
when: shell_set_default_shell | bool
|
||||
|
||||
- name: "Install managed zsh aliases file: {{ current_user }}"
|
||||
ansible.builtin.copy:
|
||||
src: files/ansible_aliases.zsh
|
||||
dest: "{{ user_home }}/{{ shell_aliases_filename }}"
|
||||
owner: "{{ current_user }}"
|
||||
group: "{{ current_user }}"
|
||||
mode: "0644"
|
||||
become: true
|
||||
|
||||
- name: "Ensure ~/.zshrc exists (do not overwrite): {{ current_user }}"
|
||||
ansible.builtin.file:
|
||||
path: "{{ user_home }}/.zshrc"
|
||||
state: touch
|
||||
owner: "{{ current_user }}"
|
||||
group: "{{ current_user }}"
|
||||
mode: "0644"
|
||||
become: true
|
||||
when: not (shell_deploy_managed_zshrc | bool)
|
||||
|
||||
- name: "Ensure ~/.zshrc sources managed aliases: {{ current_user }}"
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ user_home }}/.zshrc"
|
||||
line: "{{ shell_zshrc_source_line }}"
|
||||
state: present
|
||||
insertafter: EOF
|
||||
become: true
|
||||
when: not (shell_deploy_managed_zshrc | bool)
|
||||
|
||||
- name: "Install Oh My Zsh: {{ current_user }}"
|
||||
become: true
|
||||
@ -34,6 +63,8 @@
|
||||
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"
|
||||
changed_when: false
|
||||
when: shell_install_oh_my_zsh | bool
|
||||
|
||||
- name: "Clone Powerlevel10k theme: {{ current_user }}"
|
||||
ansible.builtin.git:
|
||||
@ -44,6 +75,9 @@
|
||||
update: false
|
||||
become: true
|
||||
become_user: "{{ current_user }}"
|
||||
when:
|
||||
- shell_install_powerlevel10k | bool
|
||||
- shell_install_oh_my_zsh | bool
|
||||
|
||||
- name: "Install zsh plugins: {{ current_user }}"
|
||||
ansible.builtin.git:
|
||||
@ -55,24 +89,33 @@
|
||||
become: true
|
||||
become_user: "{{ current_user }}"
|
||||
loop: "{{ zsh_plugins }}"
|
||||
when:
|
||||
- shell_install_plugins | bool
|
||||
- shell_install_oh_my_zsh | bool
|
||||
|
||||
- name: "Deploy .zshrc: {{ current_user }}"
|
||||
- name: "Deploy managed .zshrc (full mode): {{ current_user }}"
|
||||
ansible.builtin.copy:
|
||||
src: files/.zshrc
|
||||
src: files/zshrc.full
|
||||
dest: "{{ user_home }}/.zshrc"
|
||||
owner: "{{ current_user }}"
|
||||
group: "{{ current_user }}"
|
||||
mode: '0644'
|
||||
mode: "0644"
|
||||
backup: true
|
||||
become: true
|
||||
when: shell_deploy_managed_zshrc | bool
|
||||
|
||||
- name: "Deploy Powerlevel10k configuration: {{ current_user }}"
|
||||
- name: "Deploy Powerlevel10k config (full mode): {{ current_user }}"
|
||||
ansible.builtin.copy:
|
||||
src: files/.p10k.zsh
|
||||
src: files/p10k.zsh
|
||||
dest: "{{ user_home }}/.p10k.zsh"
|
||||
owner: "{{ current_user }}"
|
||||
group: "{{ current_user }}"
|
||||
mode: '0644'
|
||||
mode: "0644"
|
||||
backup: true
|
||||
become: true
|
||||
when:
|
||||
- shell_install_powerlevel10k | bool
|
||||
- shell_deploy_managed_zshrc | bool
|
||||
|
||||
- name: "Ensure .local/bin directory exists: {{ current_user }}"
|
||||
ansible.builtin.file:
|
||||
@ -96,9 +139,9 @@
|
||||
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"
|
||||
- "Aliases installed: {{ user_home }}/{{ shell_aliases_filename }}"
|
||||
- >-
|
||||
Mode: {{ shell_mode | default('minimal') }} ({{ 'managed ~/.zshrc deployed' if (shell_deploy_managed_zshrc | bool) else 'aliases-only appended to ~/.zshrc' }})
|
||||
- "If you want zsh as default login shell, set: shell_set_default_shell=true"
|
||||
- "If zsh was set as the default shell, log out/in or run: exec zsh"
|
||||
- "=========================================="
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
---
|
||||
- name: Install shell packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- zsh
|
||||
- tmux
|
||||
- fzf
|
||||
- git
|
||||
name: "{{ shell_packages }}"
|
||||
state: present
|
||||
become: true
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ get_version() {
|
||||
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
|
||||
@ -65,16 +64,6 @@ 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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user