forked from lab-in-a-box/liab-deployments
This commit adds the attribute `admin_token_hash` to the `dumpthings` specification. The attribute is optional. If provided it should be a valid token-hash, currently this is a hex-encoded sha256 hash (64 digits) of the plain text admin token. For security reasons, there is currently no support to store plain text admin tokens in the configuration.
228 lines
5.7 KiB
Python
228 lines
5.7 KiB
Python
from pathlib import Path
|
|
|
|
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,
|
|
)
|
|
|
|
# TODO without a static site, the --root-path must not be set to `api/`
|
|
service_unit_tmpl = """\
|
|
[Unit]
|
|
Description=DumpThings service
|
|
Wants=network-online.target
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
{service_params}
|
|
WorkingDirectory={user_home}
|
|
# we use inline script to be able to amend the PATH
|
|
ExecStart=bash -c "PATH={user_home}/.local/bin:$PATH; \\
|
|
dump-things-service \\
|
|
--port {host_port} \\
|
|
--config {config_filename} \\
|
|
--root-path /api \\
|
|
{admin_token_hash_args} \\
|
|
{service_args} \\
|
|
{user_home}/{data_dir} \\
|
|
"
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
"""
|
|
|
|
caddy_config_tmpl = """\
|
|
{serve_address} {{
|
|
reverse_proxy localhost:{host_port}
|
|
}}
|
|
"""
|
|
|
|
|
|
caddy_config_w_staticsite_tmpl = """\
|
|
{serve_address} {{
|
|
handle_path /api/* {{
|
|
reverse_proxy localhost:{host_port}
|
|
}}
|
|
|
|
handle {{
|
|
root * {staticsite_root_dir}/
|
|
file_server
|
|
}}
|
|
}}
|
|
"""
|
|
|
|
# TODO make a parameter with this default
|
|
dumpthings_collections_config = """\
|
|
type: collections
|
|
version: 1
|
|
"""
|
|
|
|
|
|
@deploy("Deploy dumpthings")
|
|
def deploy_sites():
|
|
"""
|
|
Installing multiple dumpthings sites using different versions under
|
|
the same user account is not supported.
|
|
"""
|
|
if not hasattr(host.data, 'dumpthings'):
|
|
return
|
|
for spec in host.data.dumpthings.get('sites', []):
|
|
staticsite_root_dir = spec.get('staticsite_root_dir')
|
|
caddyfile_block_tmpl = spec.get('caddyfile_block_tmpl')
|
|
if caddyfile_block_tmpl is None:
|
|
caddyfile_block_tmpl = caddy_config_w_staticsite_tmpl \
|
|
if staticsite_root_dir else caddy_config_tmpl
|
|
kwargs = {
|
|
k: spec.get(k)
|
|
for k in (
|
|
'service_args',
|
|
'systemd_service_params',
|
|
'python_version',
|
|
'dumpthings_version',
|
|
'admin_token_hash',
|
|
)
|
|
}
|
|
_deploy_site(
|
|
spec['serve_address'],
|
|
spec['config_file_asset'],
|
|
spec['user'],
|
|
spec['host_port'],
|
|
spec['collections'],
|
|
data_dir=spec.get('data_dir', 'data'),
|
|
name=spec.get('name', 'dumpthings'),
|
|
caddyfile_block_tmpl=caddyfile_block_tmpl,
|
|
staticsite_root_dir=staticsite_root_dir,
|
|
**kwargs
|
|
)
|
|
|
|
|
|
def _deploy_site(
|
|
serve_address: str,
|
|
config_file_asset: str,
|
|
user_spec: tuple[str, int],
|
|
host_port: int,
|
|
collections: list[tuple],
|
|
*,
|
|
data_dir: str,
|
|
name: str,
|
|
caddyfile_block_tmpl: str,
|
|
service_args: str | None = None,
|
|
systemd_service_params: str,
|
|
dumpthings_version: str | None = None,
|
|
python_version: str | None = None,
|
|
staticsite_root_dir: str | None = None,
|
|
admin_token_hash: str | None = None,
|
|
):
|
|
user_name, uid = user_spec
|
|
user_home = f'/home/{user_name}'
|
|
|
|
config_file = f'{user_home}/{name}_config.yaml'
|
|
|
|
user.systemd_service(
|
|
user_name,
|
|
uid,
|
|
user_home,
|
|
)
|
|
|
|
user.uv(
|
|
user_name,
|
|
)
|
|
|
|
pyarg = f"--python {python_version}" if python_version else ""
|
|
dtarg = f'dump-things-service{dumpthings_version or ""}'
|
|
server.shell(
|
|
name=f'Install dump-things-service {name!r}',
|
|
commands=[
|
|
f"$HOME/.local/bin/uv tool install {pyarg} {dtarg}"
|
|
],
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
for i in [data_dir] + [f'{data_dir}/{c[0]}' for c in collections]:
|
|
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,
|
|
host_port=host_port,
|
|
service_params=systemd_service_params or '',
|
|
service_args=service_args or '',
|
|
config_filename=config_file,
|
|
data_dir=data_dir,
|
|
admin_token_hash_args=(
|
|
f'--admin-token-hash {admin_token_hash}'
|
|
if admin_token_hash
|
|
else ''
|
|
),
|
|
),
|
|
)
|
|
|
|
files.block(
|
|
name='Dump-things API config',
|
|
path=config_file,
|
|
content=Path(config_file_asset).read_text(),
|
|
present=True,
|
|
try_prevent_shell_expansion=True,
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
files.block(
|
|
name='Dump-things collections config',
|
|
path=f'{user_home}/{data_dir}/.dumpthings.yaml',
|
|
content=dumpthings_collections_config,
|
|
present=True,
|
|
try_prevent_shell_expansion=True,
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
for c in collections:
|
|
if not c[1]:
|
|
continue
|
|
files.block(
|
|
name=f'Dump-things "{c[0]}" collection config',
|
|
path=f'{user_home}/{data_dir}/{c[0]}/.dumpthings.yaml',
|
|
content=Path(c[1]).read_text(),
|
|
present=True,
|
|
try_prevent_shell_expansion=True,
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
user_systemd.run_service(
|
|
user_name,
|
|
uid,
|
|
name,
|
|
)
|
|
|
|
server.wait(
|
|
name=f"Wait for dumpthings {serve_address!r} to start",
|
|
port=host_port,
|
|
)
|
|
|
|
caddyfile_block_params = {
|
|
'serve_address': serve_address,
|
|
'host_port': host_port,
|
|
}
|
|
if staticsite_root_dir:
|
|
caddyfile_block_params['staticsite_root_dir'] = staticsite_root_dir
|
|
caddy.caddyfile_block(
|
|
marker=f'DUMPTHINGS {serve_address}',
|
|
content=caddyfile_block_tmpl.format(**caddyfile_block_params),
|
|
)
|