Skip to content

Commit

Permalink
Merge branch 'release/v0.1.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelSasser committed Apr 10, 2020
2 parents 195aa78 + d69a4ea commit 0c9edd5
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 22 deletions.
86 changes: 74 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
![GitHub](https://img.shields.io/github/license/MichaelSasser/matrixctl?style=flat-square)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/matrixctl?style=flat-square)
![PyPI - Wheel](https://img.shields.io/pypi/wheel/matrixctl?style=flat-square)
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/michaelsasser/matrixctl?style=flat-square)
![GitHub Release Date](https://img.shields.io/github/release-date/michaelsasser/matrixctl?style=flat-square)
![PyPI - Status](https://img.shields.io/pypi/status/matrixctl?style=flat-square)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/michaelsasser/matrixctl?style=flat-square)

# MatrixCtl

MatrixCtl is a python program to control, manage, provision and deploy our
matrix homeserver. I had a bunch of shell scripts doing that. Two weeks
after using them I couldn't remember the order in which I have to use the
arguments. It was a pain. So I decided I hack something together fast.
arguments or which arguments where needed. It was a pain. So I decided I hack
something together fast.

It is not the most elegant piece of software I wrote, but it should do the
trick for now. I will continue to port the rest of the scripts. Maybe
it is also useful for someone else.
trick. I will continue to port the rest of the scripts and add a few new
features.

Maybe it is also useful for someone else.

## Branching Model

This repository uses the
[git-flow](https://danielkummer.github.io/git-flow-cheatsheet/index.html)
branching model by [Vincent Driessen](https://nvie.com/about/).
It has two branches with infinite lifetime:

* [master](https://github.com/MichaelSasser/matrixctl/tree/master)
* [develop](https://github.com/MichaelSasser/matrixctl/tree/develop)

The master branch gets updated on every release. The develop branch is the
merging branch.

## Command line tool

MatrixCtl as a pure commandline tool. You can use it as package, if you like,
but breaking changes may be introduced, even in a minor change.

```
# matrixctl
usage: matrixctl [-h] [--version] [-d] {adduser,list-users,deluser,deploy,update,maintainance} ...
usage: matrixctl [-h] [--version] [-d]
{adduser,adduser-jitsi,deluser-jitsi,list-users,deluser,deploy,update,maintainance} ...
positional arguments:
{adduser,list-users,deluser,deploy,update,maintainance}
adduser Add a user
{adduser,adduser-jitsi,deluser-jitsi,list-users,deluser,deploy,update,maintainance}
adduser Add a new matrix user
adduser-jitsi Add a new jitsi user
deluser-jitsi Deletes a jitsi user
list-users Lists users
deluser Deletes a user
deploy Provision and deploy
Expand All @@ -38,17 +68,27 @@ To use this program you need to have this config file in
```toml
[ANSIBLE]
# The absolute path to the fully configured matrix-docker-ansible-deploy
# playbook.
# playbook from https://github.com/spantaleev/matrix-docker-ansible-deploy.

MatrixDockerAnsibleDeployPath="/absolut/path/to/matrix-docker-ansible-deploy"

[SERVER]
# If you have your own playbook, to provision your matrix server, you can
# fill out the server section. matrixctl will run it before the
# fill out this section. MatrixCtl will run this before the
# matrix-docker-ansible-deploy playbook.

# AnsibleCfg="/absolut/path/to/ansible.cfg"
# AnsiblePlaybook="/absolut/path/to/site.yml"
# If you have a special "ansible.cfg" for your playbook, fill in the absolute
# path to it.

# AnsibleCfg="/absolute/path/to/ansible.cfg"

# Fill in the absolute path to your "site.yml"

# AnsiblePlaybook="/absolute/path/to/site.yml"

# If you use tags to provision or configure your matrix host, you can add them
# here. Use a comma separated string without spaces.

# AnsibleTags="MyTag,MyOtherTag"

[API]
Expand All @@ -58,10 +98,32 @@ MatrixDockerAnsibleDeployPath="/absolut/path/to/matrix-docker-ansible-deploy"
# "matrixctl adduser --ansible YourUsername" and add your privileges after
# that.

# Your domain should be something like "michaelsasser.org" without the
# "matrix." in the front. MatrixCtl will add that, if needed. An IP-Address
# is not enough.

# Domain="domain.tld"

# To use the API you need to have an administrator account. Enter your Token
# here. If you use the riot client you will find it your user settings (click
# on your username on the upper left corner on your browser) in the
# "Help & About" tab. If you scroll down click next to "Access-Token:" on
# "<click to reveal>". It will be marked for you. Copy it in here.

# Token="MyMatrixToken"
```

## Semantic Versioning

**After release "1.0.0"** this repository will use
[SemVer](https://semver.org/) for its release
cycle.

**Before release "1.0.0"** it uses "0.MAJOR.MINOR_or_PATCH".
This means, if breaking changes are introduced, it results in a major version
change (e.g. "0.1.0" -> "0.2.0"). Minor changes, like new features or patches
are bumping the last digit (e.g. "0.1.1" -> "0.1.2").

## License
Copyright &copy; 2020 Michael Sasser <[email protected]>.
Released under the GPLv3 license.
Copyright &copy; 2020 Michael Sasser <[email protected]>. Released under
the GPLv3 license.
36 changes: 36 additions & 0 deletions matrixctl/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from tabulate import tabulate
from .ansible_handler import ansible_synapse
from .config_handler import Config
from .ssh_handler import Ssh

__author__: str = "Michael Sasser"
__email__: str = "[email protected]"
Expand Down Expand Up @@ -107,10 +108,45 @@ def adduser(arg, cfg: Config, adminapi):
adminapi.adduser(arg.user, arg.passwd, arg.admin)


def adduser_jitsi(arg, cfg: Config, _):
"""Adds a User to the synapse instance"""

while True:
passwd_generated: bool = False

if arg.passwd is None:
arg.passwd = ask_password()

if arg.passwd == "":
arg.passwd = gen_password()
passwd_generated = True

print(f"Username: {arg.user}")

if passwd_generated:
print(f"Password (generated): {arg.passwd}")
else:
print(f"Password: **HIDDEN**")

answer = ask_question()

if answer:
break
arg.passwd = None

with Ssh(cfg) as ssh:
ssh.adduser(arg.user, arg.passwd)


def deluser(arg, _: Config, adminapi):
adminapi.deluser(arg.user)


def deluser_jitsi(arg, cfg: Config, _):
with Ssh(cfg) as ssh:
ssh.deluser(arg.user)


def list_users(arg, cfg: Config, adminapi):
len_domain = len(cfg.api_domain) + 1 # 1 for :
from_user: int = 0
Expand Down
42 changes: 38 additions & 4 deletions matrixctl/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .api_handler import Api
from .housekeeping import maintainance
from .updating import update
from .account import adduser, deluser, list_users
from .account import adduser, deluser, list_users, adduser_jitsi, deluser_jitsi
from .provisioning import deploy


Expand All @@ -46,8 +46,11 @@ def main():
)
subparsers = parser.add_subparsers()

##########################################################################
# adduser
adduser_parser = subparsers.add_parser("adduser", help="Add a user")
adduser_parser = subparsers.add_parser(
"adduser", help="Add a new matrix user"
)
adduser_parser.add_argument("user", help="The Username of the new user")
adduser_parser.add_argument(
"-p",
Expand All @@ -63,6 +66,33 @@ def main():
)
adduser_parser.set_defaults(func=adduser)

##########################################################################
# adduser-jitsi
adduser_jitsi_parser = subparsers.add_parser(
"adduser-jitsi", help="Add a new jitsi user"
)
adduser_jitsi_parser.add_argument(
"user", help="The Username of the new jitsi user"
)
adduser_jitsi_parser.add_argument(
"-p",
"--passwd",
help="The password of the new jitsi user. (If you don't enter a "
"password, you will be asked later.)",
)
adduser_jitsi_parser.set_defaults(func=adduser_jitsi)

##########################################################################
# deluser
deluser_jitsi_parser = subparsers.add_parser(
"deluser-jitsi", help="Deletes a jitsi user"
)
deluser_jitsi_parser.add_argument(
"user", help="The jitsi username to delete"
)
deluser_jitsi_parser.set_defaults(func=deluser_jitsi)

##########################################################################
# list-users
list_users_parser = subparsers.add_parser("list-users", help="Lists users")
list_users_parser.add_argument(
Expand All @@ -73,23 +103,27 @@ def main():
)
list_users_parser.set_defaults(func=list_users)

##########################################################################
# deluser
deluser_parser = subparsers.add_parser("deluser", help="Deletes a user")
deluser_parser.add_argument("user", help="The Username to delete")
deluser_parser.add_argument("user", help="The username to delete")
deluser_parser.set_defaults(func=deluser)

##########################################################################
# deploy
deploy_parser = subparsers.add_parser(
"deploy", help="Provision and deploy"
)
deploy_parser.set_defaults(func=deploy)

##########################################################################
# deploy
update_parser = subparsers.add_parser(
"update", help="Updates the ansible repo"
)
update_parser.set_defaults(func=update)

##########################################################################
# maintainance
maintainance_parser = subparsers.add_parser(
"maintainance", help="Run Maintainance tasks"
Expand All @@ -102,7 +136,7 @@ def main():
coloredlogs.DEFAULT_LOG_FORMAT = (
"%(asctime)s - %(levelname)s - %(message)s"
)
coloredlogs.DEFAULT_LOG_LEVEL = 0 if args.debug else 20
coloredlogs.DEFAULT_LOG_LEVEL = 0 if args.debug else 21
coloredlogs.install()

config = Config()
Expand Down
114 changes: 114 additions & 0 deletions matrixctl/ssh_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python
# matrixctl
# Copyright (c) 2020 Michael Sasser <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
from typing import NamedTuple, Optional
from logging import debug, error

from paramiko import SSHClient
from paramiko.channel import ChannelFile

from .config_handler import Config


__author__: str = "Michael Sasser"
__email__: str = "[email protected]"


class SSHResponse(NamedTuple):
stdin: Optional[str]
stdout: Optional[str]
stderr: Optional[str]


class Ssh:

JID_EXT: str = "matrix-jitsi-web"

def __init__(self, cfg: Config, address: str = None):
self.config: Config = cfg

if address is None:
address = f"matrix.{self.config.api_domain}"
self.address: str = address
self.client: SSHClient = SSHClient()
self.client.load_system_host_keys()
self.connect()

def connect(self):
"""Connects to the SSH server"""
self.client.connect(self.address)

def __enter__(self):
"""Connects to the SSH server with the "with" command"""

return self

def __exit__(self, exc_type, exc_val, exc_tb):
"""Closes the connection to the SSH after the namespace of the
"with" command ends."""
self.client.close()

def __del__(self):
"""Clos the connection to the SSH"""
self.client.close()

@staticmethod
def __str_from(f: ChannelFile) -> Optional[str]:
try:
return f.read().decode("utf-8").strip()
except OSError:
return None

def run_cmd(self, cmd: str):
debug(f'SSH Command: "{cmd}"')

response = SSHResponse(
*[self.__str_from(s) for s in self.client.exec_command(cmd)]
)

debug(f'SSH Response: "{response}"')

return response

def adduser(self, user: str, password: str):
"""Add a user to the jitsi server."""
# register: undocumented, means: "user@JID_EXT passwd" as stdin
# without asking for the password. (replaces adduser)
cmd: str = f'sudo docker exec matrix-jitsi-prosody prosodyctl --config /config/prosody.cfg.lua register "{user}" {self.__class__.JID_EXT} "{password}"'

res = self.run_cmd(cmd)

if res.stderr.startswith("bash: 7: command not found"):
error(
"BUG: It's likely that you had previously installed Jitsi "
"without auth/guest support. Please look into the "
"configuring-playbook-jitsi.md in "
"matrix-docker-ansible-deploy/docs. Read the paragraph "
"about rebuilding your Jitsi installation."
)
sys.exit(1)

return res

def deluser(self, user: str):
"""Delete a user from the jitsi server."""
cmd: str = f'sudo docker exec matrix-jitsi-prosody prosodyctl --config /config/prosody.cfg.lua deluser "{user}@{self.__class__.JID_EXT}"'

return self.run_cmd(cmd)


# vim: set ft=python :
Loading

0 comments on commit 0c9edd5

Please sign in to comment.