diff --git a/.gitignore b/.gitignore
index 8eac94986..7f1f79b20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ doc/manual/*/*.html
doc/manual/*/*.xml
!doc/manual/*/*.raw.xml
doc/plinth.1
+doc/dev/_build
\#*
.#*
*~
diff --git a/debian/copyright b/debian/copyright
index 00b05b8d2..a2bca8c0b 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -10,6 +10,9 @@ Copyright: 2011-2019 FreedomBox Authors
License: AGPL-3+
Files: doc/*.xml
+ doc/*.rst
+ doc/*.png
+ doc/*.jpg
Copyright: 2011-2019 FreedomBox Authors
License: CC-BY-SA-4.0
diff --git a/doc/dev/Makefile b/doc/dev/Makefile
new file mode 100644
index 000000000..bfc0d34a4
--- /dev/null
+++ b/doc/dev/Makefile
@@ -0,0 +1,41 @@
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+
+#
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXAUTOBUILD = sphinx-autobuild
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+livehtml: Makefile
+ @$(SPHINXAUTOBUILD) -b html -r "\\.#.*" $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html"
diff --git a/doc/dev/_static/.gitkeep b/doc/dev/_static/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/doc/dev/_templates/layout.html b/doc/dev/_templates/layout.html
new file mode 100644
index 000000000..aa02582ec
--- /dev/null
+++ b/doc/dev/_templates/layout.html
@@ -0,0 +1,15 @@
+{%- extends "alabaster/layout.html" %}
+
+{%- block footer %}
+
+
+{% endblock %}
diff --git a/doc/dev/conf.py b/doc/dev/conf.py
new file mode 100644
index 000000000..78ef99a9e
--- /dev/null
+++ b/doc/dev/conf.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+"""
+Configuration file for the Sphinx documentation builder.
+
+This file does only contain a selection of the most common options. For a full
+list see the documentation: http://www.sphinx-doc.org/en/master/config
+"""
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('../../'))
+
+# -- Project information -----------------------------------------------------
+
+# pylint: disable=invalid-name
+project = 'FreedomBox'
+copyright = '2019, FreedomBox Authors'
+author = 'FreedomBox Authors'
+
+# The short X.Y version
+version = ''
+# The full version, including alpha/beta/rc tags
+release = ''
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.viewcode',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+html_theme_options = {
+ 'fixed_sidebar': True,
+ 'show_related': True,
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'FreedomBoxdoc'
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'FreedomBox.tex', 'FreedomBox Documentation',
+ 'FreedomBox Authors', 'manual'),
+]
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, 'freedombox', 'FreedomBox Documentation', [author],
+ 1)]
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'FreedomBox', 'FreedomBox Documentation', author,
+ 'FreedomBox', 'One line description of project.', 'Miscellaneous'),
+]
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# -- Extension configuration -------------------------------------------------
+
+# -- Options for intersphinx extension ---------------------------------------
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {
+ 'python': ('https://docs.python.org/3', None),
+ 'django': ('https://docs.djangoproject.com/en/stable/',
+ 'https://docs.djangoproject.com/en/stable/_objects/'),
+}
+
+# -- Options for todo extension ----------------------------------------------
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = True
+
+# -- Options for autodoc extension -------------------------------------------
+
+autodoc_default_options = {
+ 'special-members': '__init__',
+}
diff --git a/doc/dev/index.rst b/doc/dev/index.rst
new file mode 100644
index 000000000..595e618a2
--- /dev/null
+++ b/doc/dev/index.rst
@@ -0,0 +1,34 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+FreedomBox Developer Documentation
+==================================
+
+This manual is meant for developers intending to develop app for FreedomBox. It
+provides a step by step tutorial and an API reference.
+
+.. toctree::
+ :maxdepth: 3
+ :caption: Contents:
+
+ tutorial/index
+ reference/index
+
+External References
+===================
+
+#. :doc:`Django Documentation - Getting Started `
+
+#. `Debian Packaging Portal `_
+
+#. `systemd System and Service Manager `_
+
+#. `Bootstrap - CSS Library `_
+
+#. `FreedomBox User Manual `_
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/doc/dev/reference/action_utils.rst b/doc/dev/reference/action_utils.rst
new file mode 100644
index 000000000..92c9c565d
--- /dev/null
+++ b/doc/dev/reference/action_utils.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Action Utils
+^^^^^^^^^^^^
+
+Several utilities to help with the implementation of actions and diagnostic
+tests are implemented in this module.
+
+.. automodule:: plinth.action_utils
+ :members:
diff --git a/doc/dev/reference/actions.rst b/doc/dev/reference/actions.rst
new file mode 100644
index 000000000..91d7cf59b
--- /dev/null
+++ b/doc/dev/reference/actions.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Actions
+^^^^^^^
+
+FreedomBox's web front does not directly change any aspect of the underlying
+operating system. Instead, it calls upon **actions**, as shell commands. Actions
+live in ``/usr/share/plinth/actions`` directory. They require no interaction
+beyond passing command line arguments or taking sensitive arguments via stdin.
+They change the operation of the services and apps of the FreedomBox and nothing
+else. These actions are also directly usable by a skilled administrator.
+
+The following documentation for the ``actions`` module.
+
+.. automodule:: plinth.actions
+ :members: run, superuser_run, run_as_user, _run
diff --git a/doc/dev/reference/app.rst b/doc/dev/reference/app.rst
new file mode 100644
index 000000000..45f5c1c35
--- /dev/null
+++ b/doc/dev/reference/app.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+App
+---
+
+.. autoclass:: plinth.app.App
+ :members:
diff --git a/doc/dev/reference/app_module.rst b/doc/dev/reference/app_module.rst
new file mode 100644
index 000000000..589b64481
--- /dev/null
+++ b/doc/dev/reference/app_module.rst
@@ -0,0 +1,37 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+App Module
+----------
+
+These methods are optionally provided by the module in which an app is
+implemented and FreedomBox calls/uses them if they are present.
+
+.init()
+^^^^^^^^^^^^^^^^^^^
+
+Optional. This method is called by FreedomBox soon after all the applications
+are loaded. The ``init()`` call order guarantees that other applications that
+this application depends on will be initialized before this application is
+initialized.
+
+.diagnose()
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Optional. Called when the user invokes system-wide diagnostics by visiting
+**System -> Diagnositcs**. This method must return an array of diagnostic
+results. Each diagnostic result must be a two-tuple with first element as a
+string that is shown to the user as name of the test and second element is the
+result of the test. It must be one of ``passed``, ``failed``, ``error``. Example
+return value:
+
+.. code-block:: python3
+
+ [('Check http://localhost/app is reachable', 'passed'),
+ ('Check configuration is sane', 'passed')]
+
+.depends
+^^^^^^^^^^^^^^^^^^^^
+
+Optional. This module property must contain a list of all apps that this
+application depends on. The application is specified as string containing the
+full module load path. For example, ``names``.
diff --git a/doc/dev/reference/components/daemon.rst b/doc/dev/reference/components/daemon.rst
new file mode 100644
index 000000000..293d715a5
--- /dev/null
+++ b/doc/dev/reference/components/daemon.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Daemon
+^^^^^^
+
+.. autoclass:: plinth.daemon.Daemon
+ :members:
diff --git a/doc/dev/reference/components/domain.rst b/doc/dev/reference/components/domain.rst
new file mode 100644
index 000000000..884f918a8
--- /dev/null
+++ b/doc/dev/reference/components/domain.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Domain Name
+^^^^^^^^^^^
+
+.. autoclass:: plinth.modules.names.components.DomainName
+ :members:
+
+.. autoclass:: plinth.modules.names.components.DomainType
+ :members:
diff --git a/doc/dev/reference/components/firewall.rst b/doc/dev/reference/components/firewall.rst
new file mode 100644
index 000000000..e15eafd02
--- /dev/null
+++ b/doc/dev/reference/components/firewall.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Firewall
+^^^^^^^^
+
+.. autoclass:: plinth.modules.firewall.components.Firewall
+ :members:
diff --git a/doc/dev/reference/components/frontpage.rst b/doc/dev/reference/components/frontpage.rst
new file mode 100644
index 000000000..5e185177f
--- /dev/null
+++ b/doc/dev/reference/components/frontpage.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Frontpage
+^^^^^^^^^
+
+.. autoclass:: plinth.frontpage.Shortcut
+ :members:
diff --git a/doc/dev/reference/components/index.rst b/doc/dev/reference/components/index.rst
new file mode 100644
index 000000000..16c03c09f
--- /dev/null
+++ b/doc/dev/reference/components/index.rst
@@ -0,0 +1,27 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Components
+----------
+
+.. toctree::
+ :caption: Available components:
+
+ menu
+ daemon
+ firewall
+ webserver
+ frontpage
+ domain
+ letsencrypt
+
+Base Classes
+^^^^^^^^^^^^
+
+.. autoclass:: plinth.app.Component
+ :members:
+
+.. autoclass:: plinth.app.LeaderComponent
+ :members:
+
+.. autoclass:: plinth.app.FollowerComponent
+ :members:
diff --git a/doc/dev/reference/components/letsencrypt.rst b/doc/dev/reference/components/letsencrypt.rst
new file mode 100644
index 000000000..84c423806
--- /dev/null
+++ b/doc/dev/reference/components/letsencrypt.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Let's Encrypt
+^^^^^^^^^^^^^
+
+.. autoclass:: plinth.modules.letsencrypt.components.LetsEncrypt
+ :members:
diff --git a/doc/dev/reference/components/menu.rst b/doc/dev/reference/components/menu.rst
new file mode 100644
index 000000000..9bb682c5a
--- /dev/null
+++ b/doc/dev/reference/components/menu.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Menu
+^^^^
+
+.. autoclass:: plinth.menu.Menu
+ :members:
diff --git a/doc/dev/reference/components/webserver.rst b/doc/dev/reference/components/webserver.rst
new file mode 100644
index 000000000..896d66cca
--- /dev/null
+++ b/doc/dev/reference/components/webserver.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Webserver
+^^^^^^^^^
+
+.. autoclass:: plinth.modules.apache.components.Webserver
+ :members:
+
+.. autoclass:: plinth.modules.apache.components.Uwsgi
+ :members:
diff --git a/doc/dev/reference/forms.rst b/doc/dev/reference/forms.rst
new file mode 100644
index 000000000..914438e59
--- /dev/null
+++ b/doc/dev/reference/forms.rst
@@ -0,0 +1,13 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Forms
+-----
+
+.. autoclass:: plinth.forms.AppForm
+ :members:
+
+.. autoclass:: plinth.forms.DomainSelectionForm
+ :members:
+
+.. autoclass:: plinth.forms.CheckboxSelectMultipleWithReadOnly
+ :members:
diff --git a/doc/dev/reference/index.rst b/doc/dev/reference/index.rst
new file mode 100644
index 000000000..e8915e2a6
--- /dev/null
+++ b/doc/dev/reference/index.rst
@@ -0,0 +1,23 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+=========
+Reference
+=========
+
+This section describes the FreedomBox API that is most frequently used by apps.
+Note that since FreedomBox is under development and has not yet declared a
+stable API, this API is subject to change. This is not usually a problem because
+all the FreedomBox apps currently reside in FreedomBox source repository itself
+and are updated when the API is updated.
+
+.. toctree::
+
+ app
+ components/index
+ app_module
+ actions
+ action_utils
+ views
+ forms
+
+.. automodule:: plinth.modules.ttrss
diff --git a/doc/dev/reference/views.rst b/doc/dev/reference/views.rst
new file mode 100644
index 000000000..d7c99aaf2
--- /dev/null
+++ b/doc/dev/reference/views.rst
@@ -0,0 +1,7 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Views
+-----
+
+.. autoclass:: plinth.views.AppView
+ :members:
diff --git a/doc/dev/tutorial/beginning.rst b/doc/dev/tutorial/beginning.rst
new file mode 100644
index 000000000..34f7818d9
--- /dev/null
+++ b/doc/dev/tutorial/beginning.rst
@@ -0,0 +1,65 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 1: Beginning
+-----------------
+
+Before we begin
+^^^^^^^^^^^^^^^
+
+FreedomBox Service (Plinth) is a web interface built using Python3 and Django.
+FreedomBox apps are simply Django applications within the project. Hence, for
+the most part, writing a FreedomBox app is all about :doc:`writing a Django
+application `.
+
+You should start by reading the :doc:`Django tutorial `. All
+the concepts described there are applicable for how FreedomBox and its apps are
+be built.
+
+Picking an app
+^^^^^^^^^^^^^^
+
+We must first, of course, pick an application to add to FreedomBox. For the
+purpose of this tutorial, let us pick Transmission. Transmission daemon handles
+Bitorrent file sharing. BitTorrent is a peer-to-peer file sharing protocol.
+
+.. important:: Choosing an app
+
+ When choosing an application we must make sure that it respects users'
+ freedom and privacy. By choosing to use FreedomBox, users have explicitly made
+ a choice to keep the data with themselves, to not provide privacy compromising
+ data to centralized entities and to use Free Software that respects their
+ Software Freedom. These are not properties of *some* of the applications in
+ FreedomBox but all applications *must* adhere to these principles. Apps should
+ not even ask the users questions to this effect, because users have already
+ made a choice.
+
+Packaging the application
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Majority of the effort in creating an application for FreedomBox is to package
+it for Debian and get it uploaded to Debian repositories. Going through the
+process of packaging itself is outside the scope of this tutorial. It is,
+however, well documented elsewhere. You should start at the `Debian Packaging
+Portal `_.
+
+Debian packaging might seem like an unnecessary process that takes time with its
+adherence to standards, review process, legal checks, etc. However, upon close
+examination, one will find that without these steps the goals of the FreedomBox
+project cannot be met. Some of the advantages of Debian packaging are listed
+below:
+
+* Legal check ensures that proprietary licensed code or code with bad licenses
+ does not inadvertently creep in.
+
+* Libraries have to be packaged separately easing security handling. When a
+ security vulnerability is identified in a library, just the library will have
+ to be updated and not all the applications that depend on it.
+
+* Upgrades become smoother. The dependency handling of the packaging system,
+ configuration handling tools, tools to deal with various types of well known
+ files help with ensuring a proper upgrade.
+
+* Collaborative maintenance teams ensure that the package is well cared for even
+ if you get busy with other work and can't spend time on your package.
+ Following standards and using common infrastructure is critical to enable this
+ development methodology.
diff --git a/doc/dev/tutorial/code.rst b/doc/dev/tutorial/code.rst
new file mode 100644
index 000000000..4cde693a7
--- /dev/null
+++ b/doc/dev/tutorial/code.rst
@@ -0,0 +1,61 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Full Code
+---------
+
+Transmission app is already included in FreedomBox. Here is the full source for
+the module for reference.
+
+plinth/modules/transmission/__init__.py
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/__init__.py
+ :language: python3
+
+plinth/modules/transmission/forms.py
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/forms.py
+ :language: python3
+
+plinth/modules/transmission/manifest.py
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/manifest.py
+ :language: python3
+
+plinth/modules/transmission/urls.py
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/urls.py
+ :language: python3
+
+plinth/modules/transmission/views.py
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/views.py
+ :language: python3
+
+plinth/modules/transmission/data/etc/plinth/modules-enabled/transmission
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/data/etc/plinth/modules-enabled/transmission
+ :language: text
+
+plinth/modules/transmission/data/etc/apache2/conf-available/transmission-plinth.conf
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/data/etc/apache2/conf-available/transmission-plinth.conf
+ :language: apache
+
+plinth/modules/transmission/tests/__init__.py
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../plinth/modules/transmission/tests/__init__.py
+ :language: python3
+
+actions/transmission
+^^^^^^^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../actions/transmission
+ :language: python3
diff --git a/doc/dev/tutorial/components.rst b/doc/dev/tutorial/components.rst
new file mode 100644
index 000000000..f3e9a8d59
--- /dev/null
+++ b/doc/dev/tutorial/components.rst
@@ -0,0 +1,181 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 4: Components
+------------------
+
+Managing a daemon
+^^^^^^^^^^^^^^^^^
+
+Transmission, like many services in the FreedomBox, requires a daemon to be
+running in the system to work. When the app is enabled, the daemon should be
+enabled. When the app is disabled, the daemon should be disabled. We should also
+show the status of whether the daemon is running in the app's view. All of these
+concerns are automatically handled by the framework if a
+:class:`~plinth.daemon.Daemon` component is added to the app. Let us do that in
+our app's class.
+
+.. code-block:: python3
+
+ from plinth.daemon import Daemon
+
+ managed_services = ['transmission-daemon']
+
+ class TransmissionApp(app_module.App):
+ ...
+
+ def __init__(self):
+ ...
+
+ daemon = Daemon('daemon-transmission', managed_services[0])
+ self.add(daemon)
+
+
+The first argument to instantiate the :class:`~plinth.daemon.Daemon` class is a
+unique ID. The second is the name of the `systemd
+`_ unit file which manages
+the daemon.
+
+Managing web server configuration
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Transmission provides a web interface to the user. This web interface needs to
+be proxied through a web server for security and access control. We will need to
+write a configuration snippet for Apache, the default web server on FreedomBox.
+This configuration snippet needs to be activated when our app is enabled. The
+configuration snippet needs to be deactivated when our app is disabled. All of
+these concerns are automatically handled by the framework if a
+:class:`~plinth.modules.apache.components.Webserver` component is added to the
+app. Let us do that in our app's class.
+
+.. code-block:: python3
+
+ from plinth.modules.apache.components import Webserver
+
+ class TransmissionApp(app_module.App):
+ ...
+
+ def __init__(self):
+ ...
+
+ webserver = Webserver('webserver-transmission', 'transmission-plinth')
+ self.add(webserver)
+
+The first argument to instantiate the
+:class:`~plinth.modules.apache.components.Webserver` class is a unique ID. The
+second is the name of the Apache2 web server configuration snippet that contains
+the directives to proxy Transmission web interface via Apache2. We then need to
+create the configuration file itself in ``tranmission-freedombox.conf``.
+
+.. code-block:: apache
+
+ ## On all sites, provide Transmission on a default path: /transmission
+
+ ProxyPass http://localhost:9091/transmission
+
+
+Managing the firewall
+^^^^^^^^^^^^^^^^^^^^^
+
+FreedomBox has a tight firewall that closes off all TCP/UDP ports by default. If
+a service needs to available to users on a port, it needs to open the ports in
+firewalld, the default firewall configuration manager in FreedomBox. When the
+app is enabled, the ports need to opened and when the app is disabled, the ports
+needs to be closed. The FreedomBox framework again provides a component for
+handling these operations. In case of our app, there is no need to open a
+special port since the web ports are always kept open. However, it is still good
+to specify that we operate on http/https ports so that users can be provided
+this information along with additional information on whether the service is
+available over Internet. Create the
+:class:`~plinth.modules.firewall.components.Firewall` component during app
+initialization.
+
+.. code-block:: python3
+
+ from plinth.modules.firewall.components import Firewall
+
+ class TransmissionApp(app_module.App):
+ ...
+
+ def __init__(self):
+ ...
+
+ firewall = Firewall('firewall-transmission', name,
+ ports=['http', 'https'], is_external=True)
+ self.add(firewall)
+
+The first parameter is a unique ID. Second one is the name of the app that as
+shown to the user in the firewall status page. Third argument is the list of
+services known to firewalld as listed in ``/usr/lib/firewalld/services/``.
+Custom services can also be written. The final argument decides whether the
+service should be made available by FreedomBox from external networks,
+essentially the Internet.
+
+User authentication and authorization
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+We wish that only users of FreedomBox should access the web interface of our
+app. Further, only users belonging to a specially created group are the only
+ones who should be able access the app. Again, FreedomBox handles all of this
+and we simply need to declare and use. First we need to register a user group
+with the FreedomBox framework in ``__init.py__``.
+
+.. code-block:: python3
+
+ group = ('bit-torrent', 'Download files using BitTorrent applications')
+
+ def init():
+ ...
+ register_group(group)
+
+Then in the Apache configuration snippet, we can mandate that only users of this
+group (and, of course, admin users) should be allowed to access our app. In the
+file ``tranmission-freedombox.conf``, add the following.
+
+.. code-block:: apache
+
+
+ ...
+ Include includes/freedombox-single-sign-on.conf
+
+ TKTAuthToken "admin" "bit-torrent"
+
+
+
+Showing a shortcut in the front page
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The app view we have created is only accessible by administrators of FreedomBox
+since only they can configure the app. Other users who have access to this app
+should have a way of discovering the app. This is done by providing a link in
+the front page of FreedomBox web interface. This is the page that user's see
+when they visit FreedomBox. To provide this shortcut, a
+:class:`~plinth.frontpage.Shortcut` component can added to the app.
+
+.. code-block:: python3
+
+ from plinth import frontpage
+
+ group = ('bit-torrent', 'Download files using BitTorrent applications')
+
+ class TransmissionApp(app_module.App):
+ ...
+
+ def __init__(self):
+ ...
+
+ shortcut = frontpage.Shortcut(
+ 'shortcut-transmission', name, short_description=short_description,
+ icon='transmission', url='/transmission', clients=clients,
+ login_required=True, allowed_groups=[group[0]])
+ self.add(shortcut)
+
+The first parameter, as usual, is a unique ID. The next three parameters are
+basic information about the app similar to the menu item. The URL parameter
+specifies the URL that the user should be directed to when the shortcut is
+clicked. This is the web interface provided by our app. The next parameter
+provides a list of clients. This is useful for the FreedomBox mobile app when
+the information is used to suggest installing mobile apps. This is described in
+a later section of this tutorial. The next parameter specifies whether anonymous
+users who are not logged into FreedomBox should be shown this shortcut. The
+final parameter further restricts to which group of users this shortcut must be
+shown.
diff --git a/doc/dev/tutorial/customizing.rst b/doc/dev/tutorial/customizing.rst
new file mode 100644
index 000000000..a7738f72c
--- /dev/null
+++ b/doc/dev/tutorial/customizing.rst
@@ -0,0 +1,231 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 5: Customizing
+-------------------
+
+Customizing the application page
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The view that we have written above requires a template file. A default template
+file is provided by the framework. In some cases, we will need to customize this
+template. Let us create a custom template file in ``transmission.html``.
+
+.. code-block:: django
+
+ {% extends "app.html" %}
+
+ {% load i18n %}
+
+ {% block configuration %}
+
+ {{ block.super }}
+
+ {% trans "Custom Section" %}
+
+
+ {% blocktrans trimmed %}
+ Custom paragraph content.
+ {% endblocktrans %}
+
+
+ {% endblock %}
+
+This template extends an existing template known as ``app.html``. This template
+is available in FreedomBox core to provide all the basic layout, styling, menus,
+JavaScript and CSS libraries required for a typical app view. We will override
+the configuration area after inheriting from the app template and keep the rest
+as is. ``{{ block.super }}`` adds back the overwritten content in the
+``configuration`` block.
+
+Yet again, there is nothing special about the way this template is written. This
+is a regular Django template. See :doc:`Django Template documentation
+`.
+
+For styling and UI components, FreedomBox uses the Twitter Bootstrap project.
+See `Bootstrap documentation `_ for reference.
+
+To start using our custom template, we need to pass this to our view. In
+``views.py``, add the following line:
+
+.. code-block:: python3
+
+ class TransmissionAppView(AppView):
+ ...
+ template_name = 'transmission.html'
+
+Writing a configuration form
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Our app needs some configuration. So, we need to write a configuration form to
+provide options to the user. Add the following to ``forms.py``.
+
+.. code-block:: python3
+
+ from django import forms
+
+ from plinth.forms import AppForm
+
+
+ class TransmissionForm(AppForm): # pylint: disable=W0232
+ """Transmission configuration form"""
+ download_dir = forms.CharField(
+ label='Download directory',
+ help_text='Directory where downloads are saved. If you change the '
+ 'default directory, ensure that the new directory exists '
+ 'and is writable by "debian-transmission" user.')
+
+This creates a Django form that shows a single option to set the download
+directory for our Transmission app. This is how a regular Django form is built.
+See :doc:`Django Forms documentation ` for more
+information.
+
+.. tip: Too many options
+
+ Resist the temptation to create a lot of configuration options. Although this
+ will put more control in the hands of the users, it will make FreedomBox less
+ usable. FreedomBox is a consumer product. Our target users are not technically
+ savvy and we have make most of the decisions on behalf of the user to make the
+ interface as simple and easy to use as possible.
+
+Applying the changes from the form
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The view we have created needs to display the form and process the form after
+the user submits it. Let us implement that in ``views.py``.
+
+.. code-block:: python3
+
+ from django.contrib import messages
+
+ from plinth import actions, views
+
+ from .forms import TransmissionForm
+
+ class TransmissionAppView(views.AppView):
+ ...
+ form_class = TransmissionForm
+
+ def get_initial(self):
+ """Get the current settings from Transmission server."""
+ status = super().get_initial()
+ configuration = actions.superuser_run('transmission',
+ ['get-configuration'])
+ configuration = json.loads(configuration)
+ status.update({
+ key.translate(str.maketrans({
+ '-': '_'
+ })): value
+ for key, value in configuration.items()
+ })
+ return status
+
+ def form_valid(self, form):
+ """Apply the changes submitted in the form."""
+ old_status = form.initial
+ new_status = form.cleaned_data
+
+ if old_status['download_dir'] != new_status['download_dir']:
+ new_configuration = {
+ 'download-dir': new_status['download_dir'],
+ }
+
+ actions.superuser_run('transmission', ['merge-configuration'],
+ input=json.dumps(new_configuration).encode())
+ messages.success(self.request, 'Configuration updated')
+
+ return super().form_valid(form)
+
+We check to make sure that the configuration value has actually changed after
+the form is submitted. Although FreedomBox's operations are idempotent, meaning
+that running them twice will not be problematic, we still wish to avoid
+unnecessary operations for the sake of speed.
+
+We are actually performing the operation using *actions*. We will implement this
+action a bit later.
+
+After we perform the operation, we will show a message on the response page that
+the action was successful or that nothing happened. We use the Django messaging
+framework to accomplish this. See :doc:`Django messaging framework
+` for more information.
+
+Writing actions
+^^^^^^^^^^^^^^^
+
+The actual work of performing the configuration change is carried out by an
+*action*. Actions are independent scripts that run with higher privileges
+required to perform a task. They are placed in a separate directory and invoked
+as scripts via sudo. For our application we need to write an action that can
+enable and disable the web configuration. We will do this by creating a file
+``actions/transmission``.
+
+.. code-block:: python3
+
+ import argparse
+ import json
+ import sys
+
+ from plinth import action_utils
+
+ TRANSMISSION_CONFIG = '/etc/transmission-daemon/settings.json'
+
+
+ def parse_arguments():
+ """Return parsed command line arguments as dictionary."""
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
+
+ subparsers.add_parser('get-configuration',
+ help='Return the current configuration')
+ subparsers.add_parser(
+ 'merge-configuration',
+ help='Merge JSON configuration from stdin with existing')
+
+ subparsers.required = True
+ return parser.parse_args()
+
+
+ def subcommand_get_configuration(_):
+ """Return the current configuration in JSON format."""
+ configuration = open(TRANSMISSION_CONFIG, 'r').read()
+ print(configuration)
+
+
+ def subcommand_merge_configuration(arguments):
+ """Merge given JSON configuration with existing configuration."""
+ configuration = sys.stdin.read()
+ configuration = json.loads(configuration)
+
+ current_configuration = open(TRANSMISSION_CONFIG, 'r').read()
+ current_configuration = json.loads(current_configuration)
+
+ new_configuration = current_configuration
+ new_configuration.update(configuration)
+ new_configuration = json.dumps(new_configuration, indent=4, sort_keys=True)
+
+ open(TRANSMISSION_CONFIG, 'w').write(new_configuration)
+ action_utils.service_reload('transmission-daemon')
+
+
+ def main():
+ """Parse arguments and perform all duties."""
+ arguments = parse_arguments()
+
+ subcommand = arguments.subcommand.replace('-', '_')
+ subcommand_method = globals()['subcommand_' + subcommand]
+ subcommand_method(arguments)
+
+
+ if __name__ == '__main__':
+ main()
+
+This is a simple Python3 program that parses command line arguments. While
+Python3 is preferred, it can be written in other languages also. It may use
+various helper utilities provided by the FreedomBox framework in
+:obj:`plinth.action_utils` to easily perform it's duties.
+
+This script is automatically installed to ``/usr/share/plinth/actions`` by
+FreedomBox's installation script ``setup.py``. Only from here will there is a
+possibility of running the script under ``sudo``. If you are writing an
+application that resides indenpendently of FreedomBox's source code, your app's
+``setup.py`` script will need to take care of copying the file to this target
+location.
diff --git a/doc/dev/tutorial/finishing.rst b/doc/dev/tutorial/finishing.rst
new file mode 100644
index 000000000..8f054ceb3
--- /dev/null
+++ b/doc/dev/tutorial/finishing.rst
@@ -0,0 +1,95 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 8: Finishing
+-----------------
+
+Adding a License
+^^^^^^^^^^^^^^^^
+
+FreedomBox is licensed under the GNU Affero General Public License Version 3 or
+later. FreedomBox apps, which run as modules under FreedomBox Service (Plinth),
+also need to be under the same license or under a compatible license. The
+license of our app needs to clear for our app to be accepted by users and other
+developers. Let us add license headers to our application.
+
+.. code-block:: python3
+
+ #
+ # This file is part of FreedomBox.
+ #
+ # 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 .
+ #
+
+The above header needs to be present in every file of the application. It is
+suitable for Python files. However, in template files, we need to modify it
+slightly.
+
+.. code-block:: django
+
+ {% extends "base.html" %}
+ {% comment %}
+ #
+ # This file is part of FreedomBox.
+ #
+ # 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 .
+ #
+ {% endcomment %}
+
+Coding standards
+^^^^^^^^^^^^^^^^
+
+For readability and easy collaboration it is important to follow common coding
+standards. FreedomBox uses the Python coding standards and uses the ``pylint``
+and ``flake8`` tools to check if the there are any violations. Run these tools
+on our application and fix any errors and warnings. Better yet, integrate these
+tools into your favorite IDE for on-the-fly checking.
+
+For the most part, the code we have written so far, is already compliant with
+the coding standards. This includes variable/method naming, indentation,
+document strings, comments, etc. One thing we have to add are the module
+documentation strings. Let us add those. In ``__init__.py`` add the top:
+
+.. code-block:: python3
+
+ """
+ FreedomBox app to configure Transmission.
+ """
+
+Contributing code to FreedomBox
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``HACKING.md`` and ``CONTRIBUTING.md`` files in the FredomBox source code
+have tips on how to contribute code to the project. Be sure to read them if you
+are submitting your app for including on the project.
+
+Here is ``HACKING.md``:
+
+.. literalinclude:: ../../../HACKING.md
+ :language: md
+
+Here is ``CONTRIBUTING.md``:
+
+.. literalinclude:: ../../../CONTRIBUTING.md
+ :language: md
diff --git a/doc/dev/tutorial/index.rst b/doc/dev/tutorial/index.rst
new file mode 100644
index 000000000..d34507ea4
--- /dev/null
+++ b/doc/dev/tutorial/index.rst
@@ -0,0 +1,48 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+=====================================
+Tutorial: Writing Apps for FreedomBox
+=====================================
+
+This tutorial covers writing an app for FreedomBox. FreedomBox is a pure blend
+of Debian with a web interface, that configures its apps. We shall discuss
+various aspects of building an app for FreedomBox, by creating an example app.
+The app that is discussed in the tutorial already available in FreedomBox so you
+can also study it's full source code.
+
+There are two parts to writing a FreedomBox app. First is to make sure that the
+app is available as a Debian package uploaded to its repositories. This is the
+majority of the work involved. However, if an app is already available in Debian
+repositories, the whole task is simplified.. The second part of writing an app
+for FreedomBox is to provide a thin web interface layer for configuring and
+managing the app. This is done by extending FreedomBox's user interface to
+provide visibility to the app and to let the user control its operations in a
+highly simplified way. This layer is what we typically refer to as a 'FreedomBox
+app'.
+
+FreedomBox apps can either be distributed to the end user as part of FreedomBox
+Service (Plinth) source code by submitting the apps to the project or they can
+distributed independently. This tutorial covers writing an app that is meant to
+be distributed as part of FreedomBox Service (Plinth). However, writing
+independent FreedomBox apps is also very similar and most of this tutorial is
+applicable.
+
+.. note:: The term *App*
+
+ The term app, in this tutorial, is used to mean multiple concepts. A service
+ or an application available to end users in FreedomBox is a combination of
+ Debian package and a web interface layer. The web interface layer is also
+ called a FreedomBox app which is very similar to and built upon a Django
+ application.
+
+.. toctree::
+
+ beginning
+ skeleton
+ view
+ components
+ customizing
+ setup
+ other
+ finishing
+ code
diff --git a/doc/dev/tutorial/other.rst b/doc/dev/tutorial/other.rst
new file mode 100644
index 000000000..0d66bbb4b
--- /dev/null
+++ b/doc/dev/tutorial/other.rst
@@ -0,0 +1,222 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 7: Other Changes
+---------------------
+
+Showing information about app clients
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+It would be helpful to our users if we can show how they can use our app. If
+there are desktop and mobile clients that can used to access our service, we
+need to list them and present them. Let's add this information to
+``manifest.py``.
+
+.. code-block:: python3
+
+ from plinth.clients import validate
+
+ clients = validate([{
+ 'name': _('Transmission'),
+ 'platforms': [{
+ 'type': 'web',
+ 'url': '/transmission'
+ }]
+ }])
+
+Since our app is a simple web application with no clients needed, we just list
+that. We need to include this into the main app view. In ``__init__.py``, add:
+
+.. code-block:: python3
+
+ from .manifest import clients
+
+ clients = clients
+
+In ``views.py``, add:
+
+.. code-block:: python3
+
+ from plinth.modules import transmission
+
+ class TransmissionAppView(views.AppView):
+ ...
+ clients = transmission.clients
+
+Writing a manual page
+^^^^^^^^^^^^^^^^^^^^^
+
+The description of app should provide basic information on what the app is about
+and how to use it. It is impractical, however, to explain everything about the
+app in a few short paragraphs. So, we need to write a page about the app in the
+FreedomBox manual. This page will be available to the users from within the
+FreedomBox web interface. To make this happen, let us write a `manual page entry
+`_ for our app in the
+`FreedomBox Wiki `_ and then provide
+a link to it from app page. In ``__init__.py``, add:
+
+.. code-block:: python3
+
+ manual_page = 'Transmission'
+
+Then, in ``views.py``, add:
+
+.. code-block:: python3
+
+ from plinth.modules import transmission
+
+ class TransmissionAppView(views.AppView):
+ ...
+ manual_page = transmission.manual_page
+
+Adding backup/restore functionality
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Each app in FreedomBox needs to provide the ability to backup its configuration
+and data. Apart from providing durability to users' data, this allows the user
+to migrate from one machine to another. FreedomBox framework provides a simple
+declarative mechanism to allow the app to be backed up and restored. In
+``manifest.py``, add:
+
+.. code-block:: python3
+
+ from plinth.modules.backups.api import validate as validate_backup
+
+ backup = validate_backup({
+ 'data': {
+ 'directories': ['/var/lib/transmission-daemon/.config']
+ },
+ 'secrets': {
+ 'files': ['/etc/transmission-daemon/settings.json']
+ },
+ 'services': ['transmission-daemon']
+ })
+
+The data and secrets information specifies which list of files and directories
+FreedomBox framework needs to backup. The list of services specifies which
+daemons should be stopped during the backup process. In ``__init__.py``, add:
+
+.. code-block:: python3
+
+ from .manifest import backup
+
+Creating diagnostics
+^^^^^^^^^^^^^^^^^^^^
+
+When the app does not work as expected, the user should known what is happening
+with the app. The FreedomBox framework provides an API for running and showing
+diagnostics results. The app has to implement a method for actually running the
+diagnostics and return the results as a list. FreedomBox then takes care of
+calling the diagnostics method and displaying the list in a formatted manner.
+
+To implement the diagnostics, a method called ``diagnose()`` has to be available
+as ``.diagnose()``. It must return a list in which each item is the
+result of a test performed. The item itself is a two-tuple containing the
+display name of the test followed by the result as ``passed``, ``failed`` or
+``error``.
+
+.. code-block:: python3
+
+ def diagnose():
+ """Run diagnostics and return the results."""
+ results = []
+
+ results.extend(action_utils.diagnose_url_on_all(
+ 'https://{host}/transmission', extra_options=['--no-check-certificate']))
+
+ return results
+
+Now that we have implemented diagnostics, we also need to show a diagnostics
+button in the App's page. Adding an attribute to the
+:class:`~plinth.views.AppView` will take care of this.
+
+.. code-block:: python3
+
+ class TransmissionView(views.AppView):
+ ...
+ diagnostics_module_name = 'transmission'
+
+There are several helpers available to implement some of the common diagnostic
+tests. For our application we wish to implement a test to check whether the
+``/transmission`` URL is accessible. Since this is a commonly performed test,
+there is a helper method available and we have used it in the above code. The
+``{host}`` tag replaced with various IP addresses, hostnames and domain names by
+the helper to produce different kinds of URLs and they are all tested. Results
+for all tests are returned which we then pass on to the framework.
+
+The user can trigger the diagnostics test by going to **System -> Diagnostics**
+page. This runs diagnostics for all the applications. Users can also run
+diagnostics specifically for this app from the app's page. A diagnostics button
+is shown by the `app.html` template automatically when
+``diagnostics_module_name`` attribute is set in the app's ``AppView`` derived
+from :obj:`plinth.views.AppView`.
+
+.. code-block:: django
+
+ {% include "diagnostics_button.html" with module="ttrss" enabled=True %}
+
+Logging
+^^^^^^^
+
+Sometimes we may feel the need to write some debug messages to the console and
+system logs. Doing this in FreedomBox is just like doing this any other Python
+application.
+
+.. code-block:: python3
+
+ import logging
+
+ logger = logging.getLogger(__name__)
+
+ def example_method():
+ logger.debug('A debug level message')
+
+ logger.info('Showing application page - %s', request.method)
+
+ try:
+ something()
+ except Exception as exception:
+ # Print stack trace
+ logger.exception('Encountered an exception - %s', exception)
+
+For more information see Python :doc:`logging framework `
+documentation.
+
+Internationalization
+^^^^^^^^^^^^^^^^^^^^
+
+Every string message that is visible to the user must be localized to user's
+native language. For this to happen, our app needs to be internationalized. This
+requires marking the user visible messages for translation. FreedomBox apps use
+the Django's localization methods to make that happen.
+
+.. code-block:: python3
+
+ from django.utils.translation import ugettext_lazy as _
+
+ name = _('Transmission')
+
+ short_description = _('BitTorrent Web Client')
+
+ description = [
+ _('BitTorrent is a peer-to-peer file sharing protocol. '
+ 'Transmission daemon handles Bitorrent file sharing. Note that '
+ 'BitTorrent is not anonymous.'),
+ _('Access the web interface at /transmission.')
+ ]
+
+Notice that the app's name, description, etc. are wrapped in the ``_()`` method
+call. This needs to be done for the rest of our app. We use the
+:obj:`~django.utils.translation.ugettext_lazy` in some cases and we use the
+regular :obj:`~django.utils.translation.ugettext` in other cases. This is
+because in the second case the :obj:`~django.utils.translation.gettext` lookup
+is made once and reused for every user looking at the interface. These users may
+each have a different language set for their interface. Lookup made for one
+language for a user should not be used for other users. The ``_lazy`` methods
+provided by Django makes sure that the return value is an object that will
+actually be converted to string at the final moment when the string is being
+displayed. In the first case, the lookup is made and string is returned
+immediately.
+
+All of this is the usual way internationalization is done in Django. See
+:doc:`Internationalization and localization `
+documentation for more information.
diff --git a/doc/dev/tutorial/setup.rst b/doc/dev/tutorial/setup.rst
new file mode 100644
index 000000000..5a3f111b7
--- /dev/null
+++ b/doc/dev/tutorial/setup.rst
@@ -0,0 +1,40 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 6: Setup
+-------------
+
+Installing packages required for the app
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+So far, we haven't dealt with installing the packages needed for Transmission to
+work. Nor did we take care of performing the initial configuration for
+Transmission. FreedomBox takes care of installing all the Debian packages
+required for our app to work. All we need to do is specify the list of the
+Debian packages required in the ``setup()`` method that is called during
+installation:
+
+.. code-block:: python3
+
+ managed_packages = ['transmission-daemon']
+
+ def setup(helper, old_version=None):
+ """Install and configure the module."""
+ helper.install(managed_packages)
+
+ new_configuration = {
+ 'rpc-whitelist-enabled': False,
+ 'rpc-authentication-required': False
+ }
+ helper.call('post', actions.superuser_run, 'transmission',
+ ['merge-configuration'],
+ input=json.dumps(new_configuration).encode())
+
+ helper.call('post', app.enable)
+
+The first time this app's view is accessed, FreedomBox shows an app installation
+page and allows the user to install the app. After the app installation is
+completed, the user is shown the app's configuration page.
+
+In case of our app Transmission, first we are installing the Debian packages,
+then performing the first time configuration on the app using the action script
+and finally enabling the app.
diff --git a/doc/dev/tutorial/skeleton.rst b/doc/dev/tutorial/skeleton.rst
new file mode 100644
index 000000000..771507d0a
--- /dev/null
+++ b/doc/dev/tutorial/skeleton.rst
@@ -0,0 +1,102 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 2: Skeleton
+----------------
+
+Let us get started with creating our FreedomBox app.
+
+Creating the project structure
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Create a directory structure as follows with empty files. We will fill them up
+in a step-by-step manner::
+
+ ─┬ /
+ ├─┬ plinth/
+ │ └─┬ modules/
+ │ └─┬ transmission/
+ │ ├─ __init__.py
+ │ ├─ forms.py
+ │ ├─ manifest.py
+ │ ├─ urls.py
+ │ ├─ views.py
+ │ ├─┬ data/
+ │ │ └─┬ etc/
+ │ │ ├─┬ plinth/
+ │ │ │ └─┬ modules-enabled/
+ │ │ │ └─ transmission
+ │ │ └─┬ apache2/
+ │ │ └─┬ conf-available/
+ │ │ └─ transmission-freedombox.conf
+ │ └─┬ tests
+ │ └─ __init__.py
+ └─┬ actions/
+ └─ transmission
+
+The file ``__init__.py`` indicates that the directory in which it is present is
+a Python module. For now, it is an empty file.
+
+FreedomBox's setup script ``setup.py`` will automatically install the
+``plinth/modules/transmission`` directory (along with other files described
+later) to an appropriate location. If you are creating an app that stays
+independent and outside of FreedomBox source tree, then ``setup.py`` script in
+your source tree will need to install it to a proper location on the system. The
+``plinth/modules/`` directory is a Python3 `namespace package
+`_. So, you can install it with the
+``plinth/modules/`` directory structure into any Python path and still be
+discovered as ``plinth.modules.*``.
+
+Tell FreedomBox that our app exists
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The first thing to do is tell FreedomBox that our app exists. This is done by
+writing a small file with the Python import path to our app and placing it in
+``plinth/modules/transmission/data/etc/plinth/modules-enabled/``. Let us create
+this file ``transmission``::
+
+ plinth.modules.transmission
+
+This file is automatically installed to ``/etc/plinth/modules-enabled/`` by
+FreedomBox's installation script ``setup.py``. If we are writing a module that
+resides independently outside the FreedomBox's source code, the setup script
+will need to copy it to the target location. Further, it is not necessary for
+the app to be part of the ``plinth.modules`` namespace. It can, for example, be
+``freedombox_transmission``.
+
+Creating the App class
+^^^^^^^^^^^^^^^^^^^^^^
+
+In the FreedomBox framework, each app must be a class derived from the
+:class:`plinth.app.App`. Let us to that in ``__init__.py``. We will fill up the
+class later.
+
+.. code-block:: python3
+
+ from plinth import app as app_module
+
+ class TransmissionApp(app_module.App):
+ """FreedomBox app for Transmission."""
+
+ app_id = 'transmission'
+
+ def __init__(self):
+ """Create components for the app."""
+ super().__init__()
+
+As soon as FreedomBox Service (Plinth) starts, it will load all the enabled
+modules. After this, it gives a chance to each of the modules to initialize
+itself by calling the ``init()`` method if there is such a method available as
+``.init()``. The app class must be instantiated here.
+
+.. code-block:: python3
+
+ app = None
+
+ def init():
+ """Initialize the Transmission module."""
+ global app
+ app = TransmissionApp()
+
+ setup_helper = globals()['setup_helper']
+ if setup_helper.get_state() != 'needs-setup' and app.is_enabled():
+ app.set_enabled(True)
diff --git a/doc/dev/tutorial/view.rst b/doc/dev/tutorial/view.rst
new file mode 100644
index 000000000..4e04cfb7e
--- /dev/null
+++ b/doc/dev/tutorial/view.rst
@@ -0,0 +1,112 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Part 3: View
+------------
+
+Writing the URLs
+^^^^^^^^^^^^^^^^
+
+For a user to visit our app in FreedomBox, we need to provide a URL. When the
+user visits this URL, a view is executed and a page is displayed. In ``urls.py``
+write the following:
+
+.. code-block:: python3
+
+ from django.conf.urls import url
+
+ from .views import TransmissionAppView
+
+ urlpatterns = [
+ url(r'^apps/transmission/$', TransmissionAppView.as_view(), name='index'),
+ ]
+
+This routes the ``/apps/transmission/`` URL to a view called
+``TransmissionAppView`` defined in ``plinth/modules/transmission/views.py``.
+This is no different than how routing URLs is done in Django. See :doc:`Django
+URL dispatcher ` for more information.
+
+Adding a menu item
+^^^^^^^^^^^^^^^^^^
+
+We have added a URL to be handled by our app but this does not yet show up to be
+a link in FreedomBox web interface. Let us add a link in the apps list. In
+``__init__.py`` add the following:
+
+.. code-block:: python3
+
+ from plinth.menu import main_menu
+
+ name = 'Transmission'
+
+ short_description = 'BitTorrent Web Client'
+
+ description = [
+ 'BitTorrent is a peer-to-peer file sharing protocol. '
+ 'Transmission daemon handles Bitorrent file sharing. Note that '
+ 'BitTorrent is not anonymous.',
+ 'Access the web interface at /transmission.'
+ ]
+
+ class TransmissionApp(app_module.App):
+ ...
+
+ def __init__(self):
+ ...
+
+ menu_item = menu.Menu('menu-transmission', name, short_description,
+ 'transmission', 'transmission:index',
+ parent_url_name='apps')
+ self.add(menu_item)
+
+What this does is add a menu item component into the our app. In FreedomBox
+framework, an app is made up of many simple components. When operations such as
+enable/disable are performed on the app, they will be applied on all the
+components. In case of menu components, FreedomBox framework takes care of
+presenting them appropriately. The component captures various details about the
+menu item we want to present.
+
+* The first parameter is simply a unique ID for the component.
+
+* The second parameter is the display name to use for our menu item which
+ happens to be the name of the app as well.
+
+* The third parameter is a short description for the menu item.
+
+* The fourth parameter is the name of the icon to use when showing the menu
+ item. An SVG file and a PNG should be created in the ``static/theme/icons/``
+ directory.
+
+* The fifth parameter is the URL that the user should be directed to when the
+ menu item is clicked. This is a Django URL name and we have already created a
+ URL with this name. Note that when including our app's URLs, FreedomBox will
+ automatically set the name of the module as the Django URL namespace. Hence it
+ is ``transmission:index`` and not just ``index``.
+
+* We wish to add our menu item to the list of apps in the apps page which is why
+ we have specified ``apps`` as the parent URL for the this app in the final
+ parameter.
+
+Writing a view
+^^^^^^^^^^^^^^
+
+We have a URL pointing to our view. We have also added a menu item in the apps
+section of the web interface that points to our view. We now need to create a
+view to show the app page for our app. In ``views.py``, let us add a view.
+
+.. code-block:: python3
+
+ from plinth import views
+ from plinth.modules import transmission
+
+ class TransmissionAppView(views.AppView):
+ """Serve configuration page."""
+ name = transmission.name
+ description = transmission.description
+ app_id = 'transmission'
+
+The base view :class:`~plinth.views.AppView` takes care of a lot of details for
+us. First, it shows basic information about the app like name, description,
+desktop/mobiles clients for the service (described later), link to the manual
+page (described later), link to diagnostics button, etc. Then it shows the
+status of the app whether it is running and can also present a form for
+configuration. It also presents a way to enable/disable the app.