liab-deployments/liab_deployments/deploy/owntracks.py

222 lines
5.4 KiB
Python

#
# Deploy OwnTracks (https://owntracks.org)
#
# An example inventory may look like this:
#
# group = [
# (
# 'edu.example.org',
# {
# 'owntracks': {
# 'sites': [
# {
# 'serve_address': "geo.example.org",
# 'container_tag': "docker.io/owntracks/recorder",
# 'user': ('owntracks', 1008),
# 'host_port': 8083,
# 'card_assets': 'assets/owntracks/geo.example.org-cards',
# 'config_assets': 'assets/owntracks/geo.example.org-config',
# 'auth': [
# ('usr1', '$2a$14$CWSpO...'),
# ('usr2', '$2a$14$rHlbr...'),
# ],
# }
# ],
# },
# ...
#
from pyinfra.api import deploy
from pyinfra import (
host,
)
from pyinfra.operations import (
files,
server,
systemd,
)
from liab_deployments.operations import (
caddy,
user,
user_systemd,
)
owntracks_container_setup = \
"--rm " \
"--name owntracks-recorder " \
"-v {user_home}/config:/config:Z " \
"-v {user_home}/store:/store:Z " \
"-v /etc/localtime:/etc/localtime:ro "
service_unit_tmpl = """\
[Unit]
Description=Podman-managed owntracks service
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
TimeoutStopSec=300
ExecStartPre=/bin/rm \\
-f %t/%n.ctr-id
ExecStart=/usr/bin/podman container run \\
--cidfile=%t/%n.ctr-id \\
--cgroups=no-conmon \\
--sdnotify=conmon \\
-d \\
--replace \\
--pull always \\
-p {host_port}:8083 \\
--rm \\
--network=slirp4netns \\
--name owntracks-recorder \\
{container_setup} \\
{container_tag} --port 0
# --port 0 above disable the use of MQTT in favor of direct HTTP
ExecStop=/usr/bin/podman stop \\
--ignore -t 10 \\
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \\
-f \\
--ignore -t 10 \\
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
"""
caddyfile_block_tmpl = """\
{serve_address} {{
{auth}
reverse_proxy localhost:{host_port}
}}
"""
@deploy("Deploy OwnTracks")
def deploy_sites():
if not hasattr(host.data, 'owntracks'):
return
for spec in host.data.owntracks.get('sites', []):
_deploy_site(
spec['serve_address'],
spec['container_tag'],
spec['card_assets'],
spec['config_assets'],
spec['user'],
spec['host_port'],
spec['auth'],
name=spec.get('name', 'owntracks'),
caddyfile_block_tmpl=spec.get(
'caddyfile_block_tmpl', caddyfile_block_tmpl
),
)
def _deploy_site(
serve_address,
container_tag,
card_assets,
config_assets,
user_spec: tuple[str, int],
host_port,
auth: list[tuple[str, str]],
*,
name,
caddyfile_block_tmpl,
):
user_name, uid = user_spec
user_home = f'/home/{user_name}'
user.systemd_service(
user_name,
uid,
user_home,
)
for i in ['store', 'config']:
files.directory(
path=f'{user_home}/{i}',
present=True,
_sudo_user=user_name,
)
user_systemd.service_unit(
user_name,
user_home,
name,
service_unit_tmpl.format(
user_home=user_home,
container_tag=container_tag,
host_port=host_port,
container_setup=owntracks_container_setup.format(
user_home=user_home,
),
),
)
files.sync(
name='Deposit OwnTracks user cards',
src=card_assets,
dest=f'{user_home}/store/cards',
user=user_name,
)
files.sync(
name='Deposit OwnTracks recorder config',
src=config_assets,
dest=f'{user_home}/config',
user=user_name,
)
systemd.service(
name='Stop OwnTracks to load friends',
service='owntracks.service',
running=False,
daemon_reload=True,
user_mode=True,
user_name=user_name,
)
server.shell(
name='OwnTracks load friends',
commands=[
f'cd {user_home}; podman container run '
f'-it {owntracks_container_setup.format(user_home=user_home)} '
f'--entrypoint /bin/sh {container_tag} '
"-c '[ -d /store/ghash ] || ot-recorder --initialize; ocat --load=friends < /config/friends.txt'",
],
_sudo_user=user_name,
)
user_systemd.run_service(
user_name,
uid,
'owntracks',
)
server.wait(
name=f"Wait for OwnTracks {serve_address!r} to start",
port=host_port,
)
auth_indent = ' '
auth_spec = ['basicauth {']
for usr, passwd in auth:
auth_spec.append(f'{auth_indent}{usr} {passwd}')
auth_spec.append('}')
caddy.caddyfile_block(
marker=f'OWNTRACKS {serve_address}',
content=caddyfile_block_tmpl.format(
serve_address=serve_address,
host_port=host_port,
auth='\n'.join(f'{auth_indent}{line}'
for line in auth_spec),
),
)