Release v20.4 to unstable

-----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAl5m41EWHGp2YWxsZXJv
 eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICOUkEACjg8HvoAWTRjvIrQWzhGFw4h8p
 WiLmjiSfI8TZ7L+YwyJn5B1vSXnzIjeVxo8lnC9OAQbHWjpCppehNlKP0gb8Ue9U
 HCIp0yOUg/mVmSUSt/ZAenJbniNNZiNmKmwn7/c0B1yDpEI8u9d9hiXeeziarRw8
 U+UlJXew3RqmgAizA1KyVg4Lkpanx0LHUndyHcSJjk7TSWJtUtESpbjriEWoAvz3
 fKa2aLXJzrFCIFsNcQD4+RixpPpZKUsVJupdpHlJI/M+Udl+6GBgEe3nJ9GQwNIj
 UGTFjGE6kHmu0mEWpmKTFdjH5aIxaE2U3wCaGuhf9x4adwdYw1y5IYDi2MGdMcrC
 w/LI6fYERkHKlmUFIxQLgfzxpeADKk5GfuEMRs3funmKVOs3RLOVmr+4xq3htpHI
 o28gFEes40tot9aBc6nPnmeqXekfmvA/G18oBXOHBz32P+KezRBklG1MPVn1yz2s
 3quxNc3AYA55mPTP2pyVnWeMdPQM+tWci9jR1t7wIOSJBB11+6rppj4F1TAH1hvj
 dgjFKFT0NVj/RQxJLFI/4McxC7QTXgmH/ab6paa0E2na2yhn36lC150TxnmtKd+m
 zKSJHnnDo/OFKM01F5RBqpk6zTMFkmx4ud1OzTxGkEdf2+xFfqa1AFIi+2Yj/Wtk
 VwLz31+fYgE7M8Yoyg==
 =g0x5
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAl5qGS8WHGp2YWxsZXJv
 eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICJ4REACYPuABNTr3Txm9nDmDVFUXLD01
 ThZRaJiOY+BK/s360qeiM8pjYT1gh4wunCY6tabNjl1OsHQDYqXmqqp93cOOZzz6
 xO2maQWH+yEy9Amvj9QCCcKC3fByCIOvcQz3xGSzrrcNBAQOczQKxZGY7Vm9SkBR
 7bba3F7rE5eaIeeIL8H3o0MINBrhPmRLnS26wZa+GtIP3HqZyCUsOwf6mpLMW0du
 DVuimiWwQ+f/tuFtv2vVEObaA2Kvb5ZJxMruCB9ZRDxxnBvO8Cux0FkZRd1xsw7D
 vzbcAftA9YQyTwhZFSDCvgRM3VjAg3DF84i/CgGRfy0y7Qs5KdGPMMihc6M3Wnp7
 Vq5UTndfXdZOaQnw9bD5rAr77o+G0aEE8rtV1k7urmO/S+VFJMTj5p0TYvxpzKCz
 VilIEPp8bLcA3c8ycSuzoz5igACNO526uyMIV9S+6oxuF6gzGe+DwdlTvqu1p923
 w/ffrxhHMO2Li9WR1ZuQ2/g7CG4Tc+MIDjfmKQ54B56M/yA5WTOHT+OWj9b4bwTB
 /19jYtLnPfrZsHC9zQpqCoybJ6+KtIYtBX4/D7vHKmghaGAMgJpcS5oVxCp8Rf1r
 I3k7x3eo5jNEslcE0Hjpi3FQBmwRHNi4aRDnG41chP9uz3SR/0FgmySyv2HK813u
 rXCSxvUKmZgCTgH4HA==
 =Dhqd
 -----END PGP SIGNATURE-----

Merge tag 'v20.4' into debian/buster-backports

Release v20.4 to unstable

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
James Valleroy 2020-03-12 07:12:44 -04:00
commit 55fd37304b
116 changed files with 14099 additions and 11173 deletions

View File

@ -1,4 +1,4 @@
image: debian:unstable
image: debian:testing
before_script:
- export DEBIAN_FRONTEND=noninteractive

View File

@ -7,12 +7,15 @@ Helper script for configuring Shadowsocks.
import argparse
import json
import os
import subprocess
import sys
from plinth import action_utils
from plinth.modules import shadowsocks
SHADOWSOCKS_CONFIG = '/etc/shadowsocks-libev/freedombox.json'
SHADOWSOCKS_CONFIG_SYMLINK = '/etc/shadowsocks-libev/freedombox.json'
SHADOWSOCKS_CONFIG_ACTUAL = \
'/var/lib/private/shadowsocks-libev/freedombox/freedombox.json'
def parse_arguments():
@ -26,6 +29,11 @@ def parse_arguments():
subparsers.add_parser('merge-config',
help='Merge JSON config from stdin with existing')
# Migrations
subparsers.add_parser(
'migrate-1-2',
help='Move shadowsocks config file to a secure location')
subparsers.required = True
return parser.parse_args()
@ -35,23 +43,25 @@ def subcommand_setup(_):
# Only client socks5 proxy is supported for now. Disable the
# server component.
action_utils.service_disable('shadowsocks-libev')
if not os.path.islink(SHADOWSOCKS_CONFIG_SYMLINK):
os.symlink(SHADOWSOCKS_CONFIG_ACTUAL, SHADOWSOCKS_CONFIG_SYMLINK)
def subcommand_get_config(arguments):
def subcommand_get_config(_):
"""Read and print Shadowsocks configuration."""
try:
print(open(SHADOWSOCKS_CONFIG, 'r').read())
print(open(SHADOWSOCKS_CONFIG_SYMLINK, 'r').read())
except Exception:
sys.exit(1)
def subcommand_merge_config(arguments):
def subcommand_merge_config(_):
"""Configure Shadowsocks."""
config = sys.stdin.read()
config = json.loads(config)
try:
current_config = open(SHADOWSOCKS_CONFIG, 'r').read()
current_config = open(SHADOWSOCKS_CONFIG_SYMLINK, 'r').read()
current_config = json.loads(current_config)
except (OSError, json.JSONDecodeError):
current_config = {}
@ -59,16 +69,20 @@ def subcommand_merge_config(arguments):
new_config = current_config
new_config.update(config)
new_config = json.dumps(new_config, indent=4, sort_keys=True)
open(SHADOWSOCKS_CONFIG_SYMLINK, 'w').write(new_config)
# XXX: Config file with password is world-readable. This is the
# same as the default config file, but find a way to avoid this.
# See https://salsa.debian.org/freedombox-team/plinth/-/merge_requests/1724
old_umask = os.umask(0o022)
try:
open(SHADOWSOCKS_CONFIG, 'w').write(new_config)
finally:
os.umask(old_umask)
action_utils.service_restart(shadowsocks.managed_services[0])
def subcommand_migrate_1_2(_):
"""Move shadowsocks config file to a secure location."""
if os.path.isfile(SHADOWSOCKS_CONFIG_SYMLINK): # ignoring symlinks
os.makedirs('/var/lib/private/shadowsocks-libev/freedombox/',
exist_ok=True)
os.replace(SHADOWSOCKS_CONFIG_SYMLINK, SHADOWSOCKS_CONFIG_ACTUAL)
os.symlink(SHADOWSOCKS_CONFIG_ACTUAL, SHADOWSOCKS_CONFIG_SYMLINK)
subprocess.check_call(['systemctl', 'daemon-reload'])
action_utils.service_restart(shadowsocks.managed_services[0])

View File

