From 88bdb77e83685c475b5c5fb4c1c167a0f48cf8df Mon Sep 17 00:00:00 2001 From: Benoit S Date: Mon, 15 Nov 2021 22:23:45 +0900 Subject: [PATCH] Init project --- .gitignore | 1 + README.md | 46 ++++++++++++- deploy.py | 124 +++++++++++++++++++++++++++++++++++ group_data/all.py | 37 +++++++++++ inventories/dev.py | 1 + inventories/production.py | 1 + remove.py | 16 +++++ templates/env.j2 | 3 + templates/systemd-run.sh.j2 | 13 ++++ templates/systemd.service.j2 | 20 ++++++ 10 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 deploy.py create mode 100644 group_data/all.py create mode 100644 inventories/dev.py create mode 100644 inventories/production.py create mode 100644 remove.py create mode 100644 templates/env.j2 create mode 100644 templates/systemd-run.sh.j2 create mode 100644 templates/systemd.service.j2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc9d4da --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +pyinfra-debug.log diff --git a/README.md b/README.md index 7d21e90..78f8d4c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,47 @@ # linkding -Pyinfra that deploy linkding un-dockerized. Mostly to be used in LXD/C. +Pyinfra that deploy linkding un-dockerized on a Debian 11 LXD container. + +# Deployment + +``` +# lxc launch images:debian/11 +# lxec exec bash +# apt update && apt install python3-pip git +# pip install pyinfra +# git clone https://git.benpro.fr/pyinfra/linkding.git +# cd linkding +# ###!!!### Edit group_data/production.py +# pyinfra inventories/production.py deploy.py +``` + +## Upgrade + +Run `remove.py` then `deploy.py`. + +``` +# pyinfra inventories/production.py remove.py +# pyinfra inventories/production.py deploy.py +``` + +# Attach a pictures volume + +If required, you can mount external volumes. Example: + +``` +# lxc config device add _disk disk source=/dev/vdf path=/mnt/pictures readonly=true raw.mount.options=noload +``` + +Then add it to `group_data/production.py`: + +``` +photoprism_originals_src='/mnt/pictures' +``` + +# Run photoprism command + +A systemd-run wrapper is installed and you can use it via `/usr/local/bin/photoprism`. + +``` +# /usr/local/bin/photoprism config +``` diff --git a/deploy.py b/deploy.py new file mode 100644 index 0000000..8709ce0 --- /dev/null +++ b/deploy.py @@ -0,0 +1,124 @@ +from pyinfra import host +from pyinfra.operations import apt, server, files, systemd, mysql + +apt.update( + name='Update apt repositories', +) + +apt.upgrade( + name='Upgrade apt packages', +) + +apt.packages( + name='Install dependencies', + packages=host.data.app['deps'], + update=False, +) + +server.user( + name='Create UNIX user', + user=host.data.unix_account['user'], + home=host.data.unix_account['home'], + shell=host.data.unix_account['shell'], + ensure_home=True, + system=True, + comment=f"{host.data.unix_account['user']} system user", + present=True, +) + +for directory in host.data.data_directories: + files.directory( + name=f'Make required directory {directory}', + path=directory, + user=host.data.unix_account['user'], + group=host.data.unix_account['group'], + mode=755 + ) + +files.download( + name='Download undocker', + src=host.data.undocker['url'], + dest=host.data.undocker['bin_path'], + user='root', + group='root', + mode='755', + sha256sum=host.data.undocker['sha256'], +) + +files.directory( + name='Create undocker cache', + path=host.data.undocker['cache'], + user='root', + group='root', + mode=755 +) + +if not host.fact.file(f"{host.data.undocker['cache']}/{host.data.app['name']}.tar"): + server.shell( + name='Download Docker image', + chdir=host.data.undocker['cache'], + commands=[ + f"skopeo copy {host.data.app['image']} docker-archive:{host.data.app['name']}.tar" + ], + ) + +files.directory( + name='Create undocker app destination', + path=host.data.undocker['app_dst'], + user='root', + group='root', + mode=755 +) + +# If the bootstrap file is here, undockerized image should be fine, don't extract again +if not host.fact.file(f"{host.data.undocker['app_dst']}/etc/linkding/bootstrap.sh"): + server.shell( + name='Undocker the Docker image', + chdir=host.data.undocker['app_dst'], + commands=[ + f"undocker {host.data.undocker['cache']}/{host.data.app['name']}.tar - | tar -xv" + ], + ) + +# Since the app is badly Dockerized we need to do a chown +files.directory( + name=f"Ensure {host.data.undocker['app_dst']}/etc/linkding has correct rights", + path=f"{host.data.undocker['app_dst']}/etc/linkding", + user=host.data.unix_account['user'], + group=host.data.unix_account['group'], +) + +files.template( + name='Set env file', + src='templates/env.j2', + dest=host.data.systemd['EnvironmentFile'], + mode='600', +) + +files.template( + name='Set systemd service file', + src=f'templates/systemd.service.j2', + dest=f"/etc/systemd/system/{host.data.app['name']}.service", + mode='644', +) + +systemd.daemon_reload( + name='Reload systemd', + user_mode=False, +) + +systemd.service( + name='Enable systemd service', + service=f"{host.data.app['name']}.service", + running=True, + restarted=True, + enabled=True, +) + +files.template( + name='Set systemd-run wrapper', + src='templates/systemd-run.sh.j2', + dest=f"/usr/local/bin/{host.data.app['name']}", + mode='755', +) + diff --git a/group_data/all.py b/group_data/all.py new file mode 100644 index 0000000..7dcc296 --- /dev/null +++ b/group_data/all.py @@ -0,0 +1,37 @@ +app = { + 'name': 'linkding', + 'deps': ['skopeo', 'curl'], + 'image': 'docker://sissbruecker/linkding:latest', +} + +undocker = { + 'url': 'https://git.sr.ht/~motiejus/undocker/refs/download/v1.0.2/undocker-linux-amd64-v1.0.2', + 'sha256': 'b937e69e774c530c080c1f6685763ca7db4dc58520a5a2054d111e1504f47688', + 'bin_path': '/usr/local/bin/undocker', + 'app_dst': f"/opt/{app['name']}", + 'cache': '/var/cache/undocker', +} + +unix_account = { + 'user': app['name'], + 'group': app['name'], + 'home': f"/home/{app['name']}", + 'shell': '/bin/false', +} + +data_directories = [ + f"{unix_account['home']}/data" +] + +systemd = { + 'Description': app['name'], + 'User': unix_account['user'], + 'Group': unix_account['group'], + 'EnvironmentFile': f"/etc/{app['name']}.env", + 'WorkingDirectory': f"/etc/{app['name']}", + 'RootDirectory': f"{undocker['app_dst']}", + 'ReadWritePaths': '+/etc/linkding', + 'BindPaths': f"{data_directories[0]}:/etc/linkding/data", + 'BindReadOnlyPaths': '/etc/resolv.conf /etc/hosts', + 'ExecStart': '/etc/linkding/bootstrap.sh', +} \ No newline at end of file diff --git a/inventories/dev.py b/inventories/dev.py new file mode 100644 index 0000000..062fb25 --- /dev/null +++ b/inventories/dev.py @@ -0,0 +1 @@ +dev_servers = ['@ssh/debian11.home.arpa'] diff --git a/inventories/production.py b/inventories/production.py new file mode 100644 index 0000000..6a9cad3 --- /dev/null +++ b/inventories/production.py @@ -0,0 +1 @@ +production_servers = ['@local'] diff --git a/remove.py b/remove.py new file mode 100644 index 0000000..d9e434a --- /dev/null +++ b/remove.py @@ -0,0 +1,16 @@ +from pyinfra import host +from pyinfra.operations import server, files + +if host.fact.file(host.data.undocker_cache+'/photoprism.tar'): + server.shell( + name='Remove photoprism Docker image', + chdir=host.data.undocker_cache, + commands=['rm photoprism.tar'], + ) + +if host.fact.directory(host.data.undocker_dst): + server.shell( + name='Remove undocker destination', + commands=['rm -rf {}'.format(host.data.undocker_dst)], + ) + diff --git a/templates/env.j2 b/templates/env.j2 new file mode 100644 index 0000000..6c95bee --- /dev/null +++ b/templates/env.j2 @@ -0,0 +1,3 @@ +# From Dockerfile +VIRTUAL_ENV=/opt/venv +PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ No newline at end of file diff --git a/templates/systemd-run.sh.j2 b/templates/systemd-run.sh.j2 new file mode 100644 index 0000000..976079c --- /dev/null +++ b/templates/systemd-run.sh.j2 @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +systemd-run \ + --pty \ + --property=User={{ host.data.systemd['User'] }} \ + --property=Group={{ host.data.systemd['Group'] }} \ + --property=EnvironmentFile={{ host.data.systemd['EnvironmentFile'] }} \ + --property=WorkingDirectory={{ host.data.systemd['WorkingDirectory'] }} \ + --property=RootDirectory={{ host.data.systemd['RootDirectory'] }} \ + --property='BindPaths={{ host.data.systemd['BindPaths'] }}' \ + --property='BindReadOnlyPaths={{ host.data.systemd['BindReadOnlyPaths'] }}' \ + "$@" diff --git a/templates/systemd.service.j2 b/templates/systemd.service.j2 new file mode 100644 index 0000000..ed2d333 --- /dev/null +++ b/templates/systemd.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description={{ host.data.systemd['Description'] }} +After=network.target + +[Service] +Type=simple +User={{ host.data.systemd['User'] }} +Group={{ host.data.systemd['Group'] }} +EnvironmentFile={{ host.data.systemd['EnvironmentFile'] }} +WorkingDirectory={{ host.data.systemd['WorkingDirectory'] }} +RootDirectory={{ host.data.systemd['RootDirectory'] }} +ReadWritePaths={{ host.data.systemd['ReadWritePaths'] }} +BindPaths={{ host.data.systemd['BindPaths'] }} +BindReadOnlyPaths={{ host.data.systemd['BindReadOnlyPaths'] }} +ExecStart={{ host.data.systemd['ExecStart'] }} +KillSignal=SIGQUIT +Restart=on-failure + +[Install] +WantedBy=multi-user.target