1-install-murmur-server
parent
31d3221d05
commit
6914b83d96
@ -0,0 +1,42 @@ |
||||
channels: |
||||
- name: Root |
||||
description: Root channel. No entry. |
||||
groups: |
||||
admin: |
||||
inherit: true |
||||
inheritable: true |
||||
permissions: |
||||
- group: all |
||||
deny: |
||||
- Enter |
||||
recursive: false |
||||
enabled: true |
||||
- group: all |
||||
allow: |
||||
- RegisterSelf |
||||
- MakeTempChannel |
||||
- group: admin |
||||
allow: |
||||
- MuteDeafen |
||||
- Move |
||||
- Kick |
||||
- Ban |
||||
- Register |
||||
- name: Welcome |
||||
parent: Root |
||||
description: Welcome channel |
||||
position: 0 |
||||
isdefault: true |
||||
- name: Silent |
||||
parent: Root |
||||
description: Silent channel |
||||
position: 1 |
||||
permissions: |
||||
- group: all |
||||
deny: |
||||
- Speak |
||||
- MakeTempChannel |
||||
enabled: true |
||||
- group: admin |
||||
allow: |
||||
- MakeTempChannel |
@ -0,0 +1,345 @@ |
||||
#!/usr/bin/python3 |
||||
|
||||
import argparse |
||||
import logging |
||||
import os |
||||
import sys |
||||
from typing import List, Dict |
||||
|
||||
import Ice |
||||
import yaml |
||||
|
||||
import Murmur |
||||
|
||||
CONFIG_SEARCH_PATH = [".", "/etc"] |
||||
CONFIG_DEFAULT_NAME = "mice.yml" |
||||
|
||||
ICE_DEFAULT_HOST = "127.0.0.1" |
||||
ICE_DEFAULT_PORT = 6502 |
||||
ICE_DEFAULT_TIMEOUT = 1000 |
||||
|
||||
|
||||
class Mice: |
||||
def __init__(self, host: str = ICE_DEFAULT_HOST, |
||||
port: int = ICE_DEFAULT_PORT, secret: str = None, |
||||
timeout: int = ICE_DEFAULT_TIMEOUT): |
||||
logging.debug("Initializing ICE connection") |
||||
properties = Ice.createProperties() |
||||
properties.setProperty("Ice.ImplicitContext", "Shared") |
||||
init_data = Ice.InitializationData() |
||||
init_data.properties = properties |
||||
self._communicator = Ice.initialize(init_data) |
||||
|
||||
if secret: |
||||
logging.debug("Providing ICE secret") |
||||
self._communicator.getImplicitContext().put("secret", secret) |
||||
|
||||
logging.debug("Creating meta proxy") |
||||
proxy_str = f"Meta: tcp -h {host} -p {port} -t {timeout}" |
||||
proxy = self._communicator.stringToProxy(proxy_str) |
||||
self.meta = Murmur.MetaPrx.checkedCast(proxy) |
||||
|
||||
if not self.meta: |
||||
self._communicator.destroy() |
||||
raise RuntimeError(f"Invalid Proxy: {proxy_str}") |
||||
|
||||
try: |
||||
logging.debug("Retrieving the list of Mumble servers") |
||||
servers = self.meta.getAllServers() |
||||
except Murmur.InvalidSecretException: |
||||
self._communicator.destroy() |
||||
raise RuntimeError(f"Failed to connect to {host} " |
||||
f"on port {port}: Invalid secret.") from None |
||||
self.servers = [MurmurServer(server) for server in servers] |
||||
|
||||
if len(self.servers) == 0: |
||||
self._communicator.destroy() |
||||
raise RuntimeError("No Mumble server found") |
||||
|
||||
def __enter__(self): |
||||
return self |
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): |
||||
self._communicator.destroy() |
||||
|
||||
|
||||
class ChannelGroup: |
||||
def __init__(self, name: str, inherit: bool = True, |
||||
inheritable: bool = True): |
||||
self.name = name |
||||
self.inherit = inherit |
||||
self.inheritable = inheritable |
||||
|
||||
def get_state(self) -> Murmur.Group: |
||||
return Murmur.Group( |
||||
name=self.name, |
||||
inherit=self.inherit, |
||||
inheritable=self.inheritable |
||||
) |
||||
|
||||
@staticmethod |
||||
def from_dict(group_dict: Dict[str, object]) -> "ChannelGroup": |
||||
return ChannelGroup( |
||||
name=group_dict["name"], |
||||
inherit=group_dict.get("inherit", True), |
||||
inheritable=group_dict.get("inheritable", True) |
||||
) |
||||
|
||||
|
||||
class ChannelPermission: |
||||
def __init__(self, group: str = "all", userid: int = -1, |
||||
allow: int = 0, deny: int = 0, |
||||
recursive: bool = True, enabled: bool = True): |
||||
self.group = group |
||||
self.userid = userid |
||||
self.allow = allow |
||||
self.deny = deny |
||||
self.recursive = recursive |
||||
self.enabled = enabled |
||||
|
||||
def get_acl(self) -> Murmur.ACL: |
||||
return Murmur.ACL( |
||||
applyHere=self.enabled, |
||||
applySubs=self.recursive, |
||||
userid=self.userid, |
||||
group=self.group, |
||||
allow=self.allow, |
||||
deny=self.deny) |
||||
|
||||
@staticmethod |
||||
def from_dict(permission_dict: Dict[str, object]) -> "ChannelPermission": |
||||
allow = ChannelPermission._parse_rules( |
||||
permission_dict.get("allow", [])) |
||||
deny = ChannelPermission._parse_rules( |
||||
permission_dict.get("deny", [])) |
||||
|
||||
return ChannelPermission( |
||||
group=permission_dict["group"], |
||||
allow=allow, |
||||
deny=deny, |
||||
recursive=permission_dict.get("recursive", True), |
||||
enabled=permission_dict.get("enabled", True)) |
||||
|
||||
@staticmethod |
||||
def _parse_rule(rule: str) -> int: |
||||
return getattr(Murmur, "Permission" + rule) |
||||
|
||||
@staticmethod |
||||
def _parse_rules(rules: List[str]) -> int: |
||||
result = 0 |
||||
for rule in rules: |
||||
result |= ChannelPermission._parse_rule(rule) |
||||
return result |
||||
|
||||
|
||||
class Channel: |
||||
def __init__(self, name: str, description: str = "", position: int = 0, |
||||
groups: List[ChannelGroup] = [], |
||||
permissions: List[ChannelPermission] = [], |
||||
inherit: bool = True, isdefault: bool = False, |
||||
parent: "Channel" = None): |
||||
self.id = -1 |
||||
self.name = name |
||||
self.description = description |
||||
self.position = position |
||||
self.groups = groups |
||||
self.permissions = permissions |
||||
self.inherit = inherit |
||||
self.isdefault = isdefault |
||||
self.parent = parent |
||||
self.children = [] |
||||
|
||||
def __str__(self) -> str: |
||||
return self.name |
||||
|
||||
def get_state(self) -> Murmur.Channel: |
||||
if self.parent is None: |
||||
parent_id = 0 |
||||
else: |
||||
parent_id = self.parent.id |
||||
|
||||
return Murmur.Channel( |
||||
id=self.id, |
||||
name=self.name, |
||||
description=self.description, |
||||
position=self.position, |
||||
parent=parent_id) |
||||
|
||||
def add(self, channel: "Channel") -> None: |
||||
self.children.append(channel) |
||||
channel.parent = self |
||||
|
||||
@staticmethod |
||||
def from_dict(channel_dict: Dict[str, object]) -> "Channel": |
||||
if "parent" in channel_dict: |
||||
parent = Channel(channel_dict["parent"]) |
||||
else: |
||||
parent = None |
||||
|
||||
groups = [] |
||||
for group_name, group_dict in channel_dict.get("groups", {}).items(): |
||||
group_dict["name"] = group_name |
||||
group = ChannelGroup.from_dict(group_dict) |
||||
groups.append(group) |
||||
|
||||
permissions = [] |
||||
for permission_dict in channel_dict.get("permissions", []): |
||||
permission = ChannelPermission.from_dict(permission_dict) |
||||
permissions.append(permission) |
||||
|
||||
return Channel( |
||||
name=channel_dict["name"], |
||||
description=channel_dict.get("description", ''), |
||||
position=channel_dict.get("position", 0), |
||||
groups=groups, |
||||
permissions=permissions, |
||||
inherit=channel_dict.get("inherit", True), |
||||
isdefault=channel_dict.get("isdefault", False), |
||||
parent=parent) |
||||
|
||||
|
||||
class MurmurServer: |
||||
def __init__(self, server: Murmur.Server): |
||||
self._server = server |
||||
|
||||
def clear_acls(self, channel_id=0): |
||||
_, groups, inherit = self._server.getACL(0) |
||||
self._server.setACL(channel_id, [], groups, inherit) |
||||
|
||||
def clear_channels(self) -> None: |
||||
for channel_state in self._server.getChannels().values(): |
||||
if channel_state.id != 0 and channel_state.parent == 0: |
||||
self._server.removeChannel(channel_state.id) |
||||
self.clear_acls() |
||||
|
||||
def set_channel_acls(self, channel: Channel) -> None: |
||||
acls = [] |
||||
for permission in channel.permissions: |
||||
acl = permission.get_acl() |
||||
acls.append(acl) |
||||
|
||||
_, current_groups, _ = self._server.getACL(channel.id) |
||||
current_group_names = {group.name: group for group in current_groups} |
||||
|
||||
groups = [] |
||||
for group in channel.groups: |
||||
group_state = group.get_state() |
||||
if group.name in current_group_names: |
||||
current_group_state = current_group_names[group.name] |
||||
group_state.add = current_group_state.add |
||||
group_state.remove = current_group_state.remove |
||||
del current_group_names[group.name] |
||||
groups.append(group_state) |
||||
groups.extend(current_group_names.values()) |
||||
|
||||
self._server.setACL(channel.id, acls, groups, channel.inherit) |
||||
|
||||
def create_channels(self, channel: Channel) -> None: |
||||
if channel.parent is None: |
||||
channel.id = 0 |
||||
else: |
||||
channel.id = self._server.addChannel( |
||||
channel.name, |
||||
channel.parent.id) |
||||
|
||||
self._server.setChannelState(channel.get_state()) |
||||
self.set_channel_acls(channel) |
||||
|
||||
if channel.isdefault: |
||||
self._server.setConf("defaultchannel", str(channel.id)) |
||||
|
||||
for child in channel.children: |
||||
self.create_channels(child) |
||||
|
||||
|
||||
class Config: |
||||
def __init__(self, filename: str = None): |
||||
self.filename = filename |
||||
self._data = {} |
||||
|
||||
self.load(filename) |
||||
|
||||
def load(self, filename: str = None): |
||||
if not filename: |
||||
for config_path in CONFIG_SEARCH_PATH: |
||||
filename = os.path.join(config_path, CONFIG_DEFAULT_NAME) |
||||
if os.path.exists(filename) and os.path.isfile(filename): |
||||
break |
||||
|
||||
with open(filename, 'r') as ifd: |
||||
self._data = yaml.safe_load(ifd) |
||||
|
||||
def parse_channels(self) -> Channel: |
||||
channels = [] |
||||
for channel_dict in self._data["channels"]: |
||||
channel = Channel.from_dict(channel_dict) |
||||
channels.append(channel) |
||||
|
||||
channel_names = {channel.name: channel for channel in channels} |
||||
for channel in channels: |
||||
if channel.parent is None: |
||||
continue |
||||
if channel.parent.name not in channel_names: |
||||
continue |
||||
channel_names[channel.parent.name].add(channel) |
||||
|
||||
root_channels = [channel for channel in channels |
||||
if channel.parent is None] |
||||
|
||||
if len(root_channels) == 0: |
||||
raise RuntimeError("No root channel found.") |
||||
|
||||
if len(root_channels) > 1: |
||||
root_names = root_channels.join(', ') |
||||
raise RuntimeError(f"Multiple root channels found: {root_names}") |
||||
|
||||
default_channels = [channel for channel in channels |
||||
if channel.isdefault] |
||||
|
||||
if len(default_channels) > 1: |
||||
default_names = default_channels.join(', ') |
||||
raise RuntimeError("Multiple default channels found: " |
||||
f"{default_names}") |
||||
|
||||
return root_channels[0] |
||||
|
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser( |
||||
description="Manage Mumble server through ICE connection", |
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter |
||||
) |
||||
parser.add_argument("--host", "-H", default=ICE_DEFAULT_HOST, |
||||
help="ICE host or ip") |
||||
parser.add_argument("--port", "-p", default=ICE_DEFAULT_PORT, |
||||
help="ICE port number") |
||||
parser.add_argument("--timeout", "-t", default=ICE_DEFAULT_TIMEOUT, |
||||
help="Connection timeout in milliseconds") |
||||
parser.add_argument("--secret", "-s", |
||||
default=os.environ.get("MICE_SECRET"), |
||||
help="ICE write secret") |
||||
parser.add_argument("--server-id", "-i", type=int, default=0, |
||||
help="Server ID to manage") |
||||
parser.add_argument("--config", "-c", |
||||
help="Path to the config file.") |
||||
args = parser.parse_args() |
||||
|
||||
config = Config(args.config) |
||||
root_channel = config.parse_channels() |
||||
|
||||
server_id = args.server_id |
||||
try: |
||||
with Mice(args.host, args.port, args.secret, args.timeout) as mice: |
||||
if not 0 <= server_id < len(mice.servers): |
||||
raise RuntimeError(f"Cannot find server with ID {server_id}") |
||||
server = mice.servers[server_id] |
||||
|
||||
server.clear_channels() |
||||
server.create_channels(root_channel) |
||||
except RuntimeError as e: |
||||
logging.error(e, exc_info=e) |
||||
return 1 |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
sys.exit(main()) |
@ -1,54 +0,0 @@ |
||||
#!/usr/bin/python3 |
||||
|
||||
import argparse |
||||
import logging |
||||
import sys |
||||
|
||||
import Ice |
||||
|
||||
import Murmur |
||||
|
||||
|
||||
def init(host, port, timeout): |
||||
logging.debug("Initializing ICE connection") |
||||
proxy_str = f"Meta: tcp -h {host} -p {port} -t {timeout}" |
||||
with Ice.initialize() as communicator: |
||||
proxy = communicator.stringToProxy(proxy_str) |
||||
mice = Murmur.MetaPrx.checkedCast(proxy) |
||||
|
||||
if not mice: |
||||
raise RuntimeError(f"Invalid Proxy: {proxy_str}") |
||||
|
||||
servers = mice.getAllServers() |
||||
|
||||
if len(servers) == 0: |
||||
raise RuntimeError("No Mumble server found") |
||||
|
||||
return servers |
||||
|
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser( |
||||
description="Manage Mumble server through ICE connection", |
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter |
||||
) |
||||
parser.add_argument("--host", "-H", default="127.0.0.1", |
||||
help="ICE host or ip") |
||||
parser.add_argument("--port", "-p", default=6502, |
||||
help="ICE port number") |
||||
parser.add_argument("--timeout", "-t", default=1000, |
||||
help="Connection timeout in milliseconds") |
||||
args = parser.parse_args() |
||||
|
||||
try: |
||||
servers = init(args.host, args.port, args.timeout) |
||||
except RuntimeError as e: |
||||
logging.error(e, exc_info=e) |
||||
return 1 |
||||
|
||||
for server in servers: |
||||
print(server.getChannels()) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
sys.exit(main()) |
@ -0,0 +1,23 @@ |
||||
********************************* |
||||
Vagrant driver installation guide |
||||
********************************* |
||||
|
||||
Requirements |
||||
============ |
||||
|
||||
* Vagrant |
||||
* Virtualbox, Parallels, VMware Fusion, VMware Workstation or VMware Desktop |
||||
|
||||
Install |
||||
======= |
||||
|
||||
Please refer to the `Virtual environment`_ documentation for installation best |
||||
practices. If not using a virtual environment, please consider passing the |
||||
widely recommended `'--user' flag`_ when invoking ``pip``. |
||||
|
||||
.. _Virtual environment: https://virtualenv.pypa.io/en/latest/ |
||||
.. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site |
||||
|
||||
.. code-block:: bash |
||||
|
||||
$ pip install 'molecule_vagrant' |
@ -0,0 +1,12 @@ |
||||
--- |
||||
- name: Converge |
||||
hosts: all |
||||
become: yes |
||||
|
||||
vars: |
||||
murmur_enabled: yes |
||||
umurmur_enabled: no |
||||
acme_enabled: no |
||||
|
||||
roles: |
||||
- ansible-role-mumble |
@ -0,0 +1,57 @@ |
||||
--- |
||||
.hardware: &hardware |
||||
memory: 1024 |
||||
cpu: 2 |
||||
|
||||
dependency: |
||||
name: galaxy |
||||
driver: |
||||
name: vagrant |
||||
provider: |
||||
name: virtualbox |
||||
platforms: |
||||
- name: buster-mumble-molecule |
||||
box: debian/buster64 |
||||
<<: *hardware |
||||
interfaces: |
||||
- network_name: forwarded_port |
||||
guest: 80 |
||||
host: 8080 |
||||
- network_name: forwarded_port |
||||
guest: 443 |
||||
host: 8443 |
||||
- network_name: forwarded_port |
||||
guest: 64738 |
||||
host: 14738 |
||||
protocol: udp |
||||
- network_name: forwarded_port |
||||
guest: 64738 |
||||
host: 14738 |
||||
protocol: tcp |
||||
- name: focal-mumble-molecule |
||||
box: ubuntu/focal64 |
||||
<<: *hardware |
||||
interfaces: |
||||
- network_name: forwarded_port |
||||
guest: 80 |
||||
host: 9080 |
||||
- network_name: forwarded_port |
||||
guest: 443 |
||||
host: 9443 |
||||
- network_name: forwarded_port |
||||
guest: 64738 |
||||
host: 24738 |
||||
protocol: udp |
||||
- network_name: forwarded_port |
||||
guest: 64738 |
||||
host: 24738 |
||||
protocol: tcp |
||||
provisioner: |
||||
name: ansible |
||||
config_options: |
||||
defaults: |
||||
interpreter_python: /usr/bin/python3 |
||||
ssh_connection: |
||||
pipelining: true |
||||
verifier: |
||||
name: ansible |
@ -0,0 +1,14 @@ |
||||
- name: Prepare |
||||
hosts: all |
||||
become: yes |
||||
|
||||
tasks: |
||||
- name: Update apt cache |
||||
apt: |
||||
update_cache: yes |
||||
cache_valid_time: 3600 |
||||
|
||||
- name: Install nginx package |
||||
package: |
||||
name: nginx-light |
||||
state: present |
@ -0,0 +1,21 @@ |
||||
- name: Ensure Diffie-Hellman directory exists |
||||
file: |
||||
path: "{{ mumble_web_nginx_dhparam | dirname }}" |
||||
state: directory |
||||
owner: root |
||||
group: www-data |
||||
mode: "755" |
||||
|
||||
- name: Generate Diffie-Hellman parameters |
||||
# This can take a long time... So we are doing it in async mode |
||||
openssl_dhparam: |
||||
path: "{{ mumble_web_nginx_dhparam }}" |
||||
state: present |
||||
owner: root |
||||
group: www-data |
||||
mode: "640" |
||||
size: "{{ mumble_web_nginx_dhparam_size }}" |
||||
async: 3600 |
||||
poll: 0 |
||||
changed_when: no |
||||
register: _mumble_web_nginx_dhparam |
@ -0,0 +1,7 @@ |
||||
- name: Waiting for Diffie-Hellman task to complete… |
||||
async_status: |
||||
jid: "{{ _mumble_web_nginx_dhparam.ansible_job_id }}" |
||||
register: _mumble_web_nginx_dhparam_job |
||||
retries: 60 |
||||
delay: 30 # will retry every 30s for 30min (60 retries) |
||||
until: _mumble_web_nginx_dhparam_job.finished |
@ -0,0 +1,64 @@ |
||||
- name: Install SSL dependencies |
||||
package: |
||||
name: "{{ package }}" |
||||
state: present |
||||
loop: |
||||
- ssl-cert |
||||
- python3-openssl |
||||
loop_control: |
||||
loop_var: package |
||||
tags: selfsigned_install |
||||
|
||||
- name: Create SSL config directories |
||||
file: |
||||
path: "{{ config_dir }}" |
||||
state: directory |
||||
owner: root |
||||
group: "{{ acme_ssl_group }}" |
||||
mode: "711" |
||||
loop: |
||||
- "{{ acme_config_dir }}" |
||||
- "{{ acme_keys_dir }}" |
||||
- "{{ acme_accounts_dir }}" |
||||
- "{{ acme_csr_dir }}" |
||||
loop_control: |
||||
loop_var: config_dir |
||||
tags: selfsigned_install |
||||
|
||||
- name: Create {{ domain_name }} certificates directory |
||||
file: |
||||
path: "{{ acme_certs_dir }}/{{ domain_name }}.d" |
||||
state: directory |
||||
owner: root |
||||
group: "{{ acme_ssl_group }}" |
||||
mode: "755" |
||||
tags: selfsigned_install |
||||
|
||||
- name: Generate private key for {{ domain_name }} certificate |
||||
openssl_privatekey: |
||||
path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" |
||||
owner: root |
||||
group: "{{ acme_ssl_group }}" |
||||
mode: "640" |
||||
type: RSA |
||||
size: 4096 |
||||
|
||||
- name: Generate CSR for {{ domain_name }} certificate |
||||
openssl_csr: |
||||
path: "{{ acme_csr_dir }}/{{ domain_name }}.csr" |
||||
owner: root |
||||
group: "{{ acme_ssl_group }}" |
||||
mode: "644" |
||||
privatekey_path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" |
||||
common_name: "{{ domain_name }}" |
||||
|
||||
- name: Generate self-signed certificate |
||||
openssl_certificate: |
||||
path: "{{ acme_certs_dir }}/{{ domain_name }}.d/cert.pem" |
||||
csr_path: "{{ acme_csr_dir }}/{{ domain_name }}.csr" |
||||
privatekey_path: "{{ acme_keys_dir }}/{{ domain_name }}.pem" |
||||
provider: selfsigned |
||||
state: present |
||||
owner: root |
||||
group: "{{ acme_ssl_group }}" |
||||
mode: "644" |
@ -0,0 +1,2 @@ |
||||
murmur_ice_script_dir: /opt/mice |
||||
murmur_ice_config_path: /etc/mice.yml |
Loading…
Reference in new issue