@ -333,6 +333,9 @@ def subcommand_usage_info(_):
def subcommand_validate_directory(arguments):
"""Validate a directory"""
if os.geteuid() == 0:
raise RuntimeError('You must not be root to run this command')
directory = arguments.path
def part_exists(path):
@ -349,14 +352,15 @@ def subcommand_validate_directory(arguments):
if not os.path.exists(directory):
# doesn't exist
print('ValidationError: 1')
return
if not os.path.isdir(directory):
# is not a directory
print('ValidationError: 2')
if not os.access(directory, os.R_OK):
elif not os.access(directory, os.R_OK):
# is not readable
print('ValidationError: 3')
if arguments.check_writable or arguments.check_creatable:
elif arguments.check_writable or arguments.check_creatable:
if not os.access(directory, os.W_OK):
# is not writable
print('ValidationError: 4')

60
debian/changelog vendored
View File

@ -1,3 +1,63 @@
plinth (20.4) unstable; urgency=medium
[ Thomas Vincent ]
* Translated using Weblate (French)
* Translated using Weblate (French)
[ Sunil Mohan Adapa ]
* networks: Fixes for networks wizards
* avahi: Use generic app view
* privoxy: Use generic app view
* infinoted: Move views to a separate views module
* help: Rename views modules as 'views'
* networks: Rename views modules as 'views'
* diagnostics: Rename views modules, move utilities to main module
* backups: cosmetic: Rename .inc file to .html
* css: Merge responsive.css into main style file
* css: cosmetic: Rename plinth.css to main.css
* views: Don't send app to template context
* app: Fix showing app name in port forwarding information
* networks: Rename polkit JS authority rules file
* firewalld: Add polkit JS authority rules files
* networks: Show router wizard before Internet connection type wizard
* networks: Don't show router wizard if not behind a router
* networks: If topology wizard is skipped, skip router wizard too
* apache: Handle transition to php 7.4
[ Joseph Nuthalapati ]
* Translated using Weblate (Telugu)
* shadowsocks: Move user settings to state directory
[ Veiko Aasa ]
* storage: Directory selection form improvements
* transmission: Allow one to submit download directory if it is creatable
* plinth: Increase sqlite busy timeout from default 5s to 30s
* upgrades: Clean apt cache every week
* apps: Do not show status block if service is running
* i2p: New style app page layout
* quassel: Fix unable to disable application without choosing a domain name
[ Luis A. Arizmendi ]
* Translated using Weblate (Spanish)
[ Nektarios Katakis ]
* networks: Add form for network topology
* networks: Add page for network topology form
* networks: First boot view for network topology wizard
* networks: First boot step for network topology wizard
* networks: Save networks topology type to DB
* networks: Update main networks page Internet connectivity section
[ Michael Breidenbach ]
* Translated using Weblate (Swedish)
[ James Valleroy ]
* ci: Switch to testing image
* locale: Update translation strings
* doc: Fetch latest manual
-- James Valleroy <jvalleroy@mailbox.org> Mon, 09 Mar 2020 20:01:44 -0400
plinth (20.3~bpo10+1) buster-backports; urgency=medium
* Rebuild for buster-backports.

File diff suppressed because one or more lines are too long

View File

@ -1153,13 +1153,13 @@ dd if=temp/usr/lib/u-boot/A20-OLinuXino-Lime2/u-boot-sunxi-with-spl.bin of=<lime
<section>
<title>Screenshot</title>
<para>
<remark>Add when/if an interface is made for Plinth</remark>
<remark>Add when/if an interface is made for <ulink url="https://wiki.debian.org/FreedomBox/Manual/FreedomBox#">FreedomBox</ulink></remark>
</para>
</section>
<section>
<title>Using User websites</title>
<para>The module is always enabled and offers no configuration from the Plinth web interface. Currently its existence is not even visible in the Plinth web interface. </para>
<para>Using the modules capability to serve documents requires just to place the documents in the designated directory in a Plinth user's home directory in the filesystem. </para>
<para>The module is always enabled and offers no configuration from the FreedomBox web interface. There is no configuration or status page shown for this module in the FreedomBox web interface. </para>
<para>To serve documents, place the files in the designated directory in a FreedomBox user's home directory in the filesystem. </para>
<para>This directory is: <emphasis role="strong">public_html</emphasis> </para>
<para>Thus the absolute path for the directory of a user named fbx with home directory in /home/fbx will be <emphasis role="strong">/home/fbx/public_html</emphasis>. User websites will serve documents placed in this directory when requests for documents with the URI path "~fbx" are received. For the the example.org domain thus a request for the document example.org/~fbx/index.html will transfer the file in /home/fbx/public_html/index.html. </para>
</section>
@ -10094,6 +10094,68 @@ wget https://www.thinkpenguin.com/files/ath9k_firmware_free-version/htc_7010.fw]
<section>
<title>Release Notes</title>
<para>The following are the release notes for each FreedomBox version. </para>
<section>
<title>FreedomBox 20.4 (2020-03-09)</title>
<itemizedlist>
<listitem>
<para>apache: Handle transition to php 7.4 </para>
</listitem>
<listitem>
<para>app: Fix showing app name in port forwarding information </para>
</listitem>
<listitem>
<para>apps: Do not show status block if service is running </para>
</listitem>
<listitem>
<para>i2p: New style app page layout </para>
</listitem>
<listitem>
<para>locale: Update translations for French, Telugu, Spanish, and Swedish </para>
</listitem>
<listitem>
<para>networks: Add first boot step for network topology wizard </para>
</listitem>
<listitem>
<para>networks: Add form for network topology </para>
</listitem>
<listitem>
<para>networks: Don't show router wizard if not behind a router </para>
</listitem>
<listitem>
<para>networks, firewall: Support newer version of policykit </para>
</listitem>
<listitem>
<para>networks: Fixes for networks wizards access and user experience </para>
</listitem>
<listitem>
<para>networks: If topology wizard is skipped, skip router wizard too </para>
</listitem>
<listitem>
<para>networks: Show router wizard before Internet connection type wizard </para>
</listitem>
<listitem>
<para>plinth: Increase sqlite busy timeout from default 5s to 30s </para>
</listitem>
<listitem>
<para>quassel: Fix unable to disable application without choosing a domain name </para>
</listitem>
<listitem>
<para>shadowsocks: Move user settings to state directory </para>
</listitem>
<listitem>
<para>storage: Directory selection form improvements </para>
</listitem>
<listitem>
<para>transmission: Allow to submit download directory if it is creatable </para>
</listitem>
<listitem>
<para>upgrades: Clean apt cache every week </para>
</listitem>
<listitem>
<para>views: Improve template security </para>
</listitem>
</itemizedlist>
</section>
<section>
<title>FreedomBox 20.3 (2020-02-24)</title>
<itemizedlist>

File diff suppressed because one or more lines are too long

View File

