[Tails-dev] Tails Server service template format and UI [Was…

Delete this message

Reply to this message
Author: anonym
Date:  
To: The Tails public development discussion list
Old-Topics: Re: [Tails-dev] Tails Server: updated plan and GSoC!
Subject: [Tails-dev] Tails Server service template format and UI [Was: Tails Server: updated plan and GSoC!]
anonym:
> segfault:
>>> - We know have a script to run a Mumble server from Tails [4] and are
>>> considering adding it to Tails [5].
>>>
>>> [4]: https://labs.riseup.net/code/issues/9993
>>> [5]: https://labs.riseup.net/code/issues/11241
>> Yes, that's great! And the current version is really nice. I already
>> have some scripts written on my own to start other services, but the
>> mumble server one definitely does some things nicer than I do - it will
>> be very helpful during this project. Although it's a shell script and I
>> will implement the server in python3.


One idea to have a consistent UX between the CLI and GUI is to make the
CLI output simple and parsable (YAML!) so that the GUI simply populates
its view according to it. Here follows a pretty detailed example for how
this can be designed, with quite some hints of the implementation [note,
all this is of course up for heavy discussion, and by no means will I
require you to strictly follow this -- but I want the final design to
have some of the properties of this e.g. the GUI/CLI duality, and the (I
expect) ease of maintenance and creating new templates due to modular
design and easy code sharing]:

Let's start from the CLI perspective, with an imagined `tails-service`
command. The following lists the services we have enabled:

    $ tails-service --list-enabled
    <nothing printed to stdout>


i.e. none currently (the "<...>" part is not part of the commands
output!). The following lists all services we gave templates for:

    $ tails-service --list-all
    - docuwiki
    - gobby
    - lighttpd
    - mumble-server
    - owncloud
    - prosody
    - securedrop
    - sshd
    - unrealircd


In fact, this should just be the directory listing of all executable
files in some directory, e.g. `/usr/local/lib/tails-services`. The point
here is that each service is a self-contained script, and once we
specify one to `tails-service` it is just a wrapper around it,
forwarding all arguments.

Let's check a single service's status:

    $ tails-service mumble-server
    - status: not enabled


or, equivalently:

    $ /usr/local/lib/tails-services/mumble-server
    - status: not enabled


The --description option doubles as both --help (so let's make it an
alias?) on the CLI, and as a way for the GUI to learn everything it
needs to know about a service template. For user options we may try to
keep it simple and allow two data types for values:

* 'simple' which is just a string (since these things generally are to
be inserted into configuration files, that's all we need), and
corresponds to an text field in the GUI.

* 'simple-options' which is a static list of predefined 'simple'
strings, and corresponds to a drop-down menu (or radio buttons?) in the GUI.

    $ tails-service mumble-server --description
    - description: A VoIP-enabled chat server
    - documentation:
/usr/share/doc/tails/website/doc/tails-server/mumble-server/
    - icon: /path/...
    - user-options:
      - motd
        - description: Set a message displayed to users upon logging in
        - type: simple
        - default:
      - allow-lan-connections:
        - description: Allow connections from the local network
        - type: simple-options
        - values:
          - yes
          - no
        - default: no


If we feel this output is too ugly for CLI users, the source for the
--description output can also be used to generate a nicer --help output
as well. Any way, the point here is that the GUI can bootstrap by
running `tails-service --list-all` and then for each $service it runs
`tails-service $service --description` to learn all it must know for
generating the GUI.

Here's a mockup of a possible GUI, with the point not being that U
expect it to be the final GUI, but rather that it's an example that
would be fairly simple to generate programmatically given the YAML
output I suggest for the various `tails-service` commands above:

________________________________________________
|          Tails Server configuration           X|

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| [... other services ... ]                      |
|                                                |
| R mumble-server ? - A VoIP-enabled chat server |
|                                     [ Enable ] |
|                                                |
| [... other services ... ]                      |

|_____________________________________________ __|

* The above "R" is a red indicator meaning that the service is not
enabled. Alternatively, perhaps it can be the icon, which is greyed out
when disabled?

* The "?" is a link that will open the local documentation of the
service, probably in Tor Browser via the `tails-documentation` helper
script.

* When clicking the "[ Enable ]" button the GUI changes to:

________________________________________________
|          Tails Server configuration           X|

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| [... other services ... ]                      |
|                                                |
| G mumble-server ? - A VoIP-enabled chat server |
| Address: blablablabla.onion  [ Info ]          |
|            [ Disable ] [ Configure ] [ Clear ] |
|                                                |
| [... other services ... ]                      |

|________________________________________________|

* The "G" is a green indicator => service is enabled. Or the icon idea,
now shown normally.

* The "[ Info ]" button will show a prompt similar to how the
mumble-server script presents the password and fingerprint (see the
`service-info` CLI output below). An alternative would be to show it
directly here, but I'm afraid it would bloat the GUI. Perhaps it can be
an expandable section that starts collapsed?

* The "[ Clear ]" button removes all data, and disables the service (I
guess?). I think we need to define which states we have (e.g. `status`
in the CLI output). I'd like us to simply have "enabled" and "disabled",
where enabled also implies that it is running iff Tor is running. A
service could be "disabled" but still have persistent data around so
that if we enable it, that would be restored. Hence the need for
"Remove" as well. This should probably be discussed in detail.

* The "[ Configure ]" button is only shown because this service *has*
options (if it doesn't we don't add such a button), and clicking it
transforms the GUI to:
________________________________________________
|          Tails Server configuration           X|

|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
| Configure mumble-server ?                      |
|                                                |
| Set a message displayed to users upon          |
| logging in.                                    |
| [ '' ]                                         |
|                                                |
| Allow connection for the local network         |
| [ |No| ]                                       |
|                                                |
|                                       [ Done ] |

|________________________________________________|

* "[ '' ]" is a currently empty text entry field. In proper GNOME way I
guess options (this and all others) should be applied immediately, e.g.
when the element loses focus or the window is closed.

* "[ |No| ]" is a drop-down list with "No" pre-selected, and "Yes" as
the only other value.

* Again, the "?" will open the documentation.

* "[ Done ]" brings us back to the main service listing.

Note that my idea above does not cover how to make it configurable
whether the service configuration is to be persistent (which, btw, will
require a restart, which should be clearly communicated through the GUI)
or ephemeral. Perhaps that is a reason to always show the "[ Configure
]" button, and have it as an always-present option there (similar for
the CLI version, then)?

