From d4d5511b7804162c46f3046116e9a72601c67ea3 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Sat, 6 Jan 2024 17:36:06 +0000 Subject: [PATCH] Music VM (#28) Provision a VM with Navidrome and some tools for downloading stuff from YouTube. Reviewed-on: https://git.koval.net/cyclane/kovalhome/pulls/28 --- infra/music/0000_proxmox_playbook.yaml | 116 ++++++++++++++++++++++ infra/music/0001_initialise_playbook.yaml | 64 ++++++++++++ infra/music/0002_docker_playbook.yaml | 47 +++++++++ infra/music/0003_music_playbook.yaml | 49 +++++++++ infra/music/music/.env | 4 + infra/music/music/.gitignore | 1 + infra/music/music/docker-compose.yml | 53 ++++++++++ inventory/proxmox.yaml | 3 + 8 files changed, 337 insertions(+) create mode 100644 infra/music/0000_proxmox_playbook.yaml create mode 100644 infra/music/0001_initialise_playbook.yaml create mode 100644 infra/music/0002_docker_playbook.yaml create mode 100644 infra/music/0003_music_playbook.yaml create mode 100644 infra/music/music/.env create mode 100644 infra/music/music/.gitignore create mode 100644 infra/music/music/docker-compose.yml diff --git a/infra/music/0000_proxmox_playbook.yaml b/infra/music/0000_proxmox_playbook.yaml new file mode 100644 index 0000000..984db1a --- /dev/null +++ b/infra/music/0000_proxmox_playbook.yaml @@ -0,0 +1,116 @@ +- name: Provision music Proxmox VM + hosts: music + connection: ansible.builtin.local + gather_facts: false + vars: + api_user: "{{ lookup('ansible.builtin.env', 'PROXMOX_USER') }}" + api_host: "{{ lookup('ansible.builtin.env', 'PROXMOX_HOST' ) }}" + api_token_id: "{{ lookup('ansible.builtin.env', 'PROXMOX_TOKEN_ID') }}" + api_token_secret: "{{ lookup('ansible.builtin.env', 'PROXMOX_TOKEN_SECRET') }}" + ssh_public: "{{ lookup('ansible.builtin.env', 'SSH_PUBLIC') }}" + vmname: "{{ inventory_hostname | regex_replace('^([^\\.]+)\\..+$', '\\1') }}" + node: pve2 + module_defaults: + community.general.proxmox_kvm: + api_user: "{{ api_user }}" + api_host: "{{ api_host }}" + api_token_id: "{{ api_token_id }}" + api_token_secret: "{{ api_token_secret }}" + name: "{{ vmname }}" + node: "{{ node }}" + community.general.proxmox_nic: + api_user: "{{ api_user }}" + api_host: "{{ api_host }}" + api_token_id: "{{ api_token_id }}" + api_token_secret: "{{ api_token_secret }}" + name: "{{ vmname }}" + community.general.proxmox_disk: + api_user: "{{ api_user }}" + api_host: "{{ api_host }}" + api_token_id: "{{ api_token_id }}" + api_token_secret: "{{ api_token_secret }}" + name: "{{ vmname }}" + tasks: + # Initial setup + - name: Create VM + community.general.proxmox_kvm: + clone: "{{ node }}-debian-12" + storage: nvme + register: create + - name: Wait for status + community.general.proxmox_kvm: + state: current + register: vm + retries: 30 + delay: 10 + until: vm.status is defined + + # Networking and initial config + - name: Add PUB NIC + community.general.proxmox_nic: + interface: net0 + firewall: false + bridge: PUB + - name: Add SRV NIC + community.general.proxmox_nic: + interface: net1 + firewall: false + bridge: SRV + - name: Configure cloud-init + community.general.proxmox_kvm: + update: true + ciuser: debian + sshkeys: "{{ ssh_public }}" + ipconfig: + ipconfig0: ip=dhcp,ip6=auto + ipconfig1: ip=dhcp + + # Initial boot + # For some reason debian cloud images don't use + # cloud-init for networking on first boot (cloud-init files + # are regenerated AFTER networking starts). But we need the + # hostname to be registered with DHCP later on so ¯\_(ツ)_/¯ + - name: Initial boot + when: create.changed is true + block: + - name: Start + community.general.proxmox_kvm: + state: started + register: start + - name: Wait 1.5 min # Initial apt update, apt upgrade, cloud-init + ansible.builtin.wait_for: + timeout: 90 + + # VM Configuration + - name: Resize root disk + community.general.proxmox_disk: + disk: scsi0 + size: 16G + state: resized + - name: Create data disk + community.general.proxmox_disk: + disk: scsi1 + backup: true + storage: nvme + size: 64 + - name: Create media disk + community.general.proxmox_disk: + disk: scsi2 + backup: false + storage: nvme + size: 1024 + - name: Update VM + community.general.proxmox_kvm: + update: true + agent: enabled=1 + tags: + - debian-12 + - managed + onboot: true + cores: 8 + memory: 16384 + + - name: Retart VM + community.general.proxmox_kvm: + state: restarted + timeout: 60 diff --git a/infra/music/0001_initialise_playbook.yaml b/infra/music/0001_initialise_playbook.yaml new file mode 100644 index 0000000..62ac05e --- /dev/null +++ b/infra/music/0001_initialise_playbook.yaml @@ -0,0 +1,64 @@ +- name: Initialise VM + hosts: music + gather_facts: false + tasks: + - name: Wait for connection + ansible.builtin.wait_for_connection: + timeout: 300 + - name: Install system packages + ansible.builtin.apt: + update_cache: true + pkg: + - qemu-guest-agent + - parted + become: true + - name: Enable qemu-guest-agent + ansible.builtin.systemd: + name: qemu-guest-agent + state: started + enabled: true + become: true + + - name: Create data partition + community.general.parted: + device: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:1 + label: gpt + name: data + number: 1 + state: present + become: true + - name: Create data filesystem + community.general.filesystem: + dev: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:1-part1 + fstype: ext4 + become: true + - name: Mount data partition + ansible.posix.mount: + src: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:1-part1 + path: /var/lib/docker + fstype: ext4 + opts: rw,errors=remount-ro,x-systemd.growfs + state: mounted + become: true + + - name: Create media partition + community.general.parted: + device: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:2 + label: gpt + name: media + number: 1 + state: present + become: true + - name: Create media filesystem + community.general.filesystem: + dev: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:2-part1 + fstype: ext4 + become: true + - name: Mount media partition + ansible.posix.mount: + src: /dev/disk/by-path/pci-0000:00:05.0-scsi-0:0:0:2-part1 + path: /mnt/media + fstype: ext4 + opts: rw,errors=remount-ro,x-systemd.growfs + state: mounted + become: true diff --git a/infra/music/0002_docker_playbook.yaml b/infra/music/0002_docker_playbook.yaml new file mode 100644 index 0000000..49a6e9e --- /dev/null +++ b/infra/music/0002_docker_playbook.yaml @@ -0,0 +1,47 @@ +- name: Install software + hosts: music + gather_facts: false + tasks: + - name: Wait for connection + ansible.builtin.wait_for_connection: + timeout: 300 + - name: Install dependencies + ansible.builtin.apt: + update_cache: true + pkg: + - curl + - python3-apt + - gpg + become: true + - name: Add docker key + ansible.builtin.apt_key: + url: https://download.docker.com/linux/debian/gpg + keyring: /etc/apt/keyrings/docker.gpg + become: true + - name: Add docker repo + ansible.builtin.apt_repository: + repo: deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable + become: true + - name: Install docker + ansible.builtin.apt: + update_cache: true + pkg: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + become: true + - name: Add user to docker group + ansible.builtin.user: + user: debian + groups: + - docker + append: true + become: true + - name: Enable docker + ansible.builtin.systemd: + name: docker + state: started + enabled: true + become: true diff --git a/infra/music/0003_music_playbook.yaml b/infra/music/0003_music_playbook.yaml new file mode 100644 index 0000000..14f5157 --- /dev/null +++ b/infra/music/0003_music_playbook.yaml @@ -0,0 +1,49 @@ +- name: Deploy app + hosts: music + gather_facts: false + vars: + app: music + tasks: + - name: Wait for connection + ansible.builtin.wait_for_connection: + timeout: 300 + - name: Get user + ansible.builtin.user: + name: debian + register: user + - name: Docker compose down + ansible.builtin.command: docker compose down + args: + chdir: "{{ user.home }}/{{ app }}" + ignore_errors: true + - name: Copy project + ansible.builtin.copy: + src: "./{{ app }}" + dest: "{{ user.home }}" + mode: "0744" + + - name: Replace LastFM API key secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "LASTFM_APIKEY_VALUE" + replace: "{{ lookup('infisical.vault.read_secrets', env_slug='prod', path='/music', secret_name='LASTFM_APIKEY')['value'] }}" + - name: Replace LastFM secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "LASTFM_SECRET_VALUE" + replace: "{{ lookup('infisical.vault.read_secrets', env_slug='prod', path='/music', secret_name='LASTFM_SECRET')['value'] }}" + - name: Replace Mongo Password secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "SPOTIFY_ID_VALUE" + replace: "{{ lookup('infisical.vault.read_secrets', env_slug='prod', path='/music', secret_name='SPOTIFY_ID')['value'] }}" + - name: Replace SMTP Password secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "SPOTIFY_SECRET_VALUE" + replace: "{{ lookup('infisical.vault.read_secrets', env_slug='prod', path='/music', secret_name='SPOTIFY_SECRET')['value'] }}" + + - name: Docker compose up -d + ansible.builtin.command: docker compose up -d + args: + chdir: "{{ user.home }}/{{ app }}" diff --git a/infra/music/music/.env b/infra/music/music/.env new file mode 100644 index 0000000..fdc59c6 --- /dev/null +++ b/infra/music/music/.env @@ -0,0 +1,4 @@ +LASTFM_APIKEY=LASTFM_APIKEY_VALUE +LASTFM_SECRET=LASTFM_SECRET_VALUE +SPOTIFY_ID=SPOTIFY_ID_VALUE +SPOTIFY_SECRET=SPOTIFY_SECRET_VALUE \ No newline at end of file diff --git a/infra/music/music/.gitignore b/infra/music/music/.gitignore new file mode 100644 index 0000000..1e18f27 --- /dev/null +++ b/infra/music/music/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/infra/music/music/docker-compose.yml b/infra/music/music/docker-compose.yml new file mode 100644 index 0000000..b71e423 --- /dev/null +++ b/infra/music/music/docker-compose.yml @@ -0,0 +1,53 @@ +version: "3" + +services: + navidrome: + image: deluan/navidrome:latest + restart: unless-stopped + user: 1000:1000 + ports: + - 4533:4533 + env_file: .env + environment: + - ND_BASEURL=https://music.koval.net + - ND_LASTFM_APIKEY=${LASTFM_APIKEY} + - ND_LASTFM_SECRET=${LASTFM_SECRET} + - ND_SPOTIFY_ID=${SPOTIFY_ID} + - ND_SPOTIFY_SECRET=${SPOTIFY_SECRET} + volumes: + - /mnt/nvme/navidrome:/data + - /mnt/media/music:/music:ro + metube: + image: ghcr.io/alexta69/metube + restart: unless-stopped + user: 1000:1000 + ports: + - 8081:8081 + environment: + - STATE_DIR=/data/.metube + - TEMP_DIR=/data/downloads + volumes: + - /mnt/nvme/metube:/data + - /mnt/media/downloads:/downloads + picard: + image: mikenye/picard:latest + restart: unless-stopped + user: 1000:1000 + ports: + - 5800:5800 + volumes: + - /mnt/nvme/picard:/config:rw + - /mnt/media/music:/storage/music:rw + - /mnt/media/downloads:/storage/downloads:rw + filebrowser: + image: filebrowser/filebrowser + restart: unless-stopped + user: 1000:1000 + ports: + - 8080:80 + environment: + - FB_DATABASE=/config/database.db + volumes: + - /mnt/nvme/filebrowser:/config + - /mnt/media/downloads:/srv/downloads + - /mnt/media/music:/srv/music \ No newline at end of file diff --git a/inventory/proxmox.yaml b/inventory/proxmox.yaml index c64624e..e989030 100644 --- a/inventory/proxmox.yaml +++ b/inventory/proxmox.yaml @@ -17,6 +17,9 @@ proxmox: secrets: hosts: secrets.srv.home.local.koval.net: + music: + hosts: + music.srv.home.local.koval.net: vars: ansible_user: debian ansible_ssh_private_key_file: ~/.ssh/id_rsa