@ -1028,14 +1028,13 @@ dd if=temp/usr/lib/u-boot/A20-OLinuXino-Lime2/u-boot-sunxi-with-spl.bin of=<lime
<section>
<title>Captura de pantalla</title>
<para>
<remark>Añadir cuando/si se crea un interfaz para Plinth</remark>
<remark>Añadir cuando/si se crea un interfaz para <ulink url="https://wiki.debian.org/es/FreedomBox/Manual/FreedomBox#">FreedomBox</ulink></remark>
</para>
</section>
<section>
<title>Usar User websites</title>
<para>El módulo está siempre activado y no ofrece configuración desde el interfaz web de Plinth. Actualmente ni siquiera muestra que exista. </para>
<para>Para servir documentos con el módulo solo se necesita poner los documentos en un subdirectorio designado <emphasis role="strong"><code>/home/&lt;un_usuario_de_plinth&gt;/public_html</code></emphasis>. </para>
<para><code>User websites</code> servirá los documentos que haya en este directorio cuando se reciban peticiones con la URI <code>~&lt;un_usuario_de_plinth&gt;</code>. Por tanto para un dominio <code>ejemplo.org</code> con un usuario <code>pepe</code> una petición <code>ejemplo.org/~pepe/index.html</code> transferirá el fichero <code>/home/pepe/public_html/index.html</code>. </para>
<para>El módulo está siempre activado y el interfaz web de FreedomBox no ofrece configuración ni página de estado para este módulo. Para servir documentos con el módulo solo se necesita poner los documentos en un subdirectorio designado <emphasis role="strong"><code>/home/&lt;un_usuario_de_plinth&gt;/public_html</code></emphasis>. </para>
<para><code>User websites</code> servirá los archivos que haya en este directorio cuando se reciban peticiones con la URI <code>~&lt;un_usuario_de_freedombox&gt;</code>. Por tanto para un dominio <code>ejemplo.org</code> con un usuario <code>pepe</code> una petición <code>ejemplo.org/~pepe/index.html</code> transferirá el fichero <code>/home/pepe/public_html/index.html</code>. </para>
</section>
<section>
<title>Usar SFTP para crear public_html y subir archivos</title>
@ -9976,6 +9975,68 @@ wget https://www.thinkpenguin.com/files/ath9k_firmware_free-version/htc_7010.fw]
<section>
<title>Release Notes</title>
<para>The following are the release notes for each FreedomBox version. </para>
<section>
<title>FreedomBox 20.4 (2020-03-09)</title>
<itemizedlist>
<listitem>
<para>apache: Handle transition to php 7.4 </para>
</listitem>
<listitem>
<para>app: Fix showing app name in port forwarding information </para>
</listitem>
<listitem>
<para>apps: Do not show status block if service is running </para>
</listitem>
<listitem>
<para>i2p: New style app page layout </para>
</listitem>
<listitem>
<para>locale: Update translations for French, Telugu, Spanish, and Swedish </para>
</listitem>
<listitem>
<para>networks: Add first boot step for network topology wizard </para>
</listitem>
<listitem>
<para>networks: Add form for network topology </para>
</listitem>
<listitem>
<para>networks: Don't show router wizard if not behind a router </para>
</listitem>
<listitem>
<para>networks, firewall: Support newer version of policykit </para>
</listitem>
<listitem>
<para>networks: Fixes for networks wizards access and user experience </para>
</listitem>
<listitem>
<para>networks: If topology wizard is skipped, skip router wizard too </para>
</listitem>
<listitem>
<para>networks: Show router wizard before Internet connection type wizard </para>
</listitem>
<listitem>
<para>plinth: Increase sqlite busy timeout from default 5s to 30s </para>
</listitem>
<listitem>
<para>quassel: Fix unable to disable application without choosing a domain name </para>
</listitem>
<listitem>
<para>shadowsocks: Move user settings to state directory </para>
</listitem>
<listitem>
<para>storage: Directory selection form improvements </para>
</listitem>
<listitem>
<para>transmission: Allow to submit download directory if it is creatable </para>
</listitem>
<listitem>
<para>upgrades: Clean apt cache every week </para>
</listitem>
<listitem>
<para>views: Improve template security </para>
</listitem>
</itemizedlist>
</section>
<section>
<title>FreedomBox 20.3 (2020-02-24)</title>
<itemizedlist>

View File

@ -23,12 +23,12 @@ def get_service_module(service_name):
def is_running(browser, service_name):
interface.nav_to_module(browser, get_service_module(service_name))
return len(browser.find_by_css('.running-status.active')) != 0
return len(browser.find_by_id('service-not-running')) == 0
def is_not_running(browser, service_name):
interface.nav_to_module(browser, get_service_module(service_name))
return len(browser.find_by_css('.running-status.inactive')) != 0
return len(browser.find_by_id('service-not-running')) != 0
def eventually(function, args=[], timeout=30):

View File

@ -3,4 +3,4 @@
Package init file.
"""
__version__ = '20.3'
__version__ = '20.4'

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

View File

@ -14,30 +14,18 @@
</IfModule>
<FilesMatch ".+\.ph(ar|p|tml)$">
<IfFile /etc/php/7.3>
SetHandler "proxy:unix:/run/php/php7.3-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/7.4>
<If "-e '/run/php/php-fpm.sock'">
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost"
</If>
<ElseIf "-e '/run/php/php7.4-fpm.sock'">
SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/7.5>
SetHandler "proxy:unix:/run/php/php7.5-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/7.6>
SetHandler "proxy:unix:/run/php/php7.6-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/8.0>
SetHandler "proxy:unix:/run/php/php8.0-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/8.1>
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/8.2>
SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
</IfFile>
<IfFile /etc/php/8.3>
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</IfFile>
</ElseIf>
<ElseIf "-e '/run/php/php7.3-fpm.sock'">
SetHandler "proxy:unix:/run/php/php7.3-fpm.sock|fcgi://localhost"
</ElseIf>
<Else>
Require all denied
</Else>
</FilesMatch>
<FilesMatch ".+\.phps$">
# Deny access to raw php sources by default

View File

@ -14,7 +14,6 @@ from plinth.modules.firewall.components import Firewall
from plinth.modules.names.components import DomainType
from plinth.signals import domain_added, domain_removed, post_hostname_change
from plinth.utils import format_lazy
from plinth.views import AppView
from .manifest import backup # noqa, pylint: disable=unused-import
@ -109,7 +108,3 @@ def on_post_hostname_change(sender, old_hostname, new_hostname, **kwargs):
name=old_hostname + '.local')
domain_added.send_robust(sender='avahi', domain_type='domain-type-local',
name=new_hostname + '.local', services='__all__')
class AvahiAppView(AppView):
app_id = 'avahi'

View File

@ -5,8 +5,8 @@ URLs for the service discovery module.
from django.conf.urls import url
from plinth.modules.avahi import AvahiAppView
from plinth.views import AppView
urlpatterns = [
url(r'^sys/avahi/$', AvahiAppView.as_view(), name='index'),
url(r'^sys/avahi/$', AppView.as_view(app_id='avahi'), name='index'),
]

View File

@ -58,7 +58,7 @@
<h3>{% trans 'Existing Backups' %}</h3>
{% for repository in repositories %}
{% include "backups_repository.inc" with uuid=repository.uuid %}
{% include "backups_repository.html" with uuid=repository.uuid %}
{% endfor %}
{% endblock %}

View File

@ -17,7 +17,6 @@ from .forms import BindForm
class BindAppView(AppView): # pylint: disable=too-many-ancestors
"""A specialized view for configuring Bind."""
app_id = 'bind'
show_status_block = True
form_class = BindForm
template_name = 'bind.html'
port_forwarding_info = port_forwarding_info

View File

@ -8,7 +8,6 @@ from plinth.views import AppView
class CockpitAppView(AppView):
app_id = 'cockpit'
show_status_block = True
template_name = 'cockpit.html'
def get_context_data(self, *args, **kwargs):

View File

@ -22,7 +22,6 @@ class ConfigAppView(views.AppView):
"""Serve configuration page."""
form_class = ConfigurationForm
app_id = 'config'
show_status_block = False
def get_initial(self):
"""Return the current status"""

View File

@ -17,7 +17,6 @@ class CoquelicotAppView(views.AppView):
"""Serve configuration page."""
app_id = 'coquelicot'
form_class = CoquelicotForm
show_status_block = True
def get_initial(self):
"""Return the status of the service to fill in the form."""

View File

@ -18,5 +18,5 @@ class DelugeForm(DirectorySelectForm):
check_creatable=True)
super(DelugeForm, self).__init__(
title=_('Download directory'),
default='/var/lib/deluged/Downloads/', validator=validator, *args,
default='/var/lib/deluged/Downloads', validator=validator, *args,
**kw)

View File

@ -4,7 +4,6 @@ Django views for Deluge.
"""
import json
import os
from django.contrib import messages
from django.utils.translation import ugettext as _
@ -25,8 +24,7 @@ class DelugeAppView(views.AppView):
status = super().get_initial()
configuration = json.loads(
actions.superuser_run('deluge', ['get-configuration']))
status['storage_path'] = os.path.normpath(
configuration['download_location'])
status['storage_path'] = configuration['download_location']
return status
def form_valid(self, form):