Let's return to the CLI, and enable a service:

    $ tails-service mumble-server enable


Now it's listed among the enbaled services:

    $ tails-service --list-enabled
    - mumble-server


This output could be the command used after Tails logs and the Tor is
running to determine which services to autostart, assuming we want that
(I think we do!).

Let's check the service status again, which now will print a lot of
useful info. Below 'service-info' is defined by the service template,
and list important information for the servicer operator. In this case
it is the information needed to provided to the mumble users.

    $ tails-service mumble-server
    - status: enabled
    - address: blablablabla.onion
    - port: 64738
    - configuration-paths:
      - /etc/mumble-server.ini
      - /var/lib/mumble-server/
    - user-options:
      - motd:
      - allow-lan-connections: no
    - service-info: |
        Connection information for users:
        Mumble URI: mumble://blablablabla.onion:64738
        Server password: ...
        SSL SHA-1 fingerprint: 00:11:22...


        Connection information for you, the service operator:
        From this Tails session you can also connect on
mumble://127.0.0.1:64738


        You can administrate the Mumble server by connecting with the
following
        password and e.g. set up channels:
        Server administration password: ...


And let's set an option:

    $ tails-service mumble-server set-option allow-lan-connections true


It was applied immediately (the service is reloaded/restarted if needed):

    $ tails-service mumble-server
    - status: enabled
    - address: blablablabla.onion
    - port: 64738
    - configuration-paths:
      - /etc/mumble-server.ini
      - /var/lib/mumble-server/
    - user-options:
      - motd:
      - allow-lan-connections: yes
    - service-info:
      - uri: mumble://blablabla.onion
      - fingerprint: 00:11:22...
      - password: ...


