404 lines
14 KiB
YAML
404 lines
14 KiB
YAML
---
|
|
- name: Restore Debian homelab control-plane and desktop host
|
|
hosts: debian_pc
|
|
become: true
|
|
gather_facts: true
|
|
|
|
pre_tasks:
|
|
- name: Require Debian
|
|
ansible.builtin.assert:
|
|
that:
|
|
- ansible_facts.os_family == "Debian"
|
|
- ansible_facts.distribution == "Debian"
|
|
fail_msg: "This host bootstrap is intentionally scoped to Debian."
|
|
|
|
- name: Install apt prerequisites
|
|
ansible.builtin.apt:
|
|
name: "{{ debian_pc_base_packages }}"
|
|
state: present
|
|
update_cache: true
|
|
|
|
- name: Ensure apt keyring directory exists
|
|
ansible.builtin.file:
|
|
path: /etc/apt/keyrings
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
tasks:
|
|
- name: Add Docker apt key
|
|
ansible.builtin.get_url:
|
|
url: https://download.docker.com/linux/debian/gpg
|
|
dest: /etc/apt/keyrings/docker.asc
|
|
mode: "0644"
|
|
|
|
- name: Add Docker apt source
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/docker.sources
|
|
mode: "0644"
|
|
content: |
|
|
Types: deb
|
|
URIs: https://download.docker.com/linux/debian
|
|
Suites: {{ ansible_facts.distribution_release }}
|
|
Components: stable
|
|
Architectures: {{ ansible_facts.architecture | regex_replace('x86_64', 'amd64') | regex_replace('aarch64', 'arm64') }}
|
|
Signed-By: /etc/apt/keyrings/docker.asc
|
|
|
|
- name: Add Kubernetes apt key
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
curl -fsSL "https://pkgs.k8s.io/core:/stable:/{{ debian_pc_kubernetes_minor_version }}/deb/Release.key" |
|
|
gpg --dearmor >/etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
|
chmod 0644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
|
args:
|
|
executable: /bin/bash
|
|
creates: /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
|
|
|
- name: Add Kubernetes apt source
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/kubernetes.list
|
|
mode: "0644"
|
|
content: |
|
|
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/{{ debian_pc_kubernetes_minor_version }}/deb/ /
|
|
|
|
- name: Add Helm apt key
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
helm_key_fingerprint="DDF78C3E6EBB2D2CC223C95C62BA89D07698DBC6"
|
|
tmp_key="$(mktemp)"
|
|
trap 'rm -f "${tmp_key}"' EXIT
|
|
curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey >"${tmp_key}"
|
|
actual_fingerprint="$(gpg --show-keys --with-colons "${tmp_key}" | awk -F: '$1 == "fpr" { print $10; exit }')"
|
|
if [ "${actual_fingerprint}" != "${helm_key_fingerprint}" ]; then
|
|
echo "Unexpected Helm apt key fingerprint: ${actual_fingerprint}" >&2
|
|
exit 1
|
|
fi
|
|
gpg --dearmor <"${tmp_key}" >/etc/apt/keyrings/helm.gpg
|
|
chmod 0644 /etc/apt/keyrings/helm.gpg
|
|
args:
|
|
executable: /bin/bash
|
|
creates: /etc/apt/keyrings/helm.gpg
|
|
|
|
- name: Add Helm apt source
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/helm-stable-debian.list
|
|
mode: "0644"
|
|
content: |
|
|
deb [arch={{ ansible_facts.architecture | regex_replace('x86_64', 'amd64') | regex_replace('aarch64', 'arm64') }} signed-by=/etc/apt/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main
|
|
|
|
- name: Add OpenTofu apt keys
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
curl -fsSL https://get.opentofu.org/opentofu.gpg >/etc/apt/keyrings/opentofu.gpg
|
|
curl -fsSL https://packages.opentofu.org/opentofu/tofu/gpgkey |
|
|
gpg --dearmor >/etc/apt/keyrings/opentofu-repo.gpg
|
|
chmod 0644 /etc/apt/keyrings/opentofu.gpg /etc/apt/keyrings/opentofu-repo.gpg
|
|
args:
|
|
executable: /bin/bash
|
|
creates: /etc/apt/keyrings/opentofu-repo.gpg
|
|
|
|
- name: Add OpenTofu apt source
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/opentofu.list
|
|
mode: "0644"
|
|
content: |
|
|
deb [signed-by=/etc/apt/keyrings/opentofu.gpg,/etc/apt/keyrings/opentofu-repo.gpg] https://packages.opentofu.org/opentofu/tofu/any/ any main
|
|
|
|
- name: Add Brave apt key
|
|
ansible.builtin.get_url:
|
|
url: https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
|
|
dest: /etc/apt/keyrings/brave-browser-archive-keyring.gpg
|
|
mode: "0644"
|
|
|
|
- name: Add Brave apt source
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/brave-browser-release.sources
|
|
mode: "0644"
|
|
content: |
|
|
Types: deb
|
|
URIs: https://brave-browser-apt-release.s3.brave.com/
|
|
Suites: stable
|
|
Components: main
|
|
Architectures: amd64 arm64
|
|
Signed-By: /etc/apt/keyrings/brave-browser-archive-keyring.gpg
|
|
|
|
- name: Refresh apt cache after repository setup
|
|
ansible.builtin.apt:
|
|
update_cache: true
|
|
|
|
- name: Write desired package list for availability filtering
|
|
ansible.builtin.copy:
|
|
dest: /tmp/homelab-debian-pc-desired-packages.txt
|
|
mode: "0644"
|
|
content: |
|
|
{% for package in (debian_pc_base_packages + debian_pc_apt_manual_packages) | unique | sort %}
|
|
{{ package }}
|
|
{% endfor %}
|
|
|
|
- name: Filter packages available to apt
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
while IFS= read -r package; do
|
|
[ -n "$package" ] || continue
|
|
if apt-cache policy "$package" | awk '/Candidate:/ && $2 != "(none)" { found = 1 } END { exit found ? 0 : 1 }'; then
|
|
printf '%s\n' "$package"
|
|
fi
|
|
done </tmp/homelab-debian-pc-desired-packages.txt
|
|
args:
|
|
executable: /bin/bash
|
|
register: debian_pc_installable_packages_result
|
|
changed_when: false
|
|
|
|
- name: Track installable package list
|
|
ansible.builtin.set_fact:
|
|
debian_pc_installable_packages: "{{ debian_pc_installable_packages_result.stdout_lines | default([]) }}"
|
|
|
|
- name: Install available Debian PC packages
|
|
ansible.builtin.apt:
|
|
name: "{{ debian_pc_installable_packages }}"
|
|
state: present
|
|
when: debian_pc_installable_packages | length > 0
|
|
|
|
- name: Install Zoom from upstream deb when requested on amd64
|
|
ansible.builtin.apt:
|
|
deb: https://zoom.us/client/latest/zoom_amd64.deb
|
|
state: present
|
|
when:
|
|
- "'zoom' in debian_pc_apt_manual_packages"
|
|
- ansible_facts.architecture == "x86_64"
|
|
|
|
- name: Ensure pipx binary path is configured for the homelab user
|
|
ansible.builtin.command:
|
|
cmd: python3 -m pipx ensurepath
|
|
become: true
|
|
become_user: "{{ debian_pc_user }}"
|
|
environment:
|
|
PATH: "{{ debian_pc_user_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
|
changed_when: false
|
|
|
|
- name: Install user pipx CLI tools
|
|
ansible.builtin.command:
|
|
argv:
|
|
- python3
|
|
- -m
|
|
- pipx
|
|
- install
|
|
- "{{ item.package }}"
|
|
creates: "{{ debian_pc_user_home }}/.local/bin/{{ item.app }}"
|
|
become: true
|
|
become_user: "{{ debian_pc_user }}"
|
|
environment:
|
|
PATH: "{{ debian_pc_user_home }}/.local/bin:/usr/local/bin:/usr/bin:/bin"
|
|
loop: "{{ debian_pc_pipx_packages }}"
|
|
|
|
- name: Hold Kubernetes and runtime packages
|
|
ansible.builtin.dpkg_selections:
|
|
name: "{{ item }}"
|
|
selection: hold
|
|
loop: "{{ debian_pc_hold_packages }}"
|
|
failed_when: false
|
|
|
|
- name: Ensure expected local groups exist
|
|
ansible.builtin.group:
|
|
name: "{{ item }}"
|
|
state: present
|
|
loop: "{{ debian_pc_user_groups }}"
|
|
|
|
- name: Ensure user supplementary groups
|
|
ansible.builtin.user:
|
|
name: "{{ debian_pc_user }}"
|
|
groups: "{{ debian_pc_user_groups }}"
|
|
append: true
|
|
|
|
- name: Allow passwordless sudo for homelab automation
|
|
ansible.builtin.copy:
|
|
dest: "/etc/sudoers.d/90-homelab-{{ debian_pc_user }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0440"
|
|
validate: "visudo -cf %s"
|
|
content: |
|
|
{{ debian_pc_user }} ALL=(ALL) NOPASSWD:ALL
|
|
when: debian_pc_enable_passwordless_sudo | bool
|
|
|
|
- name: Ensure persistent homelab directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "{{ debian_pc_user if item.startswith(debian_pc_user_home) else 'root' }}"
|
|
group: "{{ debian_pc_user if item.startswith(debian_pc_user_home) else 'root' }}"
|
|
mode: "{{ '0755' if item.startswith(debian_pc_user_home) else '0775' }}"
|
|
loop: "{{ debian_pc_persistent_directories }}"
|
|
|
|
- name: Check whether Ollama is already installed
|
|
ansible.builtin.shell:
|
|
cmd: command -v ollama >/dev/null 2>&1
|
|
register: debian_pc_ollama_installed
|
|
changed_when: false
|
|
failed_when: false
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Install Ollama from local Linux tarball when provided
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
tar -C /usr -xzf "{{ debian_pc_ollama_tarball_file }}"
|
|
if ! id ollama >/dev/null 2>&1; then
|
|
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama
|
|
fi
|
|
if getent group render >/dev/null 2>&1; then
|
|
usermod -aG render ollama
|
|
fi
|
|
if getent group video >/dev/null 2>&1; then
|
|
usermod -aG video ollama
|
|
fi
|
|
cat >/etc/systemd/system/ollama.service <<'EOF'
|
|
[Unit]
|
|
Description=Ollama Service
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/ollama serve
|
|
User=ollama
|
|
Group=ollama
|
|
Restart=always
|
|
RestartSec=3
|
|
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
args:
|
|
executable: /bin/bash
|
|
when:
|
|
- debian_pc_install_ollama | bool
|
|
- debian_pc_ollama_installed.rc | default(1) != 0
|
|
- debian_pc_ollama_tarball_file | length > 0
|
|
|
|
- name: Install Ollama from official Linux installer
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
tmp_installer="$(mktemp)"
|
|
trap 'rm -f "${tmp_installer}"' EXIT
|
|
curl -fsSL \
|
|
{% if debian_pc_ollama_install_curl_ip_version | length > 0 %}
|
|
"{{ debian_pc_ollama_install_curl_ip_version }}" \
|
|
{% endif %}
|
|
--connect-timeout "{{ debian_pc_ollama_install_connect_timeout }}" \
|
|
--max-time "{{ debian_pc_ollama_install_max_time }}" \
|
|
"{{ debian_pc_ollama_install_url }}" \
|
|
-o "${tmp_installer}"
|
|
sh "${tmp_installer}"
|
|
args:
|
|
executable: /bin/bash
|
|
when:
|
|
- debian_pc_install_ollama | bool
|
|
- debian_pc_ollama_installed.rc | default(1) != 0
|
|
- debian_pc_ollama_tarball_file | length == 0
|
|
|
|
- name: Ensure Ollama model storage exists on data disk
|
|
ansible.builtin.file:
|
|
path: "{{ debian_pc_ollama_models_dir }}"
|
|
state: directory
|
|
owner: ollama
|
|
group: ollama
|
|
mode: "0775"
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Ensure Ollama systemd override directory exists
|
|
ansible.builtin.file:
|
|
path: /etc/systemd/system/ollama.service.d
|
|
state: directory
|
|
mode: "0755"
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Configure Ollama for homelab LAN access and data storage
|
|
ansible.builtin.copy:
|
|
dest: /etc/systemd/system/ollama.service.d/homelab.conf
|
|
mode: "0644"
|
|
content: |
|
|
[Service]
|
|
Environment="OLLAMA_HOST={{ debian_pc_ollama_host }}"
|
|
Environment="OLLAMA_MODELS={{ debian_pc_ollama_models_dir }}"
|
|
Environment="OLLAMA_IGPU_ENABLE={{ debian_pc_ollama_igpu_enable }}"
|
|
register: debian_pc_ollama_override
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Enable expected services when installed
|
|
ansible.builtin.systemd:
|
|
name: "{{ item }}"
|
|
enabled: true
|
|
state: started
|
|
loop: "{{ debian_pc_enable_services }}"
|
|
failed_when: false
|
|
|
|
- name: Enable and start Ollama
|
|
ansible.builtin.systemd:
|
|
name: ollama
|
|
enabled: true
|
|
state: "{{ 'restarted' if debian_pc_ollama_override.changed | default(false) else 'started' }}"
|
|
daemon_reload: true
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Wait for Ollama API
|
|
ansible.builtin.uri:
|
|
url: http://127.0.0.1:11434/api/tags
|
|
method: GET
|
|
status_code: 200
|
|
register: debian_pc_ollama_api
|
|
retries: 12
|
|
delay: 5
|
|
until: debian_pc_ollama_api.status == 200
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Pull default Ollama model for backstage homelab diagnostics
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
if ollama list | awk -v model="{{ debian_pc_ollama_default_model }}" 'NR > 1 && ($1 == model || $1 == model ":latest") { found = 1 } END { exit found ? 0 : 1 }'; then
|
|
echo present
|
|
exit 0
|
|
fi
|
|
ollama pull "{{ debian_pc_ollama_default_model }}"
|
|
echo pulled
|
|
args:
|
|
executable: /bin/bash
|
|
register: debian_pc_ollama_pull_result
|
|
changed_when: "'pulled' in debian_pc_ollama_pull_result.stdout"
|
|
environment:
|
|
OLLAMA_HOST: http://127.0.0.1:11434
|
|
when: debian_pc_install_ollama | bool
|
|
|
|
- name: Check whether config archive is present
|
|
ansible.builtin.stat:
|
|
path: "{{ debian_pc_config_archive }}"
|
|
register: debian_pc_config_archive_stat
|
|
when:
|
|
- debian_pc_restore_configs | bool
|
|
- debian_pc_config_archive | length > 0
|
|
|
|
- name: Restore captured home application configs
|
|
ansible.builtin.unarchive:
|
|
src: "{{ debian_pc_config_archive }}"
|
|
dest: "{{ debian_pc_user_home }}"
|
|
remote_src: true
|
|
owner: "{{ debian_pc_user }}"
|
|
group: "{{ debian_pc_user }}"
|
|
when:
|
|
- debian_pc_restore_configs | bool
|
|
- debian_pc_config_archive | length > 0
|
|
- debian_pc_config_archive_stat.stat.exists | default(false)
|
|
|
|
- name: Report packages skipped because apt has no candidate
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
comm -23 \
|
|
<(sort -u /tmp/homelab-debian-pc-desired-packages.txt) \
|
|
<(printf '%s\n' {{ debian_pc_installable_packages | map('quote') | join(' ') }} | sort -u)
|
|
args:
|
|
executable: /bin/bash
|
|
register: debian_pc_skipped_packages_result
|
|
changed_when: false
|
|
|
|
- name: Show skipped packages
|
|
ansible.builtin.debug:
|
|
msg: "{{ debian_pc_skipped_packages_result.stdout_lines }}"
|
|
when: debian_pc_skipped_packages_result.stdout_lines | length > 0
|