View File

@ -3,6 +3,11 @@
FreedomBox app for system diagnostics.
"""
import collections
import importlib
import logging
import threading
from django.utils.translation import ugettext_lazy as _
from plinth import app as app_module
@ -23,6 +28,12 @@ _description = [
app = None
logger = logging.Logger(__name__)
running_task = None
current_results = {}
class DiagnosticsApp(app_module.App):
"""FreedomBox app for diagnostics."""
@ -59,3 +70,59 @@ def init():
global app
app = DiagnosticsApp()
app.set_enabled(True)
def start_task():
"""Start the run task in a separate thread."""
global running_task
if running_task:
raise Exception('Task already running')
running_task = threading.Thread(target=_run_on_all_enabled_modules_wrapper)
running_task.start()
def _run_on_all_enabled_modules_wrapper():
"""Wrapper over actual task to catch exceptions."""
try:
run_on_all_enabled_modules()
except Exception as exception:
logger.exception('Error running diagnostics - %s', exception)
current_results['error'] = str(exception)
global running_task
running_task = None
def run_on_all_enabled_modules():
"""Run diagnostics on all the enabled modules and store the result."""
global current_results
current_results = {
'apps': [],
'results': collections.OrderedDict(),
'progress_percentage': 0
}
apps = []
for app in app_module.App.list():
# XXX: Implement more cleanly.
# Don't run diagnostics on apps have not been setup yet.
# However, run on apps that need an upgrade.
module = importlib.import_module(app.__class__.__module__)
if module.setup_helper.get_state() == 'needs-setup':
continue
if not app.is_enabled():
continue
if not app.has_diagnostics():
continue
apps.append((app.app_id, app))
current_results['results'][app.app_id] = None
current_results['apps'] = apps
for current_index, (app_id, app) in enumerate(apps):
current_results['results'][app_id] = app.diagnose()
current_results['progress_percentage'] = \
int((current_index + 1) * 100 / len(apps))

View File

@ -1,108 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for running diagnostics.
"""
import collections
import importlib
import logging
import threading
from django.http import Http404
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import require_POST
from plinth.app import App
from plinth.modules import diagnostics
logger = logging.Logger(__name__)
current_results = {}
_running_task = None
def index(request):
"""Serve the index page"""
if request.method == 'POST' and not _running_task:
_start_task()
return TemplateResponse(
request, 'diagnostics.html', {
'app_info': diagnostics.app.info,
'is_running': _running_task is not None,
'results': current_results
})
@require_POST
def diagnose_app(request, app_id):
"""Return diagnostics for a particular app."""
try:
app = App.get(app_id)
except KeyError:
raise Http404('App does not exist')
return TemplateResponse(request, 'diagnostics_app.html', {
'title': _('Diagnostic Test'),
'app_id': app_id,
'results': app.diagnose()
})
def _start_task():
"""Start the run task in a separate thread."""
global _running_task
if _running_task:
raise Exception('Task already running')
_running_task = threading.Thread(
target=_run_on_all_enabled_modules_wrapper)
_running_task.start()
def _run_on_all_enabled_modules_wrapper():
"""Wrapper over actual task to catch exceptions."""
try:
run_on_all_enabled_modules()
except Exception as exception:
logger.exception('Error running diagnostics - %s', exception)
current_results['error'] = str(exception)
global _running_task
_running_task = None
def run_on_all_enabled_modules():
"""Run diagnostics on all the enabled modules and store the result."""
global current_results
current_results = {
'apps': [],
'results': collections.OrderedDict(),
'progress_percentage': 0
}
apps = []
for app in App.list():
# XXX: Implement more cleanly.
# Don't run diagnostics on apps have not been setup yet.
# However, run on apps that need an upgrade.
module = importlib.import_module(app.__class__.__module__)
if module.setup_helper.get_state() == 'needs-setup':
continue
if not app.is_enabled():
continue
if not app.has_diagnostics():
continue
apps.append((app.app_id, app))
current_results['results'][app.app_id] = None
current_results['apps'] = apps
for current_index, (app_id, app) in enumerate(apps):
current_results['results'][app_id] = app.diagnose()
current_results['progress_percentage'] = \
int((current_index + 1) * 100 / len(apps))

View File

