diff --git a/leylines-ansible/.gitignore b/leylines-ansible/.gitignore new file mode 100644 index 0000000..8311b76 --- /dev/null +++ b/leylines-ansible/.gitignore @@ -0,0 +1,10 @@ +.venv +*.pyc +*.pyo +*.pyd +__pycache__ +*.egg-info +.env +.vimrc +.ycm_extra_conf.py +*.db diff --git a/leylines-ansible/leylines_inv.py b/leylines-ansible/leylines_inv.py new file mode 100755 index 0000000..dc7aa0e --- /dev/null +++ b/leylines-ansible/leylines_inv.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +import sys + +from leylines import db, SERVER_NODE_ID + + +XDG_CACHE_HOME = os.path.expanduser(os.environ.get("XDG_CACHE_HOME", "~/.cache")) +CACHE_DIR = os.path.join(XDG_CACHE_HOME, "leylines") +if not os.path.isdir(CACHE_DIR): + os.mkdir(CACHE_DIR, 0o700) + + +def get_ansible_config(): + server_node = db.get_server_node() + nodes = [node for node in db.get_nodes() if node.ssh_key is not None] + + for node in nodes: + nid = node.id + ssh_key = node.ssh_key + ssh_key_path = os.path.join(CACHE_DIR, f"{nid}.key") + if not os.path.isfile(ssh_key_path): + with open(ssh_key_path, "w") as f: + f.write(ssh_key) + + return { + "leylines": { + "hosts": [node.name for node in nodes], + "vars": { + "ansible_ssh_user": "haskal", + "ansible_python_interpreter": "auto" + } + }, + "_meta": { + "hostvars": { + node.name: { + "ansible_host": str(node.ip), + "ansible_ssh_private_key_file": os.path.join(CACHE_DIR, f"{node.id}.key"), + "leylines_resources": + " ".join([f"{res}=1" for res in db.get_node_resources(node.id)]), + "leylines_ip": str(node.ip), + "leylines_server_addr": str(server_node.ip), + "leylines_is_server": "yes" if node.id == server_node.id else "no" + } for node in nodes + } + } + } + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="leylines ansible inventory plugin") + parser.add_argument("--list", action="store_true", help="List inventory") + args = parser.parse_args() + if args.list: + json.dump(get_ansible_config(), sys.stdout) + else: + raise Exception("no action specified") diff --git a/leylines-ansible/playbook-setup.yml b/leylines-ansible/playbook-setup.yml new file mode 100644 index 0000000..837122d --- /dev/null +++ b/leylines-ansible/playbook-setup.yml @@ -0,0 +1,94 @@ +--- +- hosts: all + vars: + python_version: 3.9.5 + tasks: + - name: install prerequisite packages + become: yes + apt: + name: ["build-essential", "wget", "software-properties-common", "libnss3-dev", "zlib1g-dev", + "libgdbm-dev", "libncurses5-dev", "libssl-dev", "libffi-dev", "libreadline-dev", + "libsqlite3-dev", "libbz2-dev", "libopenblas-dev"] + state: present + update_cache: yes + + - name: download build and install python {{ python_version }} + register: pyinstall + changed_when: "'NO_COMPILE_NEEDED' not in pyinstall.stdout" + args: + executable: "/bin/bash" + shell: | + set -eo pipefail + export PATH=$HOME/.local/bin:$PATH + export LD_LIBRARY_PATH=$HOME/.local/lib:$LD_LIBRARY_PATH + existing_ver="$(python3 -c 'import sys;print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")')" + if [ "$existing_ver" == "{{ python_version }}" ]; then + echo "NO_COMPILE_NEEDED" + exit + fi + if [ ! -d $HOME/python ]; then + mkdir $HOME/python + fi + cd $HOME/python + wget -O python.tgz https://www.python.org/ftp/python/{{ python_version }}/Python-{{ python_version }}.tgz + tar xf python.tgz + cd Python-{{ python_version }} + # https://bugs.python.org/issue41346 + sed -i 's/-j0 //' Makefile.pre.in + # Speed up LTO + sed -i -e "s|-flto |-flto=4 |g" configure configure.ac + export CFLAGS="-O3 -fno-semantic-interposition" + export LDFLAGS="-fno-semantic-interposition" + ./configure --prefix=$HOME/.local/ \ + --enable-shared \ + --with-computed-gotos \ + --enable-optimizations \ + --with-lto \ + --enable-ipv6 \ + --with-dbmliborder=gdbm:ndbm \ + --enable-loadable-sqlite-extensions \ + --with-tzpath=/usr/share/zoneinfo + make -j$(nproc) EXTRA_CFLAGS="$CFLAGS" + make EXTRA_CFLAGS="$CFLAGS" install + + - name: Setup dask directory and venv + register: pysetup + changed_when: false # lol + args: + executable: "/bin/bash" + shell: | + set -eo pipefail + export PATH=$HOME/.local/bin:$PATH + export LD_LIBRARY_PATH=$HOME/.local/lib:$LD_LIBRARY_PATH + if [ ! -d $HOME/dask ]; then + mkdir $HOME/dask + fi + cd $HOME/dask + python3 -m venv dask-venv + . dask-venv/bin/activate + pip3 install --upgrade dask distributed + if [ "{{ leylines_is_server }}" == "no" ]; then + pip3 install --upgrade numpy scipy pandas scikit-image matplotlib + else + pip3 install --upgrade bokeh jupyter-server-proxy + fi + + - name: Setup systemd user dir + file: + path: /home/{{ ansible_user }}/.config/systemd/user + mode: 0755 + state: directory + + - name: Install systemd task + template: + mode: 0644 + src: templates/leylines-{% if leylines_is_server == "yes" %}scheduler{% else %}worker{% endif %}.service + dest: /home/{{ ansible_user }}/.config/systemd/user + + - name: Enable and start systemd task + systemd: + scope: user + daemon_reload: true + name: leylines-{% if leylines_is_server == "yes" %}scheduler{% else %}worker{% endif %} + enabled: yes + state: restarted diff --git a/leylines-ansible/templates/leylines-scheduler.service b/leylines-ansible/templates/leylines-scheduler.service new file mode 100644 index 0000000..7368939 --- /dev/null +++ b/leylines-ansible/templates/leylines-scheduler.service @@ -0,0 +1,12 @@ +[Unit] +Description=Dask scheduler for leylines + +[Service] +Type=simple +Environment=PATH=%h/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +Environment=LD_LIBRARY_PATH=%h/.local/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ExecStart=/bin/bash -c 'source dask-venv/bin/activate && exec dask-scheduler --host {{leylines_ip}} --port 31337 --protocol tcp --dashboard-address={{leylines_ip}}:31336 --dashboard --no-show --use-xheaders true' +WorkingDirectory=%h/dask + +[Install] +WantedBy=default.target diff --git a/leylines-ansible/templates/leylines-worker.service b/leylines-ansible/templates/leylines-worker.service new file mode 100644 index 0000000..ae18f48 --- /dev/null +++ b/leylines-ansible/templates/leylines-worker.service @@ -0,0 +1,13 @@ +[Unit] +Description=Dask worker for leylines + +[Service] +Type=simple +Environment=PATH=%h/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +Environment=LD_LIBRARY_PATH=%h/.local/lib:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ExecStart=/bin/bash -c 'source dask-venv/bin/activate && exec dask-worker --dashboard-address {{leylines_ip}}:31336 --host {{leylines_ip}} --protocol tcp --nthreads 1 --nprocs auto --name {{inventory_hostname}} --local-directory $CACHE_DIRECTORY --resources "{{leylines_resources}}" {{leylines_server_addr}}:31337' +WorkingDirectory=%h/dask +CacheDirectory=leylines-worker + +[Install] +WantedBy=default.target diff --git a/leylines/leylines/__init__.py b/leylines/leylines/__init__.py index 95576d2..22104b3 100644 --- a/leylines/leylines/__init__.py +++ b/leylines/leylines/__init__.py @@ -12,7 +12,7 @@ from .database import Database, Node, SERVER_NODE_ID IFNAME = 'leyline-wg' DEFAULT_PORT = 31337 API_PORT = 31338 -db = Database("leylines.db") +db = Database() async def client_connected(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: @@ -67,6 +67,7 @@ def generate_node_config(id: int) -> Optional[str]: f"PublicKey={seckey_to_pubkey(server_node.seckey)}\n" f"AllowedIPs={interface.network}\n" f"Endpoint={server_node.public_ip}:{DEFAULT_PORT}\n" + f"PersistentKeepalive=25\n" ) @@ -76,7 +77,7 @@ def add_node_to_wg(wg: WireGuard, node: Node) -> None: peer = { "public_key": seckey_to_pubkey(node.seckey), - "persistent_keepalive": 60, + "persistent_keepalive": 25, "allowed_ips": [f"{node.ip}/32"] } wg.set(IFNAME, peer=peer) diff --git a/leylines/leylines/__main__.py b/leylines/leylines/__main__.py index e238980..ebd297c 100644 --- a/leylines/leylines/__main__.py +++ b/leylines/leylines/__main__.py @@ -48,7 +48,8 @@ elif args.cmd == "status": print("SERVER: ") else: print("SERVER:", server_node.name, server_node.ip, server_node.public_ip, - seckey_to_pubkey(server_node.seckey)) + seckey_to_pubkey(server_node.seckey), + "" if server_node.ssh_key is not None else "") for node in nodes: if node.id == SERVER_NODE_ID: continue diff --git a/leylines/leylines/database.py b/leylines/leylines/database.py index d2897b9..2b85dab 100644 --- a/leylines/leylines/database.py +++ b/leylines/leylines/database.py @@ -2,11 +2,14 @@ import binascii import ipaddress import secrets import sqlite3 +import os from typing import Iterable, List, NamedTuple, Optional, Set import monocypher -DEFAULT_DB_PATH = "/var/lib/leylines/leylines.db" +XDG_CONFIG_HOME = os.path.expanduser(os.environ.get("XDG_CONFIG_HOME", "~/.config")) +DEFAULT_DB_PATH = os.environ.get("LEYLINES_DB", + os.path.join(XDG_CONFIG_HOME, "leylines", "leylines.db")) DEFAULT_SUBNET = "172.16.10.1/24" SERVER_NODE_ID = 1 diff --git a/leylines/leylines/py.typed b/leylines/leylines/py.typed new file mode 100644 index 0000000..e69de29