From 92746d4442d3300946642f9f97b2316784e143f6 Mon Sep 17 00:00:00 2001 From: Nathan JANCZEWSKI Date: Sun, 7 Apr 2024 20:03:31 +0200 Subject: [PATCH] Initial commit --- README.md | 83 +++++++++ ansible/.gitignore | 4 + ansible/README.md | 29 +++ ansible/files/mattboll.zsh-theme | 167 +++++++++++++++++ ansible/files/nginx.conf | 15 ++ ansible/files/ssl.conf | 17 ++ ansible/group_vars/all.yml | 32 ++++ ansible/inventory.yaml.dist | 80 ++++++++ ansible/playbooks/common.yaml | 156 ++++++++++++++++ ansible/playbooks/detect_ssh.yaml | 80 ++++++++ ansible/playbooks/dns.yaml | 29 +++ ansible/playbooks/jellyfin.yaml | 31 ++++ ansible/playbooks/navidrome.yaml | 29 +++ ansible/playbooks/reverse_proxy.yaml | 64 +++++++ ansible/requirements.yaml | 8 + ansible/setup.yaml | 7 + ansible/tasks/add_docker_app.yaml | 36 ++++ ansible/tasks/add_nas_mount.yaml | 15 ++ ansible/tasks/add_nginx_app.yaml | 19 ++ ansible/templates/dnsmasq.conf.j2 | 16 ++ .../templates/jellyfin/docker-compose.yaml.j2 | 22 +++ ansible/templates/jellyfin/nginx.conf | 32 ++++ .../navidrome/docker-compose.yaml.j2 | 24 +++ ansible/templates/navidrome/nginx.conf | 32 ++++ docs/backup_day.md | 25 +++ docs/be_your_own_ca.md | 91 +++++++++ docs/renew_ssl.md | 14 ++ docs/setup_common.md | 84 +++++++++ docs/setup_dns.md | 56 ++++++ docs/setup_jellyfin.md | 172 +++++++++++++++++ docs/setup_navidrome.md | 111 +++++++++++ docs/setup_reverseproxy.md | 174 ++++++++++++++++++ docs/setup_vm.md | 61 ++++++ docs/setup_xcp.md | 33 ++++ 34 files changed, 1848 insertions(+) create mode 100644 README.md create mode 100644 ansible/.gitignore create mode 100644 ansible/README.md create mode 100644 ansible/files/mattboll.zsh-theme create mode 100644 ansible/files/nginx.conf create mode 100644 ansible/files/ssl.conf create mode 100644 ansible/group_vars/all.yml create mode 100644 ansible/inventory.yaml.dist create mode 100644 ansible/playbooks/common.yaml create mode 100644 ansible/playbooks/detect_ssh.yaml create mode 100644 ansible/playbooks/dns.yaml create mode 100644 ansible/playbooks/jellyfin.yaml create mode 100644 ansible/playbooks/navidrome.yaml create mode 100644 ansible/playbooks/reverse_proxy.yaml create mode 100644 ansible/requirements.yaml create mode 100644 ansible/setup.yaml create mode 100644 ansible/tasks/add_docker_app.yaml create mode 100644 ansible/tasks/add_nas_mount.yaml create mode 100644 ansible/tasks/add_nginx_app.yaml create mode 100644 ansible/templates/dnsmasq.conf.j2 create mode 100644 ansible/templates/jellyfin/docker-compose.yaml.j2 create mode 100644 ansible/templates/jellyfin/nginx.conf create mode 100644 ansible/templates/navidrome/docker-compose.yaml.j2 create mode 100644 ansible/templates/navidrome/nginx.conf create mode 100644 docs/backup_day.md create mode 100644 docs/be_your_own_ca.md create mode 100644 docs/renew_ssl.md create mode 100644 docs/setup_common.md create mode 100644 docs/setup_dns.md create mode 100644 docs/setup_jellyfin.md create mode 100644 docs/setup_navidrome.md create mode 100644 docs/setup_reverseproxy.md create mode 100644 docs/setup_vm.md create mode 100644 docs/setup_xcp.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d1ad98 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Oxodao's Homelab + +Documentation de mon homelab + +## Architecture + +Mon homelab est composé de trois machines: +- Un NAS Synology pour le stockage (Actuellement 2x2TB WDC WD20EFRX-68EUZN0) +- Un PC "multimédia" qui est utilisé sur la TV (Thinkcentre m75q / Athlon 300GE) +- Un PC serveur (i3-10100 / 24Gb DDR4 / 1to nvme) +- Un router TP Link Archer C6 (AC1200) sous OpenWRT (Pas encore mis en place) + +Le serveur est un host XCP-NG (hostname `rubeus`) avec deux VM principales, une dédiée aux services accessible en publique, l'autre avec les services accessible uniquement sur le réseau interne. + +Note: le serveur n'a pas de VM pour XenOrchestra, j'utilise sur mon PC une VM sur laquelle j'ai installé XOA via [XenOrchestraInstallerUpdater](https://github.com/ronivay/XenOrchestraInstallerUpdater), cela permet d'économiser de la RAM / du CPU utilisé pour autre chose. + +Le NAS Synology possède toutes les data et expose plusieurs montage samba: +- sauvegarde (Mon espace de stockage générique ou je met un peu tout) +- iso (Mon SR d'iso pour xcp-ng) +- shares (Espace pour stocker mes films / séries / musiques / ...) +- documents (Montage utilisé pour Paperless) +- images (Montage utilisé pour Immich) + +Pour la gestion des droits, j'ai mon utilisateur perso pour la connexion depuis mes machines, et des utilisateurs scopés en RO sur iso et shares pour les différents services ansi qu'un utilisateur RW pour Paperless, chacun n'ayant accès qu'aux shares qu'ils ont besoin. + +Enfin le PC multimédia est un simple debian 12 qui accèdes aux services hébergés sur le serveur. + +Ce guide note particulièrement le setup du serveur puisque le reste est basique et ne nécessite rien de spécial. + +## Sommaire + +1. [Setup XCP-NG](docs/setup_xcp.md) +2. [Setup basique des VMs](docs/setup_vm.md) + +> A partir de ce point la, les divers ansible +> vont setup tout ça. +> +> Il faut tout de même suivre chaque page pour s'assurer +> la config des logiciels est complète car tout n'est +> pas fait dans ansible pour l'instant (e.g. config +> interne de Jellyfin, création de compte utilisateur, etc...) + +3. [Setup des utilitaires communs](docs/setup_common.md) +4. [Setup serveur DNS](docs/setup_dns.md) +5. [Setup reverse-proxy](docs/setup_reverseproxy.md) + +-- Setup des apps -- + +6. [Setup Jellyfin](docs/setup_jellyfin.md) +7. [Setup Navidrome](docs/setup_navidrome.md) +8. [Setup Paperless](docs/setup_paperless.md) +9. [Setup Gitea](docs/setup_gitea.md) +10. [Setup Immich](docs/setup_immich.md) +11. [Setup JDownloader](docs/setup_jdownloader.md) +12. [Setup HomeAssistant](docs/setup_ha.md) + +-- Setup sécu -- + +13. [Setup serveurs VPN](docs/setup_vpn.md) +14. [Setup firewall](docs/setup_firewall.md) +15. [Setup cloudflared](docs/setup_cloudflared.md) +16. [Setup backups](docs/setup_backups.md) + +> A partir de ce point la, il s'agît d'informations sur +> l'utilisation usuelle des VMs et du serveur ainsi que +> comment faire du disaster recovery. + +17. [Ajouter un utilisateur sur le VPN](docs/add_user_vpn.md) +18. [Renouveller les certificats SSL](docs/renew_ssl.md) +19. [Restorer un backup](docs/disaster_recovery.md) +20. [Backup day](docs/backup_day.md) + +## Roadmap + +Chose que je vais potentiellement ajouter après que tout soit fonctionnel + +- Authentik / Authelia: Active Directory / OAuth + - [Gitea](https://docs.gitea.com/usage/authentication): LDAP + - [Jellyfin](https://github.com/jellyfin/jellyfin-plugin-ldapauth): LDAP + - [Immich](https://www.reddit.com/r/selfhosted/comments/zrkokx/immich_and_ldap/): OAuth + - [Paperless](https://github.com/paperless-ngx/paperless-ngx/pull/100): Ils ont pas l'air de vouloir ni ldap ni oauth + - [Navidrome](https://github.com/navidrome/navidrome/pull/590): huuuh ça à pas l'air très fun non plus + - JDownloader: Accès externe donc via LEUR login, à voir pour dev un client self-hosted mais API non documentée diff --git a/ansible/.gitignore b/ansible/.gitignore new file mode 100644 index 0000000..258f62a --- /dev/null +++ b/ansible/.gitignore @@ -0,0 +1,4 @@ +inventory.yaml +files/*.key +files/*.crt +.envrc \ No newline at end of file diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000..a0d25eb --- /dev/null +++ b/ansible/README.md @@ -0,0 +1,29 @@ +# Homelab ansible + +Pour utiliser cet ansible, il faut copier-coller le `inventory.yaml.dist` en `inventory.yaml` et le compléter en suivant les commentaires. + +Installation des roles: +```sh +$ ansible-galaxy install -r requirements.yaml --force +$ ansible-galaxy collection install -r requirements.yaml --force +``` + +Exécution du playbook: +```sh +$ export ANSIBLE_BECOME_PASSWORD="MON MOT DE PASSE SUDO" # A exécuter une seule fois +$ ansible-playbook -i inventory.yaml setup.yaml +``` + +Si on souhaite exécuter seulement certaines parties, on peut limiter avec les tags, ou les hosts: +```sh +$ export ANSIBLE_BECOME_PASSWORD="MON MOT DE PASSE SUDO" # A exécuter une seule fois +$ ansible-playbook -i inventory.yaml setup.yaml --limit public -t dns +``` + +Pour une réinstallation future, chiffrer le `inventory.yaml` et le stocker dans un endroit sécurisé. Il suffira alors de le reprendre et modifier en cas de mise à jour sur la version git. + +Pour cela, on peut utiliser `ansible-vault` pour utiliser un utilitaire tout-en-un: +```sh +$ ansible-vault encrypt --vault-id homelab@prompt inventory.yaml # Chiffrer +$ ansible-vault decrypt inventory.yaml # Déchiffrer +``` \ No newline at end of file diff --git a/ansible/files/mattboll.zsh-theme b/ansible/files/mattboll.zsh-theme new file mode 100644 index 0000000..866653a --- /dev/null +++ b/ansible/files/mattboll.zsh-theme @@ -0,0 +1,167 @@ +#------------------------------------------------------------------------------- +# Sunrise theme for oh-my-zsh by Adam Lindberg (eproxus@gmail.com) +# Intended to be used with Solarized: http://ethanschoonover.com/solarized +# (Needs Git plugin for current_branch method) +# +# Probably modified by mattboll +# https://raw.githubusercontent.com/mattboll/zshrc/master/mattboll.zsh-theme +#------------------------------------------------------------------------------- + +# Color shortcuts +R=$fg_no_bold[red] +G=$fg_no_bold[green] +M=$fg_no_bold[magenta] +Y=$fg_no_bold[yellow] +B=$fg_no_bold[blue] +RESET=$reset_color + +if [ "$(whoami)" = "root" ]; then + PREPROMPT="%{$R%}ROOT"; +else + if [[ -z "$SSH_CLIENT" ]]; then + PREPROMPT=""; + else + PREPROMPT="$(whoami)"; + fi +fi +if [[ -z "$SSH_CLIENT" ]]; then + prompt_host="" +else + prompt_host=%{$fg_bold[white]%}@%{$reset_color$fg[yellow]%}$(hostname -s) +fi + +local return_code="%(?..%{$R%}%? ☢ %{$RESET%})" + +git_remote_status() { + remote=${$(command git rev-parse --verify ${hook_com[branch]}@{upstream} --symbolic-full-name 2>/dev/null)/refs\/remotes\/} + if [[ -n ${remote} ]] ; then + ahead=$(command git rev-list ${hook_com[branch]}@{upstream}..HEAD 2>/dev/null | wc -l) + behind=$(command git rev-list HEAD..${hook_com[branch]}@{upstream} 2>/dev/null | wc -l) + + if [ $ahead -eq 0 ] && [ $behind -gt 0 ] + then + echo "$ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE" + elif [ $ahead -gt 0 ] && [ $behind -eq 0 ] + then + echo "$ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE" + elif [ $ahead -gt 0 ] && [ $behind -gt 0 ] + then + echo "$ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE" + fi + fi +} + +# Checks if there are commits ahead from remote +function git_prompt_ahead_number() { + nbcommits=${$(command git log origin/$(current_branch)..HEAD 2> /dev/null | grep '^commit' | wc -l )// /} + if [[ -n ${nbcommits} ]] ; then + if [ $nbcommits -gt 0 ] + then + echo %{$R%}\($nbcommits\) + else + echo %{$G%}\($nbcommits\) + fi + fi +} + +# ZSH function that shortens +# a very long path for display by removing +# the left most parts and replacing them +# with a leading ... +# +# + keep some left part of the path if asked +# thanks to liquidprompt for that +_lp_shorten_path() +{ + # the character that will replace the part of the path that is masked + local mask=" … " + # index of the directory to keep from the root (starts at 0 whith bash, 1 with zsh) + local keep=2 + + local p="${PWD/$HOME/~}" + local len="${#p}" + + local max_len=$((${COLUMNS:-80}*25/100)) + + if [[ "$len" -gt "$max_len" ]]; then + echo "%-${keep}~%${max_len}<${mask}<%~%<<" + else + echo "%~" + fi +} + + +# Get the status of the working tree (copied and modified from git.zsh) +custom_git_prompt_status() { + INDEX=$(git status --porcelain 2> /dev/null) + STATUS="" + # Non-staged + if $(echo "$INDEX" | grep '^?? ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_UNTRACKED$STATUS" + fi + if $(echo "$INDEX" | grep '^UU ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_UNMERGED$STATUS" + fi + if $(echo "$INDEX" | grep '^ D ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_DELETED$STATUS" + fi + if $(echo "$INDEX" | grep '^.M ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" + elif $(echo "$INDEX" | grep '^AM ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" + elif $(echo "$INDEX" | grep '^ T ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_MODIFIED$STATUS" + fi + # Staged + if $(echo "$INDEX" | grep '^D ' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_STAGED_DELETED$STATUS" + fi + if $(echo "$INDEX" | grep '^R' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_STAGED_RENAMED$STATUS" + fi + if $(echo "$INDEX" | grep '^M' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_STAGED_MODIFIED$STATUS" + fi + if $(echo "$INDEX" | grep '^A' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_PROMPT_STAGED_ADDED$STATUS" + fi + + if $(echo -n "$STATUS" | grep '.*' &> /dev/null); then + STATUS="$ZSH_THEME_GIT_STATUS_PREFIX$STATUS" + fi + + echo $STATUS +} + +# get the name of the branch we are on (copied and modified from git.zsh) +function custom_git_prompt() { + ref=$(git symbolic-ref HEAD 2> /dev/null) || return + echo "$ZSH_THEME_GIT_PROMPT_PREFIX${ref#refs/heads/}$(parse_git_dirty)$(git_prompt_ahead_number)$(custom_git_prompt_status)$ZSH_THEME_GIT_PROMPT_SUFFIX" +} + +# %B sets bold text +PROMPT='%B$PREPROMPT$prompt_host %{$G%} $(_lp_shorten_path) $(custom_git_prompt)%{$M%}%B»%b%{$RESET%} ' +RPS1="${return_code} %D{%a %b %d, %I:%M}" + +ZSH_THEME_GIT_PROMPT_PREFIX="%{$Y%}‹" +ZSH_THEME_GIT_PROMPT_SUFFIX="%{$Y%}›%{$RESET%} " + +ZSH_THEME_GIT_PROMPT_DIRTY="%{$R%}*" +ZSH_THEME_GIT_PROMPT_CLEAN="" + +ZSH_THEME_GIT_PROMPT_AHEAD="%{$B%}➔" + + +ZSH_THEME_GIT_STATUS_PREFIX=" " + +# Staged +ZSH_THEME_GIT_PROMPT_STAGED_ADDED="%{$G%}A" +ZSH_THEME_GIT_PROMPT_STAGED_MODIFIED="%{$G%}M" +ZSH_THEME_GIT_PROMPT_STAGED_RENAMED="%{$G%}R" +ZSH_THEME_GIT_PROMPT_STAGED_DELETED="%{$G%}D" + +# Not-staged +ZSH_THEME_GIT_PROMPT_UNTRACKED="%{$R%}?" +ZSH_THEME_GIT_PROMPT_MODIFIED="%{$R%}M" +ZSH_THEME_GIT_PROMPT_DELETED="%{$R%}D" +ZSH_THEME_GIT_PROMPT_UNMERGED="%{$R%}UU" diff --git a/ansible/files/nginx.conf b/ansible/files/nginx.conf new file mode 100644 index 0000000..78dde60 --- /dev/null +++ b/ansible/files/nginx.conf @@ -0,0 +1,15 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + include sites-enabled/*; +} \ No newline at end of file diff --git a/ansible/files/ssl.conf b/ansible/files/ssl.conf new file mode 100644 index 0000000..07b6328 --- /dev/null +++ b/ansible/files/ssl.conf @@ -0,0 +1,17 @@ +ssl_session_timeout 1d; +ssl_session_cache shared:MozSSL:10m; # about 40000 sessions +ssl_session_tickets off; + +# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam +ssl_dhparam /opt/ssl/dhparam.pem; + +# intermediate configuration +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; +ssl_prefer_server_ciphers off; + +# HSTS (ngx_http_headers_module is required) (63072000 seconds) +add_header Strict-Transport-Security "max-age=63072000" always; + +ssl_certificate /opt/ssl/cert.crt; +ssl_certificate_key /opt/ssl/cert.key; \ No newline at end of file diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000..bc3a70d --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,32 @@ +ntp_timezone: 'Europe/Paris' +ssh_port: '57982' + +tld: 'lan' + +hostname: '{{domain}}.{{tld}}' +base_ip: '{{ ansible_default_ipv4.address|default(ansible_all_ipv4_addresses[0]) }}' + +smtp_hostname: "smtp.mailgun.org" +smtp_port: "587" +smtp_tls: yes +smtp_starttls: yes + +lan_ip_prefix: "192.168.14." + +# https://www.opennic.org/ +forwarded_dns_servers: + - 51.158.108.203 + - 152.53.15.127 + - 195.10.195.195 + +predefined_machines: + - { hostname: 'home', short_name: 'home', ip: '2', description: 'My local homelab VM' } + - { hostname: 'severusdesk', short_name: 'severusdesk', ip: '10', description: 'Main desktop computer' } + - { hostname: 'lockhart', short_name: 'lockhart', ip: '11', description: 'Main laptop (Huawei)' } + - { hostname: 'tablet', short_name: 'tablet', ip: '12', description: 'Main tablet' } + - { hostname: 'op6', short_name: 'op6', ip: '13', description: 'Main phone' } + - { hostname: 'switch', short_name: 'switch', ip: '14', description: 'Main Nintendo Switch' } + +# Lets me append machines from the inventory +# without having to add them all again +machines: '{{ predefined_machines }}' \ No newline at end of file diff --git a/ansible/inventory.yaml.dist b/ansible/inventory.yaml.dist new file mode 100644 index 0000000..5f67651 --- /dev/null +++ b/ansible/inventory.yaml.dist @@ -0,0 +1,80 @@ +public: + hosts: + rubeus-public: + ansible_host: 'IP_VM_PUBLIC' + ansible_username: 'oxodao' + ansible_become_password: '{{ lookup("env", "ANSIBLE_BECOME_PASSWORD") }}' + + # Cela donnera domain.tld pour les noms de domaines + domain: 'public' + + # Utilisé pour le monitoring des diverses app + smtp_username: 'SMTP USERNAME' + smtp_password: 'SMTP PASSWORD' + smtp_from: 'SMTP FROM' + smtp_to: 'SMTP TO' + + # Utilisé par Navidrome pour récupérer + # les informations sur les artistes et les covers + spotify_client_id: 'SPOTIFY CLIENT ID' + spotify_client_secret: 'SPOTIFY CLIENT SECRET' + + # Les montages SMB qui seront utilisés + smb_mounts: + shares: + host: '//[IP NAS]/Shares' + username: 'homelab_shares' + password: '[MDP NAS]' + + # Certificat SSL utilisé par nginx + nginx_public_key: |2 + -----BEGIN CERTIFICATE----- + CLE PUBLIQUE CERTIFICAT SSL + -----END CERTIFICATE----- + + nginx_private_key: |2 + -----BEGIN PRIVATE KEY----- + CLE PRIVEE CERTIFICAT SSL + -----END PRIVATE KEY----- + + # Permet d'ajouter des machines à celles déjà existantes directement dans le inventory + machines: |2 + {{ + predefined_machines + [ + {"hostname": "some_other_machine", "short_name": "machine", "ip": "180", "description": "Some other machine"}, + ] + }} + + # Configuration du wireguard + wireguard: + public: + ip_prefix: '192.168.70.' + port: 'PORT WG PUBLIC' + private_key: '' + public_key: '' + + # Les machines auront pour IP assignée + # le prefix d'ip fournis plus haut + # puis une ip machine égale à celle sur + # le réseau local. + machines: + - hostname: 'machine_name' + public_key: '' + + private: + ip_prefix: '192.168.66.' + port: 'PORT WG PRIVEE' + private_key: '' + public_key: '' + + # Les machines auront pour IP assignée + # le prefix d'ip fournis plus haut + # puis une ip machine égale à celle sur + # le réseau local. + machines: + - hostname: 'lockhart' + public_key: '' + - hostname: 'tablet' + public_key: '' + - hostname: 'op6' + public_key: '' diff --git a/ansible/playbooks/common.yaml b/ansible/playbooks/common.yaml new file mode 100644 index 0000000..06e7eff --- /dev/null +++ b/ansible/playbooks/common.yaml @@ -0,0 +1,156 @@ +--- +- hosts: 'all' + become: yes + tags: ['common'] + + tasks: + - import_role: { name: 'geerlingguy.ntp' } + - import_role: { name: 'hifis.unattended_upgrades' } + - import_role: { name: 'geerlingguy.docker' } + + - name: 'Installing some softwares' + apt: + name: [ 'zip', 'unzip', 'fuse', 'zsh', 'rsync', 'sqlite3', 'iptables', 'vim', 'git', 'exa', 'direnv'] + + - name: 'Enabling fuse' + modprobe: + name: 'fuse' + state: 'present' + + - name: 'Change owner of /opt' + file: + path: '/opt' + owner: '{{ansible_username}}' + group: '{{ansible_username}}' + + - name: 'Clone the dotfiles' + git: + repo: 'https://github.com/oxodao/dotfiles' + dest: '/opt/dotfiles' + + # @TODO: Have a single, global OMZ install + # And use it for every users + + - name: 'Check if zsh is installed for user' + stat: + path: '/home/{{ansible_username}}/.oh-my-zsh' + register: omz_user + + - name: 'Check if zsh is installed for root' + stat: + path: '/root/.oh-my-zsh' + register: omz_root + + - name: 'Download Oh My Zsh installation script' + get_url: + url: 'https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh' + dest: '/tmp/install_ohmyzsh.sh' + when: (not omz_user.stat.exists) or (not omz_root.stat.exists) + + - name: 'Run Oh My Zsh installation script for user' + become: no + command: 'sh /tmp/install_ohmyzsh.sh --unattended' + register: ohmyzsh_result + failed_when: "'FAILED' in ohmyzsh_result.stderr" + when: not omz_user.stat.exists + + - name: 'Run Oh My Zsh installation script for root' + command: 'sh /tmp/install_ohmyzsh.sh --unattended' + register: ohmyzsh_result + failed_when: "'FAILED' in ohmyzsh_result.stderr" + when: not omz_root.stat.exists + + - name: "Install matboll's theme for user" + become: no + copy: + src: '../files/mattboll.zsh-theme' + dest: '/home/{{ansible_username}}/.oh-my-zsh/themes/mattboll.zsh-theme' + + - name: "Install matboll's theme for root" + copy: + src: '../files/mattboll.zsh-theme' + dest: '/root/.oh-my-zsh/themes/mattboll.zsh-theme' + + - name: 'Remove the user config folder' + file: + path: '/home/{{ansible_username}}/.config' + state: 'absent' + + - name: 'Remove root config folder' + file: + path: '/root/.config' + state: 'absent' + + - name: 'Symlink the config for the user' + become: no + file: + src: '/opt/dotfiles/config' + dest: '/home/{{ansible_username}}/.config' + state: 'link' + + - name: 'Symlink the config for root' + file: + src: '/opt/dotfiles/config' + dest: '/root/.config' + state: 'link' + + - name: 'Remove the user zshrc' + file: + path: '/home/{{ansible_username}}/.zshrc' + state: 'absent' + + - name: 'Remove root zshrc' + file: + path: '/root/.zshrc' + state: 'absent' + + - name: 'Symlink the zshrc for the user' + become: no + file: + src: '/opt/dotfiles/zshrc' + dest: '/home/{{ansible_username}}/.zshrc' + state: 'link' + + - name: 'Symlink the zshrc for root' + file: + src: '/opt/dotfiles/zshrc' + dest: '/root/.zshrc' + state: 'link' + + - name: 'Set the zsh shell for user' + user: + name: '{{ansible_username}}' + shell: '/usr/bin/zsh' + + - name: 'Set the zsh shell for root' + user: + name: 'root' + shell: '/usr/bin/zsh' + + - name: 'Create the memo file for the user' + become: no + file: + path: '/home/{{ansible_username}}/.memo' + state: 'touch' + + - name: 'Create the memo file for root' + file: + path: '/root/.memo' + state: 'touch' + + - name: 'Create required groups' + group: + name: '{{item}}' + state: 'present' + loop: + - 'sudo' + - 'docker' + + - name: 'Add user to the correct groups' + user: + name: '{{ansible_username}}' + groups: '{{item}}' + append: yes + loop: + - 'sudo' + - 'docker' \ No newline at end of file diff --git a/ansible/playbooks/detect_ssh.yaml b/ansible/playbooks/detect_ssh.yaml new file mode 100644 index 0000000..1abc0a9 --- /dev/null +++ b/ansible/playbooks/detect_ssh.yaml @@ -0,0 +1,80 @@ +--- +# Stolen from https://dmsimard.com/2016/03/15/changing-the-ssh-port-with-ansible/ +# I just reversed it as the port 22 will still be used by Gitea's SSH server so the way its setup would not work for me +# THIS FILE WILL CHANGE MY LIFE FOR THE BETTER + +- hosts: "all" + tags: ["always"] + gather_facts: no + + tasks: + - name: Print the gateway for each host when defined + ansible.builtin.debug: + msg: '{{machines}}' + + - name: fail + fail: + msg: 'failed' + + - name: "Check if the custom port works" + wait_for: + host: "{{ ansible_host }}" + port: "{{ ssh_port }}" + state: "started" + connect_timeout: 5 + timeout: 10 + delegate_to: "localhost" + ignore_errors: yes + register: is_custom_port + + - set_fact: + is_custom_port_setup: "{{ is_custom_port is defined and is_custom_port.state is defined and is_custom_port.state == 'started' }}" + + - name: "Set inventory ansible_port to 22 if not ok" + set_fact: + ansible_port: "22" + when: not is_custom_port_setup + + - name: "Check if the server is accessible on the fallback port" + wait_for: + host: "{{ ansible_host }}" + port: "22" + state: "started" + connect_timeout: 5 + timeout: 10 + delegate_to: "localhost" + ignore_errors: yes + register: fallback_default_port + when: not is_custom_port_setup + + - name: "Fail if can't connect at all" + fail: + msg: "The SSH port is neither 22 or {{ ssh_port }}" + when: fallback_default_port is undefined + + - name: "Changing the SSH port on the server" + become: yes + lineinfile: + dest: "/etc/ssh/sshd_config" + regexp: "^Port" + line: "Port {{ ssh_port }}" + when: not is_custom_port_setup + + - name: "Disabling password authentication" + become: yes + lineinfile: + path: "/etc/ssh/sshd_config" + regex: "^(# *)?PasswordAuthentication" + line: "PasswordAuthentication no" + when: not is_custom_port_setup + + - name: "Restart the SSH server" + become: yes + service: + name: "ssh" + state: "restarted" + when: not is_custom_port_setup + + - name: "Set back the SSH port to the correct one" + set_fact: + ansible_port: "{{ ssh_port }}" \ No newline at end of file diff --git a/ansible/playbooks/dns.yaml b/ansible/playbooks/dns.yaml new file mode 100644 index 0000000..35747c3 --- /dev/null +++ b/ansible/playbooks/dns.yaml @@ -0,0 +1,29 @@ +--- +- hosts: 'public' + tags: ['dns'] + become: yes + + vars: + homelab: + hostname: 'public' + short_name: '{{domain}}' + ip: "{{base_ip.split('.')[-1]}}" + description: 'My public homelab VM' + current_machines: "{{ [ homelab ] + machines }}" + + tasks: + - name: 'Install dnsmasq' + apt: + name: 'dnsmasq' + state: 'present' + + - name: 'Config dnsmasq' + template: + src: '../templates/dnsmasq.conf.j2' + dest: '/etc/dnsmasq.conf' + + - name: 'Enable & start the dnsmasq service' + service: + name: 'dnsmasq' + state: 'restarted' + enabled: yes diff --git a/ansible/playbooks/jellyfin.yaml b/ansible/playbooks/jellyfin.yaml new file mode 100644 index 0000000..5a40d91 --- /dev/null +++ b/ansible/playbooks/jellyfin.yaml @@ -0,0 +1,31 @@ +--- +- hosts: 'public' + become: no + tags: ['jellyfin'] + + tasks: + - name: 'Mount the shares shared folder' + include_tasks: + file: '../tasks/add_nas_mount.yaml' + vars: + mount_name: 'shares' + + - name: 'Copying the docker-compose file' + include_tasks: + file: '../tasks/add_docker_app.yaml' + vars: + app_name: 'jellyfin' + app_port: '8096' + app_directories: + - 'config' + - 'cache' + - 'transcodes' + - 'metadata' + + - name: 'Setting-up nginx for Jellyfin' + include_tasks: + file: '../tasks/add_nginx_app.yaml' + vars: + app_name: 'jellyfin' + app_port: '8096' + enabled: yes \ No newline at end of file diff --git a/ansible/playbooks/navidrome.yaml b/ansible/playbooks/navidrome.yaml new file mode 100644 index 0000000..6ae3128 --- /dev/null +++ b/ansible/playbooks/navidrome.yaml @@ -0,0 +1,29 @@ +--- +- hosts: 'public' + become: no + tags: ['navidrome'] + + tasks: + - name: 'Mount the shares shared folder' + include_tasks: + file: '../tasks/add_nas_mount.yaml' + vars: + mount_name: 'shares' + + - name: 'Copying the docker-compose file' + include_tasks: + file: '../tasks/add_docker_app.yaml' + vars: + app_name: 'navidrome' + app_port: '4533' + app_directories: + - data + - cache + + - name: 'Setting-up nginx for Navidrome' + include_tasks: + file: '../tasks/add_nginx_app.yaml' + vars: + app_name: 'navidrome' + app_port: '4533' + enabled: yes \ No newline at end of file diff --git a/ansible/playbooks/reverse_proxy.yaml b/ansible/playbooks/reverse_proxy.yaml new file mode 100644 index 0000000..5a755c7 --- /dev/null +++ b/ansible/playbooks/reverse_proxy.yaml @@ -0,0 +1,64 @@ +--- +- hosts: 'all' + tags: ['reverseproxy'] + become: yes + + tasks: + - name: 'Install nginx' + apt: + name: ['nginx', 'openssl'] + state: 'present' + + - name: 'Config nginx' + copy: + src: '../files/nginx.conf' + dest: '/etc/nginx.conf' + + - name: 'Creating nginx dirs' + file: + path: '{{item}}' + state: 'directory' + mode: 0700 + loop: + - '/etc/nginx/sites-available' + - '/etc/nginx/sites-enabled' + - '/etc/nginx/snippets' + - '/opt/ssl' + + - name: 'Copying the SSL snippet' + copy: + src: '../files/ssl.conf' + dest: '/etc/nginx/snippets/ssl.conf' + + - name: 'Check if dhparam exists' + stat: + path: '/opt/ssl/dhparam.pem' + register: dhparam_stat + + - name: 'Generating dhparam (This can take a long time)' + openssl_dhparam: + path: '/opt/ssl/dhparam.pem' + size: 2048 + when: not dhparam_stat.stat.exists + + - name: 'Adding public key' + copy: + content: '{{nginx_public_key}}' + owner: 'root' + group: 'root' + mode: 0600 + dest: '/opt/ssl/cert.crt' + + - name: 'Adding private key' + copy: + content: '{{nginx_private_key}}' + owner: 'root' + group: 'root' + mode: 0600 + dest: '/opt/ssl/cert.key' + + - name: 'Enabling nginx' + service: + name: 'nginx' + enabled: yes + state: 'restarted' \ No newline at end of file diff --git a/ansible/requirements.yaml b/ansible/requirements.yaml new file mode 100644 index 0000000..a28a7df --- /dev/null +++ b/ansible/requirements.yaml @@ -0,0 +1,8 @@ +collections: + - community.crypto + - community.docker + +roles: + - geerlingguy.ntp + - geerlingguy.docker + - hifis.unattended_upgrades diff --git a/ansible/setup.yaml b/ansible/setup.yaml new file mode 100644 index 0000000..74f4769 --- /dev/null +++ b/ansible/setup.yaml @@ -0,0 +1,7 @@ +--- +- import_playbook: 'playbooks/detect_ssh.yaml' +- import_playbook: 'playbooks/common.yaml' +- import_playbook: 'playbooks/dns.yaml' +- import_playbook: 'playbooks/reverse_proxy.yaml' +- import_playbook: 'playbooks/jellyfin.yaml' +- import_playbook: 'playbooks/navidrome.yaml' \ No newline at end of file diff --git a/ansible/tasks/add_docker_app.yaml b/ansible/tasks/add_docker_app.yaml new file mode 100644 index 0000000..bc01a76 --- /dev/null +++ b/ansible/tasks/add_docker_app.yaml @@ -0,0 +1,36 @@ +- name: 'Check if app folder exists' + stat: + path: '/home/{{ansible_username}}/{{app_name}}/docker-compose.yaml' + register: dc_exists + +- name: 'Stop the docker compose file (if exists)' + community.docker.docker_compose_v2: + project_src: '/home/{{ansible_username}}/{{app_name}}' + pull: 'missing' + state: 'absent' + when: dc_exists.stat.exists + +- name: 'Create the app folder' + file: + path: '/home/{{ansible_username}}/{{app_name}}' + state: 'directory' + mode: 0777 + +- name: 'Copy the dockerfile' + template: + src: '../templates/{{app_name}}/docker-compose.yaml.j2' + dest: '/home/{{ansible_username}}/{{app_name}}/docker-compose.yaml' + +- name: 'Creating required dirs' + file: + path: '/home/{{ansible_username}}/{{app_name}}/{{item}}' + state: 'directory' + mode: 0700 + loop: '{{ app_directories }}' + when: app_directories is defined + +- name: 'Start the docker compose file' + community.docker.docker_compose_v2: + project_src: '/home/{{ansible_username}}/{{app_name}}' + pull: 'missing' + state: 'present' \ No newline at end of file diff --git a/ansible/tasks/add_nas_mount.yaml b/ansible/tasks/add_nas_mount.yaml new file mode 100644 index 0000000..781ae23 --- /dev/null +++ b/ansible/tasks/add_nas_mount.yaml @@ -0,0 +1,15 @@ +- name: 'Creating the mount folder' + become: yes + file: + path: '/mnt/{{mount_name}}' + state: 'directory' + mode: 0777 + +- name: 'Mount the mount (and add it to fstab)' + become: yes + mount: + fstype: 'cifs' + src: '{{smb_mounts[mount_name]["host"]}}' + path: '/mnt/{{mount_name}}' + opts: 'username={{smb_mounts[mount_name]["username"]}},password={{smb_mounts[mount_name]["password"]}}' + state: 'mounted' \ No newline at end of file diff --git a/ansible/tasks/add_nginx_app.yaml b/ansible/tasks/add_nginx_app.yaml new file mode 100644 index 0000000..7b7bd56 --- /dev/null +++ b/ansible/tasks/add_nginx_app.yaml @@ -0,0 +1,19 @@ +- name: 'Adding the nginx config' + become: yes + template: + src: '../templates/{{app_name}}/nginx.conf' + dest: '/etc/nginx/sites-available/{{app_name}}.conf' + +- name: 'Symlinking if enabled' + become: yes + file: + src: '/etc/nginx/sites-available/{{app_name}}.conf' + path: '/etc/nginx/sites-enabled/{{app_name}}.conf' + state: "{{'link' if enabled is defined and enabled == True else 'absent'}}" + force: yes # Wtf, for some reason its not yet there ? + +- name: 'Restarting nginx' + become: yes + service: + name: 'nginx' + state: 'restarted' \ No newline at end of file diff --git a/ansible/templates/dnsmasq.conf.j2 b/ansible/templates/dnsmasq.conf.j2 new file mode 100644 index 0000000..cc9bdea --- /dev/null +++ b/ansible/templates/dnsmasq.conf.j2 @@ -0,0 +1,16 @@ +domain-needed +bogus-priv +no-resolv +no-hosts + +{% for i in forwarded_dns_servers %} +server={{ i }} +{% endfor %} + +local=/{{ tld }}/ + +domain={{ hostname }} + +{% for m in current_machines %} +address=/{{m.short_name}}.{{tld}}/{{lan_ip_prefix}}{{m.ip}} # {{m.description}} +{% endfor %} diff --git a/ansible/templates/jellyfin/docker-compose.yaml.j2 b/ansible/templates/jellyfin/docker-compose.yaml.j2 new file mode 100644 index 0000000..312e6f3 --- /dev/null +++ b/ansible/templates/jellyfin/docker-compose.yaml.j2 @@ -0,0 +1,22 @@ +services: + app: + image: linuxserver/jellyfin:latest + restart: 'unless-stopped' + environment: + PUID: 1000 + PGID: 1000 + TZ: 'Europe/Paris' + JELLYFIN_PublishedServerUrl: 'https://play.{{hostname}}' + volumes: + - './config:/config' + - './cache:/cache' + - './transcodes:/transcodes' + - './metadata:/metadata' + - '/mnt/shares:/shares' + devices: + - '/dev/dri/renderD128:/dev/dri/renderD128' + - '/dev/dri/card0:/dev/dri/card0' + ports: + - '127.0.0.1:{{app_port}}:8096' + - '7359:7359' # Autodiscovery + - '1900:1900' # Autodiscovery diff --git a/ansible/templates/jellyfin/nginx.conf b/ansible/templates/jellyfin/nginx.conf new file mode 100644 index 0000000..1e3d63c --- /dev/null +++ b/ansible/templates/jellyfin/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name play.{{hostname}}; + + # Using 307 ensure that the client will follow the redirection on POST requests + # Cf. https://softwareengineering.stackexchange.com/questions/99894/why-doesnt-http-have-post-redirect + return 307 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name play.{{hostname}}; + + include snippets/ssl.conf; + + location / { + proxy_pass http://localhost:{{app_port}}; + proxy_http_version 1.1; + proxy_redirect off; + proxy_buffering off; + proxy_pass_request_headers on; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + } +} \ No newline at end of file diff --git a/ansible/templates/navidrome/docker-compose.yaml.j2 b/ansible/templates/navidrome/docker-compose.yaml.j2 new file mode 100644 index 0000000..c9e0706 --- /dev/null +++ b/ansible/templates/navidrome/docker-compose.yaml.j2 @@ -0,0 +1,24 @@ +services: + app: + image: 'deluan/navidrome:latest' + restart: 'unless-stopped' + user: 1000:1000 + environment: + ND_SCANSCHEDULE: '1h' + ND_LOGLEVEL: 'info' + ND_SESSIONTIMEOUT: '24h' + ND_CACHEFOLDER: '/cache' + ND_MUSICFOLDER: '/shares/Music' + ND_AUTHWINDOWLENGTH: '60s' + ND_DEFAULTLANGUAGE: 'fr' + ND_IMAGECACHESIZE: '1024MB' + ND_SPOTIFY_ID: '{{spotify_client_id}}' + ND_SPOTIFY_SECRET: '{{spotify_client_secret}}' + ND_TRANSCODINGCACHESIZE: '1024MB' + ND_BASEURL: "" # @TODO: ? c'est dans leur doc comme ça, a voir si ça a un impact + volumes: + - './data:/data' + - './cache:/cache' + - '/mnt/shares:/shares:ro' + ports: + - '127.0.0.1:{{app_port}}:4533' \ No newline at end of file diff --git a/ansible/templates/navidrome/nginx.conf b/ansible/templates/navidrome/nginx.conf new file mode 100644 index 0000000..1e6f764 --- /dev/null +++ b/ansible/templates/navidrome/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name m.{{hostname}}; + + # Using 307 ensure that the client will follow the redirection on POST requests + # Cf. https://softwareengineering.stackexchange.com/questions/99894/why-doesnt-http-have-post-redirect + return 307 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name m.{{hostname}}; + + include snippets/ssl.conf; + + location / { + proxy_pass http://localhost:{{app_port}}; + proxy_http_version 1.1; + proxy_redirect off; + proxy_buffering off; + proxy_pass_request_headers on; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + } +} \ No newline at end of file diff --git a/docs/backup_day.md b/docs/backup_day.md new file mode 100644 index 0000000..2673a25 --- /dev/null +++ b/docs/backup_day.md @@ -0,0 +1,25 @@ +# Backup Day + +A effectuer environ tous les 3 mois, c'est le plus important pour s'assurer que ses données soient safe. + +Il faut, sur un PC autre que son homelab, restaurer le dernier backup et tenter de lancer chaque application pour vérifier la cohérence des données. + +**Note**: Il peut être intéressant de se mettre un rappel régulier sur son Google Agenda pour être notifié de quand faire les backup day. + +@TODO: Expliquer comment faire pour chaque applis +## Jellyfin + +## Navidrome + +## Paperless (CRITIQUE!) + +## Gitea + +## Immich (CRITIQUE!) + +## JDownloader +JDownloader n'ayant pas de backups car aucune données n'est présente dans ce logiciel, il peut être ignoré pour le backup day. + +## HomeAssistant + +[Page précédente](disaster_recovery.md) diff --git a/docs/be_your_own_ca.md b/docs/be_your_own_ca.md new file mode 100644 index 0000000..be09051 --- /dev/null +++ b/docs/be_your_own_ca.md @@ -0,0 +1,91 @@ +# Become your own CA (Archlinux cert-trusting only) + +__Stolen from [deliciousbrains.com](https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/)__ + +## Generating root certs +### Private key +``` +$ openssl genrsa -des3 -out oxo-ca.key 4096 +``` + +### Root certificate +``` +$ openssl req -x509 -new -nodes -key oxo-ca.key -sha256 -days 1825 -out oxo-ca.pem +``` + +### Convert the root certificate to crt + +If needed only +``` +$ openssl x509 -outform der -in oxo-ca.pem -out oxo-ca.crt +``` + +## Installing the root certificate +### NSS + +Chromium, Firefox, Thunderbird, Evolution, SeaMonkey use NSS for retrieving trusted CAs. + +Arch's (and Fedora's) NSS packages are integrated with p11-kit, so they should automatically pick up any certificates used system-wide. But if you prefer (or if your distro uses "pure" NSS), you can install certificates into your own browser profile as well – use certutil for this: + +``` +certutil -d __database__ -A -i oxo-ca.crt -n "Oxodao" -t C,, +``` + +Chromium and Evolution use the "shared" database at -d "sql:$HOME/.pki/nssdb". + +For Firefox, Thunderbird, and SeaMonkey, specify the browser's own profile directory (e.g. -d ~/.mozilla/firefox/ov6jazas.default). + +### System-wide + +If it works: +``` +$ sudo trust anchor --store oxo-ca.crt +``` + +If it doesn't (no configured writable location): +``` +$ sudo cp oxo-ca.crt /etc/ca-certificates/trust-source/anchors/ +$ [sudo] update-ca-trust # Not sure it requires sudo, probably +``` + +## Generating normal certs + +### Private key +``` +$ openssl genrsa -out router.lan.key 4096 +``` + +### CSR +``` +$ openssl req -new -key router.lan.key -out router.lan.csr +``` + +### Creating the X509 V3 certificate extension config + +`router.lan.ext` +``` +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = router.lan +DNS.2 = *.router.lan +``` + +### Signing the certificate with the Root CA certificate + +``` +openssl x509 -req -in router.lan.csr -CA oxo-ca.pem -CAkey oxo-ca.key -CAcreateserial -out router.lan.crt -days 365 -sha256 -extfile router.lan.ext +``` + +## Using the certificate + +nginx + +``` +include snippets/ssl.conf; +ssl_certificate /path/to/certs/router.lan.crt; +ssl_certificate_key /path/to/certs/router.lan.key; +``` \ No newline at end of file diff --git a/docs/renew_ssl.md b/docs/renew_ssl.md new file mode 100644 index 0000000..78930bd --- /dev/null +++ b/docs/renew_ssl.md @@ -0,0 +1,14 @@ +# Renouveller un certificat SSL + +Le guide pour générer les certificats génère un CA d'une durée de vie de 5 ans. Passé cette date il faudra re-créer un CA entièrement et trust son nouveau certificat sur tous les appareils qui l'utilisent. + +Pour les certificats SSL simples, leurs durée de vie est de un an. Cela signifie qu'une fois par an il faut générer un nouveau certificat pour la VM public et la VM locale, les ajouter dans le `inventory.yaml` de ansible et relancer l'exécution complète pour qu'il mette à jour les certificats sur les VMs et redémarre nginx. + +Alternativement, on peut simplement scp les certificats dans `/opt/ssl/cert.key` et `/opt/ssl/cert.crt` de chaque VM et redémarrer nginx manuellement. + +Mettre à jour les certificats simple ne demande pas de faire le tour de chaque appareils se connectant au homelab puisqu'ils sont signé par le même CA. + + +**Note**: Il peut être intéressant de se mettre un rappel régulier sur son Google Agenda pour être notifié avant que le certificat expire pour prévoir et éviter l'interruption de service. Aucun processus d'alerting n'est mis en place. + +[Page précédente](add_user_vpn.md) / [Page suivante](disaster_recovery.md) \ No newline at end of file diff --git a/docs/setup_common.md b/docs/setup_common.md new file mode 100644 index 0000000..3556223 --- /dev/null +++ b/docs/setup_common.md @@ -0,0 +1,84 @@ +# Setup des utilitaires communs + +> Toute la config est gérée dans le ansible. +> +> Rien est à faire à la main. + +# SSH + +Avant toute chose, le playbook ansible se charge de: +- Tester si on peut accéder en SSH sur le port custom +- Si c'est le cas il continue le playbook +- Si ça n'est pas le cas il désactive l'authentification par mot de passe, change le port et relance le service + +Cela est fait en modifiant le fichier `/etc/ssh/sshd_config`: +``` +Port 57982 +PasswordAuthentication no +``` + +Cela permet de ne pas laisser le serveur SSH sur le port par défaut afin de ne pas avoir de conflit avec Gitea. + +Accessoirement, cela permet de diminuer la quantité de requêtes faites sur son serveur par les scanner d'internet (Dans le cas d'un homelab ça ne sert pas puisque non exposé sur internet). + + +## Setup des mises à jour automatiques + +Je n'ai pas creusé le fonctionnement, j'utilise un [playbook existant](https://github.com/hifis-net/ansible-role-unattended-upgrades). + +Il se base sur le projet [unattended-upgrades](https://launchpad.net/unattended-upgrades). + +## Setup du NTP + +Permet d'avoir une horloge à l'heure automatiquement, pareil qu'au dessus, cela se base sur le playbook [ntp de geerlingguy](https://github.com/geerlingguy/ansible-role-ntp) que je n'ai pas creusé. + +## Installation des utilitaires usuels du système + +Installation des outils utils en plus sur le système: +```sh +sudo apt install zip unzip fuse zsh rsync sqlite3 iptables vim git exa direnv +``` + +> Note: exa est utilisé à la place de eza car il n'est pas dans les repos debian +> Et que je l'ai toujours pas changé sur mon PC + +## Activation de fuse +```sh +$ sudo modprobe fuse +$ echo fuse | sudo tee -a /etc/modules +``` + +## Installation de docker + +Suivre le guide officiel sur le [site de docker](https://docs.docker.com/engine/install/debian/). + +Ansible utilise le [role de guerlingguy](https://github.com/geerlingguy/ansible-role-docker) + +Penser à ajouter l'utilisateur dans le groupe docker +```sh +$ sudo /usr/sbin/usermod -aG docker oxodao +``` + +## Configuration basique + +Il s'agît la d'ajouter de la configuration pour les utilitaires installés dans l'étape 2. Principalement la configuration zsh ainsi que celle de vim. + +Installation de `oh-my-zsh` depuis le script officiel: +```shell +$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +``` + +Clone de mes dotfiles: +```shell +$ rm -rf $HOME/.config # Attention ! S'assurer que c'est bien ce qu'on veut +$ rm $HOME/.zshrc +$ cd /opt +$ git clone https://github.com/oxodao/dotfiles +$ cd dotfiles +$ ln -s $PWD/config $HOME/.config +$ ln -s $PWD/zshrc $HOME/.zshrc +$ ln -s $PWD/gitconfig $HOME/.gitconfig +$ touch $HOME/.memo $HOME/.zshrc.custom +``` + +[Page précédente](setup_vm.md) / [Page suivante](setup_dns.md) \ No newline at end of file diff --git a/docs/setup_dns.md b/docs/setup_dns.md new file mode 100644 index 0000000..c0b489a --- /dev/null +++ b/docs/setup_dns.md @@ -0,0 +1,56 @@ +# Setup du serveur DNS + +Le serveur DNS se charge de traduire les domaines en `.lan` vers l'ip de la machine correspondante sur le réseau. Les autres TLD ne sont pas pris en compte et sont cherchés via les serveurs DNS [OpenNIC](https://www.opennic.org/). + +> Toute la config est gérée dans le ansible. +> +> Rien est à faire à la main. + + +## Installation de dnsmasq +```sh +$ sudo apt install dnsmasq +``` + +> @TODO +> +> Vérifier que la config marche avec deux VMs. +> +> Voir comment faire marche l'IPv6 dans xcp-ng pour +> les androids qui veulent pas d'un DNS ipv4. + +Ajouter la configuration dans `/etc/dnsmasq.conf`: +``` +domain-needed # Ne transmet pas à OpenNIC les requête qui ne sont pas des noms de domaines complets +bogus-priv +no-resolv # N'utilise pas le fichier resolv.conf de la vm +no-hosts # N'utilise pas le fichier hosts de la vm + +# Serveur DNS forwardé (Ce qui n'est pas résolu par le serveur local) +# Il peut y en avoir plusieurs +server={{ IP_DNS_OPENNIC }} + +local=/lan/ + +domain=public.lan + +# Addresses de mes VM +address=/.public.lan/192.168.14.59 # rubeus-public +address=/.home.lan/192.168.14.1 # rubeus-local + +# Ces config sont des IPs fictives car flemme de les retrouver +# Il en manque aussi beaucoup surement +# Se référer au ansible pour avoir la liste complète +address=/severusdesk.lan/192.168.14.10 # PC de bureau +address=/severuspad.lan/192.168.14.11 # PC portable +address=/mediacenter.lan/192.168.14.20 # PC Media center +address=/switch.lan/192.168.14.30 # Nintendo Switch +``` + +Enfin on active ou redémarre le service +```sh +$ sudo systemctl enable --now dnsmasq +$ sudo systemctl restart dnsmasq +``` + +[Page précédente](setup_common.md) / [Page suivante](setup_reverseproxy.md) \ No newline at end of file diff --git a/docs/setup_jellyfin.md b/docs/setup_jellyfin.md new file mode 100644 index 0000000..1aa8282 --- /dev/null +++ b/docs/setup_jellyfin.md @@ -0,0 +1,172 @@ +# Setup Jellyfin + +**VM PUBLIC** + +Pour Jellyfin, nous allons avoir besoin tout d'abord de monter le partage samba qui contient la collection de vidéos puis nous le passerons via Docker. + +> A faire même en cas de ansible +> +> => Post-setup Jellyfin + +## Ajout du montage SMB + +On s'assure que `cifs-utils` est bien installé: +```sh +$ sudo apt install cifs-utils +``` + +On créé le dossier dans lequel le montage sera effectué: +```sh +$ sudo mkdir /mnt/shares +``` + +On ajoute l'entrée dans `/etc/fstab`: +``` +[...] +//[IP_DU_NAS]/shares /mnt/shares cifs username=homelab_shares,password=[HOMELAB_SHARES_PASSWORD] 0 0 +``` + +Enfin, on demande le montage et on vérifie que tout s'est bien passé: +```sh +$ sudo mount -a +$ ls /mnt/shares +``` + +Optionnellement, on peut stocker le mot de passe dans un fichier comme ceci: + +`/etc/fstab`: +``` +//[IP_DU_NAS]/shares /mnt/shares cifs credentials=/root/smb_shares_credentials 0 0 +``` + +`/root/smb_shares_credentials`: +``` +user=homelab_shares +password=[HOMELAB_SHARES_PASSWORD] +``` + +## Setup de Jellyfin + +En tant qu'utilisateur non-root, on créé les dossiers utiles: +```sh +$ mkdir -p /home/oxodao/jellyfin/{cache,config,metadata,transcodes} +``` + +On ajoute ensuite le `docker-compose.yaml` dans `/home/oxodao/jellyfin`: +```yaml +services: + app: + image: linuxserver/jellyfin:latest + restart: 'unless-stopped' + environment: + PUID: 1000 + PGID: 1000 + TZ: 'Europe/Paris' + JELLYFIN_PublishedServerUrl: 'https://play.public.lan' + volumes: + - './config:/config' + - './cache:/cache' + - './transcodes:/transcodes' + - './metadata:/metadata' + - '/mnt/shares:/shares' + devices: + - '/dev/dri/renderD128:/dev/dri/renderD128' + - '/dev/dri/card0:/dev/dri/card0' + ports: + - '127.0.0.1:8096:8096' + - '7359:7359' # Autodiscovery + - '1900:1900' # Autodiscovery +``` + +Puis on le lance: +```sh +$ cd /home/oxodao/jellyfin +$ docker compose up -d +``` + +Ensuite on setup le nginx, dans `/etc/nginx/sites-available/jellyfin.conf`: +``` +server { + listen 80; + server_name play.public.lan; + + # Using 307 ensure that the client will follow the redirection on POST requests + # Cf. https://softwareengineering.stackexchange.com/questions/99894/why-doesnt-http-have-post-redirect + return 307 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name play.public.lan; + + include snippets/ssl.conf; + + location / { + proxy_pass http://localhost:8096; + proxy_http_version 1.1; + proxy_redirect off; + proxy_buffering off; + proxy_pass_request_headers on; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + } +} +``` + +On fait le petit symlink qui va bien et on relance nginx: +```sh +$ sudo ln -s /etc/nginx/sites-available/jellyfin.conf /etc/nginx/sites-enabled/jellyfin.conf +$ sudo systemctl restart nginx +``` + +On test que tout marche bien en allant sur [https://play.public.lan](https://play.public.lan). + +Attention aux DNS, si c'est pas encore configuré dans la freebox on n'y aura pas accès, on peut temporairement mettre ça dans `/etc/hosts` sur son PC: +``` +192.168.14.59 play.public.lan +``` + +## Post-setup Jellyfin + +Le post-setup de Jellyfin se fait dans le navigateur. + +Le setup commence par la langue, sélectionner Français. + +Créer le compte utilisateur. + +Ajouter ensuite les médiatèques suivantes, laisser la config par défaut: +- Type: Films, Dossiers: /shares/Films +- Type: Émissions, Dossiers: /shares/Series +- Type: TV, Dossiers: /shares/TV +- Type: Concerts, Dossiers: /shares/Concerts + +Langue de métadonnées: FR, Pays FR. + +Accès à distance: laisser la config par défaut + +Se connecter après le setup puis aller dans les paramètres d'administrations. + +Optionnellement, créer les autres comptes utilisateurs nécessaires (Tant qu'on a pas d'AD) + +Aller dans l'onglet "Lecture" pour configurer le hardware transcoding: +- Accélération matérielle: "Video Acceleration API (VAAPI)" +- Appareil VA-API: "/dev/dri/renderD128" +- Activer le décodage matériel pour: h264, HEVC, VC1, VP9, AV1 +- Activer l'encodage matériel +- @TODO: Tester avec l'encoder basse conso h264 +- Laisser le reste par défaut + +Attention ces settings sont faites pour le i3-10100, cela peu changer si on change de matériel. + +Pour vérifier que le transcoding marche, lancer un film dans un autre onglet puis choisir une résolution différente de la native du fichier, puis dans l'onglet admin aller dans "Tableau de bord", dans "Appareils actifs" cliquer sur le "i". Si le transcodage fonctionne la pop-up devrait afficher "@TODO". + +Le setup de Jellyfin est terminé. + +[Page précédente](setup_reverseproxy.md) / [Page suivante](setup_navidrome.md) \ No newline at end of file diff --git a/docs/setup_navidrome.md b/docs/setup_navidrome.md new file mode 100644 index 0000000..e7da6b4 --- /dev/null +++ b/docs/setup_navidrome.md @@ -0,0 +1,111 @@ +# Setup Navidrome + +**VM PUBLIC** + +> A faire même en cas de ansible +> +> => Post-setup Navidrome + +## Ajout du montage SMB + +Même setup que Jellyfin, et on utilise le même point de montage. + +## Setup de Navidrome + +En tant qu'utilisateur non-root, on créé les dossiers utiles: +```sh +$ mkdir -p /home/oxodao/navidrome/data +``` + +On ajoute ensuite le `docker-compose.yaml` dans `/home/oxodao/navidrome`: +```yaml +services: + app: + image: 'deluan/navidrome:latest' + restart: 'unless-stopped' + user: 1000:1000 + environment: + ND_SCANSCHEDULE: '1h' + ND_LOGLEVEL: 'info' + ND_SESSIONTIMEOUT: '24h' + ND_CACHEFOLDER: '/cache' + ND_MUSICFOLDER: '/shares/Music' + ND_AUTHWINDOWLENGTH: '60s' + ND_DEFAULTLANGUAGE: 'fr' + ND_IMAGECACHESIZE: '1024MB' + ND_SPOTIFY_ID: 'SPOTIFY APP ID' + ND_SPOTIFY_SECRET: 'SPOTIFY APP SECRET' + ND_TRANSCODINGCACHESIZE: '1024MB' + ND_BASEURL: "" # @TODO: ? c'est dans leur doc comme ça, a voir si ça a un impact + volumes: + - './data:/data' + - './cache:/cache' + - '/mnt/shares:/shares:ro' + ports: + - '127.0.0.1:4533:4533' +``` + +Puis on le lance: +```sh +$ cd /home/oxodao/navidrome +$ docker compose up -d +``` + +Ensuite on setup le nginx, dans `/etc/nginx/sites-available/navidrome.conf`: +``` +server { + listen 80; + server_name m.public.lan; + + # Using 307 ensure that the client will follow the redirection on POST requests + # Cf. https://softwareengineering.stackexchange.com/questions/99894/why-doesnt-http-have-post-redirect + return 307 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name m.public.lan; + + include snippets/ssl.conf; + + location / { + proxy_pass http://localhost:4533; + proxy_http_version 1.1; + proxy_redirect off; + proxy_buffering off; + proxy_pass_request_headers on; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + } +} +``` + +On fait le petit symlink qui va bien et on relance nginx: +```sh +$ sudo ln -s /etc/nginx/sites-available/nginx.conf /etc/nginx/sites-enabled/nginx.conf +$ sudo systemctl restart nginx +``` + +On test que tout marche bien en allant sur [https://m.public.lan](https://m.public.lan). + +Attention aux DNS, si c'est pas encore configuré dans la freebox on n'y aura pas accès, on peut temporairement mettre ça dans `/etc/hosts` sur son PC: +``` +192.168.14.59 m.public.lan +``` + +## Post-setup Navidrome + +Le post-setup de Jellyfin se fait dans le navigateur. + +Créer le compte utilisateur. + +Le setup de Navidrome est terminé. + +[Page précédente](setup_jellyfin.md) / [Page suivante](setup_paperless.md) \ No newline at end of file diff --git a/docs/setup_reverseproxy.md b/docs/setup_reverseproxy.md new file mode 100644 index 0000000..0ecf83d --- /dev/null +++ b/docs/setup_reverseproxy.md @@ -0,0 +1,174 @@ +# Setup reverse-proxy + +Pour le reverse-proxy, j'utilise `nginx`. Puisque j'ai un certificat CA privé, cela nécessite une petite configuration en plus. + +> Attention +> +> L'étape "Créer son CA" n'est pas inclue dans le ansible +> +> Il faut le faire à la main et placer les certificats au bon endroit dans ansible ! + +**Note**: Pour la suite de ce guide, si il n'est pas précisé sur quelle VM effectuer les actions, il faut le faire sur les deux. + +## Créer son CA + +Se référer au guide [be your own CA](be_your_own_ca.md) pour créer son certificat CA ainsi que des certificats pour des noms de domaines. + +Créer un certificat `public.lan` / `*.public.lan` et `home.lan` / `*.home.lan`. + +*Ansible se charge de la suite* + +Sur les deux VMs créer le dossier `/opt/ssl` + +Copier via scp les certificats sur les bonnes VMs: +```sh +$ scp home.lan.crt oxodao@rubeus-local:/opt/ssl/home.lan.crt +$ scp home.lan.key oxodao@rubeus-local:/opt/ssl/home.lan.key +$ scp public.lan.crt oxodao@rubeus-public:/opt/ssl/public.lan.crt +$ scp public.lan.key oxodao@rubeus-public:/opt/ssl/public.lan.key +``` + +S'assurer que les owner des fichiers `.key` soient en `root:root` avec les permissions `600`. + +Les permissions des clés publiques ne sont pas importantes. + +## Installer nginx + +```sh +$ sudo apt install nginx openssl +``` + +## Configuration de nginx + +### Création du dhparm + +Exécuter les commandes suivantes: +```sh +$ cd /opt/ssl +$ openssl dhparam -out dhparam.pem 2048 +``` + +### Configuration du nginx + +Nous allons faire un système modulaire à la `apache2`, la configuration de nginx importera les sites depuis le dossier `sites-enabled`. + +Tout d'abord nous allons créer le fichier `/etc/nginx/nginx.conf` qui sera très simple et se contentera d'importer les sites: +```nginx +worker_processes 1; + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + include sites-enabled/*; +} +``` + +Ensuite, nous allons créer les dossiers importants pour la configuration: +```sh +$ sudo mkdir -p /etc/nginx/{sites-available,sites-enabled,snippets} +``` + +- **sites-available**: Dossier dans lequel nous mettrons la configuration des sites +- **sites-enabled**: Dossier qui contiendra des liens symboliques vers les applications actuellements utilisées +- **snippets**: Dossier qui contiendra des helpers pour les configurations des diverses applis + + +### Snippets + +Nous allons créer un snippet pour la configuration SSL qui sera réutilisée par les sites. + +Cette config est à titre d'exemple, il convient d'utiliser le [generateur de config de mozilla](https://ssl-config.mozilla.org/) pour générer une config à jour puis de récupérer les différentes portions liées au SSL. + + +``` +ssl_session_timeout 1d; +ssl_session_cache shared:MozSSL:10m; # about 40000 sessions +ssl_session_tickets off; + +# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam +ssl_dhparam /opt/ssl/dhparam.pem; + +# intermediate configuration +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; +ssl_prefer_server_ciphers off; + +# HSTS (ngx_http_headers_module is required) (63072000 seconds) +add_header Strict-Transport-Security "max-age=63072000" always; + +ssl_certificate /opt/ssl/{HOSTNAME}.lan.crt; +ssl_certificate_key /opt/ssl/{HOSTNAME}.lan.key; +``` + +## Lancement de nginx +```sh +$ sudo systemctl enable --now nginx +``` + +## Ajout d'applications + +Pour ajouter une application, on utilise la config standard suivante. + +Attention elle est à personnaliser en fonction de l'appli et peut avoir des cas d'usage spécifique. + +Cette config prends en compte les websockets et devrait donc marcher dans la plupart des cas. + +``` +server { + listen 80; + server_name {SUBDOMAIN}.{HOSTNAME}.lan; + + # Using 307 ensure that the client will follow the redirection on POST requests + # Cf. https://softwareengineering.stackexchange.com/questions/99894/why-doesnt-http-have-post-redirect + return 307 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name {SUBDOMAIN}.{HOSTNAME}.lan; + + include snippets/ssl.conf; + + location / { + proxy_pass http://localhost:{PORT}; + proxy_http_version 1.1; + proxy_redirect off; + proxy_buffering off; + proxy_pass_request_headers on; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_set_header X-Forwarded-Host $http_host; + } +} +``` + +Elle est à placer dans `/etc/nginx/sites-available/{APP_NAME}.conf` puis à symlink: +```sh +$ sudo ln -s /etc/nginx/sites-available/{APP_NAME}.conf /etc/nginx/sites-enabled/{APP_NAME}.conf +``` + +Enfin, on redémarre le serveur nginx pour prendre en compte la nouvelle config: +```sh +$ sudo systemctl restart nginx +``` + +Si on souhaite tester la config avant de redémarrer le serveur et potentiellement tout casser, on peut utiliser l'argument suivant: +```sh +$ sudo /usr/sbin/nginx -t +``` + +[Page précédente](setup_dns.md) / [Page suivante](setup_jellyfin.md) \ No newline at end of file diff --git a/docs/setup_vm.md b/docs/setup_vm.md new file mode 100644 index 0000000..d15ae92 --- /dev/null +++ b/docs/setup_vm.md @@ -0,0 +1,61 @@ +# Setup des VMs + +Nous allons créer deux VM, une pour les services accessibles par la famille et une pour les services accessibles uniquement en local. + +## Ajouter le SR des iso sur xcp-ng + +Sur XOA, allez dans `Home > Hosts > rubeus > Storage` puis cliquer sur `Add a storage`. + +Donner un nom (`NAS - Iso`), une description `ISOs on the NAS` et choisir en type `SMB ISO`. + +Pour le serveur renseigner `\\NAS-IP\iso` et en username/password le user dédié au partage du point de montage ISO. + +Valider + +## Création de la VM publique + +Via XOA nous créons donc une VM `Public VM` avec 2 vCPU, 2Go de ram (A voir à l'usage si c'est suffisant) et 80Go de stockage (Overkill mais j'ai de la place). + +Avant de lancer la VM, nous allons ajouter le passthrough pour le iGPU, sur l'host xcp-ng: +```sh +$ xe vm-param-set other-config:pci=0/0000: uuid= +``` + +Effectuer une install de debian standard avec comme hostname `rubeus-public`, server name `public.lan`. Déselectionner l'environnement de bureau pour ne garder que "serveur SSH" et "Utilitaires usuels du système". Un user/password puis on installe le Management Agent de xcp-ng. + +### Autoriser l'utilisateur à sudo +```sh +# apt update && apt install sudo vim +# EDITOR=vim visudo +``` + +S'assurer que cette ligne est bien présente: +``` +# Allow members of group sudo to execute any command +%sudo ALL=(ALL:ALL) ALL +``` + +Puis l'ajouter au groupe concerné: +```sh +# usermod -aG sudo oxodao +``` + +On note l'ip de la vm (Aussi visible dans XOA): +```sh +$ ip -br -c a +``` + +Enfin, se déconnecter du SSH et copier sa clé publique pour pouvoir se connecter avec. +```sh +$ ssh-copy-id oxodao@192.168.14.XX +``` + +## Création de la VM locale + +Même chose que pour la VM publique avec exception: +- 8Go de ram +- 250Go de stockage +- Hostname à rubeus-local +- Pas de setup pour le iGPU + +[Page précédente](setup_xcp.md) / [Page suivante](setup_common.md) \ No newline at end of file diff --git a/docs/setup_xcp.md b/docs/setup_xcp.md new file mode 100644 index 0000000..699e0d4 --- /dev/null +++ b/docs/setup_xcp.md @@ -0,0 +1,33 @@ +# Setup XCP-NG + +XCP-NG est l'hyperviseur que j'ai choisi pour mon homelab. Ce dernier est consitué d'un seul host. + +## Installation + +Rien de spécifique pour l'installation, télécharger l'iso [via le site officiel](https://xcp-ng.org/#easy-to-install) et suivre les instructions de base. + +## Post-install + +Après l'installation, il faut préparer le iGPU à être utilisé en passthrough pour la VM qui proposera Jellyfin. + +[Doc officielle](https://docs.xcp-ng.org/compute/#pci-passthrough) + +```shell +$ lspci | grep VGA +00:02.0 VGA compatible controller: Intel Corporation CometLake-S GT2 [UHD Graphics 630] (rev 03) +``` + +On prends note du device id (00:02.0) puis on l'exclu de `dom0`: +```shell +# /opt/xensource/libexec/xen-cmdline --set-dom0 "xen-pciback.hide=(0000:00:02.0)" +``` + +On reboot le host puis on vérifie que le iGPU est bien disponible: +```shell +# xl pci-assignable-list +0000:00:02.0 +``` + +Une fois cela fait, on est prêt à créer nos VMs. + +[Page suivante](setup_vm.md) \ No newline at end of file