@ -5,7 +5,7 @@ URLs for the Diagnostics module
from django.conf.urls import url
from . import diagnostics as views
from . import views
urlpatterns = [
url(r'^sys/diagnostics/$', views.index, name='index'),

View File

@ -0,0 +1,40 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for running diagnostics.
"""
from django.http import Http404
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.http import require_POST
from plinth.app import App
from plinth.modules import diagnostics
def index(request):
"""Serve the index page"""
if request.method == 'POST' and not diagnostics.running_task:
diagnostics.start_task()
return TemplateResponse(
request, 'diagnostics.html', {
'app_info': diagnostics.app.info,
'is_running': diagnostics.running_task is not None,
'results': diagnostics.current_results
})
@require_POST
def diagnose_app(request, app_id):
"""Return diagnostics for a particular app."""
try:
app = App.get(app_id)
except KeyError:
raise Http404('App does not exist')
return TemplateResponse(request, 'diagnostics_app.html', {
'title': _('Diagnostic Test'),
'app_id': app_id,
'results': app.diagnose()
})

View File

@ -0,0 +1,16 @@
/*
# SPDX-License-Identifier: AGPL-3.0-or-later
This file is used only by policykit-1 version > 0.105. A corresponding .pkla
file is used by policykit-1 <= 0.105. See:
https://davidz25.blogspot.com/2012/06/authorization-rules-in-polkit.html
*/
polkit.addRule(function(action, subject) {
if ((action.id == "org.fedoraproject.FirewallD1.config.info" ||
action.id == "org.fedoraproject.FirewallD1.config") &&
subject.user == "plinth") {
return polkit.Result.YES;
}
});

View File

@ -23,7 +23,6 @@ class GitwebAppView(views.AppView):
"""Serve configuration page."""
app_id = 'gitweb'
show_status_block = False
template_name = 'gitweb_configure.html'
def get_context_data(self, *args, **kwargs):

View File

@ -7,7 +7,7 @@ from django.conf.urls import url
from plinth.utils import non_admin_view
from . import help as views
from . import views
urlpatterns = [
url(r'^help/$', non_admin_view(views.index), name='index'),

View File

@ -7,16 +7,31 @@
{% load i18n %}
{% block configuration %}
{{ block.super }}
<h3>{% trans "Configuration" %}</h3>
<h3>{% trans "I2P Proxies and Tunnels" %}</h3>
{% for line in proxies_description %}
<p>{{ line|safe }}</p>
{% endfor %}
<form class="form form-configuration" method="post">
{% csrf_token %}
<p>
<a class="btn btn-primary" target="_blank" role="button"
href="/i2p/i2ptunnel/"
{{ is_enabled|yesno:',disabled="disabled"' }}>
{% trans "Launch" %}
</a>
</p>
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary"
value="{% trans "Update setup" %}"/>
</form>
<h3>{% trans "Anonymous Torrents" %}</h3>
{% for line in torrents_description %}
<p>{{ line|safe }}</p>
{% endfor %}
<p>
<a class="btn btn-primary" target="_blank" role="button"
href="/i2p/i2psnark/"
{{ is_enabled|yesno:',disabled="disabled"' }}>
{% trans "Launch" %}
</a>
</p>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends "app.html" %}
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block configuration %}
{% for line in service_description %}
<p>{{ line|safe }}</p>
{% endfor %}
<p>
<a class="btn btn-primary" target="_blank" role="button"
href="{{ service_path }}">
{% trans "Launch" %}
</a>
</p>
{% endblock %}

View File

@ -4,12 +4,6 @@ URLs for the I2P module.
"""
from django.conf.urls import url
from plinth.modules.i2p import views
urlpatterns = [
url(r'^apps/i2p/$', views.I2PAppView.as_view(), name='index'),
url(r'^apps/i2p/tunnels/?$', views.TunnelsView.as_view(), name='tunnels'),
url(r'^apps/i2p/torrents/?$', views.TorrentsView.as_view(),
name='torrents'),
]
urlpatterns = [url(r'^apps/i2p/$', views.I2PAppView.as_view(), name='index')]

View File

@ -3,67 +3,16 @@
Views for I2P application.
"""
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views.generic import TemplateView
import plinth.modules.i2p as i2p
from plinth.modules import i2p
from plinth.views import AppView
subsubmenu = [{
'url': reverse_lazy('i2p:index'),
'text': ugettext_lazy('Configure')
}, {
'url': reverse_lazy('i2p:tunnels'),
'text': ugettext_lazy('Proxies')
}, {
'url': reverse_lazy('i2p:torrents'),
'text': ugettext_lazy('Anonymous torrents')
}]
class I2PAppView(AppView):
"""Serve configuration page."""
app_id = 'i2p'
show_status_block = True
template_name = 'i2p.html'
def get_context_data(self, **kwargs):
"""Return the context data for rendering the template view."""
context = super().get_context_data(**kwargs)
context['title'] = i2p.app.info.name
context['app_info'] = i2p.app.info
context['subsubmenu'] = subsubmenu
context['port_forwarding_info'] = i2p.port_forwarding_info
return context
class ServiceBaseView(TemplateView):
"""View to describe and launch a service."""
service_description = None
service_title = None
service_path = None
def get_context_data(self, **kwargs):
"""Add context data for template."""
context = super().get_context_data(**kwargs)
context['title'] = i2p.app.info.name
context['app_info'] = i2p.app.info
context['subsubmenu'] = subsubmenu
context['is_enabled'] = i2p.app.is_enabled()
context['service_title'] = self.service_title
context['service_path'] = self.service_path
context['service_description'] = self.service_description
return context
class TunnelsView(ServiceBaseView):
"""View to describe and launch tunnel configuration."""
template_name = 'i2p_service.html'
service_title = _('I2P Proxies and Tunnels')
service_path = '/i2p/i2ptunnel/'
service_description = [
proxies_description = [
_('I2P lets you browse the Internet and hidden services (eepsites) '
'anonymously. For this, your browser, preferably a Tor Browser, '
'needs to be configured for a proxy.'),
@ -71,15 +20,17 @@ class TunnelsView(ServiceBaseView):
'proxies and tunnels may be configured using the tunnel '
'configuration interface.'),
]
class TorrentsView(ServiceBaseView):
"""View to describe and launch I2P torrents application."""
template_name = 'i2p_service.html'
service_title = _('Anonymous Torrents')
service_path = '/i2p/i2psnark/'
service_description = [
torrents_description = [
_('I2P provides an application to download files anonymously in a '
'peer-to-peer network. Download files by adding torrents or '
'create a new torrent to share a file.'),
]
def get_context_data(self, **kwargs):
"""Return the context data for rendering the template view."""
context = super().get_context_data(**kwargs)
context['port_forwarding_info'] = i2p.port_forwarding_info
context['proxies_description'] = self.proxies_description
context['torrents_description'] = self.torrents_description
return context

View File

@ -18,7 +18,6 @@ from .forms import IkiwikiCreateForm
class IkiwikiAppView(views.AppView):
"""Serve configuration page."""
app_id = 'ikiwiki'
show_status_block = False
template_name = 'ikiwiki_configure.html'
def get_context_data(self, **kwargs):

View File

@ -12,7 +12,6 @@ from plinth import cfg, frontpage, menu
from plinth.daemon import Daemon
from plinth.modules.firewall.components import Firewall
from plinth.utils import format_lazy
from plinth.views import AppView
from .manifest import backup, clients # noqa, pylint: disable=unused-import
@ -83,11 +82,6 @@ def init():
app.set_enabled(True)
class InfinotedAppView(AppView):
app_id = 'infinoted'
port_forwarding_info = port_forwarding_info
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages)

View File

@ -5,7 +5,7 @@ URLs for the infinoted module.
from django.conf.urls import url
from plinth.modules.infinoted import InfinotedAppView
from .views import InfinotedAppView
urlpatterns = [
url(r'^apps/infinoted/$', InfinotedAppView.as_view(), name='index'),

View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Views for the infinoted app.
"""
from plinth.modules import infinoted
from plinth.views import AppView
class InfinotedAppView(AppView):
"""Main app view for Infinoted."""
app_id = 'infinoted'
port_forwarding_info = infinoted.port_forwarding_info

View File

@ -13,7 +13,6 @@ class JSXCAppView(AppView):
"""Show ejabberd as an app."""
app_id = 'jsxc'
template_name = 'jsxc.html'
show_status_block = False
class JsxcView(TemplateView):

View File

@ -22,7 +22,6 @@ class MediaWikiAppView(views.AppView):
"""App configuration page."""
app_id = 'mediawiki'
form_class = MediaWikiForm
show_status_block = False
template_name = 'mediawiki.html'
def get_initial(self):

View File

@ -17,7 +17,6 @@ from .forms import MinetestForm
class MinetestAppView(AppView): # pylint: disable=too-many-ancestors
"""A specialized view for configuring minetest."""
app_id = 'minetest'
show_status_block = True
template_name = 'minetest.html'
form_class = MinetestForm
port_forwarding_info = minetest.port_forwarding_info

View File

@ -8,9 +8,5 @@ from django.conf.urls import url
from plinth.views import AppView
urlpatterns = [
url(r'^apps/mldonkey/$',
AppView.as_view(
app_id='mldonkey',
show_status_block=True,
), name='index'),
url(r'^apps/mldonkey/$', AppView.as_view(app_id='mldonkey'), name='index')
]

View File

@ -20,13 +20,18 @@ managed_packages = ['network-manager', 'batctl']
first_boot_steps = [
{
'id': 'internet_connectivity_type_wizard',
'url': 'networks:firstboot_internet_connection_type',
'order': 3,
'id': 'network_topology_wizard',
'url': 'networks:network-topology-first-boot',
'order': 2,
},
{
'id': 'router_setup_wizard',
'url': 'networks:firstboot_router_setup',
'url': 'networks:router-configuration-first-boot',
'order': 3,
},
{
'id': 'internet_connectivity_type_wizard',
'url': 'networks:internet-connection-type-first-boot',
'order': 4,
},
]
@ -42,6 +47,7 @@ logger = Logger(__name__)
app = None
NETWORK_TOPOLOGY_TYPE_KEY = 'networks_topology_type'
ROUTER_CONFIGURATION_TYPE_KEY = 'networks_router_configuration_type'
INTERNET_CONNECTION_TYPE_KEY = 'networks_internet_type'

View File

@ -1,5 +1,10 @@
/*
# SPDX-License-Identifier: AGPL-3.0-or-later
This file is used only by policykit-1 version > 0.105. A corresponding .pkla
file is used by policykit-1 <= 0.105. See:
https://davidz25.blogspot.com/2012/06/authorization-rules-in-polkit.html
*/
polkit.addRule(function(action, subject) {

View File

@ -285,6 +285,48 @@ requires clients to have the password to connect.'),
return settings
class NetworkTopologyForm(forms.Form):
"""Form to ask the user for network topology.
That is how the FreedomBox is connected to the internal network topology.
Store this information for future suggestions when setting up services.
"""
network_topology = forms.ChoiceField(
label=format_lazy(
_('Choose how your {box_name} is connected to your network'),
box_name=cfg.box_name),
required=True,
widget=forms.RadioSelect,
choices=[
('to_router',
format_lazy(
_('Connected to a router '
'<p class="help-block">Your {box_name} gets its Internet '
'connection from your router via Wi-Fi or Ethernet cable. '
'This is a typical home setup.</p>'), box_name=cfg.box_name,
allow_markup=True)),
('as_router',
format_lazy(
_('{box_name} is your router '
'<p class="help-block">Your {box_name} has multiple '
'network interfaces such as multiple Ethernet ports or '
'a Wi-Fi adapter. {box_name} is directly connected to the '
'Internet and all your devices connect to {box_name} '
'for their Internet connectivity.</p>'),
box_name=cfg.box_name, allow_markup=True)),
('direct',
format_lazy(
_('Directly connected to the Internet '
'<p class="help-block">Your Internet connection is '
'directly attached to your {box_name} and there are no '
'other devices on the network. This can happen on '
'community or cloud setups.</p>'), box_name=cfg.box_name,
allow_markup=True)),
],
)
class InternetConnectionTypeForm(forms.Form):
"""Form for type of public/private IP address ISP provides.
@ -334,13 +376,18 @@ class InternetConnectionTypeForm(forms.Form):
'{box_name} provides many workaround solutions but each '
'solution has some limitations.</p>'),
box_name=cfg.box_name, allow_markup=True)),
('unknown',
format_lazy(
_('I do not know the type of connection my ISP provides '
'<p class="help-block">You will be suggested the most '
'conservative actions.</p>'), allow_markup=True)),
],
required=True,
widget=forms.RadioSelect,
)
class RouterConfigurationWizardForm(forms.Form):
class RouterConfigurationForm(forms.Form):
"""Form to suggest how to configure a router.
Suggest depending on wan connectivity/specific setup. The choice will

View File

@ -15,7 +15,9 @@
{{ form|bootstrap }}
<a href='{{ first_boot_next_step }}'>{% trans "Skip this step" %}</a>
<input type="submit" class="btn btn-primary pull-right" value="{% trans "Next" %}"/>
<input type="submit" class="btn btn-link" name="skip"
value="{% trans "Skip this step" %}"/>
<input type="submit" class="btn btn-primary pull-right" name="next"
value="{% trans "Next" %}"/>
</form>
{% endblock %}

View File

@ -27,12 +27,16 @@
{% blocktrans trimmed %}
My ISP provides a public IP address that may change over time.
{% endblocktrans %}
{% else %}
{% elif internet_connectivity_type == "private_ip" %}
{% blocktrans trimmed %}
My ISP does not provide a public IP address.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
I do not know the type of connection my ISP provides.
{% endblocktrans %}
{% endif %}
<a href="{% url 'networks:internet_connection_type_setup' %}" class="btn btn-default"
<a href="{% url 'networks:internet-connection-type' %}" class="btn btn-default"
role="button">
{% trans 'Update...' %}
</a>

View File

@ -0,0 +1,21 @@
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
<h1>
{% blocktrans trimmed %}
How is Your {{ box_name }} Connected to the Internet?
{% endblocktrans %}
</h1>
<p>
{% blocktrans trimmed %}
Select an option that best describes how your {{ box_name }} is connected in
your network. This information is used to guide you with further setup. It can
be changed later.
{% endblocktrans %}
</p>

View File

@ -0,0 +1,24 @@
{% extends "base_firstboot.html" %}
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block content %}
{% include "network_topology_content.html" %}
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-link" name="skip"
value="{% trans "Skip this step" %}"/>
<input type="submit" class="btn btn-primary pull-right" name="next"
value="{% trans "Next" %}"/>
</form>
{% endblock %}

View File

@ -0,0 +1,43 @@
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
<h3>
{% blocktrans trimmed %}
{{ box_name }} Internet Connectivity
{% endblocktrans %}
</h3>
<p>
{% blocktrans trimmed %}
The following best describes how your {{ box_name }} is connected in your
network. This information is used only to suggest necessary configuration
actions.
{% endblocktrans %}
</p>
<p>
{% if network_topology == "to_router" %}
{% blocktrans trimmed %}
Your {{ box_name }} gets its Internet connection from your router via Wi-Fi
or Ethernet cable. This is a typical home setup.
{% endblocktrans %}
{% elif network_topology == "as_router" %}
{% blocktrans trimmed %}
Your {{ box_name }} is directly connected to the Internet and all your
devices connect to {{ box_name }} for their Internet connectivity.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
Your Internet connection is directly attached to your {{ box_name }} and there
are no other devices on the network.
{% endblocktrans %}
{% endif %}
<a href="{% url 'networks:network-topology' %}" class="btn btn-default"
role="button">
{% trans 'Update...' %}
</a>
</p>

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block content %}
{% include "network_topology_content.html" %}
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary" value="{% trans "Submit" %}"/>
</form>
{% endblock %}

View File

@ -42,7 +42,7 @@
{% block configuration %}
{% include "connections_list.html" %}
{% include "router_configuration_main.html" %}
{% include "network_topology_main.html" %}
{% include "internet_connectivity_main.html" %}
{% endblock %}

View File

@ -15,8 +15,10 @@
{{ form|bootstrap }}
<a href='{{ first_boot_next_step }}'>{% trans "Skip this step" %}</a>
<input type="submit" class="btn btn-primary pull-right" value="{% trans "Next" %}"/>
<input type="submit" class="btn btn-link" name="skip"
value="{% trans "Skip this step" %}"/>
<input type="submit" class="btn btn-primary pull-right" name="next"
value="{% trans "Next" %}"/>
</form>
{% endblock %}

View File

@ -1,31 +0,0 @@
{% comment %}
# SPDX-License-Identifier: AGPL-3.0-or-later
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
<h3>
{% blocktrans trimmed %}
{{ box_name }} Internet Connectivity
{% endblocktrans %}
</h3>
<p>
{% blocktrans trimmed %}
The following best describes how your {{ box_name }} is connected in your
network. This information is used only to suggest necessary configuration
actions.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
Your {{ box_name }} gets its Internet from your Router via Wi-Fi or
Ethernet cable. This is a typical home setup.
{% endblocktrans %}
<a href="{% url 'networks:router_setup' %}" class="btn btn-default"
role="button">
{% trans 'Update...' %}
</a>
</p>

View File

@ -4,9 +4,8 @@ URLs for the Network module
"""
from django.conf.urls import url
from stronghold.decorators import public
from . import networks as views
from . import views
urlpatterns = [
url(r'^sys/networks/$', views.index, name='index'),
@ -27,16 +26,20 @@ urlpatterns = [
r'(?P<interface_name>[^/]+)/)?$', views.add_wifi, name='add_wifi'),
url(r'^sys/networks/(?P<uuid>[\w.@+-]+)/delete/$', views.delete,
name='delete'),
url(r'^sys/networks/router-setup-guide/$',
views.router_configuration_help_page,
name='router_setup'),
url(r'^sys/networks/firstboot/router_setup/$',
public(views.router_configuration_help_page),
name='firstboot_router_setup'),
url(r'^sys/networks/router-configuration/$',
views.RouterConfigurationView.as_view(), name='router-configuration'),
url(r'^sys/networks/firstboot/router-configuration/$',
views.RouterConfigurationFirstBootView.as_view(),
name='router-configuration-first-boot'),
url(r'^sys/networks/internet-connection-type/$',
views.internet_connection_type_help_page,
name='internet_connection_type_setup'),
url(r'^sys/networks/firstboot/internet_connection_type/$',
public(views.internet_connection_type_help_page),
name='firstboot_internet_connection_type'),
views.InternetConnectionTypeView.as_view(),
name='internet-connection-type'),
url(r'^sys/networks/firstboot/internet-connection-type/$',
views.InternetConnectionTypeFirstBootView.as_view(),
name='internet-connection-type-first-boot'),
url(r'^sys/networks/network-topology/$',
views.NetworkTopologyView.as_view(), name='network-topology'),
url(r'^sys/networks/firstboot/network-topology-first-boot/$',
views.NetworkTopologyFirstBootView.as_view(),
name='network-topology-first-boot'),
]

View File

@ -3,18 +3,20 @@
import logging
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST
from django.views.generic.edit import FormView
from plinth import kvstore, network
from plinth.modules import first_boot, networks
from .forms import (ConnectionTypeSelectForm, EthernetForm, GenericForm,
InternetConnectionTypeForm, PPPoEForm,
RouterConfigurationWizardForm, WifiForm)
InternetConnectionTypeForm, NetworkTopologyForm, PPPoEForm,
RouterConfigurationForm, WifiForm)
logger = logging.getLogger(__name__)
@ -23,8 +25,10 @@ def index(request):
"""Show connection list."""
connections = network.get_connection_list()
network_topology = kvstore.get_default(networks.NETWORK_TOPOLOGY_TYPE_KEY,
'to_router')
internet_connection_type = kvstore.get_default(
networks.INTERNET_CONNECTION_TYPE_KEY, None)
networks.INTERNET_CONNECTION_TYPE_KEY, 'unknown')
return TemplateResponse(
request, 'networks_configuration.html', {
'app_id': 'networks',
@ -33,7 +37,8 @@ def index(request):
'has_diagnostics': True,
'is_enabled': True,
'connections': connections,
'internet_connectivity_type': internet_connection_type
'network_topology': network_topology,
'internet_connectivity_type': internet_connection_type,
})
@ -405,89 +410,133 @@ def delete(request, uuid):
})
def router_configuration_help_page(request):
"""Show the router configuration wizard page/form.
class NetworkTopologyView(FormView):
"""View for local network topology form."""
template_name = 'network_topology_update.html'
form_class = NetworkTopologyForm
success_url = reverse_lazy('networks:index')
Used both for fistboot step and same networks page.
def get_initial(self):
"""Get initial form data."""
return {
'network_topology':
kvstore.get_default(networks.NETWORK_TOPOLOGY_TYPE_KEY,
'to_router')
}
"""
is_firstboot = True \
if 'firstboot' in request.build_absolute_uri() else False
def form_valid(self, form):
"""Save value to DB."""
network_topology = form.cleaned_data['network_topology']
logger.info('Updating network topology type with value %s' %
network_topology)
kvstore.set(networks.NETWORK_TOPOLOGY_TYPE_KEY, network_topology)
if network_topology == 'to_router':
self.success_url = reverse_lazy('networks:router-configuration')
if request.method == 'POST' and request.POST['router_config']:
form = RouterConfigurationWizardForm(request.POST)
if form.is_valid():
logger.info('Updating router configuration setup with value: %s' %
request.POST['router_config'])
kvstore.set(networks.ROUTER_CONFIGURATION_TYPE_KEY,
request.POST['router_config'])
if is_firstboot:
resp = reverse_lazy(first_boot.next_step())
else:
resp = reverse_lazy('networks:index')
messages.success(request, _('Router configuration type saved.'))
return super().form_valid(form)
return redirect(resp)
else:
html = 'router_configuration_update.html'
initial = {
class NetworkTopologyFirstBootView(NetworkTopologyView):
"""View for network topology form during first wizard."""
template_name = 'network_topology_firstboot.html'
def get_success_url(self):
"""Return next fistboot step."""
return reverse_lazy(first_boot.next_step())
def form_valid(self, form):
"""Mark the first wizard step as done, save value and redirect."""
first_boot.mark_step_done('network_topology_wizard')
if 'skip' in form.data:
first_boot.mark_step_done('router_setup_wizard')
return FormView.form_valid(self, form)
return super().form_valid(form)
class RouterConfigurationView(FormView):
"""View for router configuration form."""
template_name = 'router_configuration_update.html'
form_class = RouterConfigurationForm
success_url = reverse_lazy('networks:index')
def get_initial(self):
"""Return initial data for the form."""
return {
'router_config':
kvstore.get_default(networks.ROUTER_CONFIGURATION_TYPE_KEY,
'not_configured'),
'not_configured')
}
template_kwargs = {
'form': RouterConfigurationWizardForm(initial=initial),
}
if is_firstboot:
html = 'router_configuration_firstboot.html'
# mark step done on firstboot visit to get the next_step
def form_valid(self, form):
"""Save value to DB and redirect."""
type_ = form.cleaned_data['router_config']
logger.info('Updating router configuration: %s', type_)
kvstore.set(networks.ROUTER_CONFIGURATION_TYPE_KEY, type_)
return super().form_valid(form)
class RouterConfigurationFirstBootView(RouterConfigurationView):
"""View for router configuration form during first wizard."""
template_name = 'router_configuration_firstboot.html'
def dispatch(self, request, *args, **kwargs):
"""Don't show wizard step if FreedomBox is not behind a router."""
network_topology = kvstore.get_default(
networks.NETWORK_TOPOLOGY_TYPE_KEY, 'to_router')
if network_topology != 'to_router':
first_boot.mark_step_done('router_setup_wizard')
template_kwargs.update({
'first_boot_next_step': reverse_lazy(first_boot.next_step()),
})
return HttpResponseRedirect(reverse_lazy(first_boot.next_step()))
return TemplateResponse(request, html, template_kwargs)
return super().dispatch(request, *args, *kwargs)
def get_success_url(self):
"""Return the next wizard step after this one."""
return reverse_lazy(first_boot.next_step())
def form_valid(self, form):
"""Mark the first wizard step as done, save value and redirect."""
first_boot.mark_step_done('router_setup_wizard')
if 'skip' in form.data:
return FormView.form_valid(self, form)
return super().form_valid(form)
def internet_connection_type_help_page(request):
"""Show the internet connection type page.
class InternetConnectionTypeView(FormView):
"""View for Internet connection type form."""
template_name = 'internet_connectivity_type.html'
form_class = InternetConnectionTypeForm
success_url = reverse_lazy('networks:index')
Used for first boot step and networks page.
"""
is_firstboot = True \
if 'firstboot' in request.build_absolute_uri() else False
if request.method == 'POST' and request.POST['internet_connection_type']:
form = InternetConnectionTypeForm(request.POST)
if form.is_valid():
logger.info('Updating internet connectivity type with value: %s' %
request.POST['internet_connection_type'])
kvstore.set(
networks.INTERNET_CONNECTION_TYPE_KEY,
request.POST['internet_connection_type'],
)
if is_firstboot:
return redirect(reverse_lazy(first_boot.next_step()))
else:
messages.success(request, _('Internet connection type saved.'))
return redirect(reverse_lazy('networks:index'))
else:
html = 'internet_connectivity_type.html'
initial = {
def get_initial(self):
"""Return initial data for the form."""
return {
'internet_connection_type':
kvstore.get_default(networks.INTERNET_CONNECTION_TYPE_KEY,
None)
'unknown')
}
template_kwargs = {'form': InternetConnectionTypeForm(initial=initial)}
if is_firstboot:
html = 'internet_connectivity_firstboot.html'
# mark step done on firstboot visit to get the next_step
first_boot.mark_step_done('internet_connectivity_type_wizard')
template_kwargs.update({
'first_boot_next_step': reverse_lazy(first_boot.next_step()),
})
def form_valid(self, form):
"""Save value to DB and redirect."""
type_ = form.cleaned_data['internet_connection_type']
logger.info('Updating internet connectivity type: %s', type_)
kvstore.set(networks.INTERNET_CONNECTION_TYPE_KEY, type_)
return super().form_valid(form)
return TemplateResponse(request, html, template_kwargs)
class InternetConnectionTypeFirstBootView(InternetConnectionTypeView):
"""View to show Internet connection type form during first wizard."""
template_name = 'internet_connectivity_firstboot.html'
def get_success_url(self):
"""Return the next wizard step after this one."""
return reverse_lazy(first_boot.next_step())
def form_valid(self, form):
"""Mark the first wizard step as done, save value and redirect."""
first_boot.mark_step_done('internet_connectivity_type_wizard')
if 'skip' in form.data:
return FormView.form_valid(self, form)
return super().form_valid(form)

View File

@ -48,7 +48,6 @@ def index(request):
'port_forwarding_info': openvpn.port_forwarding_info,
'status': status,
'form': form,
'show_status_block': True,
'is_running': status['is_running'],
'has_diagnostics': True,
'is_enabled': status['enabled'],

View File

@ -13,7 +13,6 @@ from plinth.daemon import Daemon
from plinth.modules.apache.components import diagnose_url
from plinth.modules.firewall.components import Firewall
from plinth.utils import format_lazy
from plinth.views import AppView
from .manifest import backup # noqa, pylint: disable=unused-import
@ -104,10 +103,6 @@ def setup(helper, old_version=None):
helper.call('post', app.enable)
class PrivoxyAppView(AppView):
app_id = 'privoxy'
def diagnose_url_with_proxy():
"""Run a diagnostic on a URL with a proxy."""
url = 'https://debian.org/' # Gives a simple redirect to www.

View File

@ -5,8 +5,8 @@ URLs for the Privoxy module.
from django.conf.urls import url
from plinth.modules.privoxy import PrivoxyAppView
from plinth.views import AppView
urlpatterns = [
url(r'^apps/privoxy/$', PrivoxyAppView.as_view(), name='index'),
url(r'^apps/privoxy/$', AppView.as_view(app_id='privoxy'), name='index'),
]

View File

@ -24,4 +24,5 @@ class QuasselForm(AppForm):
help_text=_(
'Select a domain to use TLS with. If the list is empty, please '
'configure at least one domain with certificates.'),
required=False,
)

