mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-10 11:00:22 +00:00
freedombox Debian release 21.13
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmGJ5rkACgkQd8DHXntl CAih2xAAvBe1QaU+lW+oWGaKvCB2kyg32ghDWLmhGv6iw62zPwRe4K1ZUmpgQWI5 hpZRPchZKvfTeyBmJ+l0sT3sQKgzwvh8iUiB9axKDziN/C8KicDSgLjdXiUki0Gc UI7/BEP9kfujY1XwSCbBK2Z9N99uClyezaMneIhxSqVgD/2XXUquSRBanOisWru+ UJBxz961A6JlpvbrFGDJ6yHKgwFI/EZXJqVccUlFcPBpYgymTyPfVpoc8X0bWet0 8GhwpbB0wQVTczoPWdE0caaF1de1GBW4KNm+iV0o1pw7A1pAsHJv7vf1Nh86ISwl BPVlVM0oGaCaRO62UBJjvEoFkgflbAgEGpZAuFKZ73H4UhQlcciYPkx6MyGfvE2r KxRLYvWX9BY4CiOCj4FFi+YRPKvqqBV8WfosMIhH7q6jR5Do6lHgSAiKROf5GzOp gEc8wVmR8oXoFHDdm/e+GhaIYBJrOlW3goHSI2lfUEB1BzCdYsmxiU4sS8QLjgKW qlK7QWtaEkH2Eb5H6xCjT7PzFR6F9VMpbwM+vpemihOWtUsm1SgBtbSEwpp1L1nI HoVnxdI2rVltpvccF9c9CgMzT+BMBJ5zWlQensl6dbCgMzvZBN8aTGgy2mKMv6sp ikJ2DqdSWkvT0gq2TycUhSbs5e7o0ohin3QmHWj9z9/pvngJ+r0= =HK0g -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmGNJuMACgkQd8DHXntl CAi+dg//Y7k3rvjsopUnLFDt8vIQ+4FhyLKQ2JlVb4LEaiJW5ytoBipMqAGsyV9v /floz2KU3zFV2Rg5CzBKEaVOFb/R7AHpL8YCvLPUZp0mn44w8L1toXxMCil/5maP jA1tCcZlNWOD1MUeI3hWXGm6BC7cznUTIJPKI7nlKoaBvDorMUn1q4QdHDS60wtQ caBSd1lFZLE1H0esIFE4MKBvTsKWgMsiqYdiW0jdKIcxsoGQYvy/CE+rE2qBXpuz 6wDK+61efs8NA3mzZj+BCHcxanEgUIowAOPXYDsqqd05TOjig7hJZqUaDVBcgFgr It2WolgTSJDX1xeI2my5TVkREzFxs2vLa5tvcDPSLJ/7hplFD/c+T2wEYXOIs9gE e/pFECcilorCVYPWY26iBXicvIBKVEXUurM3zVlOTmpn2YGfnlQ9vgQt8O8882y+ aeoHL96tTSIWSb0ygAmui9VbJi0pkUDuyMiF/DA86J4q7TbGYP0eB56O/gnPmyzi YzGJTAUnng8KimTINtAn4R4RpM5vGPNx1c7XwSN5Nf+1a2sscYNBcjwcKZUz887Z hfs8JushUhFs7tpLm4gh4JaoDaAEHjSXJEc9i/VNROyHbiY7RQH7+CM5PGEd08VV kvvBZ/ObJVIWG8wWwYO9BkUE8GcdPyRh/W8U6v2NWbETeJCbPGw= =KQd6 -----END PGP SIGNATURE----- Merge tag 'v21.13' into debian/bullseye-backports freedombox Debian release 21.13 Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
commit
69b68e8d38
@ -1,5 +1,8 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Configuration helper for email server.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
@ -7,50 +10,37 @@ import os
|
||||
import sys
|
||||
|
||||
import plinth.log
|
||||
from plinth.modules.email_server import audit
|
||||
|
||||
EXIT_SYNTAX = 10
|
||||
EXIT_PERM = 20
|
||||
|
||||
# Set up logging
|
||||
plinth.log.pipe_to_syslog(to_stderr='tty')
|
||||
logger = logging.getLogger(os.path.basename(__file__))
|
||||
|
||||
|
||||
def reserved_for_root(fun):
|
||||
def wrapped(*args, **kwargs):
|
||||
if os.getuid() != 0:
|
||||
logger.critical('This action is reserved for root')
|
||||
sys.exit(EXIT_PERM)
|
||||
return fun(*args, **kwargs)
|
||||
return wrapped
|
||||
logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def main():
|
||||
if not sys.stdin.isatty():
|
||||
print('WARNING: Output will not be shown. Check syslog for logs',
|
||||
file=sys.stderr)
|
||||
"""Parse arguments."""
|
||||
plinth.log.action_init()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('-i', nargs='+', dest='ipc')
|
||||
parser.add_argument('module', help='Module to trigger action in')
|
||||
parser.add_argument('action', help='Action to trigger in module')
|
||||
parser.add_argument('arguments', help='String arguments for action',
|
||||
nargs='*')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Select the first non-empty dict item
|
||||
adict = vars(parser.parse_args())
|
||||
generator = (kv for kv in adict.items() if kv[1] is not None)
|
||||
subcommand, arguments = next(generator)
|
||||
|
||||
function = globals()['subcommand_' + subcommand]
|
||||
try:
|
||||
function(*arguments)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
_log_additional_info()
|
||||
_call(args.module, args.action, args.arguments)
|
||||
except Exception as exception:
|
||||
logger.exception(exception)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@reserved_for_root
|
||||
def subcommand_ipc(module_name, action_name, *args):
|
||||
import plinth.modules.email_server.audit as audit
|
||||
def _call(module_name, action_name, arguments):
|
||||
"""Import the module and run action as superuser."""
|
||||
if os.getuid() != 0:
|
||||
logger.critical('This action is reserved for root')
|
||||
sys.exit(EXIT_PERM)
|
||||
|
||||
# We only run actions defined in the audit module
|
||||
if module_name not in audit.__all__:
|
||||
@ -58,24 +48,18 @@ def subcommand_ipc(module_name, action_name, *args):
|
||||
sys.exit(EXIT_SYNTAX)
|
||||
|
||||
module = getattr(audit, module_name)
|
||||
function = getattr(module, 'action_' + action_name, None)
|
||||
if function is None:
|
||||
try:
|
||||
action = getattr(module, 'action_' + action_name)
|
||||
except AttributeError:
|
||||
logger.critical('Bad action: %s/%r', module_name, action_name)
|
||||
sys.exit(EXIT_SYNTAX)
|
||||
|
||||
function(*args)
|
||||
for argument in arguments:
|
||||
if not isinstance(argument, str):
|
||||
logger.critical('Bad argument: %s', argument)
|
||||
sys.exit(EXIT_SYNTAX)
|
||||
|
||||
|
||||
def _log_additional_info():
|
||||
import grp
|
||||
import pwd
|
||||
resu = ','.join(pwd.getpwuid(uid).pw_name for uid in os.getresuid())
|
||||
resg = ','.join(grp.getgrgid(gid).gr_name for gid in os.getresgid())
|
||||
pyver = sys.version.replace('\n', ' ')
|
||||
logger.error('--- Additional Information ---')
|
||||
logger.error('resuid=%s, resgid=%s', resu, resg)
|
||||
logger.error('argv=%r, cwd=%r', sys.argv, os.getcwd())
|
||||
logger.error('pyver=%s (%s)', pyver, os.uname().machine)
|
||||
action(*arguments)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
91
debian/changelog
vendored
91
debian/changelog
vendored
@ -1,3 +1,94 @@
|
||||
freedombox (21.13) unstable; urgency=medium
|
||||
|
||||
[ Burak Yavuz ]
|
||||
* Translated using Weblate (Turkish)
|
||||
|
||||
[ Andrij Mizyk ]
|
||||
* Translated using Weblate (Ukrainian)
|
||||
|
||||
[ Michael Breidenbach ]
|
||||
* Translated using Weblate (Swedish)
|
||||
* Translated using Weblate (Swedish)
|
||||
|
||||
[ Joseph Nuthalapati ]
|
||||
* utils: Fix ruamel.yaml deprecation warnings
|
||||
* components: Introduce new component - Packages
|
||||
* setup: Use packages from Packages component
|
||||
* components: Add docstrings & tutorial for Packages
|
||||
|
||||
[ Sunil Mohan Adapa ]
|
||||
* email_server: Refactor the home directory page
|
||||
* email_server: Add button for setting up home directory
|
||||
* email_server: Turn home view into a simple page rather than a tab
|
||||
* email_server: Add button for managing aliases
|
||||
* email_server: Remove aliases view from tabs list
|
||||
* email_server: Add heading for manage aliases page
|
||||
* email_server: Reduce the size of headings for aliases/homedir pages
|
||||
* email_server: aliases: Add method for checking of an alias is taken
|
||||
* email_server: aliases: Using Django forms instead of custom forms
|
||||
* email_server: aliases: Drop validation already done by form
|
||||
* email_server: aliases: Move sanitizing to form
|
||||
* email_server: aliases: Drop unnecessary sanitizing
|
||||
* email_server: aliases: Drop unused sanitizing method
|
||||
* email_server: aliases: Drop unused regex
|
||||
* email_server: yapf formatting
|
||||
* email_server: aliases: Drop hash DB and use sqlite3 directly
|
||||
* email_server: aliases: Minor refactoring
|
||||
* email_server: aliases: Minor refactoring to DB schema
|
||||
* email_server: aliases: Minor refactor to list view
|
||||
* email_server: aliases: Fix showing empty alias list message
|
||||
* email_server: aliases: Refactor for simpler organization
|
||||
* email_server: tls: Drop unimplemented TLS forms/view
|
||||
* email_server: rspamd: Turn spam management link to a button
|
||||
* email_server: domains: Add button for domain management form
|
||||
* email_server: Remove tabs from the interface
|
||||
* email_server: homedir: Fix styling to not show everything as header
|
||||
* email_server: Minor refactor of license statement in templates
|
||||
* email_server: domains: Use Django forms and views
|
||||
* email_server: domains: Add validation to form
|
||||
* email_server: action: Refactor for simplicity
|
||||
* email_server: yapf formatting
|
||||
* log, email_server: Don't use syslog instead of journald
|
||||
* email_server: action: Add argument type checking for extra safety
|
||||
* email_server: Don't use user IDs when performing lookups
|
||||
* email_server: Lookup LDAP local recipients via PAM
|
||||
* email_server: dovecot: Authenticate using PAM instead of LDAP
|
||||
* email_server: dovecot: Don't deliver mail to home directory
|
||||
* email_server: Setup /var/mail, drop home setup view
|
||||
* email_server: Use rollback journal for aliases sqlite DB
|
||||
* security: Properly handle sandbox analysis of timer units
|
||||
|
||||
[ Johannes Keyser ]
|
||||
* Translated using Weblate (German)
|
||||
|
||||
[ James Valleroy ]
|
||||
* tests: Use background fixture for each test
|
||||
* bepasty: Use BaseAppTests for functional tests
|
||||
* bind: Use BaseAppTests for functional tests
|
||||
* calibre: Use BaseAppTests for functional tests
|
||||
* deluge: Use BaseAppTests for functional tests
|
||||
* ejabberd: Use BaseAppTests for functional tests
|
||||
* gitweb: Use BaseAppTests for functional tests
|
||||
* ikiwiki: Use BaseAppTests for functional tests
|
||||
* mediawiki: Use BaseAppTests for functional tests
|
||||
* mldonkey: Use BaseAppTests for functional tests
|
||||
* openvpn: Use BaseAppTests for functional tests
|
||||
* pagekite: Use BaseAppTests for functional tests
|
||||
* radicale: Use BaseAppTests for functional tests
|
||||
* samba: Use BaseAppTests for functional tests
|
||||
* shadowsocks, syncthing: Use BaseAppTests for functional tests
|
||||
* transmission: Use BaseAppTests for functional tests
|
||||
* tahoe: Use BaseAppTests for functional tests
|
||||
* tor: Use BaseAppTests for functional tests
|
||||
* tests: functional: Add diagnostics delay parameter
|
||||
* avahi: Use systemd sandboxing
|
||||
* samba: Use systemd sandboxing for smbd/nmbd
|
||||
* debian: Add python3-openssl to autopkgtest depends
|
||||
* locale: Update translation strings
|
||||
* doc: Fetch latest manual
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Mon, 08 Nov 2021 21:34:27 -0500
|
||||
|
||||
freedombox (21.12~bpo11+1) bullseye-backports; urgency=medium
|
||||
|
||||
* Rebuild for bullseye-backports.
|
||||
|
||||
2
debian/tests/control
vendored
2
debian/tests/control
vendored
@ -14,4 +14,4 @@ Restrictions: needs-root
|
||||
# Run unit and integration tests on installed files.
|
||||
#
|
||||
Test-Command: PYTHONPATH='/usr/lib/python3/dist-packages/plinth/' py.test-3 -p no:cacheprovider --cov=plinth --cov-report=html:debci/htmlcov --cov-report=term
|
||||
Depends: git, python3-pytest, python3-pytest-cov, python3-pytest-django, @
|
||||
Depends: git, python3-openssl, python3-pytest, python3-pytest-cov, python3-pytest-django, @
|
||||
|
||||
@ -8,6 +8,7 @@ Components
|
||||
|
||||
info
|
||||
menu
|
||||
packages
|
||||
daemon
|
||||
firewall
|
||||
webserver
|
||||
|
||||
7
doc/dev/reference/components/packages.rst
Normal file
7
doc/dev/reference/components/packages.rst
Normal file
@ -0,0 +1,7 @@
|
||||
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
|
||||
Packages
|
||||
^^^^^^^^
|
||||
|
||||
.. autoclass:: plinth.package.Packages
|
||||
:members:
|
||||
@ -112,6 +112,36 @@ the daemon. The final argument is the list of ports that this daemon listens on.
|
||||
This information is used to check if the daemon is listening on the expected
|
||||
ports when the user requests diagnostic tests on the app.
|
||||
|
||||
Package management
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Transmission server is installed through a set of packages fetched from Debian
|
||||
package repositories. The packages required for this are passed on to a
|
||||
:class:`~plinth.package.Packages` component which takes care of installing,
|
||||
upgrading and uninstalling the Debian packages. An app might require one or more
|
||||
Debian packages to be installed.
|
||||
|
||||
.. code-block:: python3
|
||||
:caption: ``__init__.py``
|
||||
|
||||
from plinth.package import Packages
|
||||
|
||||
managed_packages = ['transmission-daemon']
|
||||
|
||||
class TransmissionApp(app_module.App):
|
||||
...
|
||||
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
packages = Packages('packages-transmission', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
The first argument uniquely identifies this instance of the `Packages`
|
||||
component. Choose an appropriate unique identifier if your app has multiple
|
||||
`Packages` components. The second argument is a list of Debian packages that
|
||||
this component is responsible for.
|
||||
|
||||
Managing web server configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@ -10,62 +10,20 @@
|
||||
|
||||
!FreedomBox includes the ability to backup and restore data, preferences, configuration and secrets from most of the applications. The Backups feature is built using Borg backup software. Borg is a deduplicating and compressing backup program. It is designed for efficient and secure backups. This backups feature can be used to selectively backup and restore data on an app-by-app basis. Backed up data can be stored on the !FreedomBox machine itself or on a remote server. Any remote server providing SSH access can be used as a backup storage repository for !FreedomBox backups. Data stored remotely may be encrypted and in such cases remote server cannot access your decrypted data.
|
||||
|
||||
=== Notes for Specific App Backups ===
|
||||
|
||||
=== Status of Backups Feature ===
|
||||
Unless otherwise noted here, backup of an app's data will include its configuration, secrets and other data.
|
||||
|
||||
|| '''App/Feature''' || '''Support in Version''' || '''Notes''' ||
|
||||
|| Avahi || - || no backup needed ||
|
||||
|| Backups || - || no backup needed ||
|
||||
|| bepasty || 20.14 || ||
|
||||
|| Bind || 0.41 || ||
|
||||
|| calibre || 20.15 || ||
|
||||
|| Cockpit || - || no backup needed ||
|
||||
|| Coturn || 20.8 || ||
|
||||
|| Datetime || 0.41 || ||
|
||||
|| Deluge || 0.41 || does not include downloaded/seeding files ||
|
||||
|| Diagnostics || - || no backup needed ||
|
||||
|| Dynamic DNS || 0.39 || ||
|
||||
|| ejabberd || 0.39 || includes all data and configuration ||
|
||||
|| Firewall || - || no backup needed ||
|
||||
|| Gitweb || 19.19 || ||
|
||||
|| I2P || 19.6 || ||
|
||||
|| ikiwiki || 0.39 || includes all wikis/blogs and their content ||
|
||||
|| infinoted || 0.39 || includes all data and keys ||
|
||||
|| JSXC || - || no backup needed ||
|
||||
|| Let's Encrypt || 0.42 || ||
|
||||
|| Matrix Synapse || 0.39 || includes media and uploads ||
|
||||
|| !MediaWiki || 0.39 || includes wiki pages and uploaded files ||
|
||||
|| Minetest || 0.39 || ||
|
||||
|| MiniDLNA || 19.23 || ||
|
||||
|| MLDonkey || 19.0 || ||
|
||||
|| Monkeysphere || 0.42 || ||
|
||||
|| Mumble || 0.40 || ||
|
||||
|| Names || - || no backup needed ||
|
||||
|| Networks || No || No plans currently to implement backup ||
|
||||
|| OpenVPN || 0.48 || includes all user and server keys ||
|
||||
|| Pagekite || 0.40 || ||
|
||||
|| Power || - || no backup needed ||
|
||||
|| Privoxy || - || no backup needed ||
|
||||
|| Quassel || 0.40 || includes users and logs ||
|
||||
|| Radicale || 0.39 || includes calendar and cards data for all users ||
|
||||
|| Roundcube || - || no backup needed ||
|
||||
|| Samba || 19.22 || does not include the data in the shared folders ||
|
||||
|| SearX || - || no backup needed ||
|
||||
|| Secure Shell (SSH) Server || 0.41 || includes host keys ||
|
||||
|| Security || 0.41 || ||
|
||||
|| Shadowsocks || 0.40 || only secrets ||
|
||||
|| Sharing || 0.40 || does not include the data in the shared folders ||
|
||||
|| Snapshot || 0.41 || only configuration, does not include snapshot data ||
|
||||
|| Storage || - || no backup needed ||
|
||||
|| Syncthing || 0.48 || does not include data in the shared folders ||
|
||||
|| Tahoe-LAFS || 0.42 || includes all data and configuration ||
|
||||
|| Tiny Tiny RSS || 19.2 || includes database containing feeds, stories, etc. ||
|
||||
|| Tor || 0.42 || includes configuration and secrets such as onion service keys ||
|
||||
|| Transmission || 0.40 || does not include downloaded/seeding files ||
|
||||
|| Upgrades || 0.42 || ||
|
||||
|| Users || No || No plans currently to implement backup ||
|
||||
|| Wordpress || 21.8 || ||
|
||||
|| Zoph || 21.3 || ||
|
||||
|| '''App/Feature''' || '''Notes''' ||
|
||||
|| [[FreedomBox/Manual/Deluge|Deluge]] || Does not include downloaded/seeding files ||
|
||||
|| [[FreedomBox/Manual/MiniDLNA|MiniDLNA]] || Does not include the data in the shared folders ||
|
||||
|| [[FreedomBox/Manual/Networks|Networks]] || No plans currently to implement backup ||
|
||||
|| [[FreedomBox/Manual/Samba|Samba]] || Does not include the data in the shared folders ||
|
||||
|| [[FreedomBox/Manual/Sharing|Sharing]] || Does not include the data in the shared folders ||
|
||||
|| Snapshot || Only configuration, does not include snapshot data ||
|
||||
|| [[FreedomBox/Manual/Syncthing|Syncthing]] || Does not include data in the shared folders ||
|
||||
|| [[FreedomBox/Manual/Transmission|Transmission]] || Does not include downloaded/seeding files ||
|
||||
|| [[FreedomBox/Manual/Users|Users]] || Backup of user accounts is [[https://salsa.debian.org/freedombox-team/freedombox/-/issues/2051|planned]] ||
|
||||
|
||||
=== How to install and use Backups ===
|
||||
|
||||
|
||||
@ -21,9 +21,6 @@ Only users who are members of the ''calibre'' group have access to the libraries
|
||||
|
||||
You might be familiar with the e-book reader shipped with the calibre application on your desktop. The server version of calibre that's installed on your !FreedomBox has a web-based e-book reader with similar look and feel. This allows you to read your e-books from any device with a web browser.
|
||||
|
||||
'''Note on calibre versions:'''
|
||||
Please note that depending on the Debian version your !FreedomBox is running, you might be running a different major version of calibre. Debian stable (Buster) has calibre 3.x, testing and unstable have calibre 5.x. This means that some of the experimental features like the web-based e-book reader might not work very well if you're on Debian stable. This situation will improve will the Debian 11 (Bullseye) release next year. !FreedomBox doesn't ship backported packages of calibre.
|
||||
|
||||
=== Managing Libraries ===
|
||||
|
||||
After installation of calibre, a default library called "Library" will be made available. The !FreedomBox administrator can add or delete any of the libraries including the default one from the app settings in !FreedomBox web interface.
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
== MLDonkey (Peer-to-peer File Sharing) ==
|
||||
||<tablestyle="float: right;"> {{attachment:MLDonkey-icon_en_V01.png|MLDonkey icon}} ||
|
||||
|
||||
'''Available since:''' version 0.48.0
|
||||
'''Availability:''' MLDonkey is not available in either Bullseye (stable) or Bookworm (testing).
|
||||
|
||||
=== What is MLDonkey? ===
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
* !FreedomBox Service comes installed with all !FreedomBox images. You can [[FreedomBox/Download|download]] !FreedomBox images and run on any of the supported hardware. Then, to access !FreedomBox interface see [[FreedomBox/Manual/QuickStart|quick start]] instructions.
|
||||
|
||||
* If you are on a Debian box, you may install !FreedomBox Service from Debian package archive. Currently, only Buster (stable), Bullseye (testing), and Sid (unstable) are supported. To install !FreedomBox Service run:
|
||||
* If you are on a Debian box, you may install !FreedomBox Service from Debian package archive. Currently, only bullseye (stable), bookworm (testing), and sid (unstable) are supported. To install !FreedomBox Service run:
|
||||
|
||||
{{{
|
||||
$ sudo apt-get install freedombox
|
||||
|
||||
@ -139,46 +139,6 @@ file = /etc/radicale/rights
|
||||
==== Importing files ====
|
||||
If you are using a contacts file exported from another service or application, it should be copied to: /var/lib/radicale/collections/''user''/''contact file name''.vcf.
|
||||
|
||||
=== Migrating from Radicale Version 1.x to Version 2.x ===
|
||||
|
||||
During the month of February 2019, radicale in Debian testing was upgraded from version 1.x to version 2.x. Version 2.x is a better version but is incompatible with data and configuration used with 1.x. Automatic upgrade mechanism in !FreedomBox, handled by unattended-upgrades does not automatically upgrade radicale to version 2.x due to changes in configuration files. However, !FreedomBox version 19.1, which is available on February 23rd, 2019 in testing will perform data and configuration migration to radicale version 2.x. Typical users require no action, this will happen automatically.
|
||||
|
||||
If for some reason, you need to manually run `apt dist-upgrade` on your machine, then radicale will be upgraded to 2.x and then !FreedomBox will not be able to perform its upgrade (due to upstream project deciding to remove migration tools in radicale 2.x version). To avoid this situation, the following process is recommended if you wish to perform an upgrade.
|
||||
|
||||
{{{
|
||||
sudo su -
|
||||
apt hold radicale
|
||||
apt dist-upgrade
|
||||
apt unhold radicale
|
||||
}}}
|
||||
|
||||
However, if you already happen to perform an upgrade to radicale 2.x without help from !FreedomBox, you need to perform data and configuration migration yourself. Follow this procedure:
|
||||
|
||||
{{{
|
||||
sudo su -
|
||||
tar -cvzf /root/radicale_backup.tgz /var/lib/radicale/ /etc/radicale/ /etc/default/radicale
|
||||
apt install -y python-radicale
|
||||
python -m radicale --export-storage=/root/radicale-migration
|
||||
cp -dpR /root/radicale-migration/collection-root /var/lib/radicale/collections/collection-root/
|
||||
(remove this directory if it already exists. Or perhaps merge the contents.)
|
||||
chown -R radicale:radicale /var/lib/radicale/collections/collection-root/
|
||||
apt remove -y python-radicale
|
||||
if [ -f /etc/radicale/config.dpkg-dist ] ; then cp /etc/radicale/config.dpkg-dist /etc/radicale/config ; fi
|
||||
if [ -f /etc/default/radicale.dpkg-dist ] ; then cp /etc/default/radicale.dpkg-dist /etc/default/radicale ; fi
|
||||
(After FreedomBox 19.1 is available, goto FreedomBox web interface and set your preference for calendar sharing again, if it is not the default option, as it will have been lost.)
|
||||
}}}
|
||||
|
||||
Notes:
|
||||
* python-radicale is an old package from radicale 1.x version that is still available in testing. This is a hack to use the `--export-storage` feature that is responsible for data migration. This feature is not available in radicale 2.x unfortunately.
|
||||
* Files ending with .dpkg-dist will exist only if you have chosen 'Keep your currently-installed version' when prompted for configuration file override during radicale 2.x upgrade. The above process will overwrite the old configuration with new fresh configuration. No changes are necessary to the two configuration files unless you have changed the setting for sharing calendars.
|
||||
* Note that during the migration, your data is safe in /var/lib/radicale/collections directory. New data will be created and used in /var/lib/radicale/collections/collections-root/ directory.
|
||||
* The tar command takes a backup your configuration and data in /root/radicale_backup.tgz in case you do something goes wrong and you want to undo the changes.
|
||||
|
||||
=== Troubleshooting ===
|
||||
|
||||
1. If you are using !FreedomBox Pioneer Edition or installing !FreedomBox on Debian Buster, then radicale may not be usable immediately after installation. This is due to a bug which has been fixed later. To overcome the problem, upgrade !FreedomBox by clicking on 'Manual Update' from 'Updates' app. Otherwise, simply wait a day or two and let !FreedomBox upgrade itself. After that install radicale. If radicale is already installed, disable and re-enable it after the update is completed. This will fix the problem and get radicale working properly.
|
||||
|
||||
|
||||
=== External links ===
|
||||
|
||||
* Website: https://radicale.org/3.0.html
|
||||
|
||||
@ -10,6 +10,27 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
|
||||
|
||||
The following are the release notes for each !FreedomBox version.
|
||||
|
||||
== FreedomBox 21.13 (2021-11-08) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* avahi, samba: Use systemd sandboxing
|
||||
* components: Introduce new component - Packages
|
||||
* security: Properly handle sandbox analysis of timer units
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* email_server (not enabled yet):
|
||||
* Add buttons for managing aliases, domains, spam
|
||||
* Authenticate using PAM instead of LDAP
|
||||
* Delivery mail to /var/mail instead of home directory
|
||||
* Don't use user IDs when performing lookups
|
||||
* Drop hash DB and use sqlite3 directly
|
||||
* Use Django forms and views
|
||||
* locale: Update translations for German, Swedish, Turkish, Ukrainian
|
||||
* tests: Use !BaseAppTests for functional tests of most apps
|
||||
* utils: Fix ruamel.yaml deprecation warnings
|
||||
|
||||
== FreedomBox 21.12 (2021-10-25) ==
|
||||
|
||||
* locale: Update translations for Bulgarian, Czech, French, German, Turkish, Ukrainian
|
||||
|
||||
@ -19,10 +19,7 @@
|
||||
|
||||
=== Installation ===
|
||||
|
||||
On [[DebianBuster]], wireguard is available from [[Backports]]. If your sources list contains the backports stanza, you can install wireguard from the Apps section of !FreedomBox web interface.
|
||||
{{{#!wiki caution
|
||||
WireGuard cannot be installed in !FreedomBox on buster-backports yet, because a newer version of NetworkManager is required by the !FreedomBox service to complete the setup.
|
||||
}}}
|
||||
You can install wireguard from the Apps section of the !FreedomBox web interface.
|
||||
|
||||
=== Configuration - Debian Peers ===
|
||||
|
||||
|
||||
@ -11,61 +11,19 @@
|
||||
!FreedomBox incluye la posibilidad de copiar y restaurar datos, preferencias, configuración y secretos de la mayoría de las aplicaciones. La funcionalidad de ''Backups'' se resuelve con el software de ''backup'' ''Borg''. ''Borg'' es un programa de ''backup'' con deduplicación y compresión. Está diseñado para hacer ''backups'' eficientes y seguros. Esta funcionalidad de ''backups'' se puede emplear para respaldar y recuperar datos aplicación por aplicación. Las copias de respaldado se pueden almacenar en la propia máquina !FreedomBox o en un servidor remoto. Cualquier servidor remoto con acceso por SSH se puede emplear como almacenamiento para los ''backups'' de la !FreedomBox. Las copias remotas se pueden cifrar para que el servidor remoto no pueda leer los datos que alberga.
|
||||
|
||||
|
||||
=== Estados de la Funcionalidad de Backups ===
|
||||
=== Notas para respaldos específicos ===
|
||||
Salvo que aquí se diga lo contrario, el respaldo de los datos de una aplicación incluirá su configuración, secretos y otros datos.
|
||||
|
||||
|| '''App/Funcionalidad''' || '''Soporte en Versión''' || '''Notas''' ||
|
||||
|| Avahi || - || no precisa ''backup'' ||
|
||||
|| Backups || - || no precisa ''backup'' ||
|
||||
|| bepasty || 20.14 || ||
|
||||
|| Bind || 0.41 || ||
|
||||
|| calibre || 20.15 || ||
|
||||
|| Cockpit || - || no precisa ''backup'' ||
|
||||
|| Coturn || 20.8 || ||
|
||||
|| Datetime || 0.41 || ||
|
||||
|| Deluge || 0.41 || '''no''' incluye archivos descargados ni semillas ||
|
||||
|| Diagnostics || - || no precisa ''backup'' ||
|
||||
|| Dynamic DNS || 0.39 || ||
|
||||
|| ejabberd || 0.39 || incluye todos los datos y configuración ||
|
||||
|| Firewall || - || no precisa ''backup'' ||
|
||||
|| Gitweb || 19.19 || ||
|
||||
|| I2P || 19.6 || ||
|
||||
|| ikiwiki || 0.39 || incluye todos los wikis/blogs y sus contenidos ||
|
||||
|| infinoted || 0.39 || incluye todos los datos y claves ||
|
||||
|| JSXC || - || no precisa ''backup'' ||
|
||||
|| Let's Encrypt || 0.42 || ||
|
||||
|| Matrix Synapse || 0.39 || incluye media y cargas ||
|
||||
|| !MediaWiki || 0.39 || incluye páginas de wiki y archivos adjuntos ||
|
||||
|| Minetest || 0.39 || ||
|
||||
|| MiniDLNA || 19.23 || ||
|
||||
|| MLDonkey || 19.0 || ||
|
||||
|| Monkeysphere || 0.42 || ||
|
||||
|| Mumble || 0.40 || ||
|
||||
|| Names || - || no precisa ''backup'' ||
|
||||
|| Networks || No || sin planes para implementar ''backup'', de momento ||
|
||||
|| OpenVPN || 0.48 || incluye a todos los usuarios y claves de servidor ||
|
||||
|| Pagekite || 0.40 || ||
|
||||
|| Power || - || no precisa ''backup'' ||
|
||||
|| Privoxy || - || no precisa ''backup'' ||
|
||||
|| Quassel || 0.40 || incluye usuarios y registros de ejeución (''logs'') ||
|
||||
|| Radicale || 0.39 || incluye calendario y datos de tarjetas de todos los usuarios ||
|
||||
|| Roundcube || - || no precisa ''backup'' ||
|
||||
|| Samba || 19.22 || ''no''' incluye datos de las carpetas compartidas ||
|
||||
|| SearX || - || no precisa ''backup'' ||
|
||||
|| Secure Shell (SSH) Server || 0.41 || incluye las claves del servidor ||
|
||||
|| Security || 0.41 || ||
|
||||
|| Shadowsocks || 0.40 || solo secretos ||
|
||||
|| Sharing || 0.40 || '''no''' incluye datos de las carpetas compartidas ||
|
||||
|| Snapshot || 0.41 || solo configuración, '''no''' incluye datos de capturas (snapshots) ||
|
||||
|| Storage || - || no precisa ''backup'' ||
|
||||
|| Syncthing || 0.48 || '''no''' incluye datos de las carpetas compartidas ||
|
||||
|| Tahoe-LAFS || 0.42 || incluye todos los datos y configuración ||
|
||||
|| Tiny Tiny RSS || 19.2 || incluye base de datos con ''feeds'', historias, etc. ||
|
||||
|| Tor || 0.42 || includes configuración y secretos como las claves de servicios Tor Onion ||
|
||||
|| Transmission || 0.40 || '''no''' incluye archivos descargados ni semillas ||
|
||||
|| Upgrades || 0.42 || ||
|
||||
|| Users || No || sin planes para implementar ''backup'', de momento ||
|
||||
|| Wordpress || 21.8 || ||
|
||||
|| Zoph || 21.3 || ||
|
||||
|| '''App/Funcionalidad''' || '''Notas''' ||
|
||||
|| [[es/FreedomBox/Manual/Deluge|Deluge]] || No incluye archivos descargados/sembrados ||
|
||||
|| [[es/FreedomBox/Manual/MiniDLNA|MinDLNA]] || No incluye los datos en carpetas compartidas ||
|
||||
|| [[es/FreedomBox/Manual/Networks|Redes]] || Actualmente no hay planes para implementar respaldos ||
|
||||
|| [[es/FreedomBox/Manual/Samba|Samba]] || No incluye los datos en carpetas compartidas ||
|
||||
|| [[es/FreedomBox/Manual/Sharing|Sharing]] || No incluye los datos en carpetas compartidas ||
|
||||
|| Instantáneas || Solo configuración, no incluye datos de instantánea ||
|
||||
|| [[es/FreedomBox/Manual/Syncthing|Syncthing]] || No incluye los datos en carpetas compartidas ||
|
||||
|| [[es/FreedomBox/Manual/Transmission|Transmission]] || No incluye archivos descargados/sembrados ||
|
||||
|| [[es/FreedomBox/Manual/Users|Usuarios]] || El respaldo de cuentas de usuario está [[https://salsa.debian.org/freedombox-team/freedombox/-/issues/2051|planificado]] ||
|
||||
|
||||
=== Cómo instalar y usar Backups ===
|
||||
|
||||
|
||||
@ -19,9 +19,6 @@ Sólo los usuarios del grupo ''calibre'' tienen acceso a las bibliotecas. Puedes
|
||||
|
||||
Quizá ya estés familiarizado con el lector de libros para escritorio que viene con Calibre. El servidor Calibre que se instala en tu !FreedomBox viene con un lector web con aspecto similar, lo que te permite leer tus libros desde cualquier dispositivo con navegador web.
|
||||
|
||||
'''Nota acerca de las versiones de Calibre:'''
|
||||
Dependiendo de la versión de Debian sobre la que se ejecuta tu !FreedomBox is running, tendrás una versión diferente de Calibre. Debian estable (Buster) lleva Calibre 3.x, ''en pruebas'' e ''inestable'' llevan Calibre 5.x. Esto implica que algunas funcionalidades experimentales como el lector web podrían no funcionar muy bien si estás en ''estable''. Esta situación mejorará el próximo año con la publicación de Debian 11 (Bullseye). Las actualizaciones frecuentes no abarcan a Calibre.
|
||||
|
||||
=== Administrar Bibliotecas ===
|
||||
|
||||
Tras la instalación, estará disponible una biblioteca inicial "Library". El administrador de !FreedomBox puede añadir o eliminar cualquier biblioteca incluyendo la inicial desde los ajustes de la app en el interfaz web de !FreedomBox.
|
||||
|
||||
@ -10,8 +10,7 @@
|
||||
== MLDonkey (Compartir archivos entre pares) ==
|
||||
||<tablestyle="float: right;"> {{attachment:FreedomBox/Manual/MLDonkey/MLDonkey-icon_en_V01.png|icono de MLDonkey}} ||
|
||||
|
||||
'''Disponible desde:''' versión 0.48.0
|
||||
|
||||
'''Disponible desde:''' versión 0.48.0, pero ya no está disponible ni en Bullseye (estable) ni en Bookworm (en pruebas).
|
||||
=== ¿Qué es MLDonkey? ===
|
||||
|
||||
''MLDonkey'' es una aplicación libre y multiprotocolo para compartir archivos entre pares (P2P) que ejecuta un servidor ''back-end'' sobre muchas plataformas. Se puede controlar mediante algún interfaz ''front-end'', ya sea web, telnet o cualquier otro de entre una docena de programas cliente nativos.
|
||||
|
||||
@ -6,9 +6,9 @@ El servicio !FreedomBox es [[https://www.gnu.org/philosophy/|Software Libre]] ba
|
||||
|
||||
=== Uso ===
|
||||
|
||||
* El servicio !FreedomBox viene instalado en todas las imágenes de !FreedomBox. Puedes [[FreedomBox/Download|descargar]] imágenes de !FreedomBox y ejecutarlas en cualquier hardware soportado. El servicio !FreedomBox (Plinth) estará accesible visitando la URL [[http://freedombox/plinth]] o [[https://freedombox.local/plinth]].
|
||||
* El servicio !FreedomBox viene instalado en todas las imágenes de !FreedomBox. Puedes [[FreedomBox/Download|descargar]] imágenes de !FreedomBox y ejecutarlas en cualquier hardware soportado. Para acceder al interfaz de !FreedomBox consulta [[es/FreedomBox/Manual/QuickStart|Guía rápida]].
|
||||
|
||||
* Si estás en una máquina Debian puedes instalar el servicio !FreedomBox desde el archivo de paquetes de Debian. Actualmente solo se soportan Buster (estable), Bullseye (en pruebas) y Sid (inestable). Para instalar el servicio !FreedomBox ejecuta:
|
||||
* Si estás en una máquina Debian puedes instalar el servicio !FreedomBox desde el archivo de paquetes de Debian. Actualmente solo se soportan Bullseye (estable), Bookworm (en pruebas) y Sid (inestable). Para instalar el servicio !FreedomBox ejecuta:
|
||||
|
||||
{{{
|
||||
$ sudo apt-get install freedombox
|
||||
|
||||
@ -137,45 +137,6 @@ file = /etc/radicale/rights
|
||||
==== Importar archivos ====
|
||||
Si estás usando un archivo de contactos exportado desde otro servicio o aplicación hay que copiarlo a: /var/lib/radicale/collections/<usuario>/<nombre_del_archivo_de_contactos>'.vcf.
|
||||
|
||||
=== Migrar desde Radicale versión 1.x a versión 2.x ===
|
||||
|
||||
En Febrero de 2019 se actualizó Radicale en las versiones "en pruebas" (testing) de Debian desde la versión 1.x a la 2.x. La versión 2.x es mejor pero incompatible con los datos y la configuración empleados en la 1.x. El mecanismo automático de actualización de !FreedomBox que emplean las actualizaciones desatendidas no actualiza automaticamente la version 2.x de Radicale debido a cambios en los archivos de configuración. No obstante la version 19.1 de !FreedomBox, disponible en en las versiones "en pruebas" (testing) desde el 23 de Febrero de 2019, realizará la migración de los datos y la configuración a la versión 2.x de Radicale. No se requiere ninguna acción por parte de los usuarios típicos. Ocurrirá automáticamente.
|
||||
|
||||
Si por algún motivo necesitas ejecutar a mano `apt dist-upgrade` en tu máquina Radicale se actualizará a 2.x y entonces tu !FreedomBox no podrá ejecutar esta actualización (ya que el proyecto de origen decidió eliminar las herramientas de migración de la versión 2.x de Radicale). Para evitar esta situación se recomienda el siguiente procedimiento para actualizar.
|
||||
|
||||
{{{
|
||||
sudo su -
|
||||
apt hold radicale
|
||||
apt dist-upgrade
|
||||
apt unhold radicale
|
||||
}}}
|
||||
|
||||
En cualquier caso, si ya has actualizado a Radicale 2.x sin ayuda de !FreedomBox necesitas realizar la migración de los datos y la configuración por tí mismo. Sigue este procedimiento:
|
||||
|
||||
{{{
|
||||
sudo su -
|
||||
tar -cvzf /root/radicale_backup.tgz /var/lib/radicale/ /etc/radicale/ /etc/default/radicale
|
||||
apt install -y python-radicale
|
||||
python -m radicale --export-storage=/root/radicale-migration
|
||||
cp -dpR /root/radicale-migration/collection-root /var/lib/radicale/collections/collection-root/
|
||||
(elimina este directorio si ya existe. O mezcla los contenidos.)
|
||||
chown -R radicale:radicale /var/lib/radicale/collections/collection-root/
|
||||
apt remove -y python-radicale
|
||||
if [ -f /etc/radicale/config.dpkg-dist ] ; then cp /etc/radicale/config.dpkg-dist /etc/radicale/config ; fi
|
||||
if [ -f /etc/default/radicale.dpkg-dist ] ; then cp /etc/default/radicale.dpkg-dist /etc/default/radicale ; fi
|
||||
(Cuando FreedomBox 19.1 está disponble ve al interfaz web de FreedomBox y vuelve a configurar tu preferencia de compartición de calendario si no se muestra bien porque se habrá perdido durante la operación.)
|
||||
}}}
|
||||
|
||||
Notas:
|
||||
* `python-radicale` es un paquete antigüo de la versión 1.x de Radicale que sigue disponible en las versiones "en pruebas" (testing) de Debian. Esto es un ''hack'' alternativo para emplear la funcionalidad `--export-storage` que es responsable de la migración de datos. Por desgracia esta funcionalidad ya no está disponible en Radicale 2.x.
|
||||
* Los ficheros que acaban en `.dpkg-dist` solo existirán si has elegido "Conservar tu versión actualmente instalada" cuando se te preguntó durante la actualización a Radicale 2.x. El procedimiento anterior sobrescribirá la configuración antigüa con una nueva. No se necesitan cambios a los 2 ficheros de configuración salvo que hayas cambiado la preferencia de compartición de calendario.
|
||||
* Nota: Durante la migración tus datos permanecen a salvo en el directorio `/var/lib/radicale/collections`. Los datos nuevos se crearán y usarán en el directorio `/var/lib/radicale/collections/collections-root/`.
|
||||
* El comando `tar` hace una copia de seguridad de tu configuración y tus datos en `/root/radicale_backup.tgz` por si haces o algo va mal y quieres deshacer los cambios.
|
||||
|
||||
=== Resolución de Problemas ===
|
||||
|
||||
1. Si estás usando !FreedomBox Pioneer Edition o instalando !FreedomBox sobre Debian Buster Radicale podría no estar operativo inmediatamente después de la instalación. Esto se debe a un defecto ya corregido posteriormente. Para superar el problema actualiza !FreedomBox haciendo clic en 'Actualización Manual' desde la app 'Actualizaciones'. Otra opción es simplemente esperar un par de días y dejar que !FreedomBox se actualice solo. Después instala Radicale. Si Radicale ya está instalado deshabilitalo y rehabilitalo después de que se complete la actualización. Esto arreglará el problema y dejará a Radicale trabajando correctamente.
|
||||
|
||||
|
||||
=== Enlaces externos ===
|
||||
|
||||
|
||||
@ -10,6 +10,27 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
|
||||
|
||||
The following are the release notes for each !FreedomBox version.
|
||||
|
||||
== FreedomBox 21.13 (2021-11-08) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* avahi, samba: Use systemd sandboxing
|
||||
* components: Introduce new component - Packages
|
||||
* security: Properly handle sandbox analysis of timer units
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* email_server (not enabled yet):
|
||||
* Add buttons for managing aliases, domains, spam
|
||||
* Authenticate using PAM instead of LDAP
|
||||
* Delivery mail to /var/mail instead of home directory
|
||||
* Don't use user IDs when performing lookups
|
||||
* Drop hash DB and use sqlite3 directly
|
||||
* Use Django forms and views
|
||||
* locale: Update translations for German, Swedish, Turkish, Ukrainian
|
||||
* tests: Use !BaseAppTests for functional tests of most apps
|
||||
* utils: Fix ruamel.yaml deprecation warnings
|
||||
|
||||
== FreedomBox 21.12 (2021-10-25) ==
|
||||
|
||||
* locale: Update translations for Bulgarian, Czech, French, German, Turkish, Ukrainian
|
||||
|
||||
@ -16,10 +16,7 @@
|
||||
|
||||
=== Instalación ===
|
||||
|
||||
En está disponible para [[DebianBuster]] en [[Backports]]. Si tu lista de fuentes incluye a backports, Puedes instalar wireguard desde la sección Apps del interfaz web de !FreedomBox.
|
||||
{{{#!wiki caution
|
||||
WireGuard no se puede instalar aún en !FreedomBox con buster-backports porque el servicio !FreedomBox necesita una nueva versión de NetworkManager para ponerla en funcionamiento.
|
||||
}}}
|
||||
Puedes instalar !WireGuard desde la sección ''Apps'' de la interfaz de !FreedomBox.
|
||||
|
||||
=== Configuración - Debian Peers ===
|
||||
|
||||
|
||||
@ -3,4 +3,4 @@
|
||||
Package init file.
|
||||
"""
|
||||
|
||||
__version__ = '21.12'
|
||||
__version__ = '21.13'
|
||||
|
||||
@ -109,6 +109,9 @@ def main():
|
||||
if arguments.list_dependencies is not False:
|
||||
log.default_level = 'ERROR'
|
||||
web_framework.init(read_only=True)
|
||||
module_loader.include_urls()
|
||||
menu.init()
|
||||
module_loader.load_modules()
|
||||
list_dependencies(arguments.list_dependencies)
|
||||
|
||||
log.init()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,12 +5,9 @@ Setup logging for the application.
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import logging.handlers
|
||||
import sys
|
||||
import logging.config
|
||||
import warnings
|
||||
|
||||
import cherrypy
|
||||
|
||||
from . import cfg
|
||||
|
||||
default_level = None
|
||||
@ -63,13 +60,8 @@ class ColoredFormatter(logging.Formatter):
|
||||
return super().format(record)
|
||||
|
||||
|
||||
def init():
|
||||
"""Setup the logging framework."""
|
||||
# Remove default handlers and let the log message propagate to root logger.
|
||||
for cherrypy_logger in [cherrypy.log.error_log, cherrypy.log.access_log]:
|
||||
for handler in list(cherrypy_logger.handlers):
|
||||
cherrypy_logger.removeHandler(handler)
|
||||
|
||||
def _capture_warnings():
|
||||
"""Capture all warnings include deprecation warnings."""
|
||||
# Capture all Python warnings such as deprecation warnings
|
||||
logging.captureWarnings(True)
|
||||
|
||||
@ -80,6 +72,25 @@ def init():
|
||||
warnings.filterwarnings('default', '', ImportWarning)
|
||||
|
||||
|
||||
def action_init():
|
||||
"""Initialize logging for action scripts."""
|
||||
_capture_warnings()
|
||||
|
||||
logging.config.dictConfig(get_configuration())
|
||||
|
||||
|
||||
def init():
|
||||
"""Setup the logging framework."""
|
||||
import cherrypy
|
||||
|
||||
# Remove default handlers and let the log message propagate to root logger.
|
||||
for cherrypy_logger in [cherrypy.log.error_log, cherrypy.log.access_log]:
|
||||
for handler in list(cherrypy_logger.handlers):
|
||||
cherrypy_logger.removeHandler(handler)
|
||||
|
||||
_capture_warnings()
|
||||
|
||||
|
||||
def setup_cherrypy_static_directory(app):
|
||||
"""Hush output from cherrypy static file request logging.
|
||||
|
||||
@ -129,24 +140,3 @@ def get_configuration():
|
||||
configuration['root']['handlers'].append('journal')
|
||||
|
||||
return configuration
|
||||
|
||||
|
||||
def pipe_to_syslog(level=logging.INFO, to_stderr=True):
|
||||
"""Make the root logger write to syslog and stderr. Useful in actions"""
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(level)
|
||||
|
||||
fmt = '/freedombox/%(name)s[%(process)d]: %(levelname)s: %(message)s'
|
||||
formatter = logging.Formatter(fmt=fmt)
|
||||
|
||||
# Using syslog in Python: https://stackoverflow.com/q/3968669
|
||||
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
|
||||
syslog_handler.setFormatter(formatter)
|
||||
logger.addHandler(syslog_handler)
|
||||
|
||||
if to_stderr == 'tty' and sys.stdin.isatty():
|
||||
to_stderr = True
|
||||
if to_stderr is True:
|
||||
stderr_handler = logging.StreamHandler()
|
||||
stderr_handler.setFormatter(formatter)
|
||||
logger.addHandler(stderr_handler)
|
||||
|
||||
@ -12,6 +12,7 @@ from plinth import cfg
|
||||
from plinth.daemon import Daemon
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.letsencrypt.components import LetsEncrypt
|
||||
from plinth.package import Packages
|
||||
from plinth.utils import format_lazy, is_valid_user_name
|
||||
|
||||
version = 9
|
||||
@ -41,6 +42,9 @@ class ApacheApp(app_module.App):
|
||||
name=_('Apache HTTP Server'))
|
||||
self.add(info)
|
||||
|
||||
packages = Packages('packages-apache', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
web_server_ports = Firewall('firewall-web', _('Web Server'),
|
||||
ports=['http', 'https'], is_external=True)
|
||||
self.add(web_server_ports)
|
||||
|
||||
@ -13,6 +13,7 @@ from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.config import get_hostname
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.names.components import DomainType
|
||||
from plinth.package import Packages
|
||||
from plinth.signals import domain_added, domain_removed, post_hostname_change
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
@ -65,6 +66,9 @@ class AvahiApp(app_module.App):
|
||||
'avahi:index', parent_url_name='system')
|
||||
self.add(menu_item)
|
||||
|
||||
packages = Packages('packages-avahi', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
domain_type = DomainType('domain-type-local',
|
||||
_('Local Network Domain'), 'config:index',
|
||||
can_have_certificate=False)
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
[Service]
|
||||
LockPersonality=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
PrivateMounts=yes
|
||||
PrivateTmp=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=full
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
RestrictRealtime=yes
|
||||
SystemCallArchitectures=native
|
||||
@ -17,6 +17,7 @@ from django.utils.translation import gettext_noop
|
||||
from plinth import actions
|
||||
from plinth import app as app_module
|
||||
from plinth import cfg, glib, menu
|
||||
from plinth.package import Packages
|
||||
|
||||
from . import api
|
||||
|
||||
@ -49,6 +50,7 @@ class BackupsApp(app_module.App):
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(
|
||||
app_id=self.app_id, version=version, depends=depends,
|
||||
name=_('Backups'), icon='fa-files-o', description=_description,
|
||||
@ -60,6 +62,9 @@ class BackupsApp(app_module.App):
|
||||
'backups:index', parent_url_name='system')
|
||||
self.add(menu_item)
|
||||
|
||||
packages = Packages('packages-backups', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
# Check every hour (every 3 minutes in debug mode) to perform scheduled
|
||||
# backups.
|
||||
interval = 180 if cfg.develop else 3600
|
||||
|
||||
@ -13,6 +13,7 @@ from plinth import frontpage, menu
|
||||
from plinth.modules.apache.components import Uwsgi, Webserver
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.package import Packages
|
||||
|
||||
from . import manifest
|
||||
|
||||
@ -79,6 +80,9 @@ class BepastyApp(app_module.App):
|
||||
clients=manifest.clients)
|
||||
self.add(shortcut)
|
||||
|
||||
packages = Packages('packages-bepasty', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-bepasty', info.name,
|
||||
ports=['http', 'https'], is_external=True)
|
||||
self.add(firewall)
|
||||
|
||||
@ -10,68 +10,49 @@ from plinth.tests import functional
|
||||
pytestmark = [pytest.mark.apps, pytest.mark.bepasty]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def fixture_background(session_browser):
|
||||
"""Login and install the app."""
|
||||
functional.login(session_browser)
|
||||
functional.install(session_browser, 'bepasty')
|
||||
yield
|
||||
functional.app_disable(session_browser, 'bepasty')
|
||||
class TestBepastyApp(functional.BaseAppTests):
|
||||
app_name = 'bepasty'
|
||||
has_service = False
|
||||
has_web = True
|
||||
|
||||
def test_set_default_permissions_list_and_read_all(self, session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
_logout(session_browser)
|
||||
_set_default_permissions(session_browser, 'read list')
|
||||
|
||||
def test_enable_disable(session_browser):
|
||||
"""Test enabling the app."""
|
||||
functional.app_disable(session_browser, 'bepasty')
|
||||
assert _can_list_all(session_browser)
|
||||
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
assert functional.is_available(session_browser, 'bepasty')
|
||||
def test_set_default_permissions_read_files(self, session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
_logout(session_browser)
|
||||
_set_default_permissions(session_browser, 'read')
|
||||
|
||||
functional.app_disable(session_browser, 'bepasty')
|
||||
assert not functional.is_available(session_browser, 'bepasty')
|
||||
assert _cannot_list_all(session_browser)
|
||||
|
||||
def test_add_password(self, session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
password_added = _add_and_save_password(session_browser)
|
||||
|
||||
def test_set_default_permissions_list_and_read_all(session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
_logout(session_browser)
|
||||
_set_default_permissions(session_browser, 'read list')
|
||||
assert _can_login(session_browser, password_added)
|
||||
|
||||
assert _can_list_all(session_browser)
|
||||
def test_remove_password(self, session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
password_added = _add_and_save_password(session_browser)
|
||||
_remove_all_passwords(session_browser)
|
||||
|
||||
assert not _can_login(session_browser, password_added)
|
||||
|
||||
def test_set_default_permissions_read_files(session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
_logout(session_browser)
|
||||
_set_default_permissions(session_browser, 'read')
|
||||
@pytest.mark.backups
|
||||
def test_backup_and_restore(self, session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
password_added = _add_and_save_password(session_browser)
|
||||
functional.backup_create(session_browser, 'bepasty', 'test_bepasty')
|
||||
|
||||
assert _cannot_list_all(session_browser)
|
||||
_remove_all_passwords(session_browser)
|
||||
functional.backup_restore(session_browser, 'bepasty', 'test_bepasty')
|
||||
|
||||
|
||||
def test_add_password(session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
password_added = _add_and_save_password(session_browser)
|
||||
|
||||
assert _can_login(session_browser, password_added)
|
||||
|
||||
|
||||
def test_remove_password(session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
password_added = _add_and_save_password(session_browser)
|
||||
_remove_all_passwords(session_browser)
|
||||
|
||||
assert not _can_login(session_browser, password_added)
|
||||
|
||||
|
||||
@pytest.mark.backups
|
||||
def test_backup_and_restore(session_browser):
|
||||
functional.app_enable(session_browser, 'bepasty')
|
||||
password_added = _add_and_save_password(session_browser)
|
||||
functional.backup_create(session_browser, 'bepasty', 'test_bepasty')
|
||||
|
||||
_remove_all_passwords(session_browser)
|
||||
functional.backup_restore(session_browser, 'bepasty', 'test_bepasty')
|
||||
|
||||
assert functional.is_available(session_browser, 'bepasty')
|
||||
assert _can_login(session_browser, password_added)
|
||||
assert functional.is_available(session_browser, 'bepasty')
|
||||
assert _can_login(session_browser, password_added)
|
||||
|
||||
|
||||
def _add_and_save_password(session_browser):
|
||||
|
||||
@ -16,6 +16,7 @@ from plinth import cfg, menu
|
||||
from plinth.daemon import Daemon
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.package import Packages
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import manifest
|
||||
@ -74,6 +75,7 @@ class BindApp(app_module.App):
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=version,
|
||||
name=_('BIND'), icon='fa-globe-w',
|
||||
short_description=_('Domain Name Server'),
|
||||
@ -85,6 +87,9 @@ class BindApp(app_module.App):
|
||||
parent_url_name='system')
|
||||
self.add(menu_item)
|
||||
|
||||
packages = Packages('packages-bind', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-bind', info.name, ports=['dns'],
|
||||
is_external=False)
|
||||
self.add(firewall)
|
||||
|
||||
@ -4,66 +4,50 @@ Functional, browser based tests for bind app.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from plinth.tests import functional
|
||||
|
||||
pytestmark = [pytest.mark.system, pytest.mark.bind]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def fixture_background(session_browser):
|
||||
"""Login and install the app."""
|
||||
functional.login(session_browser)
|
||||
functional.install(session_browser, 'bind')
|
||||
yield
|
||||
functional.app_disable(session_browser, 'bind')
|
||||
class TestBindApp(functional.BaseAppTests):
|
||||
app_name = 'bind'
|
||||
has_service = True
|
||||
has_web = False
|
||||
|
||||
def test_set_forwarders(self, session_browser):
|
||||
"""Test setting forwarders."""
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
functional.set_forwarders(session_browser, '1.1.1.1')
|
||||
|
||||
def test_enable_disable(session_browser):
|
||||
"""Test enabling the app."""
|
||||
functional.app_disable(session_browser, 'bind')
|
||||
functional.set_forwarders(session_browser, '1.1.1.1 1.0.0.1')
|
||||
assert functional.get_forwarders(session_browser) == '1.1.1.1 1.0.0.1'
|
||||
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
assert functional.service_is_running(session_browser, 'bind')
|
||||
def test_enable_disable_dnssec(self, session_browser):
|
||||
"""Test enabling/disabling DNSSEC."""
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
_enable_dnssec(session_browser, False)
|
||||
|
||||
functional.app_disable(session_browser, 'bind')
|
||||
assert functional.service_is_not_running(session_browser, 'bind')
|
||||
_enable_dnssec(session_browser, True)
|
||||
assert _get_dnssec(session_browser)
|
||||
|
||||
_enable_dnssec(session_browser, False)
|
||||
assert not _get_dnssec(session_browser)
|
||||
|
||||
def test_set_forwarders(session_browser):
|
||||
"""Test setting forwarders."""
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
functional.set_forwarders(session_browser, '1.1.1.1')
|
||||
@pytest.mark.backups
|
||||
def test_backup_restore(self, session_browser):
|
||||
"""Test backup and restore."""
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
functional.set_forwarders(session_browser, '1.1.1.1')
|
||||
_enable_dnssec(session_browser, False)
|
||||
functional.backup_create(session_browser, 'bind', 'test_bind')
|
||||
|
||||
functional.set_forwarders(session_browser, '1.1.1.1 1.0.0.1')
|
||||
assert functional.get_forwarders(session_browser) == '1.1.1.1 1.0.0.1'
|
||||
functional.set_forwarders(session_browser, '1.0.0.1')
|
||||
_enable_dnssec(session_browser, True)
|
||||
|
||||
|
||||
def test_enable_disable_dnssec(session_browser):
|
||||
"""Test enabling/disabling DNSSEC."""
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
_enable_dnssec(session_browser, False)
|
||||
|
||||
_enable_dnssec(session_browser, True)
|
||||
assert _get_dnssec(session_browser)
|
||||
|
||||
_enable_dnssec(session_browser, False)
|
||||
assert not _get_dnssec(session_browser)
|
||||
|
||||
|
||||
@pytest.mark.backups
|
||||
def test_backup(session_browser):
|
||||
"""Test backup and restore."""
|
||||
functional.app_enable(session_browser, 'bind')
|
||||
functional.set_forwarders(session_browser, '1.1.1.1')
|
||||
_enable_dnssec(session_browser, False)
|
||||
functional.backup_create(session_browser, 'bind', 'test_bind')
|
||||
|
||||
functional.set_forwarders(session_browser, '1.0.0.1')
|
||||
_enable_dnssec(session_browser, True)
|
||||
|
||||
functional.backup_restore(session_browser, 'bind', 'test_bind')
|
||||
assert functional.get_forwarders(session_browser) == '1.1.1.1'
|
||||
assert not _get_dnssec(session_browser)
|
||||
functional.backup_restore(session_browser, 'bind', 'test_bind')
|
||||
assert functional.get_forwarders(session_browser) == '1.1.1.1'
|
||||
assert not _get_dnssec(session_browser)
|
||||
|
||||
|
||||
def _enable_dnssec(browser, enable):
|
||||
|
||||
@ -16,6 +16,7 @@ from plinth.modules.apache.components import Webserver
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.users.components import UsersAndGroups
|
||||
from plinth.package import Packages
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import manifest
|
||||
@ -78,6 +79,9 @@ class CalibreApp(app_module.App):
|
||||
allowed_groups=list(groups))
|
||||
self.add(shortcut)
|
||||
|
||||
packages = Packages('packages-calibre', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-calibre', info.name,
|
||||
ports=['http', 'https'], is_external=True)
|
||||
self.add(firewall)
|
||||
|
||||
@ -13,63 +13,45 @@ from plinth.tests import functional
|
||||
pytestmark = [pytest.mark.apps, pytest.mark.sso, pytest.mark.calibre]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def fixture_background(session_browser):
|
||||
"""Login and install the app."""
|
||||
functional.login(session_browser)
|
||||
functional.install(session_browser, 'calibre')
|
||||
yield
|
||||
functional.app_disable(session_browser, 'calibre')
|
||||
class TestCalibreApp(functional.BaseAppTests):
|
||||
app_name = 'calibre'
|
||||
has_service = True
|
||||
has_web = True
|
||||
|
||||
def test_add_delete_library(self, session_browser):
|
||||
"""Test adding/deleting a new library."""
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
_delete_library(session_browser, 'FunctionalTest', True)
|
||||
|
||||
def test_enable_disable(session_browser):
|
||||
"""Test enabling the app."""
|
||||
functional.app_disable(session_browser, 'calibre')
|
||||
_add_library(session_browser, 'FunctionalTest')
|
||||
assert _is_library_available(session_browser, 'FunctionalTest')
|
||||
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
assert functional.service_is_running(session_browser, 'calibre')
|
||||
assert functional.is_available(session_browser, 'calibre')
|
||||
_delete_library(session_browser, 'FunctionalTest')
|
||||
assert not _is_library_available(session_browser, 'FunctionalTest')
|
||||
|
||||
functional.app_disable(session_browser, 'calibre')
|
||||
assert not functional.service_is_running(session_browser, 'calibre')
|
||||
assert not functional.is_available(session_browser, 'calibre')
|
||||
def test_add_delete_book(self, session_browser):
|
||||
"""Test adding/delete book in the library."""
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
_add_library(session_browser, 'FunctionalTest')
|
||||
_delete_book(session_browser, 'FunctionalTest', 'sample.txt', True)
|
||||
|
||||
|
||||
def test_add_delete_library(session_browser):
|
||||
"""Test adding/deleting a new library."""
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
_delete_library(session_browser, 'FunctionalTest', True)
|
||||
|
||||
_add_library(session_browser, 'FunctionalTest')
|
||||
assert _is_library_available(session_browser, 'FunctionalTest')
|
||||
|
||||
_delete_library(session_browser, 'FunctionalTest')
|
||||
assert not _is_library_available(session_browser, 'FunctionalTest')
|
||||
|
||||
|
||||
def test_add_delete_book(session_browser):
|
||||
"""Test adding/delete book in the library."""
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
_add_library(session_browser, 'FunctionalTest')
|
||||
_delete_book(session_browser, 'FunctionalTest', 'sample.txt', True)
|
||||
|
||||
_add_book(session_browser, 'FunctionalTest', 'sample.txt')
|
||||
assert _is_book_available(session_browser, 'FunctionalTest', 'sample.txt')
|
||||
|
||||
_delete_book(session_browser, 'FunctionalTest', 'sample.txt')
|
||||
assert not _is_book_available(session_browser, 'FunctionalTest',
|
||||
_add_book(session_browser, 'FunctionalTest', 'sample.txt')
|
||||
assert _is_book_available(session_browser, 'FunctionalTest',
|
||||
'sample.txt')
|
||||
|
||||
_delete_book(session_browser, 'FunctionalTest', 'sample.txt')
|
||||
assert not _is_book_available(session_browser, 'FunctionalTest',
|
||||
'sample.txt')
|
||||
|
||||
@pytest.mark.backups
|
||||
def test_backup(session_browser):
|
||||
"""Test backing up and restoring."""
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
_add_library(session_browser, 'FunctionalTest')
|
||||
functional.backup_create(session_browser, 'calibre', 'test_calibre')
|
||||
_delete_library(session_browser, 'FunctionalTest')
|
||||
functional.backup_restore(session_browser, 'calibre', 'test_calibre')
|
||||
assert _is_library_available(session_browser, 'FunctionalTest')
|
||||
@pytest.mark.backups
|
||||
def test_backup_restore(self, session_browser):
|
||||
"""Test backing up and restoring."""
|
||||
functional.app_enable(session_browser, 'calibre')
|
||||
_add_library(session_browser, 'FunctionalTest')
|
||||
functional.backup_create(session_browser, 'calibre', 'test_calibre')
|
||||
_delete_library(session_browser, 'FunctionalTest')
|
||||
functional.backup_restore(session_browser, 'calibre', 'test_calibre')
|
||||
assert _is_library_available(session_browser, 'FunctionalTest')
|
||||
|
||||
|
||||
def _add_library(browser, name):
|
||||
|
||||
@ -14,6 +14,7 @@ from plinth.modules import names
|
||||
from plinth.modules.apache.components import Webserver
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.package import Packages
|
||||
from plinth.signals import domain_added, domain_removed
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
@ -60,6 +61,7 @@ class CockpitApp(app_module.App):
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=version,
|
||||
is_essential=is_essential, name=_('Cockpit'),
|
||||
icon='fa-wrench', icon_filename='cockpit',
|
||||
@ -81,6 +83,9 @@ class CockpitApp(app_module.App):
|
||||
allowed_groups=['admin'])
|
||||
self.add(shortcut)
|
||||
|
||||
packages = Packages('packages-cockpit', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-cockpit', info.name,
|
||||
ports=['http', 'https'], is_external=True)
|
||||
self.add(firewall)
|
||||
|
||||
@ -20,6 +20,7 @@ from plinth.modules.coturn.components import TurnConfiguration, TurnConsumer
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.letsencrypt.components import LetsEncrypt
|
||||
from plinth.modules.users.components import UsersAndGroups
|
||||
from plinth.package import Packages
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import manifest
|
||||
@ -69,6 +70,9 @@ class CoturnApp(app_module.App):
|
||||
parent_url_name='apps')
|
||||
self.add(menu_item)
|
||||
|
||||
packages = Packages('packages-coturn', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-coturn', info.name,
|
||||
ports=['coturn-freedombox'], is_external=True)
|
||||
self.add(firewall)
|
||||
|
||||
@ -20,8 +20,6 @@ is_essential = True
|
||||
|
||||
managed_services = ['systemd-timesyncd']
|
||||
|
||||
managed_packages = []
|
||||
|
||||
_description = [
|
||||
_('Network time server is a program that maintains the system time '
|
||||
'in synchronization with servers on the Internet.')
|
||||
@ -65,6 +63,7 @@ class DateTimeApp(app_module.App):
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=version,
|
||||
is_essential=is_essential,
|
||||
name=_('Date & Time'), icon='fa-clock-o',
|
||||
|
||||
@ -14,6 +14,7 @@ from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.users import add_user_to_share_group
|
||||
from plinth.modules.users.components import UsersAndGroups
|
||||
from plinth.package import Packages
|
||||
|
||||
from . import manifest
|
||||
|
||||
@ -69,6 +70,9 @@ class DelugeApp(app_module.App):
|
||||
allowed_groups=list(groups))
|
||||
self.add(shortcut)
|
||||
|
||||
packages = Packages('packages-deluge', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-deluge', info.name,
|
||||
ports=['http', 'https'], is_external=True)
|
||||
self.add(firewall)
|
||||
|
||||
@ -12,69 +12,50 @@ from plinth.tests import functional
|
||||
pytestmark = [pytest.mark.apps, pytest.mark.deluge]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def fixture_background(session_browser):
|
||||
"""Login and install the app."""
|
||||
functional.login(session_browser)
|
||||
functional.install(session_browser, 'deluge')
|
||||
yield
|
||||
functional.app_disable(session_browser, 'deluge')
|
||||
class TestDelugeApp(functional.BaseAppTests):
|
||||
app_name = 'deluge'
|
||||
has_service = True
|
||||
has_web = True
|
||||
|
||||
def test_bittorrent_group(self, session_browser):
|
||||
"""Test if only users in bit-torrent group can access Deluge."""
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
if not functional.user_exists(session_browser, 'delugeuser'):
|
||||
functional.create_user(session_browser, 'delugeuser',
|
||||
groups=['bit-torrent'])
|
||||
|
||||
def test_enable_disable(session_browser):
|
||||
"""Test enabling the app."""
|
||||
functional.app_disable(session_browser, 'deluge')
|
||||
if not functional.user_exists(session_browser, 'nogroupuser'):
|
||||
functional.create_user(session_browser, 'nogroupuser')
|
||||
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
assert functional.service_is_running(session_browser, 'deluge')
|
||||
assert functional.is_available(session_browser, 'deluge')
|
||||
functional.login_with_account(session_browser, functional.base_url,
|
||||
'delugeuser')
|
||||
assert functional.is_available(session_browser, 'deluge')
|
||||
|
||||
functional.app_disable(session_browser, 'deluge')
|
||||
assert functional.service_is_not_running(session_browser, 'deluge')
|
||||
assert not functional.is_available(session_browser, 'deluge')
|
||||
functional.login_with_account(session_browser, functional.base_url,
|
||||
'nogroupuser')
|
||||
assert not functional.is_available(session_browser, 'deluge')
|
||||
|
||||
functional.login(session_browser)
|
||||
|
||||
def test_bittorrent_group(session_browser):
|
||||
"""Test if only users in bit-torrent group can access Deluge."""
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
if not functional.user_exists(session_browser, 'delugeuser'):
|
||||
functional.create_user(session_browser, 'delugeuser',
|
||||
groups=['bit-torrent'])
|
||||
def test_upload_torrent(self, session_browser):
|
||||
"""Test uploading a torrent."""
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
_remove_all_torrents(session_browser)
|
||||
_upload_sample_torrent(session_browser)
|
||||
assert _get_number_of_torrents(session_browser) == 1
|
||||
|
||||
if not functional.user_exists(session_browser, 'nogroupuser'):
|
||||
functional.create_user(session_browser, 'nogroupuser')
|
||||
@pytest.mark.backups
|
||||
def test_backup_restore(self, session_browser):
|
||||
"""Test backup and restore."""
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
_remove_all_torrents(session_browser)
|
||||
_upload_sample_torrent(session_browser)
|
||||
functional.backup_create(session_browser, 'deluge', 'test_deluge')
|
||||
|
||||
functional.login_with_account(session_browser, functional.base_url,
|
||||
'delugeuser')
|
||||
assert functional.is_available(session_browser, 'deluge')
|
||||
|
||||
functional.login_with_account(session_browser, functional.base_url,
|
||||
'nogroupuser')
|
||||
assert not functional.is_available(session_browser, 'deluge')
|
||||
|
||||
functional.login(session_browser)
|
||||
|
||||
|
||||
def test_upload_torrent(session_browser):
|
||||
"""Test uploading a torrent."""
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
_remove_all_torrents(session_browser)
|
||||
_upload_sample_torrent(session_browser)
|
||||
assert _get_number_of_torrents(session_browser) == 1
|
||||
|
||||
|
||||
@pytest.mark.backups
|
||||
def test_backup_restore(session_browser):
|
||||
"""Test backup and restore."""
|
||||
functional.app_enable(session_browser, 'deluge')
|
||||
_remove_all_torrents(session_browser)
|
||||
_upload_sample_torrent(session_browser)
|
||||
functional.backup_create(session_browser, 'deluge', 'test_deluge')
|
||||
|
||||
_remove_all_torrents(session_browser)
|
||||
functional.backup_restore(session_browser, 'deluge', 'test_deluge')
|
||||
assert functional.service_is_running(session_browser, 'deluge')
|
||||
assert _get_number_of_torrents(session_browser) == 1
|
||||
_remove_all_torrents(session_browser)
|
||||
functional.backup_restore(session_browser, 'deluge', 'test_deluge')
|
||||
assert functional.service_is_running(session_browser, 'deluge')
|
||||
assert _get_number_of_torrents(session_browser) == 1
|
||||
|
||||
|
||||
def _get_active_window_title(browser):
|
||||
|
||||
@ -12,6 +12,7 @@ from plinth.daemon import Daemon
|
||||
from plinth.errors import DomainNotRegisteredError
|
||||
from plinth.modules.apache.components import Webserver, diagnose_url
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.package import Packages
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
domain_name_file = "/etc/diaspora/domain_name"
|
||||
@ -64,6 +65,7 @@ class DiasporaApp(app_module.App):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
from . import manifest
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=version,
|
||||
name=_('diaspora*'), icon_filename='diaspora',
|
||||
short_description=_('Federated Social Network'),
|
||||
@ -82,6 +84,9 @@ class DiasporaApp(app_module.App):
|
||||
clients=info.clients, login_required=True)
|
||||
self.add(shortcut)
|
||||
|
||||
packages = Packages('packages-diaspora', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-diaspora', info.name,
|
||||
ports=['http', 'https'], is_external=True)
|
||||
self.add(firewall)
|
||||
|
||||
@ -11,6 +11,7 @@ from plinth import cfg, menu
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.names.components import DomainType
|
||||
from plinth.modules.users.components import UsersAndGroups
|
||||
from plinth.package import Packages
|
||||
from plinth.signals import domain_added
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
@ -51,6 +52,7 @@ class DynamicDNSApp(app_module.App):
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=version,
|
||||
is_essential=is_essential, depends=depends,
|
||||
name=_('Dynamic DNS Client'), icon='fa-refresh',
|
||||
@ -62,6 +64,9 @@ class DynamicDNSApp(app_module.App):
|
||||
'dynamicdns:index', parent_url_name='system')
|
||||
self.add(menu_item)
|
||||
|
||||
packages = Packages('packages-dynamicdns', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
domain_type = DomainType('domain-type-dynamic',
|
||||
_('Dynamic Domain Name'), 'dynamicdns:index',
|
||||
can_have_certificate=True)
|
||||
|
||||
@ -21,6 +21,7 @@ from plinth.modules.coturn.components import TurnConfiguration, TurnConsumer
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.letsencrypt.components import LetsEncrypt
|
||||
from plinth.modules.users.components import UsersAndGroups
|
||||
from plinth.package import Packages
|
||||
from plinth.signals import (domain_added, post_hostname_change,
|
||||
pre_hostname_change)
|
||||
from plinth.utils import format_lazy
|
||||
@ -67,6 +68,7 @@ class EjabberdApp(app_module.App):
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=version,
|
||||
name=_('ejabberd'), icon_filename='ejabberd',
|
||||
short_description=_('Chat Server'),
|
||||
@ -88,6 +90,9 @@ class EjabberdApp(app_module.App):
|
||||
login_required=True)
|
||||
self.add(shortcut)
|
||||
|
||||
packages = Packages('packages-ejabberd', managed_packages)
|
||||
self.add(packages)
|
||||
|
||||
firewall = Firewall('firewall-ejabberd', info.name,
|
||||
ports=['xmpp-client', 'xmpp-server',
|
||||
'xmpp-bosh'], is_external=True)
|
||||
|
||||
@ -12,47 +12,31 @@ pytestmark = [pytest.mark.apps, pytest.mark.ejabberd]
|
||||
# TODO Check domain name display
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def fixture_background(session_browser):
|
||||
"""Login and install the app."""
|
||||
functional.login(session_browser)
|
||||
functional.install(session_browser, 'ejabberd')
|
||||
yield
|
||||
functional.app_disable(session_browser, 'ejabberd')
|
||||
class TestEjabberdApp(functional.BaseAppTests):
|
||||
app_name = 'ejabberd'
|
||||
has_service = True
|
||||
has_web = False
|
||||
|
||||
def test_message_archive_management(self, session_browser):
|
||||
"""Test enabling message archive management."""
|
||||
functional.app_enable(session_browser, 'ejabberd')
|
||||
_enable_message_archive_management(session_browser)
|
||||
assert functional.service_is_running(session_browser, 'ejabberd')
|
||||
|
||||
def test_enable_disable(session_browser):
|
||||
"""Test enabling the app."""
|
||||
functional.app_disable(session_browser, 'ejabberd')
|
||||
_disable_message_archive_management(session_browser)
|
||||
assert functional.service_is_running(session_browser, 'ejabberd')
|
||||
|
||||
functional.app_enable(session_browser, 'ejabberd')
|
||||
assert functional.service_is_running(session_browser, 'ejabberd')
|
||||
@pytest.mark.backups
|
||||
def test_backup_restore(self, session_browser):
|
||||
"""Test backup and restore of app data."""
|
||||
functional.app_enable(session_browser, 'ejabberd')
|
||||
_jsxc_add_contact(session_browser)
|
||||
functional.backup_create(session_browser, 'ejabberd', 'test_ejabberd')
|
||||
|
||||
functional.app_disable(session_browser, 'ejabberd')
|
||||
assert functional.service_is_not_running(session_browser, 'ejabberd')
|
||||
_jsxc_delete_contact(session_browser)
|
||||
functional.backup_restore(session_browser, 'ejabberd', 'test_ejabberd')
|
||||
|
||||
|
||||
def test_message_archive_management(session_browser):
|
||||
"""Test enabling message archive management."""
|
||||
functional.app_enable(session_browser, 'ejabberd')
|
||||
_enable_message_archive_management(session_browser)
|
||||
assert functional.service_is_running(session_browser, 'ejabberd')
|
||||
|
||||
_disable_message_archive_management(session_browser)
|
||||
assert functional.service_is_running(session_browser, 'ejabberd')
|
||||
|
||||
|
||||
@pytest.mark.backups
|
||||
def test_backup_restore(session_browser):
|
||||
"""Test backup and restore of app data."""
|
||||
functional.app_enable(session_browser, 'ejabberd')
|
||||
_jsxc_add_contact(session_browser)
|
||||
functional.backup_create(session_browser, 'ejabberd', 'test_ejabberd')
|
||||
|
||||
_jsxc_delete_contact(session_browser)
|
||||
functional.backup_restore(session_browser, 'ejabberd', 'test_ejabberd')
|
||||
|
||||
_jsxc_assert_has_contact(session_browser)
|
||||
_jsxc_assert_has_contact(session_browser)
|
||||
|
||||
|
||||
def _enable_message_archive_management(browser):
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import plinth.app
|
||||
@ -30,6 +29,7 @@ package_conflicts_action = 'ignore'
|
||||
|
||||
packages = [
|
||||
'postfix-ldap',
|
||||
'postfix-sqlite',
|
||||
'dovecot-pop3d',
|
||||
'dovecot-imapd',
|
||||
'dovecot-ldap',
|
||||
@ -111,13 +111,6 @@ class EmailServerApp(plinth.app.App):
|
||||
parent_url_name='apps')
|
||||
self.add(menu_item)
|
||||
|
||||
shortcut = plinth.frontpage.Shortcut(
|
||||
'shortcut_' + self.app_id, name=info.name,
|
||||
short_description=info.short_description, icon='roundcube',
|
||||
url=reverse_lazy('email_server:my_mail'), clients=manifest.clients,
|
||||
login_required=True)
|
||||
self.add(shortcut)
|
||||
|
||||
def _add_daemons(self):
|
||||
for srvname in managed_services:
|
||||
# Construct `listen_ports` parameter for the daemon
|
||||
@ -169,6 +162,7 @@ def setup(helper, old_version=None):
|
||||
helper.install(packages_bloat, skip_recommends=True)
|
||||
|
||||
# Setup
|
||||
helper.call('post', audit.home.repair)
|
||||
helper.call('post', audit.domain.repair)
|
||||
helper.call('post', audit.ldap.repair)
|
||||
helper.call('post', audit.spam.repair)
|
||||
|
||||
115
plinth/modules/email_server/aliases.py
Normal file
115
plinth/modules/email_server/aliases.py
Normal file
@ -0,0 +1,115 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Manage email aliases."""
|
||||
|
||||
import contextlib
|
||||
import pwd
|
||||
import sqlite3
|
||||
from dataclasses import InitVar, dataclass, field
|
||||
|
||||
from plinth import actions
|
||||
|
||||
|
||||
@dataclass
|
||||
class Alias:
|
||||
value: str
|
||||
name: str
|
||||
enabled: bool = field(init=False)
|
||||
status: InitVar[int]
|
||||
|
||||
def __post_init__(self, status):
|
||||
self.enabled = (status != 0)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _get_cursor():
|
||||
"""Return a DB cursor as context manager."""
|
||||
# Turn ON autocommit mode
|
||||
db_path = '/var/lib/postfix/freedombox-aliases/aliases.sqlite3'
|
||||
connection = sqlite3.connect(db_path, isolation_level=None)
|
||||
connection.row_factory = sqlite3.Row
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
yield cursor
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def get(username):
|
||||
"""Get all aliases of a user."""
|
||||
query = 'SELECT name, value, status FROM alias WHERE value=?'
|
||||
with _get_cursor() as cursor:
|
||||
rows = cursor.execute(query, (username, ))
|
||||
return [Alias(**row) for row in rows]
|
||||
|
||||
|
||||
def exists(name):
|
||||
"""Return whether alias is already taken."""
|
||||
try:
|
||||
pwd.getpwnam(name)
|
||||
return True
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
with _get_cursor() as cursor:
|
||||
query = 'SELECT COUNT(*) FROM alias WHERE name=?'
|
||||
cursor.execute(query, (name, ))
|
||||
return cursor.fetchone()[0] != 0
|
||||
|
||||
|
||||
def put(username, name):
|
||||
"""Insert if not exists a new alias."""
|
||||
query = 'INSERT INTO alias (name, value, status) VALUES (?, ?, ?)'
|
||||
with _get_cursor() as cursor:
|
||||
try:
|
||||
cursor.execute(query, (name, username, 1))
|
||||
except sqlite3.IntegrityError:
|
||||
pass # Alias exists, rare since we are already checking
|
||||
|
||||
|
||||
def delete(username, aliases):
|
||||
"""Delete a set of aliases."""
|
||||
query = 'DELETE FROM alias WHERE value=? AND name=?'
|
||||
parameter_seq = ((username, name) for name in aliases)
|
||||
with _get_cursor() as cursor:
|
||||
cursor.execute('BEGIN')
|
||||
cursor.executemany(query, parameter_seq)
|
||||
cursor.execute('COMMIT')
|
||||
|
||||
|
||||
def enable(username, aliases):
|
||||
"""Enable a list of aliases."""
|
||||
return _set_status(username, aliases, 1)
|
||||
|
||||
|
||||
def disable(username, aliases):
|
||||
"""Disable a list of aliases."""
|
||||
return _set_status(username, aliases, 0)
|
||||
|
||||
|
||||
def _set_status(username, aliases, status):
|
||||
"""Set the status value of a list of aliases."""
|
||||
query = 'UPDATE alias SET status=? WHERE value=? AND name=?'
|
||||
parameter_seq = ((status, username, name) for name in aliases)
|
||||
with _get_cursor() as cursor:
|
||||
cursor.execute('BEGIN')
|
||||
cursor.executemany(query, parameter_seq)
|
||||
cursor.execute('COMMIT')
|
||||
|
||||
|
||||
def first_setup():
|
||||
"""Create the database file and schema inside it."""
|
||||
actions.superuser_run('email_server', ['aliases', 'setup'])
|
||||
|
||||
# Create schema if not exists
|
||||
query = '''
|
||||
BEGIN;
|
||||
CREATE TABLE IF NOT EXISTS alias (
|
||||
name TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
PRIMARY KEY (name)
|
||||
);
|
||||
COMMIT;
|
||||
'''
|
||||
with _get_cursor() as cursor:
|
||||
cursor.executescript(query)
|
||||
@ -1,152 +0,0 @@
|
||||
"""Manages email aliases"""
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import contextlib
|
||||
import dbm
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
import sqlite3
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth.modules.email_server import lock
|
||||
|
||||
from . import models
|
||||
|
||||
map_db_schema_script = """
|
||||
PRAGMA journal_mode=WAL;
|
||||
BEGIN;
|
||||
CREATE TABLE IF NOT EXISTS Alias (
|
||||
email_name TEXT NOT NULL,
|
||||
uid_number INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
PRIMARY KEY (email_name)
|
||||
);
|
||||
COMMIT;
|
||||
"""
|
||||
|
||||
mailsrv_dir = '/var/lib/plinth/mailsrv'
|
||||
hash_db_path = mailsrv_dir + '/aliases'
|
||||
sqlite_db_path = mailsrv_dir + '/aliases.sqlite3'
|
||||
|
||||
alias_sync_mutex = lock.Mutex('alias-sync')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def db_cursor():
|
||||
# Turn ON autocommit mode
|
||||
con = sqlite3.connect(sqlite_db_path, isolation_level=None)
|
||||
con.row_factory = sqlite3.Row
|
||||
try:
|
||||
cur = con.cursor()
|
||||
yield cur
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def get(uid_number):
|
||||
s = 'SELECT * FROM Alias WHERE uid_number=?'
|
||||
with db_cursor() as cur:
|
||||
rows = cur.execute(s, (uid_number, ))
|
||||
result = [models.Alias(**r) for r in rows]
|
||||
return result
|
||||
|
||||
|
||||
def put(uid_number, email_name):
|
||||
s = """INSERT INTO Alias(email_name, uid_number, status)
|
||||
SELECT ?,?,? WHERE NOT EXISTS(
|
||||
SELECT 1 FROM Alias WHERE email_name=?
|
||||
)"""
|
||||
email_name = models.sanitize_email_name(email_name)
|
||||
# email_name cannot be the same as a user name
|
||||
try:
|
||||
pwd.getpwnam(email_name)
|
||||
raise ValidationError(_('The alias was taken'))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
with db_cursor() as cur:
|
||||
cur.execute(s, (email_name, uid_number, 1, email_name))
|
||||
if cur.rowcount == 0:
|
||||
raise ValidationError(_('The alias was taken'))
|
||||
|
||||
schedule_hash_update()
|
||||
|
||||
|
||||
def delete(uid_number, alias_list):
|
||||
s = 'DELETE FROM Alias WHERE uid_number=? AND email_name=?'
|
||||
for i in range(len(alias_list)):
|
||||
alias_list[i] = models.sanitize_email_name(alias_list[i])
|
||||
|
||||
parameter_seq = ((uid_number, a) for a in alias_list)
|
||||
with db_cursor() as cur:
|
||||
cur.execute('BEGIN')
|
||||
cur.executemany(s, parameter_seq)
|
||||
cur.execute('COMMIT')
|
||||
schedule_hash_update()
|
||||
|
||||
|
||||
def set_enabled(uid_number, alias_list):
|
||||
return _set_status(uid_number, alias_list, 1)
|
||||
|
||||
|
||||
def set_disabled(uid_number, alias_list):
|
||||
return _set_status(uid_number, alias_list, 0)
|
||||
|
||||
|
||||
def _set_status(uid_number, alias_list, status):
|
||||
s = 'UPDATE Alias SET status=? WHERE uid_number=? AND email_name=?'
|
||||
for i in range(len(alias_list)):
|
||||
alias_list[i] = models.sanitize_email_name(alias_list[i])
|
||||
|
||||
parameter_seq = ((status, uid_number, a) for a in alias_list)
|
||||
with db_cursor() as cur:
|
||||
cur.execute('BEGIN')
|
||||
cur.executemany(s, parameter_seq)
|
||||
cur.execute('COMMIT')
|
||||
schedule_hash_update()
|
||||
|
||||
|
||||
def schedule_hash_update():
|
||||
tmp = hash_db_path + '-tmp'
|
||||
with alias_sync_mutex.lock_all(), db_cursor() as cur:
|
||||
all_aliases = cur.execute('SELECT * FROM Alias')
|
||||
|
||||
# Delete the temp file if exists
|
||||
if os.path.exists(tmp):
|
||||
os.unlink(tmp)
|
||||
|
||||
# Create new alias db at temp path
|
||||
db = dbm.ndbm.open(tmp, 'c')
|
||||
try:
|
||||
for row in all_aliases:
|
||||
alias = models.Alias(**row)
|
||||
key = alias.email_name.encode('ascii') + b'\0'
|
||||
if alias.enabled:
|
||||
value = str(alias.uid_number).encode('ascii')
|
||||
value += b'@localhost\0'
|
||||
else:
|
||||
value = b'/dev/null\0'
|
||||
db[key] = value
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# Atomically replace old alias db, rename(2)
|
||||
os.rename(tmp + '.db', hash_db_path + '.db')
|
||||
|
||||
|
||||
def first_setup():
|
||||
_create_db_schema_if_not_exists()
|
||||
schedule_hash_update()
|
||||
|
||||
|
||||
def _create_db_schema_if_not_exists():
|
||||
# Create folder
|
||||
if not os.path.isdir(mailsrv_dir):
|
||||
os.mkdir(mailsrv_dir)
|
||||
# Create schema if not exists
|
||||
with db_cursor() as cur:
|
||||
cur.executescript(map_db_schema_script)
|
||||
@ -1,31 +0,0 @@
|
||||
import re
|
||||
from dataclasses import InitVar, dataclass, field
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
email_positive_pattern = re.compile('^[a-zA-Z0-9-_\\.]+')
|
||||
|
||||
|
||||
def sanitize_email_name(email_name):
|
||||
email_name = email_name.strip().lower()
|
||||
if len(email_name) < 2:
|
||||
raise ValidationError(_('Must be at least 2 characters long'))
|
||||
if not re.match('^[a-z0-9-_\\.]+$', email_name):
|
||||
raise ValidationError(_('Contains illegal characters'))
|
||||
if not re.match('^[a-z0-9].*[a-z0-9]$', email_name):
|
||||
raise ValidationError(_('Must start and end with a-z or 0-9'))
|
||||
if re.match('^[0-9]+$', email_name):
|
||||
raise ValidationError(_('Cannot be a number'))
|
||||
return email_name
|
||||
|
||||
|
||||
@dataclass
|
||||
class Alias:
|
||||
uid_number: int
|
||||
email_name: str
|
||||
enabled: bool = field(init=False)
|
||||
status: InitVar[int]
|
||||
|
||||
def __post_init__(self, status):
|
||||
self.enabled = (status != 0)
|
||||
@ -3,6 +3,8 @@
|
||||
Provides diagnosis and repair of email server configuration issues
|
||||
"""
|
||||
|
||||
from . import domain, home, ldap, models, rcube, spam, tls
|
||||
from . import aliases, domain, home, ldap, models, rcube, spam, tls
|
||||
|
||||
__all__ = ['domain', 'home', 'ldap', 'models', 'rcube', 'spam', 'tls']
|
||||
__all__ = [
|
||||
'aliases', 'domain', 'home', 'ldap', 'models', 'rcube', 'spam', 'tls'
|
||||
]
|
||||
|
||||
12
plinth/modules/email_server/audit/aliases.py
Normal file
12
plinth/modules/email_server/audit/aliases.py
Normal file
@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Privileged operations for managing aliases."""
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
|
||||
def action_setup():
|
||||
"""Create a the sqlite3 database to be managed by FreedomBox."""
|
||||
path = pathlib.Path('/var/lib/postfix/freedombox-aliases/')
|
||||
path.mkdir(mode=0o750, exist_ok=True)
|
||||
shutil.chown(path, user='plinth', group='postfix')
|
||||
@ -4,12 +4,12 @@
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -42,14 +42,14 @@ def get():
|
||||
|
||||
|
||||
def repair():
|
||||
superuser_run('email_server', ['-i', 'domain', 'set_up'])
|
||||
superuser_run('email_server', ['domain', 'set_up'])
|
||||
|
||||
|
||||
def repair_component(action_name):
|
||||
allowed_actions = {'set_up': ['postfix']}
|
||||
if action_name not in allowed_actions:
|
||||
return
|
||||
superuser_run('email_server', ['-i', 'domain', action_name])
|
||||
superuser_run('email_server', ['domain', action_name])
|
||||
return allowed_actions[action_name]
|
||||
|
||||
|
||||
@ -181,22 +181,11 @@ def _apply_domain_changes(conf_dict):
|
||||
|
||||
|
||||
def get_domain_config():
|
||||
fields = []
|
||||
|
||||
# Special keys
|
||||
mailname = SimpleNamespace(key='_mailname', name='/etc/mailname')
|
||||
with open('/etc/mailname', 'r') as fd:
|
||||
mailname.value = fd.readline().strip()
|
||||
fields.append(mailname)
|
||||
|
||||
# Postconf keys
|
||||
postconf_keys = [k for k in managed_keys if not k.startswith('_')]
|
||||
result_dict = postconf.get_many(postconf_keys)
|
||||
for key, value in result_dict.items():
|
||||
field = SimpleNamespace(key=key, value=value, name='$' + key)
|
||||
fields.append(field)
|
||||
|
||||
return fields
|
||||
"""Return the current domain configuration."""
|
||||
postconf_keys = [key for key in managed_keys if not key.startswith('_')]
|
||||
config = postconf.get_many(postconf_keys)
|
||||
config['_mailname'] = pathlib.Path('/etc/mailname').read_text().strip()
|
||||
return config
|
||||
|
||||
|
||||
def set_keys(raw):
|
||||
@ -210,7 +199,7 @@ def set_keys(raw):
|
||||
raise ClientError('POST data exceeds max line length')
|
||||
|
||||
try:
|
||||
superuser_run('email_server', ['-i', 'domain', 'set_keys'], input=ipc)
|
||||
superuser_run('email_server', ['domain', 'set_keys'], input=ipc)
|
||||
except ActionError as e:
|
||||
stdout = e.args[1]
|
||||
if not stdout.startswith('ClientError:'):
|
||||
|
||||
@ -1,71 +1,22 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Privileged actions to setup users' dovecot mail home directory."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth.actions import superuser_run
|
||||
from plinth.errors import ActionError
|
||||
from plinth.modules.email_server import interproc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from plinth import actions
|
||||
|
||||
|
||||
def exists_nam(username):
|
||||
"""Returns True if the user's home directory exists"""
|
||||
try:
|
||||
passwd = pwd.getpwnam(username)
|
||||
except KeyError as e:
|
||||
raise ValidationError(_('User does not exist')) from e
|
||||
return _exists(passwd)
|
||||
def repair():
|
||||
"""Set correct permissions on /var/mail/ directory.
|
||||
|
||||
For each user, /var/mail/<user> is the 'dovecot mail home' for that user.
|
||||
Dovecot creates new directories with the same permissions as the parent
|
||||
directory. Ensure that 'others' can access /var/mail/.
|
||||
|
||||
"""
|
||||
actions.superuser_run('email_server', ['home', 'set_up'])
|
||||
|
||||
|
||||
def exists_uid(uid_number):
|
||||
"""Returns True if the user's home directory exists"""
|
||||
try:
|
||||
passwd = pwd.getpwuid(uid_number)
|
||||
except KeyError as e:
|
||||
raise ValidationError(_('User does not exist')) from e
|
||||
return _exists(passwd)
|
||||
|
||||
|
||||
def _exists(passwd):
|
||||
return os.path.exists(passwd.pw_dir)
|
||||
|
||||
|
||||
def put_nam(username):
|
||||
"""Create a home directory for the user (identified by username)"""
|
||||
_put('nam', username)
|
||||
|
||||
|
||||
def put_uid(uid_number):
|
||||
"""Create a home directory for the user (identified by UID)"""
|
||||
_put('uid', str(uid_number))
|
||||
|
||||
|
||||
def _put(arg_type, user_info):
|
||||
try:
|
||||
args = ['-i', 'home', 'mk', arg_type, user_info]
|
||||
superuser_run('email_server', args)
|
||||
except ActionError as e:
|
||||
raise RuntimeError('Action script failure') from e
|
||||
|
||||
|
||||
def action_mk(arg_type, user_info):
|
||||
if arg_type == 'nam':
|
||||
passwd = pwd.getpwnam(user_info)
|
||||
elif arg_type == 'uid':
|
||||
passwd = pwd.getpwuid(int(user_info))
|
||||
else:
|
||||
raise ValueError('Unknown arg_type')
|
||||
|
||||
args = ['sudo', '-n', '--user=#' + str(passwd.pw_uid)]
|
||||
args.extend(['/bin/sh', '-c', 'mkdir -p ~'])
|
||||
completed = subprocess.run(args, capture_output=True, check=False)
|
||||
if completed.returncode != 0:
|
||||
interproc.log_subprocess(completed)
|
||||
raise OSError('Could not create home directory')
|
||||
def action_set_up():
|
||||
"""Run chmod on /var/mail to remove all permissions for 'others'."""
|
||||
subprocess.run(['chmod', 'o-rwx', '/var/mail'], check=True)
|
||||
|
||||
@ -13,21 +13,27 @@ from plinth import actions
|
||||
from . import models
|
||||
|
||||
default_config = {
|
||||
'smtpd_sasl_auth_enable': 'yes',
|
||||
'smtpd_sasl_type': 'dovecot',
|
||||
'smtpd_sasl_path': 'private/auth',
|
||||
'mailbox_transport': 'lmtp:unix:private/dovecot-lmtp',
|
||||
'virtual_transport': 'lmtp:unix:private/dovecot-lmtp',
|
||||
|
||||
'smtpd_relay_restrictions': ','.join([
|
||||
'permit_sasl_authenticated', 'defer_unauth_destination',
|
||||
])
|
||||
'smtpd_sasl_auth_enable':
|
||||
'yes',
|
||||
'smtpd_sasl_type':
|
||||
'dovecot',
|
||||
'smtpd_sasl_path':
|
||||
'private/auth',
|
||||
'mailbox_transport':
|
||||
'lmtp:unix:private/dovecot-lmtp',
|
||||
'virtual_transport':
|
||||
'lmtp:unix:private/dovecot-lmtp',
|
||||
'smtpd_relay_restrictions':
|
||||
','.join([
|
||||
'permit_sasl_authenticated',
|
||||
'defer_unauth_destination',
|
||||
])
|
||||
}
|
||||
|
||||
submission_flags = postconf.ServiceFlags(
|
||||
service='submission', type='inet', private='n', unpriv='-', chroot='y',
|
||||
wakeup='-', maxproc='-', command_args='smtpd'
|
||||
)
|
||||
submission_flags = postconf.ServiceFlags(service='submission', type='inet',
|
||||
private='n', unpriv='-', chroot='y',
|
||||
wakeup='-', maxproc='-',
|
||||
command_args='smtpd')
|
||||
|
||||
default_submission_options = {
|
||||
'syslog_name': 'postfix/submission',
|
||||
@ -36,10 +42,9 @@ default_submission_options = {
|
||||
'smtpd_relay_restrictions': 'permit_sasl_authenticated,reject'
|
||||
}
|
||||
|
||||
smtps_flags = postconf.ServiceFlags(
|
||||
service='smtps', type='inet', private='n', unpriv='-', chroot='y',
|
||||
wakeup='-', maxproc='-', command_args='smtpd'
|
||||
)
|
||||
smtps_flags = postconf.ServiceFlags(service='smtps', type='inet', private='n',
|
||||
unpriv='-', chroot='y', wakeup='-',
|
||||
maxproc='-', command_args='smtpd')
|
||||
|
||||
default_smtps_options = {
|
||||
'syslog_name': 'postfix/smtps',
|
||||
@ -49,9 +54,7 @@ default_smtps_options = {
|
||||
}
|
||||
|
||||
MAILSRV_DIR = '/var/lib/plinth/mailsrv'
|
||||
ETC_ALIASES = 'hash:/etc/aliases'
|
||||
BEFORE_ALIASES = 'ldap:/etc/postfix/freedombox-username-to-uid-number.cf'
|
||||
AFTER_ALIASES = 'hash:' + aliases.hash_db_path
|
||||
SQLITE_ALIASES = 'sqlite:/etc/postfix/freedombox-aliases.cf'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -65,7 +68,6 @@ def get():
|
||||
translation_table = [
|
||||
(check_sasl, _('Postfix-Dovecot SASL integration')),
|
||||
(check_alias_maps, _('Postfix alias maps')),
|
||||
(check_local_recipient_maps, _('Postfix local recipient maps')),
|
||||
]
|
||||
results = []
|
||||
with postconf.mutex.lock_all():
|
||||
@ -81,7 +83,7 @@ def repair():
|
||||
POST /audit/ldap/repair
|
||||
"""
|
||||
aliases.first_setup()
|
||||
actions.superuser_run('email_server', ['-i', 'ldap', 'set_up'])
|
||||
actions.superuser_run('email_server', ['ldap', 'set_up'])
|
||||
|
||||
|
||||
def action_set_up():
|
||||
@ -118,67 +120,23 @@ def check_alias_maps(title=''):
|
||||
"""Check the ability to mail to usernames and user aliases"""
|
||||
diagnosis = models.MainCfDiagnosis(title)
|
||||
|
||||
analysis = models.AliasMapsAnalysis()
|
||||
analysis.parsed = postconf.parse_maps_by_key_unsafe('alias_maps')
|
||||
analysis.isystem = list_find(analysis.parsed, ETC_ALIASES)
|
||||
analysis.ibefore = list_find(analysis.parsed, BEFORE_ALIASES)
|
||||
analysis.iafter = list_find(analysis.parsed, AFTER_ALIASES)
|
||||
|
||||
if analysis.ibefore == -1 or analysis.iafter == -1:
|
||||
diagnosis.flag_once('alias_maps', user=analysis)
|
||||
alias_maps = postconf.get_unsafe('alias_maps').replace(',', ' ').split(' ')
|
||||
if SQLITE_ALIASES not in alias_maps:
|
||||
diagnosis.flag_once('alias_maps', user=alias_maps)
|
||||
diagnosis.critical('Required maps not in list')
|
||||
if analysis.ibefore > analysis.iafter:
|
||||
diagnosis.flag_once('alias_maps', user=analysis)
|
||||
diagnosis.critical('Insecure map order')
|
||||
|
||||
return diagnosis
|
||||
|
||||
|
||||
def fix_alias_maps(diagnosis):
|
||||
diagnosis.repair('alias_maps', rearrange_alias_maps)
|
||||
diagnosis.apply_changes(postconf.set_many_unsafe)
|
||||
|
||||
def fix_value(alias_maps):
|
||||
if SQLITE_ALIASES not in alias_maps:
|
||||
alias_maps.append(SQLITE_ALIASES)
|
||||
|
||||
def rearrange_alias_maps(analysis):
|
||||
# Delete *all* references to BEFORE_ALIASES and AFTER_ALIASES
|
||||
for i in range(len(analysis.parsed)):
|
||||
if analysis.parsed[i] in (BEFORE_ALIASES, AFTER_ALIASES):
|
||||
analysis.parsed[i] = ''
|
||||
# Does hash:/etc/aliases exist in list?
|
||||
if analysis.isystem >= 0:
|
||||
# Put the maps around hash:/etc/aliases
|
||||
val = '%s %s %s' % (BEFORE_ALIASES, ETC_ALIASES, AFTER_ALIASES)
|
||||
analysis.parsed[analysis.isystem] = val
|
||||
else:
|
||||
# To the end
|
||||
analysis.parsed.append(BEFORE_ALIASES)
|
||||
analysis.parsed.append(AFTER_ALIASES)
|
||||
# List -> string
|
||||
return ' '.join(filter(None, analysis.parsed))
|
||||
return ' '.join(alias_maps)
|
||||
|
||||
|
||||
def check_local_recipient_maps(title=''):
|
||||
diagnosis = models.MainCfDiagnosis(title)
|
||||
lrcpt_maps = postconf.parse_maps_by_key_unsafe('local_recipient_maps')
|
||||
list_modified = False
|
||||
|
||||
# Block mails to system users
|
||||
# local_recipient_maps must not contain proxy:unix:passwd.byname
|
||||
ipasswd = list_find(lrcpt_maps, 'proxy:unix:passwd.byname')
|
||||
if ipasswd >= 0:
|
||||
diagnosis.critical('Mail to system users (/etc/passwd) possible')
|
||||
# Propose a fix
|
||||
lrcpt_maps[ipasswd] = ''
|
||||
list_modified = True
|
||||
|
||||
if list_modified:
|
||||
fix = ' '.join(filter(None, lrcpt_maps))
|
||||
diagnosis.flag('local_recipient_maps', corrected_value=fix)
|
||||
|
||||
return diagnosis
|
||||
|
||||
|
||||
def fix_local_recipient_maps(diagnosis):
|
||||
diagnosis.repair('alias_maps', fix_value)
|
||||
diagnosis.apply_changes(postconf.set_many_unsafe)
|
||||
|
||||
|
||||
@ -186,7 +144,6 @@ def action_set_ulookup():
|
||||
"""Handles email_server -i ldap set_ulookup"""
|
||||
with postconf.mutex.lock_all():
|
||||
fix_alias_maps(check_alias_maps())
|
||||
fix_local_recipient_maps(check_local_recipient_maps())
|
||||
|
||||
|
||||
def list_find(lst, element, start=None, end=None):
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Models of the audit module"""
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
import typing
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -207,14 +205,3 @@ class MainCfDiagnosis(Diagnosis):
|
||||
contains an unresolved issue (i.e. an uncorrected key)"""
|
||||
if None in self.advice.values():
|
||||
raise UnresolvedIssueError('Assertion failed')
|
||||
|
||||
|
||||
@dataclasses.dataclass(init=False)
|
||||
class AliasMapsAnalysis:
|
||||
parsed = typing.List[str]
|
||||
ibefore = int
|
||||
isystem = int
|
||||
iafter = int
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@ -32,7 +32,7 @@ def get():
|
||||
'rc_config_header': _('RoundCube configured for FreedomBox email'),
|
||||
}
|
||||
|
||||
output = actions.superuser_run('email_server', ['-i', 'rcube', 'check'])
|
||||
output = actions.superuser_run('email_server', ['rcube', 'check'])
|
||||
results = json.loads(output)
|
||||
for i in range(0, len(results)):
|
||||
results[i] = models.Diagnosis.from_json(results[i], translation.get)
|
||||
@ -41,14 +41,14 @@ def get():
|
||||
|
||||
|
||||
def repair():
|
||||
actions.superuser_run('email_server', ['-i', 'rcube', 'set_up'])
|
||||
actions.superuser_run('email_server', ['rcube', 'set_up'])
|
||||
|
||||
|
||||
def repair_component(action):
|
||||
action_to_services = {'set_up': []}
|
||||
if action not in action_to_services:
|
||||
return
|
||||
actions.superuser_run('email_server', ['-i', 'rcube', action])
|
||||
actions.superuser_run('email_server', ['rcube', action])
|
||||
return action_to_services[action]
|
||||
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ def get():
|
||||
|
||||
|
||||
def repair():
|
||||
actions.superuser_run('email_server', ['-i', 'spam', 'set_filter'])
|
||||
actions.superuser_run('email_server', ['spam', 'set_filter'])
|
||||
|
||||
|
||||
def check_filter(title=''):
|
||||
|
||||
@ -90,20 +90,20 @@ def _get_superuser_results(results):
|
||||
translation = {
|
||||
'cert_availability': _('Has a TLS certificate'),
|
||||
}
|
||||
dump = actions.superuser_run('email_server', ['-i', 'tls', 'check'])
|
||||
dump = actions.superuser_run('email_server', ['tls', 'check'])
|
||||
for jmap in json.loads(dump):
|
||||
results.append(models.Diagnosis.from_json(jmap, translation.get))
|
||||
|
||||
|
||||
def repair():
|
||||
actions.superuser_run('email_server', ['-i', 'tls', 'set_up'])
|
||||
actions.superuser_run('email_server', ['tls', 'set_up'])
|
||||
|
||||
|
||||
def repair_component(action):
|
||||
action_to_services = {'set_cert': ['dovecot', 'postfix']}
|
||||
if action not in action_to_services: # action not allowed
|
||||
return
|
||||
actions.superuser_run('email_server', ['-i', 'tls', action])
|
||||
actions.superuser_run('email_server', ['tls', action])
|
||||
return action_to_services[action]
|
||||
|
||||
|
||||
@ -168,8 +168,8 @@ def write_dovecot_cert_config(cert, key):
|
||||
def check_postfix_cert_usage(title=''):
|
||||
prefix = '/etc/letsencrypt/live/'
|
||||
diagnosis = models.Diagnosis(title, action='set_cert')
|
||||
conf = postconf.get_many_unsafe(['smtpd_tls_cert_file',
|
||||
'smtpd_tls_key_file'])
|
||||
conf = postconf.get_many_unsafe(
|
||||
['smtpd_tls_cert_file', 'smtpd_tls_key_file'])
|
||||
if not conf['smtpd_tls_cert_file'].startswith(prefix):
|
||||
diagnosis.error("Cert file not in Let's Encrypt directory")
|
||||
if not conf['smtpd_tls_key_file'].startswith(prefix):
|
||||
@ -197,7 +197,7 @@ def action_set_cert():
|
||||
|
||||
|
||||
def action_check():
|
||||
checks = ('cert_availability',)
|
||||
checks = ('cert_availability', )
|
||||
results = []
|
||||
for check_name in checks:
|
||||
check_function = globals()['su_check_' + check_name]
|
||||
|
||||
@ -1,31 +1,10 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth <https://localhost/plinth/apps/email_server>
|
||||
|
||||
# Outlook and Windows Mail works only with LOGIN mechanism, not the standard
|
||||
# PLAIN:
|
||||
auth_mechanisms = plain login
|
||||
|
||||
passdb {
|
||||
driver = ldap
|
||||
args = /etc/dovecot/freedombox-ldap-passdb.conf.ext
|
||||
# Block the default passdb lookup (pam, 10-auth.conf)
|
||||
result_failure = return-fail
|
||||
result_internalfail = return-fail
|
||||
result_success = return-ok
|
||||
}
|
||||
|
||||
userdb {
|
||||
# UID number lookup (10001@example.com)
|
||||
driver = ldap
|
||||
args = /etc/dovecot/freedombox-ldap-userdb-uid.conf.ext
|
||||
result_failure = continue
|
||||
result_internalfail = return-fail
|
||||
result_success = return-ok
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = ldap
|
||||
args = /etc/dovecot/freedombox-ldap-userdb.conf.ext
|
||||
# Block the default userdb lookup (passwd, 10-auth.conf)
|
||||
result_failure = return-fail
|
||||
result_internalfail = return-fail
|
||||
result_success = return-ok
|
||||
}
|
||||
# Allow authentication from attempts that provide username as
|
||||
# user@domain.example.
|
||||
auth_username_format = %Ln
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth <https://localhost/plinth/apps/email_server>
|
||||
|
||||
# Users in FreedomBox are not expected to access mail by logging into the
|
||||
# system. Storing the mail in single location instead of home directories and
|
||||
# with single UID/GID simplifies security reasoning and backup/restore
|
||||
# operations.
|
||||
#
|
||||
# When FreedomBox has multiple domains a user is expected to get a mailbox that
|
||||
# is same across the domains. Changing an domain name is not uncommon in
|
||||
# FreedomBox. So, authenticate and store mails based on username only instead of
|
||||
# including domain names in storage path.
|
||||
#
|
||||
# For authdb and userdb, the passwd driver looks up using NSS. In FreedomBox,
|
||||
# NSS is configured to lookup LDAP with the help of libnss-ldapd. There is no
|
||||
# need to configure LDAP lookup separately.
|
||||
#
|
||||
# Directories are created under /var/mail as necessary by dovecot. Permissions
|
||||
# for newly created directories are inherited from parent directory. FreedomBox
|
||||
# will remove all permissions for 'others' from /var/mail to ensure that mail is
|
||||
# not read by non-root users.
|
||||
userdb {
|
||||
driver = passwd
|
||||
override_fields = home=/var/mail/%Ln uid=mail gid=mail
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth <https://localhost/plinth/apps/email_server>
|
||||
|
||||
# Use sdbox, a format specific to dovecot, for storing mails. The format allows
|
||||
# better performance with some IMAP queries. When this is combined with Full
|
||||
# Text Search (FTS), users will get optimal web and desktop mail experience.
|
||||
# Don't pick mdbox format because is requires regular expunge maintenance. We
|
||||
# have enabled btrfs filesystem compression by default.
|
||||
mail_location = sdbox:~/mail
|
||||
|
||||
# We try to deliver all mail using a single UID 'mail' and a single GID 'mail'.
|
||||
# In Debian, UID of mail user is 8 and GID of mail user is 8 as set in
|
||||
# /usr/share/base-passwd/{passwd|group}.master. By default first valid UID in
|
||||
# dovecot is 500.
|
||||
first_valid_uid = 8
|
||||
last_valid_uid = 8
|
||||
@ -1,10 +1,6 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth <https://localhost/plinth/apps/email_server>
|
||||
|
||||
# Privacy: remove the recipient's UID number from email headers
|
||||
lmtp_add_received_header = no
|
||||
lmtp_hdr_delivery_address = original
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth <https://localhost/plinth/apps/email_server>
|
||||
|
||||
uris = ldap://127.0.0.1
|
||||
base = dc=thisbox
|
||||
|
||||
auth_bind = yes
|
||||
auth_bind_userdn = uid=%u,ou=users,dc=thisbox
|
||||
@ -1,22 +0,0 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth https://localhost/plinth/apps/email_server
|
||||
|
||||
uris = ldap://127.0.0.1
|
||||
base = dc=thisbox
|
||||
|
||||
user_attrs = \
|
||||
=home=%{ldap:homeDirectory}, \
|
||||
=uid=%{ldap:uidNumber}, \
|
||||
=gid=%{ldap:gidNumber}, \
|
||||
=user=%{ldap:uid}, \
|
||||
=mail=maildir:~/Maildir:LAYOUT=index
|
||||
|
||||
# Support user lookup by UID number
|
||||
|
||||
user_filter = \
|
||||
(&(objectClass=posixAccount)(!(uidNumber=0))(uidNumber=%n))
|
||||
|
||||
# doveadm -A
|
||||
|
||||
iterate_attrs = =user=%{ldap:uid}
|
||||
iterate_filter = (objectClass=posixAccount)
|
||||
@ -1,20 +0,0 @@
|
||||
# Direct edits to this file will be lost!
|
||||
# Manage your settings on Plinth <https://localhost/plinth/apps/email_server>
|
||||
|
||||
uris = ldap://127.0.0.1
|
||||
base = dc=thisbox
|
||||
|
||||
user_attrs = \
|
||||
=home=%{ldap:homeDirectory}, \
|
||||
=uid=%{ldap:uidNumber}, \
|
||||
=gid=%{ldap:gidNumber}, \
|
||||
=mail=maildir:~/Maildir:LAYOUT=index
|
||||
|
||||
# Support user lookup by username
|
||||
|
||||
user_filter = (&(objectClass=posixAccount)(uid=%Ln)(!(uidNumber=0)))
|
||||
|
||||
# For doveadm
|
||||
|
||||
iterate_attrs = =user=%{ldap:uid}
|
||||
iterate_filter = (objectClass=posixAccount)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user