An important point, I think, is that the above command parses the
configuration to read all option values, i.e. we do not save any
configuration state ourselves outside of the configuration since that
just risks to get out-of-sync with reality.


So, what about the implementation of these services? Well,
/usr/local/lib/tails-services/mumble-server could be a shell script or
anything really as long as it implements the expected CLI options and
the output adheres to the (implicit, above) YAML schema, but we should
provide a nice library so the implementation of a service could be as
simple as this:

    import tails_services


    class MumbleService(tails_services.TailsService):
      def __init__(self):
        self.name = 'mumble-server'
        self.description = 'A VoIP-enabled chat server'
        self.documentation =
'/usr/share/doc/tails/website/doc/tails-server/mumble-server/'
        # TODO: don't use sqlite3 from the command-line in configure()
but query through python-sqlite instead
        self.packages = ['mumble-server', 'sqlite3']
        self.hs_port = 64738
        self.configurations = [
          tails_services.TailsServiceConfFile('/etc/mumble-server.ini'),
          tails_services.TailsServiceConfDir('/var/lib/mumble-server/')
        ]
        self.options = [
          tails_services.TailsServiceOption(
            name = 'motd',
            description = 'Set a message displayed to users upon logging
in',
            type = tails_services.TailsServiceOption.SIMPLE_TYPE,
            default = None,
            getter = self.get_option_motd,
            setter = self.set_option_motd
          ),
          tails_services.TailsServiceOption(
            name = 'allow-lan-connections',
            description = 'Allow connections from the local network',
            type = tails_services.TailsServiceOption.SIMPLE_OPTIONS_TYPE,
            values = ['yes', 'no'],
            default = 'no',
            getter = self.get_option_allow_lan,
            setter = self.set_option_allow_lan
          ),
        ]


      def configure(self):
        """Massage configuration files according to self.options,
self.hs_port
           and any thing else specific to Tails' configuration"""


      def enable(self):
        """This method should ideally just be inherited, and will
        * make sure self.packages are installed
        * call `self.configure()`
        * execute 'systemctl start ' + self.name + '.service'.
        * Or perhaps 'reload' if started? Or always 'restart'?
        """


      def service_info(self): return """ ... service-info above ... """


      def get_option_motd(self, name): ...
      def set_option_motd(self, name, value): ...
      def get_option_allow_lan(self, name): ...
      def set_option_allow_lan(self, name, value): ...


    if __name__ == '__main__':
      MumbleService().run_cli_on_argv()



Some notes:

* With this approach, you can pretty much start in whatever end you
want, e.g. for starting with the GUI generation you just need to make a
few dummy scripts that output YAML in the expected format.

* For the {get,set}_option_* functions we can probably provide nice
method constructors in the library that will cover most cases. We
actually have something like this in our shell library see
set_simple_config_key() in
config/chroot_local-includes/usr/local/lib/tails-shell-library/common.sh. For
instance, we could have a function simple_option_accessor_gen(), that
given a file, separator and key will return a getter and setter function
for said key.

* TailsServiceConfFile() vs TailsServiceConfDir() can be interesting for
persistence since --bind mounts actually work for files. Our persistence
system doesn't currently support it, though, but we have other reasons
for adding such support, so we can make it happen. In fact, I'm not sure
if we need to distinguish files vs dirs, so maybe we can get away with a
TailsServiceConf class, or even just String:s?

* We can probably utilize systemd more (I'm no expert!). Perhaps we can
generate unit files at build time and mostly skip the `tails-service`
command some how?

* I think I will be completely fine if we skip all forms of async even
if it isn't the best UX, especially for GUIs. I.e. `tails-service`
command blocks until it's done (e.g. with `enable` it block until the
service is up) and the GUI similarly freezes until whatever action the
user requested has finished. That is, unless it turns out to be easy to
implement (especially in the GUI).

Cheers!