Skip to content
Snippets Groups Projects
charm.py 10.3 KiB
Newer Older
Mark Beierl's avatar
Mark Beierl committed
#!/usr/bin/env python3
# Copyright 2020 David Garcia
# See LICENSE file for licensing details.

from apt.progress.base import InstallProgress
import logging
import os
import shutil
import time
Mark Beierl's avatar
Mark Beierl committed

from jinja2 import Template
from ops.charm import CharmBase
from ops.framework import StoredState
from ops.main import main
from ops.model import (
    MaintenanceStatus,
    ActiveStatus,
    # BlockedStatus,
)
from utils import (
    service_stop,
    service_restart,
    install_apt,
Mark Beierl's avatar
Mark Beierl committed
    remove_apt,
Mark Beierl's avatar
Mark Beierl committed
    shell,
Mark Beierl's avatar
Mark Beierl committed
    upgrade_apt,
Mark Beierl's avatar
Mark Beierl committed
)


# from typing import Dict, Any
logger = logging.getLogger(__name__)

Mark Beierl's avatar
Mark Beierl committed
APT_PROXY_PATH = "/etc/apt/apt.conf.d/99-HIVE-apt-proxy"
APT_PROXY_TEMPLATE = "./templates/proxy"
Mark Beierl's avatar
Mark Beierl committed
APT_REQUIREMENTS = [
    "firefox",
Mark Beierl's avatar
Mark Beierl committed
    "indicator-applet-session",
    "libnotify-bin",
    "mate-desktop",
Mark Beierl's avatar
Mark Beierl committed
    "mate-applets",
    "mate-applet-brisk-menu",
    "mate-control-center",
Mark Beierl's avatar
Mark Beierl committed
    "mate-indicator-applet",
Mark Beierl's avatar
Mark Beierl committed
    "mate-notification-daemon",
Mark Beierl's avatar
Mark Beierl committed
    "mate-session-manager",
    "mate-terminal",
    "xrdp",
]
Mark Beierl's avatar
Mark Beierl committed

POLKIT_PATH = "/etc/polkit-1/localauthority/50-local.d/color.pkla"
POLKIT_TEMPLATE = "./templates/color.pkla"
PUBLIC_IP_PATH = "/etc/netplan/60-no-public-ip.yaml"
PUBLIC_IP_TEMPLATE = "./templates/60-no-public-ip.yaml"
Mark Beierl's avatar
Mark Beierl committed
SNAP_INSTALLS = [
Mark Beierl's avatar
Mark Beierl committed
    #"code --classic",
Mark Beierl's avatar
Mark Beierl committed
]
STARTWM_PATH = "/etc/xrdp/startwm.sh"
Mark Beierl's avatar
Mark Beierl committed
STARTWM_TEMPLATE = "./templates/startwm.sh"
Mark Beierl's avatar
Mark Beierl committed
# WM_COMMAND = "startxfce4" # xubuntu-desktop
# WM_COMMAND = "budgie-desktop" # budgie-desktop-environment
WM_COMMAND = "mate-session"  # mate-desktop


class VirtualPCCharm(CharmBase, InstallProgress):
    _stored = StoredState()

    def __init__(self, *args):
        super().__init__(*args)
        InstallProgress.__init__(self)

        self._stored.set_default()
        self.last_status_update = time.time()
        self._stored.set_default(ldap_installed=False)
Mark Beierl's avatar
Mark Beierl committed

        # Basic hooks
        self.framework.observe(self.on.install, self._on_install)
        self.framework.observe(self.on.start, self._on_start)
        self.framework.observe(self.on.stop, self._on_stop)
        self.framework.observe(self.on.config_changed, self._on_config_changed)
        self.framework.observe(self.on.update_status, self._on_update_status)

        # Actions hooks
Mark Beierl's avatar
Mark Beierl committed
        self.framework.observe(self.on["add-package"].action, self._add_package)
Mark Beierl's avatar
Mark Beierl committed
        self.framework.observe(self.on["add-snap"].action, self._add_snap)
        self.framework.observe(self.on["announce"].action, self._announce)
Mark Beierl's avatar
Mark Beierl committed
        self.framework.observe(self.on["reboot"].action, self._reboot)
        self.framework.observe(self.on["remove-package"].action, self._remove_package)
