From 7515b3116463fa3ae9e7f8af514d666796b16eaf Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 28 Jun 2018 00:20:01 +0300 Subject: [PATCH 1/6] Move Matrix state cache to main database. Fixes #159 --- ...d51e4_move_state_store_to_main_database.py | 126 ++++++++++++++++++ mautrix_telegram/__main__.py | 4 +- mautrix_telegram/db.py | 49 ++++++- mautrix_telegram/puppet.py | 8 +- mautrix_telegram/sqlstatestore.py | 99 ++++++++++++++ 5 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 alembic/versions/6ca3d74d51e4_move_state_store_to_main_database.py create mode 100644 mautrix_telegram/sqlstatestore.py diff --git a/alembic/versions/6ca3d74d51e4_move_state_store_to_main_database.py b/alembic/versions/6ca3d74d51e4_move_state_store_to_main_database.py new file mode 100644 index 00000000..7e06de1f --- /dev/null +++ b/alembic/versions/6ca3d74d51e4_move_state_store_to_main_database.py @@ -0,0 +1,126 @@ +"""Move state store to main database + +Revision ID: 6ca3d74d51e4 +Revises: 2228d49c383f +Create Date: 2018-06-26 21:31:26.911307 + +""" +from alembic import context, op +import sqlalchemy.orm as orm +import sqlalchemy as sa +import json +import re + +from mautrix_telegram.config import Config +from mautrix_telegram.base import Base + +# revision identifiers, used by Alembic. +revision = "6ca3d74d51e4" +down_revision = "2228d49c383f" +branch_labels = None +depends_on = None + + +class RoomState(Base): + query = None + __tablename__ = "mx_room_state" + __table_args__ = {"extend_existing": True} + + room_id = sa.Column(sa.String, primary_key=True) + power_levels = sa.Column("power_levels", sa.Text, nullable=True) + + +class UserProfile(Base): + query = None + __tablename__ = "mx_user_profile" + __table_args__ = {"extend_existing": True} + + room_id = sa.Column(sa.String, primary_key=True) + user_id = sa.Column(sa.String, primary_key=True) + membership = sa.Column(sa.String, nullable=False, default="leave") + displayname = sa.Column(sa.String, nullable=True) + avatar_url = sa.Column(sa.String, nullable=True) + + +class Puppet(Base): + query = None + __tablename__ = "puppet" + __table_args__ = {"extend_existing": True} + + id = sa.Column(sa.Integer, primary_key=True) + displayname = sa.Column(sa.String, nullable=True) + displayname_source = sa.Column(sa.Integer, nullable=True) + username = sa.Column(sa.String, nullable=True) + photo_id = sa.Column(sa.String, nullable=True) + is_bot = sa.Column(sa.Boolean, nullable=True) + matrix_registered = sa.Column(sa.Boolean, nullable=False, default=False) + + +def upgrade(): + op.add_column("puppet", sa.Column("matrix_registered", sa.Boolean(), nullable=False, + server_default=sa.sql.expression.false())) + op.create_table("mx_room_state", + sa.Column("room_id", sa.String(), nullable=False), + sa.Column("power_levels", sa.Text(), nullable=True), + sa.PrimaryKeyConstraint("room_id")) + op.create_table("mx_user_profile", + sa.Column("room_id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=False), + sa.Column("membership", sa.String(), nullable=False, + default="leave"), + sa.Column("displayname", sa.String(), nullable=True), + sa.Column("avatar_url", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("room_id", "user_id")) + + conn = op.get_bind() + session = orm.sessionmaker(bind=conn) + session = orm.scoping.scoped_session(session) + Puppet.query = session.query_property() + + with open("mx-state.json") as file: + data = json.load(file) + if not data: + return + registrations = data.get("registrations", []) + + mxtg_config_path = context.get_x_argument(as_dictionary=True).get("config", "config.yaml") + mxtg_config = Config(mxtg_config_path, None, None) + mxtg_config.load() + + username_template = mxtg_config.get("bridge.username_template", "telegram_{userid}") + hs_domain = mxtg_config["homeserver.domain"] + localpart = username_template.format(userid="(.+)") + mxid_regex = re.compile(f"@{localpart}:{hs_domain}") + for user in registrations: + match = mxid_regex.match(user) + if not match: + continue + + puppet = Puppet.query.get(match.group(1)) + if not puppet: + continue + + puppet.matrix_registered = True + session.merge(puppet) + session.commit() + + user_profiles = [UserProfile(room_id=room, user_id=user, + membership=member.get("membership", "leave"), + displayname=member.get("displayname", None), + avatar_url=member.get("avatar_url", None)) + for room, members in data.get("members", {}).items() + for user, member in members.items()] + session.add_all(user_profiles) + session.commit() + + room_state = [RoomState(room_id=room, power_levels=json.dumps(levels)) + for room, levels in data.get("power_levels", {}).items()] + session.add_all(room_state) + session.commit() + + +def downgrade(): + op.drop_table("mx_user_profile") + op.drop_table("mx_room_state") + with op.batch_alter_table("puppet") as batch_op: + batch_op.drop_column("matrix_registered") diff --git a/mautrix_telegram/__main__.py b/mautrix_telegram/__main__.py index b8f29d58..f50bfc92 100644 --- a/mautrix_telegram/__main__.py +++ b/mautrix_telegram/__main__.py @@ -38,6 +38,7 @@ from .puppet import init as init_puppet from .formatter import init as init_formatter from .public import PublicBridgeWebsite from .context import Context +from .sqlstatestore import SQLStateStore log = logging.getLogger("mau") time_formatter = logging.Formatter("[%(asctime)s] [%(levelname)s@%(name)s] %(message)s") @@ -87,10 +88,11 @@ telethon_session_container = AlchemySessionContainer(engine=db_engine, session=d loop = asyncio.get_event_loop() +state_store = SQLStateStore(db_session) appserv = AppService(config["homeserver.address"], config["homeserver.domain"], config["appservice.as_token"], config["appservice.hs_token"], config["appservice.bot_username"], log="mau.as", loop=loop, - verify_ssl=config["homeserver.verify_ssl"]) + verify_ssl=config["homeserver.verify_ssl"], state_store=state_store) context = Context(appserv, db_session, config, loop, None, None, telethon_session_container) diff --git a/mautrix_telegram/db.py b/mautrix_telegram/db.py index 2ef9525e..4709fbff 100644 --- a/mautrix_telegram/db.py +++ b/mautrix_telegram/db.py @@ -15,8 +15,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from sqlalchemy import (Column, UniqueConstraint, ForeignKey, ForeignKeyConstraint, Integer, - BigInteger, String, Boolean) + BigInteger, String, Boolean, Text) +from sqlalchemy.sql import expression from sqlalchemy.orm import relationship +import json from .base import Base @@ -80,6 +82,48 @@ class User(Base): portals = relationship("Portal", secondary="user_portal") +class RoomState(Base): + query = None + __tablename__ = "mx_room_state" + + room_id = Column(String, primary_key=True) + _power_levels_text = Column("power_levels", Text, nullable=True) + _power_levels_json = None + +# def __init__(self, *args, **kwargs): +# super().__init__(*args, **kwargs) +# self._power_levels_json = None + + @property + def power_levels(self): + if not self._power_levels_json and self._power_levels_text: + self._power_levels_json = json.loads(self._power_levels_text) + return self._power_levels_json or {} + + @power_levels.setter + def power_levels(self, val): + self._power_levels_json = val + self._power_levels_text = json.dumps(val) + + +class UserProfile(Base): + query = None + __tablename__ = "mx_user_profile" + + room_id = Column(String, primary_key=True) + user_id = Column(String, primary_key=True) + membership = Column(String, nullable=False, default="leave") + displayname = Column(String, nullable=True) + avatar_url = Column(String, nullable=True) + + def dict(self): + return { + "membership": self.membership, + "displayname": self.displayname, + "avatar_url": self.avatar_url, + } + + class Contact(Base): query = None __tablename__ = "contact" @@ -98,6 +142,7 @@ class Puppet(Base): username = Column(String, nullable=True) photo_id = Column(String, nullable=True) is_bot = Column(Boolean, nullable=True) + matrix_registered = Column(Boolean, nullable=False, server_default=expression.false()) # Fucking Telegram not telling bots what chats they are in 3:< @@ -132,3 +177,5 @@ def init(db_session): Puppet.query = db_session.query_property() BotChat.query = db_session.query_property() TelegramFile.query = db_session.query_property() + UserProfile.query = db_session.query_property() + RoomState.query = db_session.query_property() diff --git a/mautrix_telegram/puppet.py b/mautrix_telegram/puppet.py index ad0648bc..b0977036 100644 --- a/mautrix_telegram/puppet.py +++ b/mautrix_telegram/puppet.py @@ -36,7 +36,7 @@ class Puppet: cache = {} def __init__(self, id=None, username=None, displayname=None, displayname_source=None, - photo_id=None, is_bot=None, db_instance=None): + photo_id=None, is_bot=None, is_registered=False, db_instance=None): self.id = id self.mxid = self.get_mxid_from_id(self.id) @@ -45,6 +45,7 @@ class Puppet: self.displayname_source = displayname_source self.photo_id = photo_id self.is_bot = is_bot + self.is_registered = is_registered self._db_instance = db_instance self.intent = self.az.intent.user(self.mxid) @@ -67,13 +68,13 @@ class Puppet: def new_db_instance(self): return DBPuppet(id=self.id, username=self.username, displayname=self.displayname, displayname_source=self.displayname_source, photo_id=self.photo_id, - is_bot=self.is_bot) + is_bot=self.is_bot, matrix_registered=self.is_registered) @classmethod def from_db(cls, db_puppet): return Puppet(db_puppet.id, db_puppet.username, db_puppet.displayname, db_puppet.displayname_source, db_puppet.photo_id, db_puppet.is_bot, - db_instance=db_puppet) + db_puppet.matrix_registered, db_instance=db_puppet) def save(self): self.db_instance.username = self.username @@ -81,6 +82,7 @@ class Puppet: self.db_instance.displayname_source = self.displayname_source self.db_instance.photo_id = self.photo_id self.db_instance.is_bot = self.is_bot + self.db_instance.matrix_registered = self.is_registered self.db.commit() def similarity(self, query): diff --git a/mautrix_telegram/sqlstatestore.py b/mautrix_telegram/sqlstatestore.py new file mode 100644 index 00000000..1d9442e2 --- /dev/null +++ b/mautrix_telegram/sqlstatestore.py @@ -0,0 +1,99 @@ +# -*- coding: future_fstrings -*- +# mautrix-telegram - A Matrix-Telegram puppeting bridge +# Copyright (C) 2018 Tulir Asokan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +import json + +from mautrix_appservice import StateStore + +from . import puppet as pu +from .db import RoomState, UserProfile + + +class SQLStateStore(StateStore): + def __init__(self, db): + super().__init__() + self.db = db + + def is_registered(self, user: str) -> bool: + puppet = pu.Puppet.get_by_mxid(user) + return puppet.is_registered if puppet else False + + def registered(self, user: str): + puppet = pu.Puppet.get_by_mxid(user) + if puppet: + puppet.is_registered = True + puppet.save() + + def update_state(self, event: dict): + event_type = event["type"] + if event_type == "m.room.power_levels": + self.set_power_levels(event["room_id"], event["content"]) + elif event_type == "m.room.member": + self.set_member(event["room_id"], event["state_key"], event["content"]) + + def get_member(self, room: str, user: str) -> dict: + profile = UserProfile.query.get((room, user)) + if profile: + return profile.dict() + return {} + + def set_member(self, room: str, user: str, member: dict): + profile = UserProfile(room_id=room, user_id=user, + membership=member.get("membership", "leave"), + displayname=member.get("displayname", None), + avatar_url=member.get("avatar_url", None)) + self.db.merge(profile) + self.db.commit() + + def set_membership(self, room: str, user: str, membership: str): + profile = UserProfile.query.get((room, user)) + if not profile: + profile = UserProfile(room_id=room, user_id=user, membership=membership) + self.db.add(profile) + else: + profile.membership = membership + self.db.commit() + + def has_power_levels(self, room: str) -> bool: + room = RoomState.query.get(room) + return room and room._power_levels_text + + def get_power_levels(self, room: str) -> dict: + return RoomState.query.get(room).power_levels + + def set_power_level(self, room: str, user: str, level: int): + room_state = RoomState.query.get(room) + if not room_state: + room_state = RoomState(room) + self.db.add(room_state) + + power_levels = room_state.power_levels + if not power_levels: + power_levels = { + "users": {}, + "events": {}, + } + power_levels[room]["users"][user] = level + room_state.power_levels = power_levels + self.db.commit() + + def set_power_levels(self, room: str, content: dict): + state = RoomState.query.get(room) + if not state: + state = RoomState(room_id=room) + self.db.add(state) + state.power_levels = content + self.db.commit() From e16e53c261dc068b780960c6e4a8a9f8de8fb181 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 14 Jul 2018 23:31:11 +0300 Subject: [PATCH 2/6] Ignore alembic in code climate --- .codeclimate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index ad83e79a..e2fdfb75 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -4,3 +4,5 @@ engines: checks: python:S107: enabled: false +exclude_patterns: +- "alembic/" From 2a65ccc6748318e5ba6da752140ee0d438ec7051 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Jul 2018 00:07:45 +0300 Subject: [PATCH 3/6] Cache RoomStates and UserProfiles --- mautrix_telegram/db.py | 6 +-- mautrix_telegram/sqlstatestore.py | 75 +++++++++++++++++++------------ 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/mautrix_telegram/db.py b/mautrix_telegram/db.py index 4709fbff..5393acad 100644 --- a/mautrix_telegram/db.py +++ b/mautrix_telegram/db.py @@ -90,9 +90,9 @@ class RoomState(Base): _power_levels_text = Column("power_levels", Text, nullable=True) _power_levels_json = None -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self._power_levels_json = None + @property + def has_power_levels(self): + return bool(self._power_levels_text) @property def power_levels(self): diff --git a/mautrix_telegram/sqlstatestore.py b/mautrix_telegram/sqlstatestore.py index 1d9442e2..63b030d2 100644 --- a/mautrix_telegram/sqlstatestore.py +++ b/mautrix_telegram/sqlstatestore.py @@ -14,7 +14,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import json +from typing import Dict, Tuple from mautrix_appservice import StateStore @@ -26,6 +26,8 @@ class SQLStateStore(StateStore): def __init__(self, db): super().__init__() self.db = db + self.profile_cache = {} # type: Dict[Tuple[str, str], UserProfile] + self.room_state_cache = {} # type: Dict[str, RoomState] def is_registered(self, user: str) -> bool: puppet = pu.Puppet.get_by_mxid(user) @@ -44,42 +46,60 @@ class SQLStateStore(StateStore): elif event_type == "m.room.member": self.set_member(event["room_id"], event["state_key"], event["content"]) - def get_member(self, room: str, user: str) -> dict: - profile = UserProfile.query.get((room, user)) + def _get_user_profile(self, room_id: str, user_id: str, create: bool = True) -> UserProfile: + key = (room_id, user_id) + try: + return self.profile_cache[key] + except KeyError: + pass + + profile = UserProfile.query.get(key) if profile: - return profile.dict() - return {} + self.profile_cache[key] = profile + elif create: + profile = UserProfile(room_id=room_id, user_id=user_id) + self.db.add(profile) + self.db.commit() + self.profile_cache[key] = profile + return profile + + def get_member(self, room: str, user: str) -> dict: + return self._get_user_profile(room, user).dict() def set_member(self, room: str, user: str, member: dict): - profile = UserProfile(room_id=room, user_id=user, - membership=member.get("membership", "leave"), - displayname=member.get("displayname", None), - avatar_url=member.get("avatar_url", None)) - self.db.merge(profile) + profile = self._get_user_profile(room, user) + profile.membership = member.get("membership", profile.membership or "leave") + profile.displayname = member.get("displayname", profile.displayname) + profile.avatar_url = member.get("avatar_url", profile.avatar_url) self.db.commit() def set_membership(self, room: str, user: str, membership: str): - profile = UserProfile.query.get((room, user)) - if not profile: - profile = UserProfile(room_id=room, user_id=user, membership=membership) - self.db.add(profile) - else: - profile.membership = membership - self.db.commit() + self.set_member(room, user, { + "membership": membership, + }) + + def _get_room_state(self, room_id: str, create: bool = True) -> RoomState: + try: + return self.room_state_cache[room_id] + except KeyError: + pass + + room = RoomState.query.get(room_id) + if room: + self.room_state_cache[room_id] = room + elif create: + room = RoomState(room_id=room_id) + self.room_state_cache[room_id] = room + return room def has_power_levels(self, room: str) -> bool: - room = RoomState.query.get(room) - return room and room._power_levels_text + return self._get_room_state(room).has_power_levels def get_power_levels(self, room: str) -> dict: - return RoomState.query.get(room).power_levels + return self._get_room_state(room).power_levels def set_power_level(self, room: str, user: str, level: int): - room_state = RoomState.query.get(room) - if not room_state: - room_state = RoomState(room) - self.db.add(room_state) - + room_state = self._get_room_state(room) power_levels = room_state.power_levels if not power_levels: power_levels = { @@ -91,9 +111,6 @@ class SQLStateStore(StateStore): self.db.commit() def set_power_levels(self, room: str, content: dict): - state = RoomState.query.get(room) - if not state: - state = RoomState(room_id=room) - self.db.add(state) + state = self._get_room_state(room) state.power_levels = content self.db.commit() From 0f1ac98b9f8fc3ae751bebd9de9968c9b5fc606c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Jul 2018 00:14:43 +0300 Subject: [PATCH 4/6] Remove old things from gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index b7e3188b..2eca9ad6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,3 @@ __pycache__ config.yaml registration.yaml *.db -*.session -*.json From b4dd05ab041633e4b203c2cbbb5af7d42274613b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Jul 2018 00:49:24 +0300 Subject: [PATCH 5/6] Simplify docker setup --- .dockerignore | 4 ++++ Dockerfile | 18 +++++++-------- .../mautrix-telegram/run => docker-run.sh | 22 +++++++++---------- docker/root/etc/s6.d/.s6-svscan/finish | 1 - docker/root/etc/s6.d/mautrix-telegram/finish | 2 -- 5 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 .dockerignore rename docker/root/etc/s6.d/mautrix-telegram/run => docker-run.sh (67%) delete mode 100755 docker/root/etc/s6.d/.s6-svscan/finish delete mode 100755 docker/root/etc/s6.d/mautrix-telegram/finish diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ec191c92 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.editorconfig +.codeclimate.yml +*.png +*.md diff --git a/Dockerfile b/Dockerfile index 6c6b5c28..5ac19659 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,14 @@ -FROM docker.io/alpine:3.7 +FROM docker.io/alpine:3.8 ENV UID=1337 \ - GID=1337 + GID=1337 \ + FFMPEG_BINARY=/usr/bin/ffmpeg -COPY . /opt/mautrixtelegram +COPY . /opt/mautrix-telegram +WORKDIR /opt/mautrix-telegram RUN apk add --no-cache \ python3-dev \ + build-base \ py3-virtualenv \ py3-pillow \ py3-aiohttp \ @@ -14,17 +17,12 @@ RUN apk add --no-cache \ py3-numpy \ py3-asn1crypto \ py3-sqlalchemy \ - build-base \ + py3-markdown \ ffmpeg \ - bash \ ca-certificates \ su-exec \ - s6 \ - && cd /opt/mautrixtelegram \ - && cp -r docker/root/* / \ - && rm docker -rf \ && pip3 install -r requirements.txt -r optional-requirements.txt VOLUME /data -CMD ["/bin/s6-svscan", "/etc/s6.d"] +CMD ["/opt/mautrix-telegram/run.sh"] diff --git a/docker/root/etc/s6.d/mautrix-telegram/run b/docker-run.sh similarity index 67% rename from docker/root/etc/s6.d/mautrix-telegram/run rename to docker-run.sh index 41eb26aa..228e9f2f 100755 --- a/docker/root/etc/s6.d/mautrix-telegram/run +++ b/docker-run.sh @@ -1,22 +1,22 @@ -#!/bin/bash +#!/bin/sh -# Define functions +# Define functions. function fixperms { - chown -R ${UID}:${GID} /data /opt/mautrixtelegram + chown -R $UID:$GID /data /opt/mautrix-telegram } - -# Go into env -cd /opt/mautrixtelegram -export FFMPEG_BINARY=/usr/bin/ffmpeg +cd /opt/mautrix-telegram # Replace database path in config. sed -i "s#sqlite:///mautrix-telegram.db#sqlite:////data/mautrix-telegram.db#" /data/config.yaml +if [ -f /data/mx-state.json ]; then + ln -s /data/mx-state.json +fi # Check that database is in the right state alembic -x config=/data/config.yaml upgrade head -if [[ ! -f /data/config.yaml ]]; then +if [ ! -f /data/config.yaml ]; then cp example-config.yaml /data/config.yaml echo "Didn't find a config file." echo "Copied default config file to /data/config.yaml" @@ -26,14 +26,14 @@ if [[ ! -f /data/config.yaml ]]; then exit fi -if [[ ! -f /data/registration.yaml ]]; then +if [ ! -f /data/registration.yaml ]; then python3 -m mautrix_telegram -g -c /data/config.yaml -r /data/registration.yaml echo "Didn't find a registration file." - echo "Generated ode for you." + echo "Generated one for you." echo "Copy that over to synapses app service directory." fixperms exit fi fixperms -exec su-exec ${UID}:${GID} python3 -m mautrix_telegram -c /data/config.yaml +exec su-exec $UID:$GID python3 -m mautrix_telegram -c /data/config.yaml diff --git a/docker/root/etc/s6.d/.s6-svscan/finish b/docker/root/etc/s6.d/.s6-svscan/finish deleted file mode 100755 index 1a248525..00000000 --- a/docker/root/etc/s6.d/.s6-svscan/finish +++ /dev/null @@ -1 +0,0 @@ -#!/bin/sh diff --git a/docker/root/etc/s6.d/mautrix-telegram/finish b/docker/root/etc/s6.d/mautrix-telegram/finish deleted file mode 100755 index e90c4912..00000000 --- a/docker/root/etc/s6.d/mautrix-telegram/finish +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -s6-svscanctl -t /etc/s6.d From e71f7280b84dd31a40ae42023fe6c612bb442208 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Jul 2018 01:22:14 +0300 Subject: [PATCH 6/6] Fix command in dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5ac19659..b371bd44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,4 +25,4 @@ RUN apk add --no-cache \ VOLUME /data -CMD ["/opt/mautrix-telegram/run.sh"] +CMD ["/opt/mautrix-telegram/docker-run.sh"]