View File

@ -20,7 +20,9 @@ class QuasselAppView(AppView):
def form_valid(self, form):
"""Change the access control of Radicale service."""
data = form.cleaned_data
if quassel.get_domain() != data['domain']:
app_disable = form.initial['is_enabled'] and not data['is_enabled']
if not app_disable and quassel.get_domain() != data['domain']:
quassel.set_domain(data['domain'])
quassel.app.get_component(
'letsencrypt-quassel').setup_certificates()

View File

@ -8,7 +8,6 @@ from django.conf.urls import url
from plinth.views import AppView
urlpatterns = [
url(r'^apps/roundcube/$',
AppView.as_view(app_id='roundcube', show_status_block=False),
name='index'),
url(r'^apps/roundcube/$', AppView.as_view(app_id='roundcube'),
name='index')
]

View File

@ -17,7 +17,6 @@ class SearxAppView(views.AppView):
"""Serve configuration page."""
app_id = 'searx'
form_class = SearxForm
show_status_block = False
def get_initial(self):
"""Return the status of the service to fill in the form."""

View File

@ -8,7 +8,5 @@ from django.conf.urls import url
from plinth.views import AppView
urlpatterns = [
url(r'^apps/shaarli/$',
AppView.as_view(app_id='shaarli', show_status_block=False),
name='index'),
url(r'^apps/shaarli/$', AppView.as_view(app_id='shaarli'), name='index')
]