Mark Beierl's avatar
Mark Beierl committed
        self.framework.observe(self.on["remove-snap"].action, self._remove_snap)
Mark Beierl's avatar
Mark Beierl committed
        self.framework.observe(self.on["update-system"].action, self._update_system)
        self.framework.observe(self.on["integrate-ldap"].action, self._ldap_integration)
Mark Beierl's avatar
Mark Beierl committed

        # Relations hooks

    # Override InstallProgress to update our status
    def status_change(self, pkg, percent, status):
        if (time.time() - self.last_status_update) < 2:
            return
        self.last_status_update = time.time()
Mark Beierl's avatar
Mark Beierl committed
        message = str(int(percent)) + "% " + status
        self.unit.status = MaintenanceStatus(message)

    # Basic hooks
    def _on_install(self, _):
Mark Beierl's avatar
Mark Beierl committed

        self.unit.status = MaintenanceStatus("Setting up apt proxy")
        with open(APT_PROXY_TEMPLATE, "r") as template:
            content = Template(template.read()).render()
            with open(APT_PROXY_PATH, "w") as proxy:
                proxy.write(content)

Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = MaintenanceStatus("Installing apt packages")
        install_apt(packages=APT_REQUIREMENTS, update=True, progress=self)
        service_stop('xrdp')

        self.unit.status = MaintenanceStatus("Installing snaps")
        for snap in SNAP_INSTALLS:
            shell("snap install " + snap)
Mark Beierl's avatar
Mark Beierl committed

        self.unit.status = MaintenanceStatus("Setting default display manager")
        shell("echo /usr/sbin/lightdm | sudo tee /etc/X11/default-display-manager")

        self.unit.status = MaintenanceStatus("Adding XRDP to ssl-cert group")
        shell("adduser xrdp ssl-cert")
Mark Beierl's avatar
Mark Beierl committed

        self.unit.status = MaintenanceStatus("Generating Window Manager startup script")
        with open(STARTWM_TEMPLATE, "r") as template:
            content = Template(template.read()).render(command=WM_COMMAND)
            with open(STARTWM_PATH, "w") as startwm:
                startwm.write(content)

        self.unit.status = MaintenanceStatus("Generating Polkit files")
        with open(POLKIT_TEMPLATE, "r") as template:
            content = Template(template.read()).render()
            with open(POLKIT_PATH, "w") as polkit:
                polkit.write(content)

Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = MaintenanceStatus("Removing public IP interface")
        with open(PUBLIC_IP_TEMPLATE, "r") as template:
            content = Template(template.read()).render()
            with open(PUBLIC_IP_PATH, "w") as public_ip:
                public_ip.write(content)
        shell("netplan apply")

Mark Beierl's avatar
Mark Beierl committed
        self._stored.installed = True
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = self._get_current_status()
Mark Beierl's avatar
Mark Beierl committed

    def _on_start(self, _):
        self.unit.status = MaintenanceStatus("Starting XRDP server")
        service_restart('xrdp')
        self._stored.started = True
        self.unit.status = self._get_current_status()

    def _on_stop(self, _):
        service_stop('xrdp')
        self._stored.started = False
        self.unit.status = self._get_current_status()

    def _on_config_changed(self, _):
        self.unit.status = self._get_current_status()

    def _on_update_status(self, _):
        self.unit.status = self._get_current_status()

    # Action hooks
Mark Beierl's avatar
Mark Beierl committed
    def _add_package(self, event):
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = MaintenanceStatus("Installing apt packages")
        install_apt(packages=event.params["package"].split(','),
                    update=True, progress=self)
        self.unit.status = self._get_current_status()

Mark Beierl's avatar
Mark Beierl committed
    def _add_snap(self, event):
        self.unit.status = MaintenanceStatus("Installing snaps")
        for snap in event.params["package"].split(','):
            shell("snap install " + snap)
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = self._get_current_status()

    def _announce(self, event):
        self.unit.status = MaintenanceStatus("Announce")
        message = event.params["message"]
Mark Beierl's avatar
Mark Beierl committed
        shell("su - ubuntu -c 'XDG_RUNTIME_DIR=/run/user/$(id -u) notify-send \"" + message + "\"'")
        self.unit.status = self._get_current_status()

Mark Beierl's avatar
Mark Beierl committed
    def _reboot(self, _):
        self.unit.status = MaintenanceStatus("Rebooting server")
