move signal bot to its own DB

This commit is contained in:
2026-03-18 08:35:45 -04:00
parent 2661127426
commit 976c3f9d3e
15 changed files with 402 additions and 52 deletions

View File

@@ -0,0 +1,171 @@
"""seprating signal_bot database.
Revision ID: 6b275323f435
Revises: 2ef7ba690159
Create Date: 2026-03-18 08:34:28.785885
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
from python.orm import RichieBase
if TYPE_CHECKING:
from collections.abc import Sequence
# revision identifiers, used by Alembic.
revision: str = "6b275323f435"
down_revision: str | None = "2ef7ba690159"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
schema = RichieBase.schema_name
def upgrade() -> None:
"""Upgrade."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("device_role", schema=schema)
op.drop_table("signal_device", schema=schema)
op.drop_table("role", schema=schema)
op.drop_table("dead_letter_message", schema=schema)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"dead_letter_message",
sa.Column("source", sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column("message", sa.TEXT(), autoincrement=False, nullable=False),
sa.Column("received_at", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False),
sa.Column(
"status",
postgresql.ENUM("UNPROCESSED", "PROCESSED", name="message_status", schema=schema),
autoincrement=False,
nullable=False,
),
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column(
"created",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.Column(
"updated",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_dead_letter_message")),
schema=schema,
)
op.create_table(
"role",
sa.Column("name", sa.VARCHAR(length=50), autoincrement=False, nullable=False),
sa.Column(
"id",
sa.SMALLINT(),
server_default=sa.text("nextval(f'{schema}.role_id_seq'::regclass)"),
autoincrement=True,
nullable=False,
),
sa.Column(
"created",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.Column(
"updated",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_role")),
sa.UniqueConstraint(
"name", name=op.f("uq_role_name"), postgresql_include=[], postgresql_nulls_not_distinct=False
),
schema=schema,
)
op.create_table(
"signal_device",
sa.Column("phone_number", sa.VARCHAR(length=50), autoincrement=False, nullable=False),
sa.Column("safety_number", sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column(
"trust_level",
postgresql.ENUM("VERIFIED", "UNVERIFIED", "BLOCKED", name="trust_level", schema=schema),
autoincrement=False,
nullable=False,
),
sa.Column("last_seen", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False),
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column(
"created",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.Column(
"updated",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_signal_device")),
sa.UniqueConstraint(
"phone_number",
name=op.f("uq_signal_device_phone_number"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
schema=schema,
)
op.create_table(
"device_role",
sa.Column("device_id", sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column("role_id", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column(
"created",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.Column(
"updated",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.ForeignKeyConstraint(
["device_id"], [f"{schema}.signal_device.id"], name=op.f("fk_device_role_device_id_signal_device")
),
sa.ForeignKeyConstraint(["role_id"], [f"{schema}.role.id"], name=op.f("fk_device_role_role_id_role")),
sa.PrimaryKeyConstraint("id", name=op.f("pk_device_role")),
sa.UniqueConstraint(
"device_id",
"role_id",
name=op.f("uq_device_role_device_role"),
postgresql_include=[],
postgresql_nulls_not_distinct=False,
),
schema=schema,
)
# ### end Alembic commands ###

View File

@@ -0,0 +1,100 @@
"""seprating signal_bot database.
Revision ID: 6eaf696e07a5
Revises:
Create Date: 2026-03-17 21:35:37.612672
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
from python.orm import SignalBotBase
if TYPE_CHECKING:
from collections.abc import Sequence
# revision identifiers, used by Alembic.
revision: str = "6eaf696e07a5"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
schema = SignalBotBase.schema_name
def upgrade() -> None:
"""Upgrade."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"dead_letter_message",
sa.Column("source", sa.String(), nullable=False),
sa.Column("message", sa.Text(), nullable=False),
sa.Column("received_at", sa.DateTime(timezone=True), nullable=False),
sa.Column(
"status", postgresql.ENUM("UNPROCESSED", "PROCESSED", name="message_status", schema=schema), nullable=False
),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_dead_letter_message")),
schema=schema,
)
op.create_table(
"role",
sa.Column("name", sa.String(length=50), nullable=False),
sa.Column("id", sa.SmallInteger(), nullable=False),
sa.Column("created", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_role")),
sa.UniqueConstraint("name", name=op.f("uq_role_name")),
schema=schema,
)
op.create_table(
"signal_device",
sa.Column("phone_number", sa.String(length=50), nullable=False),
sa.Column("safety_number", sa.String(), nullable=True),
sa.Column(
"trust_level",
postgresql.ENUM("VERIFIED", "UNVERIFIED", "BLOCKED", name="trust_level", schema=schema),
nullable=False,
),
sa.Column("last_seen", sa.DateTime(timezone=True), nullable=False),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_signal_device")),
sa.UniqueConstraint("phone_number", name=op.f("uq_signal_device_phone_number")),
schema=schema,
)
op.create_table(
"device_role",
sa.Column("device_id", sa.Integer(), nullable=False),
sa.Column("role_id", sa.SmallInteger(), nullable=False),
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.ForeignKeyConstraint(
["device_id"], [f"{schema}.signal_device.id"], name=op.f("fk_device_role_device_id_signal_device")
),
sa.ForeignKeyConstraint(["role_id"], [f"{schema}.role.id"], name=op.f("fk_device_role_role_id_role")),
sa.PrimaryKeyConstraint("id", name=op.f("pk_device_role")),
sa.UniqueConstraint("device_id", "role_id", name="uq_device_role_device_role"),
schema=schema,
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("device_role", schema=schema)
op.drop_table("signal_device", schema=schema)
op.drop_table("role", schema=schema)
op.drop_table("dead_letter_message", schema=schema)
# ### end Alembic commands ###

View File

@@ -83,6 +83,13 @@ DATABASES: dict[str, DatabaseConfig] = {
base_class_name="VanInventoryBase", base_class_name="VanInventoryBase",
models_module="python.orm.van_inventory.models", models_module="python.orm.van_inventory.models",
), ),
"signal_bot": DatabaseConfig(
env_prefix="SIGNALBOT",
version_location="python/alembic/signal_bot/versions",
base_module="python.orm.signal_bot.base",
base_class_name="SignalBotBase",
models_module="python.orm.signal_bot.models",
),
} }

View File

@@ -1,9 +1,11 @@
"""ORM package exports.""" """ORM package exports."""
from python.orm.richie.base import RichieBase from python.orm.richie.base import RichieBase
from python.orm.signal_bot.base import SignalBotBase
from python.orm.van_inventory.base import VanInventoryBase from python.orm.van_inventory.base import VanInventoryBase
__all__ = [ __all__ = [
"RichieBase", "RichieBase",
"SignalBotBase",
"VanInventoryBase", "VanInventoryBase",
] ]

View File

@@ -11,22 +11,15 @@ from python.orm.richie.contact import (
Need, Need,
RelationshipType, RelationshipType,
) )
from python.orm.richie.dead_letter_message import DeadLetterMessage
from python.orm.richie.signal_device import DeviceRole, RoleRecord, SignalDevice
__all__ = [ __all__ = [
"Bill", "Bill",
"Contact", "Contact",
"ContactNeed", "ContactNeed",
"ContactRelationship", "ContactRelationship",
"DeadLetterMessage",
"DeviceRole",
"RoleRecord",
"Legislator", "Legislator",
"Need", "Need",
"RelationshipType", "RelationshipType",
"RichieBase", "RichieBase",
"SignalDevice",
"TableBase", "TableBase",
"TableBaseBig", "TableBaseBig",
"TableBaseSmall", "TableBaseSmall",

View File

@@ -1,26 +0,0 @@
"""Dead letter queue for Signal bot messages that fail processing."""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import DateTime, Text
from sqlalchemy.dialects.postgresql import ENUM
from sqlalchemy.orm import Mapped, mapped_column
from python.orm.richie.base import TableBase
from python.signal_bot.models import MessageStatus
class DeadLetterMessage(TableBase):
"""A Signal message that failed processing and was sent to the dead letter queue."""
__tablename__ = "dead_letter_message"
source: Mapped[str]
message: Mapped[str] = mapped_column(Text)
received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
status: Mapped[MessageStatus] = mapped_column(
ENUM(MessageStatus, name="message_status", create_type=True, schema="main"),
default=MessageStatus.UNPROCESSED,
)

View File

@@ -0,0 +1,16 @@
"""Signal bot database ORM exports."""
from __future__ import annotations
from python.orm.signal_bot.base import SignalBotBase, SignalBotTableBase, SignalBotTableBaseSmall
from python.orm.signal_bot.models import DeadLetterMessage, DeviceRole, RoleRecord, SignalDevice
__all__ = [
"DeadLetterMessage",
"DeviceRole",
"RoleRecord",
"SignalBotBase",
"SignalBotTableBase",
"SignalBotTableBaseSmall",
"SignalDevice",
]

View File

@@ -0,0 +1,52 @@
"""Signal bot database ORM base."""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import DateTime, MetaData, SmallInteger, func
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from python.orm.common import NAMING_CONVENTION
class SignalBotBase(DeclarativeBase):
"""Base class for signal_bot database ORM models."""
schema_name = "main"
metadata = MetaData(
schema=schema_name,
naming_convention=NAMING_CONVENTION,
)
class _TableMixin:
"""Shared timestamp columns for all table bases."""
created: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
)
updated: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(),
)
class SignalBotTableBaseSmall(_TableMixin, AbstractConcreteBase, SignalBotBase):
"""Table with SmallInteger primary key."""
__abstract__ = True
id: Mapped[int] = mapped_column(SmallInteger, primary_key=True)
class SignalBotTableBase(_TableMixin, AbstractConcreteBase, SignalBotBase):
"""Table with Integer primary key."""
__abstract__ = True
id: Mapped[int] = mapped_column(primary_key=True)

View File

@@ -1,18 +1,18 @@
"""Signal bot device and role ORM models.""" """Signal bot device, role, and dead letter ORM models."""
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, SmallInteger, String, UniqueConstraint from sqlalchemy import DateTime, ForeignKey, SmallInteger, String, Text, UniqueConstraint
from sqlalchemy.dialects.postgresql import ENUM from sqlalchemy.dialects.postgresql import ENUM
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from python.orm.richie.base import TableBase, TableBaseSmall from python.orm.signal_bot.base import SignalBotTableBase, SignalBotTableBaseSmall
from python.signal_bot.models import TrustLevel from python.signal_bot.models import MessageStatus, TrustLevel
class RoleRecord(TableBaseSmall): class RoleRecord(SignalBotTableBaseSmall):
"""Lookup table for RBAC roles, keyed by smallint.""" """Lookup table for RBAC roles, keyed by smallint."""
__tablename__ = "role" __tablename__ = "role"
@@ -20,7 +20,7 @@ class RoleRecord(TableBaseSmall):
name: Mapped[str] = mapped_column(String(50), unique=True) name: Mapped[str] = mapped_column(String(50), unique=True)
class DeviceRole(TableBase): class DeviceRole(SignalBotTableBase):
"""Association between a device and a role.""" """Association between a device and a role."""
__tablename__ = "device_role" __tablename__ = "device_role"
@@ -33,7 +33,7 @@ class DeviceRole(TableBase):
role_id: Mapped[int] = mapped_column(SmallInteger, ForeignKey("main.role.id")) role_id: Mapped[int] = mapped_column(SmallInteger, ForeignKey("main.role.id"))
class SignalDevice(TableBase): class SignalDevice(SignalBotTableBase):
"""A Signal device tracked by phone number and safety number.""" """A Signal device tracked by phone number and safety number."""
__tablename__ = "signal_device" __tablename__ = "signal_device"
@@ -47,3 +47,17 @@ class SignalDevice(TableBase):
last_seen: Mapped[datetime] = mapped_column(DateTime(timezone=True)) last_seen: Mapped[datetime] = mapped_column(DateTime(timezone=True))
roles: Mapped[list[RoleRecord]] = relationship(secondary=DeviceRole.__table__) roles: Mapped[list[RoleRecord]] = relationship(secondary=DeviceRole.__table__)
class DeadLetterMessage(SignalBotTableBase):
"""A Signal message that failed processing and was sent to the dead letter queue."""
__tablename__ = "dead_letter_message"
source: Mapped[str]
message: Mapped[str] = mapped_column(Text)
received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
status: Mapped[MessageStatus] = mapped_column(
ENUM(MessageStatus, name="message_status", create_type=True, schema="main"),
default=MessageStatus.UNPROCESSED,
)

View File

@@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
import requests import requests
@@ -10,11 +11,15 @@ if TYPE_CHECKING:
from python.signal_bot.models import SignalMessage from python.signal_bot.models import SignalMessage
from python.signal_bot.signal_client import SignalClient from python.signal_bot.signal_client import SignalClient
logger = logging.getLogger(__name__)
def _get_entity_state(ha_url: str, ha_token: str, entity_id: str) -> dict[str, Any]: def _get_entity_state(ha_url: str, ha_token: str, entity_id: str) -> dict[str, Any]:
"""Fetch an entity's state from Home Assistant.""" """Fetch an entity's state from Home Assistant."""
entity_url = f"{ha_url}/api/states/{entity_id}"
logger.debug(f"Fetching {entity_url=}")
response = requests.get( response = requests.get(
f"{ha_url}/api/states/{entity_id}", entity_url,
headers={"Authorization": f"Bearer {ha_token}"}, headers={"Authorization": f"Bearer {ha_token}"},
timeout=30, timeout=30,
) )
@@ -24,10 +29,7 @@ def _get_entity_state(ha_url: str, ha_token: str, entity_id: str) -> dict[str, A
def _format_location(latitude: str, longitude: str) -> str: def _format_location(latitude: str, longitude: str) -> str:
"""Render a friendly location response.""" """Render a friendly location response."""
return ( return f"Van location: {latitude}, {longitude}\nhttps://maps.google.com/?q={latitude},{longitude}"
f"Van location: {latitude}, {longitude}\n"
f"https://maps.google.com/?q={latitude},{longitude}"
)
def handle_location_request( def handle_location_request(
@@ -41,10 +43,14 @@ def handle_location_request(
signal.reply(message, "Location command is not configured (missing HA_URL or HA_TOKEN).") signal.reply(message, "Location command is not configured (missing HA_URL or HA_TOKEN).")
return return
lat_payload = None
lon_payload = None
try: try:
lat_payload = _get_entity_state(ha_url, ha_token, "sensor.van_last_known_latitude") lat_payload = _get_entity_state(ha_url, ha_token, "sensor.van_last_known_latitude")
lon_payload = _get_entity_state(ha_url, ha_token, "sensor.van_last_known_longitude") lon_payload = _get_entity_state(ha_url, ha_token, "sensor.van_last_known_longitude")
except requests.RequestException: except requests.RequestException:
logger.exception("Couldn't fetch van location from Home Assistant right now.")
logger.debug(f"{ha_url=} {lat_payload=} {lon_payload=}")
signal.reply(message, "Couldn't fetch van location from Home Assistant right now.") signal.reply(message, "Couldn't fetch van location from Home Assistant right now.")
return return

View File

@@ -10,7 +10,7 @@ from sqlalchemy import delete, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from python.common import utcnow from python.common import utcnow
from python.orm.richie.signal_device import RoleRecord, SignalDevice from python.orm.signal_bot.models import RoleRecord, SignalDevice
from python.signal_bot.models import Role, TrustLevel from python.signal_bot.models import Role, TrustLevel
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@@ -21,10 +21,18 @@ class LLMClient:
temperature: Sampling temperature. temperature: Sampling temperature.
""" """
def __init__(self, model: str, host: str, port: int = 11434, *, temperature: float = 0.1) -> None: def __init__(
self,
*,
model: str,
host: str,
port: int = 11434,
temperature: float = 0.1,
timeout: int = 300,
) -> None:
self.model = model self.model = model
self.temperature = temperature self.temperature = temperature
self._client = httpx.Client(base_url=f"http://{host}:{port}", timeout=120) self._client = httpx.Client(base_url=f"http://{host}:{port}", timeout=timeout)
def chat(self, prompt: str, image_data: bytes | None = None, system: str | None = None) -> str: def chat(self, prompt: str, image_data: bytes | None = None, system: str | None = None) -> str:
"""Send a text prompt and return the response.""" """Send a text prompt and return the response."""

View File

@@ -11,12 +11,14 @@ if TYPE_CHECKING:
from collections.abc import Callable from collections.abc import Callable
import typer import typer
from alembic.command import upgrade
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from tenacity import before_sleep_log, retry, stop_after_attempt, wait_exponential from tenacity import before_sleep_log, retry, stop_after_attempt, wait_exponential
from python.common import configure_logger, utcnow from python.common import configure_logger, utcnow
from python.database_cli import DATABASES
from python.orm.common import get_postgres_engine from python.orm.common import get_postgres_engine
from python.orm.richie.dead_letter_message import DeadLetterMessage from python.orm.signal_bot.models import DeadLetterMessage
from python.signal_bot.commands.inventory import handle_inventory_update from python.signal_bot.commands.inventory import handle_inventory_update
from python.signal_bot.commands.location import handle_location_request from python.signal_bot.commands.location import handle_location_request
from python.signal_bot.device_registry import DeviceRegistry, sync_roles from python.signal_bot.device_registry import DeviceRegistry, sync_roles
@@ -181,7 +183,7 @@ class Bot:
def main( def main(
log_level: Annotated[str, typer.Option()] = "INFO", log_level: Annotated[str, typer.Option()] = "DEBUG",
llm_timeout: Annotated[int, typer.Option()] = 600, llm_timeout: Annotated[int, typer.Option()] = 600,
) -> None: ) -> None:
"""Run the Signal command and control bot.""" """Run the Signal command and control bot."""
@@ -200,6 +202,8 @@ def main(
error = "INVENTORY_API_URL environment variable not set" error = "INVENTORY_API_URL environment variable not set"
raise ValueError(error) raise ValueError(error)
signal_bot_config = DATABASES["signal_bot"].alembic_config()
upgrade(signal_bot_config, "head")
engine = get_postgres_engine(name="SIGNALBOT") engine = get_postgres_engine(name="SIGNALBOT")
sync_roles(engine) sync_roles(engine)
config = BotConfig( config = BotConfig(

View File

@@ -31,7 +31,7 @@ in
local gitea gitea trust local gitea gitea trust
# signalbot # signalbot
local richie signalbot trust local signalbot signalbot trust
# math # math
local postgres math trust local postgres math trust
@@ -103,6 +103,7 @@ in
} }
{ {
name = "signalbot"; name = "signalbot";
ensureDBOwnership = true;
ensureClauses = { ensureClauses = {
login = true; login = true;
}; };
@@ -114,6 +115,7 @@ in
"math" "math"
"n8n" "n8n"
"richie" "richie"
"signalbot"
]; ];
# Thank you NotAShelf # Thank you NotAShelf
# https://github.com/NotAShelf/nyx/blob/d407b4d6e5ab7f60350af61a3d73a62a5e9ac660/modules/core/roles/server/system/services/databases/postgresql.nix#L74 # https://github.com/NotAShelf/nyx/blob/d407b4d6e5ab7f60350af61a3d73a62a5e9ac660/modules/core/roles/server/system/services/databases/postgresql.nix#L74

View File

@@ -26,7 +26,7 @@ in
environment = { environment = {
PYTHONPATH = "${inputs.self}"; PYTHONPATH = "${inputs.self}";
SIGNALBOT_DB = "richie"; SIGNALBOT_DB = "signalbot";
SIGNALBOT_USER = "signalbot"; SIGNALBOT_USER = "signalbot";
SIGNALBOT_HOST = "/run/postgresql"; SIGNALBOT_HOST = "/run/postgresql";
SIGNALBOT_PORT = "5432"; SIGNALBOT_PORT = "5432";
@@ -34,6 +34,7 @@ in
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
WorkingDirectory = "${inputs.self}";
User = "signalbot"; User = "signalbot";
Group = "signalbot"; Group = "signalbot";
EnvironmentFile = "${vars.secrets}/services/signal-bot"; EnvironmentFile = "${vars.secrets}/services/signal-bot";