View File

@ -15,7 +15,7 @@ from plinth.utils import format_lazy
from .manifest import backup # noqa, pylint: disable=unused-import
version = 1
version = 2
managed_services = ['shadowsocks-libev-local@freedombox']
@ -89,6 +89,11 @@ def init():
def setup(helper, old_version=None):
"""Install and configure the module."""
if old_version == 1:
helper.call('migration', actions.superuser_run, 'shadowsocks',
['migrate-1-2'])
helper.install(managed_packages)
helper.call('post', actions.superuser_run, 'shadowsocks', ['setup'])
helper.call('post', app.enable)

View File

@ -0,0 +1,2 @@
[Service]
StateDirectory=shadowsocks-libev/%i

View File

@ -7,7 +7,9 @@ from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({
'secrets': {
'files': ['/etc/shadowsocks-libev/freedombox.json']
'files': [
'/var/lib/private/shadowsocks-libev/freedombox/freedombox.json'
]
},
'services': ['shadowsocks-libev-local@freedombox']
})

View File

@ -103,7 +103,7 @@ class DirectorySelectForm(AppForm):
if title:
self.fields['storage_dir'].label = title
self.validator = validator
self.default = os.path.normpath(default)
self.default = default
self.set_form_data()
def clean(self):

View File

@ -250,45 +250,25 @@ class TestActions:
assert output == error
@pytest.mark.usefixtures('needs_not_root')
@pytest.mark.parametrize('directory', [{
'path': '/missing',
'error': '1'
}, {
'path': '/etc/os-release',
'error': '2'
}, {
'path': '/root',
'error': '3'
}, {
'path': '/',
'error': ''
}])
def test_validate_directory(self, directory):
@pytest.mark.parametrize('path,error', [('/missing', '1'),
('/etc/os-release', '2'),
('/root', '3'), ('/', ''),
('/etc/..', '')])
def test_validate_directory(self, path, error):
"""Test that directory validation returns expected output."""
self.assert_validate_directory(directory['path'], directory['error'])
self.assert_validate_directory(path, error)
@pytest.mark.usefixtures('needs_not_root')
@pytest.mark.parametrize('directory', [{
'path': '/',
'error': '4'
}, {
'path': '/tmp',
'error': ''
}])
def test_validate_directory_writable(self, directory):
@pytest.mark.parametrize('path,error', [('/', '4'), ('/tmp', '')])
def test_validate_directory_writable(self, path, error):
"""Test that directory writable validation returns expected output."""
self.assert_validate_directory(directory['path'], directory['error'],
check_writable=True)
self.assert_validate_directory(path, error, check_writable=True)
@pytest.mark.usefixtures('needs_not_root')
@pytest.mark.parametrize('directory', [{
'path': '/var/lib/plinth_storage_test_not_exists',
'error': '4'
}, {
'path': '/tmp/plint_storage_test_not_exists',
'error': ''
}])
def test_validate_directory_creatable(self, directory):
@pytest.mark.parametrize(
'path,error', [('/var/lib/plinth_storage_test_not_exists', '4'),
('/tmp/plint_storage_test_not_exists', ''),
('/var/../tmp/plint_storage_test_not_exists', '')])
def test_validate_directory_creatable(self, path, error):
"""Test that directory creatable validation returns expected output."""
self.assert_validate_directory(directory['path'], directory['error'],
check_creatable=True)
self.assert_validate_directory(path, error, check_creatable=True)

Some files were not shown because too many files have changed in this diff Show More