Mark Beierl's avatar
Mark Beierl committed
        shell("su - ubuntu -c 'XDG_RUNTIME_DIR=/run/user/$(id -u) notify-send \"System is going down for reboot in 60 seconds\" -u critical'")
        shell("shutdown -r +1")
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = self._get_current_status()

Mark Beierl's avatar
Mark Beierl committed
    def _remove_package(self, event):
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = MaintenanceStatus("Removing apt packages")
        remove_apt(packages=event.params["package"].split(','),
                   update=True, progress=self)
        self.unit.status = self._get_current_status()

Mark Beierl's avatar
Mark Beierl committed
    def _remove_snap(self, event):
        self.unit.status = MaintenanceStatus("Removing snaps")
        for snap in event.params["package"].split(','):
            shell("snap remove " + snap)
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = self._get_current_status()

Mark Beierl's avatar
Mark Beierl committed
    def _update_system(self, _):
        self.unit.status = MaintenanceStatus("Updating system")
Mark Beierl's avatar
Mark Beierl committed
        upgrade_apt(update=True, progress=self)
Mark Beierl's avatar
Mark Beierl committed
        self.unit.status = self._get_current_status()

    def _ldap_integration(self, event):
        self.unit.status = MaintenanceStatus("Configuring Ldap autentication")
        self._configure_ldap(event.params["ldap_host"], event.params["ldap_domain"], event.params["ldap_password"])
        if not self._stored.ldap_installed:
            install_apt(packages=["ldap-auth-client", "nscd"],
                        update=True, progress=self)
            # edit /etc/nsswitch.conf
            shell("sudo sed -E -i '/passwd|group|shadow/ !b; s/$/ ldap/' /etc/nsswitch.conf")
            # restart nscd
            shell("sudo systemctl restart nscd")
            #create a home directory when user login
            shell("echo session required pam_mkhomedir.so skel=/etc/skel umask=077 | sudo tee /etc/pam.d/common-session")
            self._stored.ldap_installed = True
        else:
            shell("dpkg-reconfigure -f noninteractive ldap-auth-client")
        self.unit.status = ActiveStatus("Ldap autentication configured")

Mark Beierl's avatar
Mark Beierl committed
    # Relation hooks

    # Private functions
    def _get_current_status(self):
        status_type = ActiveStatus
        status_msg = ""
        if self._stored.installed:
            status_msg = "Ready"
        return status_type(status_msg)

    def _configure_ldap(self, host, domain, password):
        #configure ldap-auth-config with debconf
        shell("echo ldap-auth-config ldap-auth-config/rootbindpw password {} | sudo debconf-set-selections".format(password))
        shell("echo ldap-auth-config ldap-auth-config/bindpw password {} | sudo debconf-set-selections".format(password))
        shell("echo ldap-auth-config ldap-auth-config/binddn string cn=admin,dc={},dc={} | sudo debconf-set-selections".format(domain.split(".")[0],domain.split(".")[1]))
        shell("echo ldap-auth-config ldap-auth-config/dblogin boolean false | sudo debconf-set-selections")
        shell("echo ldap-auth-config ldap-auth-config/rootbinddn string cn=admin,dc={},dc={} | sudo debconf-set-selections".format(domain.split(".")[0],domain.split(".")[1]))
        shell("echo ldap-auth-config ldap-auth-config/ldapns/ldap-server string ldap://{} | sudo debconf-set-selections".format(host))
        shell("echo ldap-auth-config ldap-auth-config/override boolean true | sudo debconf-set-selections")
        shell("echo ldap-auth-config ldap-auth-config/ldapns/ldap_version select 3 | sudo debconf-set-selections")
        shell("echo ldap-auth-config ldap-auth-config/dbrootlogin boolean true | sudo debconf-set-selections")
        shell("echo ldap-auth-config ldap-auth-config/ldapns/base-dn string dc={},dc={} | sudo debconf-set-selections".format(domain.split(".")[0],domain.split(".")[1]))
        shell("echo ldap-auth-config ldap-auth-config/move-to-debconf boolean true | sudo debconf-set-selections")
        shell("echo ldap-auth-config ldap-auth-config/pam_password select clear | sudo debconf-set-selections")

Mark Beierl's avatar
Mark Beierl committed

if __name__ == "__main__":
    main(VirtualPCCharm)