diff --git a/.github/workflows/infra.yaml b/.github/workflows/infra.yaml index e16d55a..41518c8 100644 --- a/.github/workflows/infra.yaml +++ b/.github/workflows/infra.yaml @@ -72,4 +72,8 @@ jobs: PROXMOX_TOKEN_ID: ${{ secrets.PROXMOX_TOKEN_ID }} PROXMOX_TOKEN_SECRET: ${{ secrets.PROXMOX_TOKEN_SECRET }} SSH_PUBLIC: ${{ secrets.SSH_PUBLIC }} + SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} + INFISICAL_ENCRYPTION_KEY: ${{ secrets.INFISICAL_ENCRYPTION_KEY }} + INFISICAL_AUTH_SECRET: ${{ secrets.INFISICAL_AUTH_SECRET }} + INFISICAL_MONGO_PASSWORD: ${{ secrets.INFISICAL_MONGO_PASSWORD }} run: ansible-playbook --inventory ./inventory ${{ steps.playbooks.outputs.to_run }} -vv diff --git a/infra/secrets/0000_proxmox_playbook.yaml b/infra/secrets/0000_proxmox_playbook.yaml new file mode 100644 index 0000000..eb36ec4 --- /dev/null +++ b/infra/secrets/0000_proxmox_playbook.yaml @@ -0,0 +1,110 @@ +- name: Provision secrets Proxmox VM + hosts: secrets + 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: pve + 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: Update VM + community.general.proxmox_kvm: + update: true + agent: enabled=1 + tags: + - debian-12 + - managed + onboot: true + cores: 4 + memory: 4096 + + - name: Retart VM + community.general.proxmox_kvm: + state: restarted + timeout: 60 diff --git a/infra/secrets/0001_initialise_playbook.yaml b/infra/secrets/0001_initialise_playbook.yaml new file mode 100644 index 0000000..b2aff9d --- /dev/null +++ b/infra/secrets/0001_initialise_playbook.yaml @@ -0,0 +1,42 @@ +- name: Initialise VM + hosts: secrets + 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 \ No newline at end of file diff --git a/infra/secrets/0002_docker_playbook.yaml b/infra/secrets/0002_docker_playbook.yaml new file mode 100644 index 0000000..e7b346f --- /dev/null +++ b/infra/secrets/0002_docker_playbook.yaml @@ -0,0 +1,47 @@ +- name: Install software + hosts: secrets + 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/secrets/0003_infiscal_playbook.yaml b/infra/secrets/0003_infiscal_playbook.yaml new file mode 100644 index 0000000..c1987be --- /dev/null +++ b/infra/secrets/0003_infiscal_playbook.yaml @@ -0,0 +1,47 @@ +- name: Deploy app + hosts: secrets + gather_facts: false + vars: + app: infisical + 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 Encryption Key secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "ENCRYPTION_KEY_VALUE" + replace: "{{ lookup('ansible.builtin.env', 'INFISICAL_ENCRYPTION_KEY') }}" + - name: Replace Auth secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "AUTH_SECRET_VALUE" + replace: "{{ lookup('ansible.builtin.env', 'INFISICAL_AUTH_SECRET') }}" + - name: Replace Mongo Password secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "MONGO_PASSWORD_VALUE" + replace: "{{ lookup('ansible.builtin.env', 'INFISICAL_MONGO_PASSWORD') }}" + - name: Replace SMTP Password secret + ansible.builtin.replace: + path: "{{ user.home }}/{{ app }}/.env" + regexp: "SMTP_PASSWORD_VALUE" + replace: "{{ lookup('ansible.builtin.env', 'SMTP_PASSWORD') }}" + - name: Docker compose up -d + ansible.builtin.command: docker compose up -d + args: + chdir: "{{ user.home }}/{{ app }}" diff --git a/infra/secrets/infisical/.env b/infra/secrets/infisical/.env new file mode 100644 index 0000000..6a20688 --- /dev/null +++ b/infra/secrets/infisical/.env @@ -0,0 +1,68 @@ +# Keys +# Required key for platform encryption/decryption ops +# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION +ENCRYPTION_KEY=ENCRYPTION_KEY_VALUE + +# JWT +# Required secrets to sign JWT tokens +# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION +AUTH_SECRET=AUTH_SECRET_VALUE + +# MongoDB +# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref +# to the MongoDB container instance or Mongo Cloud +# Required +MONGO_URL=mongodb://root:MONGO_PASSWORD_VALUE@mongo:27017/?authSource=admin + +# Redis +REDIS_URL=redis://redis:6379 + +# Optional credentials for MongoDB container instance and Mongo-Express +MONGO_USERNAME=root +MONGO_PASSWORD=MONGO_PASSWORD_VALUE + +# Website URL +# Required +SITE_URL=https://secrets.koval.net + +# Mail/SMTP +SMTP_HOST=mx.koval.net +SMTP_PORT=465 +SMTP_SECURE=true +SMTP_USERNAME=no-reply@koval.net +SMTP_PASSWORD=SMTP_PASSWORD_VALUE +SMTP_FROM_NAME=KovalHome Infisical + +# Integration +# Optional only if integration is used +CLIENT_ID_HEROKU= +CLIENT_ID_VERCEL= +CLIENT_ID_NETLIFY= +CLIENT_ID_GITHUB= +CLIENT_ID_GITLAB= +CLIENT_ID_BITBUCKET= +CLIENT_SECRET_HEROKU= +CLIENT_SECRET_VERCEL= +CLIENT_SECRET_NETLIFY= +CLIENT_SECRET_GITHUB= +CLIENT_SECRET_GITLAB= +CLIENT_SECRET_BITBUCKET= +CLIENT_SLUG_VERCEL= + +# Sentry (optional) for monitoring errors +SENTRY_DSN= + +# Infisical Cloud-specific configs +# Ignore - Not applicable for self-hosted version +POSTHOG_HOST= +POSTHOG_PROJECT_API_KEY= + +# SSO-specific variables +CLIENT_ID_GOOGLE_LOGIN= +CLIENT_SECRET_GOOGLE_LOGIN= + +CLIENT_ID_GITHUB_LOGIN= +CLIENT_SECRET_GITHUB_LOGIN= + +CLIENT_ID_GITLAB_LOGIN= +CLIENT_SECRET_GITLAB_LOGIN= diff --git a/infra/secrets/infisical/.gitignore b/infra/secrets/infisical/.gitignore new file mode 100644 index 0000000..1e18f27 --- /dev/null +++ b/infra/secrets/infisical/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/infra/secrets/infisical/docker-compose.yml b/infra/secrets/infisical/docker-compose.yml new file mode 100644 index 0000000..de38fa6 --- /dev/null +++ b/infra/secrets/infisical/docker-compose.yml @@ -0,0 +1,33 @@ +version: "3" + +services: + backend: + restart: unless-stopped + depends_on: + - mongo + image: infisical/infisical:latest + env_file: .env + ports: + - 80:8080 + environment: + - NODE_ENV=production + + redis: + image: redis + env_file: .env + environment: + - ALLOW_EMPTY_PASSWORD=yes + ports: + - 6379:6379 + volumes: + - /mnt/nvme/redis-data:/data + + mongo: + image: mongo + restart: always + env_file: .env + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} + volumes: + - /mnt/nvme/mongo-data:/data/db diff --git a/inventory/proxmox.yaml b/inventory/proxmox.yaml index 8fbb791..c64624e 100644 --- a/inventory/proxmox.yaml +++ b/inventory/proxmox.yaml @@ -14,6 +14,9 @@ proxmox: photos: hosts: photos.srv.home.local.koval.net: + secrets: + hosts: + secrets.srv.home.local.koval.net: vars: ansible_user: debian ansible_ssh_private_key_file: ~/.ssh/id_rsa