Skip to content

Commit

Permalink
vaultwarden: use v2-lib
Browse files Browse the repository at this point in the history
  • Loading branch information
stavros-k committed Nov 1, 2024
1 parent ecbd948 commit 766f4d3
Show file tree
Hide file tree
Showing 55 changed files with 5,344 additions and 150 deletions.
6 changes: 3 additions & 3 deletions ix-dev/community/vaultwarden/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ icon: https://media.sys.truenas.net/apps/vaultwarden/icons/icon.png
keywords:
- password
- manager
lib_version: 1.1.6
lib_version_hash: 0f6c609f0e7b2b737114163d22e389eb9aef5de009b1161d6f4ba7acecfd81ee
lib_version: 2.0.7
lib_version_hash: a483d30e0180b3e5f0cff93a55b867dbebe8e3b7561cacc376bfde91865f40d8
maintainers:
- email: [email protected]
name: truenas
Expand All @@ -34,4 +34,4 @@ sources:
- https://github.com/dani-garcia/vaultwarden
title: Vaultwarden
train: community
version: 1.0.10
version: 1.1.0
170 changes: 47 additions & 123 deletions ix-dev/community/vaultwarden/templates/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,135 +1,59 @@
{# Stores storage items that contains info for volumes, vol mounts, perms dirs and perms mounts #}
{% set storage_items = namespace(items=[]) %}
{% set pg_storage_items = namespace(items=[]) %}
{# Stores the top level volumes #}
{% set volumes = namespace(items={}) %}
{# Stores the container volume mounts #}
{% set volume_mounts = namespace(items=[]) %}
{% set pg_volume_mounts = namespace(items=[]) %}
{# Stores the perms container volume mounts #}
{% set perms_mounts = namespace(items=[]) %}
{# Stores the perms container dirs #}
{% set perms_dirs = namespace(items=[]) %}
{% set tpl = ix_lib.base.render.Render(values) %}

{% set c1 = tpl.add_container(values.consts.vaultwarden_container_name, "image") %}
{% set perm_container = tpl.deps.perms(values.consts.perms_container_name) %}

{% set pg_config = {
"user": values.consts.db_user,
"password": values.vaultwarden.db_password,
"database": values.consts.db_name,
"volume": values.storage.postgres_data,
} %}
{% set postgres = tpl.deps.postgres(values.consts.postgres_container_name, "postgres_image", pg_config, perm_container) %}

{% do c1.set_user(values.run_as.user, values.run_as.group) %}
{% do c1.depends.add_dependency(values.consts.postgres_container_name, "service_healthy") %}
{% do c1.healthcheck.set_custom_test("/healthcheck.sh") %}

{% do c1.environment.add_env("ROCKET_PORT", values.network.web_port) %}
{% do c1.environment.add_env("WEBSOCKET_PORT", values.network.ws_port) %}
{% do c1.environment.add_env("WEBSOCKET_ENABLED", values.network.ws_enabled) %}
{% do c1.environment.add_env("ADMIN_TOKEN", values.vaultwarden.admin_token) %}
{% do c1.environment.add_env("DATABASE_URL", postgres.get_url("postgres")) %}
{% if values.network.domain %}
{% do c1.environment.add_env("DOMAIN", values.network.domain) %}
{% endif %}
{% if values.network.certificate_id %}
{% do c1.environment.add_env("ROCKET_TLS", '{certs="%s",key="%s"}'|format(values.consts.ssl_cert_path, values.consts.ssl_key_path)) %}
{% do c1.configs.add("private", values.ix_certificates[values.network.certificate_id].privatekey, values.consts.ssl_key_path) %}
{% do c1.configs.add("public", values.ix_certificates[values.network.certificate_id].certificate, values.consts.ssl_cert_path) %}
{% endif %}

{% do storage_items.items.append(ix_lib.base.storage.storage_item(data=dict(values.storage.data, **{"mount_path": "/data"}),
values=values, perm_opts={"mount_path": "/mnt/vaultwarden/config", "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group}
)) %}
{% do storage_items.items.append(ix_lib.base.storage.storage_item(data={"type": "temporary", "mount_path": "/tmp"},
perm_opts={"mount_path": "/mnt/vaultwarden/tmp", "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group}
)) %}
{% do c1.environment.add_user_envs(values.vaultwarden.additional_envs) %}

{% set perms_config = {"uid": values.run_as.user, "gid": values.run_as.group, "mode": "check"} %}
{% do c1.add_storage("/data", values.storage.data) %}
{% do perm_container.add_or_skip_action("data", values.storage.data, perms_config) %}
{% for store in values.storage.additional_storage %}
{% do storage_items.items.append(ix_lib.base.storage.storage_item(data=store, values=values,
perm_opts={"mount_path": "/mnt/vaultwarden/dir_%s"|format(loop.index0), "mode": "check", "uid": values.run_as.user, "gid": values.run_as.group}
)) %}
{% endfor %}

{# Add each item to the above lists #}
{% for item in storage_items.items %}
{% if item.vol and volumes.items.update(item.vol) %}{% endif %}
{% if item.vol_mount and volume_mounts.items.append(item.vol_mount) %}{% endif %}
{% if item.perms_item and (perms_dirs.items.append(item.perms_item.perm_dir), perms_mounts.items.append(item.perms_item.vol_mount)) %}{% endif %}
{% endfor %}

{% do pg_storage_items.items.append(ix_lib.base.storage.storage_item(data={"type": "temporary", "mount_path": "/tmp"},
perm_opts={"mount_path": "/mnt/postgres/tmp", "mode": "check", "uid": values.consts.pg_run_user, "gid": values.consts.pg_run_group}
)) %}
{% do pg_storage_items.items.append(ix_lib.base.storage.storage_item(data=dict(values.storage.postgres_data, **{"mount_path": "/var/lib/postgresql/data"}),
values=values, perm_opts={"mount_path": "/mnt/postgres/data", "mode": "check", "uid": values.consts.pg_run_user, "gid": values.consts.pg_run_group,}
)) %}
{% for item in pg_storage_items.items %}
{% if item.vol and volumes.items.update(item.vol) %}{% endif %}
{% if item.vol_mount and pg_volume_mounts.items.append(item.vol_mount) %}{% endif %}
{% if item.perms_item and (perms_dirs.items.append(item.perms_item.perm_dir), perms_mounts.items.append(item.perms_item.vol_mount)) %}{% endif %}
{% do c1.add_storage(store.mount_path, store) %}
{% do perm_container.add_or_skip_action(store.mount_path, store, perms_config) %}
{% endfor %}

{% if values.network.certificate_id %}
configs:
private:
content: {{ values.ix_certificates[values.network.certificate_id].privatekey | tojson }}
public:
content: {{ values.ix_certificates[values.network.certificate_id].certificate | tojson }}
{% do c1.ports.add_port(values.network.web_port, values.network.web_port) %}
{% if values.network.ws_enabled %}
{% do c1.ports.add_port(values.network.ws_port, values.network.ws_port) %}
{% endif %}

{# Containers #}
services:
{{ values.consts.vaultwarden_container_name }}:
user: {{ "%d:%d" | format(values.run_as.user, values.run_as.group) }}
image: {{ ix_lib.base.utils.get_image(images=values.images, name="image") }}
restart: unless-stopped
deploy:
resources: {{ ix_lib.base.resources.resources(values.resources) | tojson }}
devices: {{ ix_lib.base.resources.get_devices(values.resources) | tojson }}
{% if values.network.certificate_id %}
configs:
- source: private
target: {{ values.consts.ssl_key_path }}
- source: public
target: {{ values.consts.ssl_cert_path }}
{% endif %}
depends_on:
{{ values.consts.postgres_container_name }}:
condition: service_healthy
{% if perms_dirs.items %}
{{ values.consts.perms_container_name }}:
condition: service_completed_successfully
{% endif %}
cap_drop: {{ ix_lib.base.security.get_caps().drop | tojson }}
security_opt: {{ ix_lib.base.security.get_sec_opts() | tojson }}
{% if values.network.dns_opts %}
dns_opt: {{ ix_lib.base.network.dns_opts(values.network.dns_opts) | tojson }}
{% endif %}
healthcheck: {{ ix_lib.base.healthchecks.check_health("/healthcheck.sh") | tojson }}
environment: {{ ix_lib.base.environment.envs(app=ix_lib.base.utils.merge_dicts({
"ROCKET_PORT": values.network.web_port,
"WEBSOCKET_PORT": values.network.ws_port,
"WEBSOCKET_ENABLED": values.network.ws_enabled,
"ADMIN_TOKEN": values.vaultwarden.admin_token,
"DATABASE_URL": ix_lib.base.postgres.pg_url(variant="postgres", host=values.consts.postgres_container_name, user=values.consts.db_user, password=values.vaultwarden.db_password, dbname=values.consts.db_name),
},
{
"ROCKET_TLS": '{certs="%s",key="%s"}'|format(values.consts.ssl_cert_path, values.consts.ssl_key_path)
} if values.network.certificate_id else {},
{
"DOMAIN": values.network.domain
} if values.network.domain else {},
), user=values.vaultwarden.additional_envs, values=values) | tojson }}
ports:
- {{ ix_lib.base.ports.get_port(port={"target": values.network.web_port, "published": values.network.web_port}) | tojson }}
{% if values.network.ws_enabled %}
- {{ ix_lib.base.ports.get_port(port={"target": values.network.ws_port, "published": values.network.ws_port}) | tojson }}
{% endif %}
volumes: {{ volume_mounts.items | tojson }}

{% set resource_without_gpus = ix_lib.base.utils.copy_dict(values.resources) %}
{% do resource_without_gpus.pop("gpus", None) %}
{{ values.consts.postgres_container_name }}: {{ ix_lib.base.postgres.pg_container(data={
"image": ix_lib.base.utils.get_image(images=values.images, name="postgres_image"),
"volumes": pg_volume_mounts.items,
"user": values.consts.pg_run_user, "group": values.consts.pg_run_group,
"db_user": values.consts.db_user, "db_name": values.consts.db_name,
"db_password": values.vaultwarden.db_password,
"dns_opts": values.network.dns_opts, "resources": resource_without_gpus,
"depends_on": {
values.consts.perms_container_name: {
"condition": "service_completed_successfully"
}
}
}) | tojson }}


{% if perms_dirs.items %}
{{ values.consts.perms_container_name }}: {{ ix_lib.base.permissions.perms_container(items=perms_dirs.items, volumes=perms_mounts.items) | tojson }}
{% endif %}

{% if volumes.items %}
volumes: {{ volumes.items | tojson }}
{% if perm_container.has_actions() %}
{% do perm_container.activate() %}
{% do c1.depends.add_dependency(values.consts.perms_container_name, "service_completed_successfully") %}
{% do postgres.container.depends.add_dependency(values.consts.perms_container_name, "service_completed_successfully") %}
{% endif %}

{% set portals = namespace(items=[{"port": values.network.web_port, "scheme": "https" if values.network.certificate_id else "http"}]) %}
{% set portal_scheme = "https" if values.network.certificate_id else "http" %}
{% do tpl.portals.add_portal({"port": values.network.web_port, "scheme": portal_scheme}) %}
{% if values.vaultwarden.admin_token %}
{% do portals.items.append({"name": "Admin Portal", "path": "/admin", "port": values.network.web_port, "scheme": "https" if values.network.certificate_id else "http"}) %}
{% do tpl.portals.add_portal({"name": "Admin Portal", "path": "/admin", "port": values.network.web_port, "scheme": portal_scheme}) %}
{% endif %}

x-portals: {{ ix_lib.base.metadata.get_portals(portals.items) | tojson }}
x-notes: {{ ix_lib.base.metadata.get_notes("Vaultwarden") | tojson }}
{{ tpl.render() | tojson }}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from render import Render

try:
from .error import RenderError
from .formatter import escape_dollar
from .validations import valid_octal_mode_or_raise, valid_fs_path_or_raise
except ImportError:
from error import RenderError
from formatter import escape_dollar
from validations import valid_octal_mode_or_raise, valid_fs_path_or_raise


class Configs:
def __init__(self, render_instance: "Render"):
self._render_instance = render_instance
self._configs: dict[str, dict] = {}

def add(self, name: str, data: str):
if not isinstance(data, str):
raise RenderError(f"Expected [data] to be a string, got [{type(data)}]")

if name not in self._configs:
self._configs[name] = {"name": name, "data": data}
return

if data == self._configs[name]["data"]:
return

raise RenderError(f"Config [{name}] already added with different data")

def has_configs(self):
return bool(self._configs)

def render(self):
return {
c["name"]: {"content": escape_dollar(c["data"])}
for c in sorted(self._configs.values(), key=lambda c: c["name"])
}


class ContainerConfigs:
def __init__(self, render_instance: "Render", configs: Configs):
self._render_instance = render_instance
self.top_level_configs: Configs = configs
self.container_configs: set[ContainerConfig] = set()

def add(self, name: str, data: str, target: str, mode: str = ""):
self.top_level_configs.add(name, data)

if target == "":
raise RenderError(f"Expected [target] to be set for config [{name}]")
if mode != "":
mode = valid_octal_mode_or_raise(mode)

if target in [c.target for c in self.container_configs]:
raise RenderError(f"Target [{target}] already used for another config")
target = valid_fs_path_or_raise(target)
self.container_configs.add(ContainerConfig(self._render_instance, name, target, mode))

def has_configs(self):
return bool(self.container_configs)

def render(self):
return [c.render() for c in sorted(self.container_configs, key=lambda c: c.source)]


class ContainerConfig:
def __init__(self, render_instance: "Render", source: str, target: str, mode: str):
self._render_instance = render_instance
self.source = source
self.target = target
self.mode = mode

def render(self):
result: dict[str, str | int] = {
"source": self.source,
"target": self.target,
}

if self.mode:
result["mode"] = int(self.mode, 8)

return result
Loading

0 comments on commit 766f4d3

Please sign in to comment.