From 43ac0f3ac21ec2e21ad6f368ad2e97b540f5d545 Mon Sep 17 00:00:00 2001 From: missytake Date: Wed, 4 Dec 2024 14:42:14 +0100 Subject: [PATCH 1/7] doc: documented alpine installation --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/README.md b/README.md index a0bdb62..b6efeab 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,57 @@ or - run `git pull` to fetch the newest version - run `pyinfra @local deploy.py` to install/update `0x90.ssh_config` trustmebro - run `pyinfra --dry inventory.py deploy.py` and check that you are on the same state that is already deployed + + +# Set up alpine on hetzner + +This was only tested with a cloud VPS so far. +Source: +(but it's less of a hassle than described there) + +To create an alpine server on hetzner, +you need to first create a Debian VPS or something similar. + +Then you boot into the rescue system. + +Get the download link of the latest VIRTUAL x86_64 alpine iso +from . + +Login to the rescue system via console or SSH, +and write the ISO to the disk: + +``` +ssh root@xxxx:xxxx:xxxx:xxxx::1 +wipefs -a /dev/sda +wget https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-virt-3.20.3-x86_64.iso # or whatever link you got from alpine +dd if=alpine-virt-3.20.3-x86_64.iso of=/dev/sda +reboot +``` + +Then open the server console (SSH doesn't work), +login to root (no password required), +and proceed with: + +``` +cp -r /.modloop /root +cp -r /media/sda /root +umount /.modloop /media/sda +rm /lib/modules +mv /root/.modloop/modules /lib +mv /root/sda /media +setup-alpine +``` + +Then select what you wish, +contrary to the guide above, +DHCP is actually fine. +The drive should be sda, +the installation type can be sys +(why go through the hassle). + +VoilĂ ! reboot and login. +Probably the first SSH login will be via root password, +as copy-pasting your public SSH key into the console doesn't work really. +Make sure the SSH config allows this +(and turn passwort root access off afterwards). + -- 2.43.5 From aebff70524ef3a6f9f6e984e73e0470807de5988 Mon Sep 17 00:00:00 2001 From: missytake Date: Wed, 4 Dec 2024 14:42:53 +0100 Subject: [PATCH 2/7] doc: documented encrypting /var/lib/libvirt on a VPS --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index b6efeab..43e7669 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,34 @@ as copy-pasting your public SSH key into the console doesn't work really. Make sure the SSH config allows this (and turn passwort root access off afterwards). + +## Encrypting /var/lib/libvirt partition + +**Status: tested with Hetzner VPS, not deployed in production yet** + +Messing with file systems and partitions +should not be done by automation scripts, +so I created the LUKS-encrypted /dev/sdb partition manually. + +(So far, /dev/sdb was added via a Hetzner volume, +but it can be any partition actually) + +To create a partition in the VPS volume +(which was formatted to ext4 originally), +- I ran `fdisk /dev/sdb`, +- entered `o` to create a DOS partition table, +- added `n` to add a new primary partition, using all available space, +- and `w` to save to disk and exit. + +Then I ran `cryptsetup luksFormat /dev/sdb1` +and entered the passphrase from `pass 0x90/ararat/sdb-crypt` +to create a LUKS volume. + +Now I could decrypt the new volume with +`cryptsetup luksOpen /dev/sdb1 sdb_crypt` +and entering the passphrase from `pass 0x90/ararat/sdb-crypt`. + +Finally, I ran `mkfs.ext4` +to create an ext4 file system +in the encrypted partition. + -- 2.43.5 From c6dc45b72484d89026a6367e9f7d52d960640d79 Mon Sep 17 00:00:00 2001 From: missytake Date: Sat, 30 Nov 2024 09:27:13 +0100 Subject: [PATCH 3/7] ararat: rough structure of deploy.py --- ararat/deploy.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ararat/deploy.py b/ararat/deploy.py index e69de29..06a505d 100644 --- a/ararat/deploy.py +++ b/ararat/deploy.py @@ -0,0 +1,16 @@ + +# if it doesn't exist, create encrypted data partition +# mount encrypted data partition + +# apk add kvm virsh +# rc-update add libvirtd + +# add networking: https://wiki.alpinelinux.org/wiki/KVM#Networking +# modprobe tun +# echo "tun" >> /etc/modules-load.d/tun.conf +# cat /etc/modules | grep tun || echo tun >> /etc/modules + +# if it doesn't exist, create debian base image (later: and other base images): https://mop.koeln/blog/creating-a-local-debian-vm-using-cloud-init-and-libvirt/#download-the-image +# for every active VM, if no image exists, run virt-install with the chosen base image and their cloud-init.yml file: https://mop.koeln/blog/creating-a-local-debian-vm-using-cloud-init-and-libvirt/#preparing-a-cloud-init-file +# for every active VM, make sure an IP is assigned and traffic is passed to it + -- 2.43.5 From 22870b7fd2c3926bfa5b0515ef1f56598b76e148 Mon Sep 17 00:00:00 2001 From: missytake Date: Sat, 30 Nov 2024 21:29:42 +0100 Subject: [PATCH 4/7] Created an alpine hypervisor VPS and playground VM --- inventory.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/inventory.py b/inventory.py index 6352601..ab5dd73 100644 --- a/inventory.py +++ b/inventory.py @@ -1,5 +1,13 @@ -targets = [ - "@local", - ("ararat.0x90.space", dict(ssh_port=42022)), - ("baixun.0x90.space", dict(ssh_port=42023)), +localhost = "@local" + +hypervisor = [("95.217.163.200", dict(ssh_user="root"))] + +debian_vms = [ +# "cloud", + ( + "playground", + { + "authorized_keys": ["missytake", "hagi", "vmann"], + } + ), ] -- 2.43.5 From 50e0547e23c9e085d158e11b2aa3e8d8869b96dd Mon Sep 17 00:00:00 2001 From: missytake Date: Sat, 30 Nov 2024 21:30:46 +0100 Subject: [PATCH 5/7] util: added get_pass function --- .../pyinfra_util.egg-info/PKG-INFO | 3 + .../pyinfra_util.egg-info/SOURCES.txt | 7 +++ .../dependency_links.txt | 1 + .../pyinfra_util.egg-info/top_level.txt | 1 + lib/pyinfra-util/pyinfra_util/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 222 bytes .../__pycache__/util.cpython-310.pyc | Bin 0 -> 1759 bytes lib/pyinfra-util/pyinfra_util/util.py | 56 ++++++++++++++++++ lib/pyinfra-util/pyproject.toml | 7 +++ 9 files changed, 76 insertions(+) create mode 100644 lib/pyinfra-util/pyinfra_util.egg-info/PKG-INFO create mode 100644 lib/pyinfra-util/pyinfra_util.egg-info/SOURCES.txt create mode 100644 lib/pyinfra-util/pyinfra_util.egg-info/dependency_links.txt create mode 100644 lib/pyinfra-util/pyinfra_util.egg-info/top_level.txt create mode 100644 lib/pyinfra-util/pyinfra_util/__init__.py create mode 100644 lib/pyinfra-util/pyinfra_util/__pycache__/__init__.cpython-310.pyc create mode 100644 lib/pyinfra-util/pyinfra_util/__pycache__/util.cpython-310.pyc create mode 100644 lib/pyinfra-util/pyinfra_util/util.py create mode 100644 lib/pyinfra-util/pyproject.toml diff --git a/lib/pyinfra-util/pyinfra_util.egg-info/PKG-INFO b/lib/pyinfra-util/pyinfra_util.egg-info/PKG-INFO new file mode 100644 index 0000000..7d9179b --- /dev/null +++ b/lib/pyinfra-util/pyinfra_util.egg-info/PKG-INFO @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: pyinfra-util +Version: 0.1 diff --git a/lib/pyinfra-util/pyinfra_util.egg-info/SOURCES.txt b/lib/pyinfra-util/pyinfra_util.egg-info/SOURCES.txt new file mode 100644 index 0000000..1d99e4c --- /dev/null +++ b/lib/pyinfra-util/pyinfra_util.egg-info/SOURCES.txt @@ -0,0 +1,7 @@ +pyproject.toml +pyinfra_util/__init__.py +pyinfra_util/util.py +pyinfra_util.egg-info/PKG-INFO +pyinfra_util.egg-info/SOURCES.txt +pyinfra_util.egg-info/dependency_links.txt +pyinfra_util.egg-info/top_level.txt \ No newline at end of file diff --git a/lib/pyinfra-util/pyinfra_util.egg-info/dependency_links.txt b/lib/pyinfra-util/pyinfra_util.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/pyinfra-util/pyinfra_util.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/lib/pyinfra-util/pyinfra_util.egg-info/top_level.txt b/lib/pyinfra-util/pyinfra_util.egg-info/top_level.txt new file mode 100644 index 0000000..1c602f1 --- /dev/null +++ b/lib/pyinfra-util/pyinfra_util.egg-info/top_level.txt @@ -0,0 +1 @@ +pyinfra_util diff --git a/lib/pyinfra-util/pyinfra_util/__init__.py b/lib/pyinfra-util/pyinfra_util/__init__.py new file mode 100644 index 0000000..675fb98 --- /dev/null +++ b/lib/pyinfra-util/pyinfra_util/__init__.py @@ -0,0 +1 @@ +from .util import get_pass, deploy_tmux diff --git a/lib/pyinfra-util/pyinfra_util/__pycache__/__init__.cpython-310.pyc b/lib/pyinfra-util/pyinfra_util/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cc203edfae7d8c1f145f3f6d56954a926264a23 GIT binary patch literal 222 zcmd1j<>g`kf>+PI(lvneV-N=!FabFZKwK;WBvKes7;_kM8KW3;nWC5&8B&;n88n$+ zG6JPEnQn2UrlIYq;;_lhPbtkwwF9}P7-W|S0{|y9IPm}g literal 0 HcmV?d00001 diff --git a/lib/pyinfra-util/pyinfra_util/__pycache__/util.cpython-310.pyc b/lib/pyinfra-util/pyinfra_util/__pycache__/util.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9f24fcdc0ee418679c335e583b0deb5826d7acf GIT binary patch literal 1759 zcmZ`)Uysx_5YHbco69Z7ar~`#arY3gs;Hm9b%4;90eu7F>i#oHrpaZFnVtZyhlb^@GapaLBp#|-iKfcMX`UIOx0HlPfe3ttIC%|uYn45MQurwgfKTJKB6>6Dc$6Kd2ERCPp8 z&SgpEn95o$*R6sqfi_-k#?j?ruM2g(TPc|e zfat2a?2g@l#NLwThOA3|T`8$LuTptwIwEGd>3nTCAn$xGQpv^8RY-u&R$ZtB<5Q8; zs`$-PTcG{>YCMrs5!YI%7+1%*zo?q$@#D|10xV$8iCfJkFyQSenrS`j2=+j6vY>PT1lR-e=;ovr zCX$uG(~L4&DKXAxv>CjsW?81-0Q1Kpf-!Fdm1T44-8EAQr-1si@9I%9xq-q$%oO)qb_&zl1F=DP9gElskhQkR0}XH5g*bsAgMOdK6uIf z=Jeb3dvq@^ib&%=0LXeg!jz0sS&lI;6*H5L6nQC{U&C$6Y9pZt6jUIr3iC1jkw%7I zq7kD}LkH*XetYlx0euicRg@v6i?Uc4E}{c1a=@_qmL3l1`SVoFM7oGZOR-NQ4MFbR zx&GaNw(a}2FVKAMqA_#YQp56sMk)R1;~_#=d4=OVY$%{FMG7G0GV-xZ|3& zcm&mlciG(`QKx{N>I`&U4?K;4qC0EUJb@%W1|iWCQ51=oRjkA$4Sj_~SEtd#XH^9W zR}N}EK#jLs$aD{CJisDMcn&2hG~7NSf#d3qG0t-&7Aq3Wa-MbmY9zT4(`AsOTtf9N zAtvClsOx2S96z$YyrdJ%Dk}74=!oq=dHSyd-+|fThx`?+_w4|z0qBF*#3MZz7i+?Q z)H!e-23;>nxJ;8oVcm9Ht2<&OE1?)7*2`Vu<~tXNxwrrp&WGUP1*8@jgHNZD*M;~D Se-Rr0X2c5IO?%5dvHl;23i${C literal 0 HcmV?d00001 diff --git a/lib/pyinfra-util/pyinfra_util/util.py b/lib/pyinfra-util/pyinfra_util/util.py new file mode 100644 index 0000000..41d5d9d --- /dev/null +++ b/lib/pyinfra-util/pyinfra_util/util.py @@ -0,0 +1,56 @@ +""" +nginx deploy +""" +import subprocess +from pyinfra.operations import files, apt + + +def get_pass(filename: str) -> str: + """Get the data from the password manager.""" + try: + r = subprocess.run(["pass", "show", filename], capture_output=True) + except FileNotFoundError: + readme_url = "https://git.0x90.space/deltachat/secrets" + print(f"Please install pass and pull the latest version of our pass secrets from {readme_url}") + exit() + return r.stdout.decode('utf-8') + + +def deploy_tmux(home_dir="/root", escape_key="C-b", additional_config=[]): + apt.packages( + name="apt install tmux", + packages=["tmux"], + ) + + config = [ + f"set-option -g prefix {escape_key}", + "set-option -g aggressive-resize on", + "set-option -g mouse on", + "set-option -g set-titles on", + "set-option -g set-titles-string '#I:#W - \"#H\"'", + "unbind-key C-b", + "bind-key ` send-prefix", + "bind-key a last-window", + "bind-key k kill-session", + ] + for item in additional_config: + config.append(item) + for line in config: + files.line( + path=f"{home_dir}/.tmux.conf", + line=line, + ) + + dot_profile_add = """ +# autostart tmux +if [ -t 0 -a -z "$TMUX" ] +then + test -z "$(tmux list-sessions)" && exec tmux new -s "$USER" || exec tmux new -A -s $(tty | tail -c +6) -t "$USER" +fi +""" + files.block( + name="connect to tmux session on login", + path=f"{home_dir}/.profile", + content=dot_profile_add, + try_prevent_shell_expansion=True, + ) diff --git a/lib/pyinfra-util/pyproject.toml b/lib/pyinfra-util/pyproject.toml new file mode 100644 index 0000000..25f8663 --- /dev/null +++ b/lib/pyinfra-util/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires = ["setuptools>=45"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyinfra-util" +version = "0.1" -- 2.43.5 From 67f06ce24dacfc86a86e82893cfeb262dde17de4 Mon Sep 17 00:00:00 2001 From: missytake Date: Sat, 30 Nov 2024 21:30:21 +0100 Subject: [PATCH 6/7] ararat: Set up kvm, initiate debian VM --- ararat/deploy.py | 96 +++++++++++++++++++++++++++++++--- ararat/files/cloud-init.yml.j2 | 25 +++++++++ 2 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 ararat/files/cloud-init.yml.j2 diff --git a/ararat/deploy.py b/ararat/deploy.py index 06a505d..d0e52a7 100644 --- a/ararat/deploy.py +++ b/ararat/deploy.py @@ -1,9 +1,56 @@ +import os -# if it doesn't exist, create encrypted data partition -# mount encrypted data partition +from pyinfra import host, inventory +from pyinfra.operations import server, apk, files, openrc +from pyinfra.facts.server import Mounts -# apk add kvm virsh -# rc-update add libvirtd +from pyinfra_util import get_pass + + +files.replace( + name="Enable TCP forwarding via SSH server", + path="/etc/ssh/sshd_config", + text="AllowTcpForwarding no", + replace="AllowTcpForwarding yes", +) +openrc.service( + name="Restart sshd", + service="sshd", + restarted=True, +) + +files.replace( + name="Enable community repository", + path="/etc/apk/repositories", + text="#http://dl-cdn.alpinelinux.org/alpine/v3.20/community", + replace="http://dl-cdn.alpinelinux.org/alpine/v3.20/community", +) +apk.update() +apk.packages( + packages=["cryptsetup", "vim"] +) + +mounts = host.get_fact(Mounts) +if "/var/lib/libvirt" not in mounts: + decryption_password = get_pass('0x90/ararat/sdb-crypt').strip() + if decryption_password: + server.shell( + name="Decrypt and mount /data", + commands=[ + f" echo -n '{decryption_password}' | cryptsetup luksOpen --key-file - /dev/sdb1 sdb_crypt || true", + "mount /dev/mapper/sdb_crypt /var/lib/libvirt", + ] + ) + +apk.packages( + packages=["libvirt-daemon", "qemu-img", "qemu-system-x86_64", "virt-install"] +) +openrc.service( + name="Start libvirtd", + service="libvirtd", + running=True, + enabled=False, +) # add networking: https://wiki.alpinelinux.org/wiki/KVM#Networking # modprobe tun @@ -12,5 +59,42 @@ # if it doesn't exist, create debian base image (later: and other base images): https://mop.koeln/blog/creating-a-local-debian-vm-using-cloud-init-and-libvirt/#download-the-image # for every active VM, if no image exists, run virt-install with the chosen base image and their cloud-init.yml file: https://mop.koeln/blog/creating-a-local-debian-vm-using-cloud-init-and-libvirt/#preparing-a-cloud-init-file -# for every active VM, make sure an IP is assigned and traffic is passed to it - +debian_image_path = "/var/lib/libvirt/images/debian-12-generic-amd64.qcow2" +files.download( + name="Download Debian 12 base image", + src="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2", + dest=debian_image_path, +) +for vm in inventory.groups.get("debian_vms"): + if os.path.isfile(f"{vm}/files/cloud-init.yml"): + files.put( + name=f"Upload {vm}-cloud-init.yml", + src=f"{vm}/files/cloud-init.yml", + dest=f"/root/{vm}-cloud-init.yml", + ) + #virt-install + else: + if vm.data.get("authorized_keys"): + authorized_keys = "ssh_authorized_keys:\n - " + " - ".join( + [get_pass(f"0x90/ssh_keys/{admin}.pub") for admin in vm.data.get("authorized_keys")] + ) + else: + authorized_keys = "" + files.template( + name=f"Upload {vm}-cloud-init.yml", + src="ararat/files/cloud-init.yml.j2", + dest=f"/root/{vm}-cloud-init.yml", + ssh_authorized_keys=authorized_keys, + ) + memory = 1024 + vcpus = 1 + disk_size = 4 + server.shell( + name=f"virt-install {vm}", + commands=[ + f"virt-install --name {vm} --disk=size={disk_size},backing_store={debian_image_path} " + f"--memory {memory} --vcpus {vcpus} --cloud-init user-data=/root/{vm}-cloud-init.yml,disable=on " + "--network bridge=virbr0 --osinfo=debian12 || true", + ] + ) + # for every active VM, make sure an IP is assigned and traffic is passed to it diff --git a/ararat/files/cloud-init.yml.j2 b/ararat/files/cloud-init.yml.j2 new file mode 100644 index 0000000..84d57b0 --- /dev/null +++ b/ararat/files/cloud-init.yml.j2 @@ -0,0 +1,25 @@ +#cloud-config + +keyboard: + layout: de + variant: nodeadkeys + +locale: en_US + +timezone: UTC + +disable_root: false + +users: + - name: root + shell: /bin/bash + {{ ssh_authorized_keys }} + - name: mop + # so our user can just sudo without any password + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + # content from $HOME/.ssh/id_rsa.pub on your host system + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKZYJ91RLXRCQ4ZmdW6ucIltzukQ/k+lDOqlRIYwxNRv missytake@systemli.org + +# Examples: https://cloudinit.readthedocs.io/en/latest/reference/examples_library.html#examples-library -- 2.43.5 From cc666a832b676585792623bc4e228662d310d5c0 Mon Sep 17 00:00:00 2001 From: missytake Date: Mon, 2 Dec 2024 17:11:30 +0100 Subject: [PATCH 7/7] added gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b64319d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +pyinfra-debug.log +.venv/ -- 2.43.5