mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
Merge tag 'v19.23' into debian/buster-backports
Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
commit
06fe129bfb
@ -109,8 +109,9 @@ def subcommand_generate_ticket(arguments):
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, fil.read().encode())
|
||||
valid_until = minutes_from_now(12 * 60)
|
||||
grace_period = minutes_from_now(11 * 60)
|
||||
print(create_ticket(pkey, uid, valid_until, tokens=tokens,
|
||||
graceperiod=grace_period))
|
||||
print(
|
||||
create_ticket(pkey, uid, valid_until, tokens=tokens,
|
||||
graceperiod=grace_period))
|
||||
|
||||
|
||||
def minutes_from_now(minutes):
|
||||
@ -120,8 +121,8 @@ def minutes_from_now(minutes):
|
||||
|
||||
def seconds_from_now(seconds):
|
||||
"""Return a timestamp at the given number of seconds from now."""
|
||||
return (
|
||||
datetime.datetime.now() + datetime.timedelta(0, seconds)).timestamp()
|
||||
return (datetime.datetime.now() +
|
||||
datetime.timedelta(0, seconds)).timestamp()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@ -66,8 +66,8 @@ def subcommand_reset_home_page(_):
|
||||
config_file = FREEDOMBOX_APACHE_CONFIG
|
||||
default_path = 'plinth'
|
||||
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
||||
aug.set('/augeas/load/Httpd/incl[last() + 1]', config_file)
|
||||
aug.load()
|
||||
|
||||
@ -72,8 +72,8 @@ def set_domain_name(domain_name):
|
||||
# {'url': domain_name})
|
||||
# Manually changing the domain name in the conf files.
|
||||
conf_file = '/etc/diaspora.conf'
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
|
||||
# lens for shell-script config file
|
||||
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
|
||||
|
||||
@ -62,8 +62,9 @@ def parse_arguments():
|
||||
# Add a service
|
||||
add_service = subparsers.add_parser('add-service', help='Add a service')
|
||||
add_service.add_argument('service', help='Name of the service to add')
|
||||
add_service.add_argument(
|
||||
'--zone', help='Zone to which service is to be added', required=True)
|
||||
add_service.add_argument('--zone',
|
||||
help='Zone to which service is to be added',
|
||||
required=True)
|
||||
|
||||
# Remove a service status
|
||||
remove_service = subparsers.add_parser('remove-service',
|
||||
@ -111,8 +112,8 @@ def _flush_iptables_rules():
|
||||
def set_firewall_backend(backend):
|
||||
"""Set FirewallBackend attribute to the specified string."""
|
||||
conf_file = '/etc/firewalld/firewalld.conf'
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
|
||||
# lens for shell-script config file
|
||||
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
|
||||
|
||||
@ -38,7 +38,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class ValidateRepoName(argparse.Action):
|
||||
"""Validate a repository name and add .git extension if necessary."""
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
RepositoryValidator()(values)
|
||||
if not values.endswith('.git'):
|
||||
@ -48,7 +47,6 @@ class ValidateRepoName(argparse.Action):
|
||||
|
||||
class ValidateRepoUrl(argparse.Action):
|
||||
"""Validate a repository URL."""
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
RepositoryValidator(input_should_be='url')(values)
|
||||
setattr(namespace, self.dest, values)
|
||||
@ -359,8 +357,9 @@ def subcommand_repo_info(arguments):
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
dict(name=arguments.name[:-4], description=_get_repo_description(
|
||||
arguments.name), owner=_get_repo_owner(arguments.name),
|
||||
dict(name=arguments.name[:-4],
|
||||
description=_get_repo_description(arguments.name),
|
||||
owner=_get_repo_owner(arguments.name),
|
||||
access=_get_access_status(arguments.name))))
|
||||
|
||||
|
||||
|
||||
@ -48,8 +48,8 @@ def parse_arguments():
|
||||
help_private_mode = 'Enable/Disable/Status private mode.'
|
||||
private_mode = subparsers.add_parser('private-mode',
|
||||
help=help_private_mode)
|
||||
private_mode.add_argument('command', choices=('enable', 'disable',
|
||||
'status'),
|
||||
private_mode.add_argument('command',
|
||||
choices=('enable', 'disable', 'status'),
|
||||
help=help_private_mode)
|
||||
|
||||
change_password = subparsers.add_parser('change-password',
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Configuration helper for Minetest server.
|
||||
"""
|
||||
@ -25,7 +24,6 @@ import augeas
|
||||
|
||||
from plinth import action_utils
|
||||
|
||||
|
||||
CONFIG_FILE = '/etc/minetest/minetest.conf'
|
||||
AUG_PATH = '/files' + CONFIG_FILE + '/.anon'
|
||||
|
||||
|
||||
114
actions/minidlna
Executable file
114
actions/minidlna
Executable file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
Configuration actions for the minidlna server.
|
||||
"""
|
||||
import argparse
|
||||
from tempfile import mkstemp
|
||||
from shutil import move
|
||||
import subprocess
|
||||
from os import fdopen, remove, chmod, stat
|
||||
|
||||
import augeas
|
||||
from plinth import action_utils
|
||||
from plinth.utils import grep
|
||||
|
||||
CONFIG_PATH = '/etc/minidlna.conf'
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Return parsed command line arguments as dictionary."""
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||
|
||||
subparsers.add_parser('setup', help='Setup SSH server')
|
||||
|
||||
subparsers.add_parser('get-media-dir', help='Get media directory')
|
||||
|
||||
set_media_dir = subparsers.add_parser('set-media-dir',
|
||||
help='Set custom media directory')
|
||||
set_media_dir.add_argument('--dir')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def subcommand_setup(arguments):
|
||||
"""
|
||||
Increase inotify watches per folder to allow minidlna to
|
||||
monitor changes in large media-dirs.
|
||||
"""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Sysctl/lens', 'Sysctl.lns')
|
||||
aug.set('/augeas/load/Sysctl/incl[last() + 1]', '/etc/sysctl.conf')
|
||||
aug.load()
|
||||
|
||||
aug.set('/files/etc/sysctl.conf/fs.inotify.max_user_watches', '100000')
|
||||
aug.save()
|
||||
|
||||
subprocess.run(['sysctl', '--system'], check=True)
|
||||
|
||||
|
||||
def subcommand_get_media_dir(arguments):
|
||||
"""Retrieve media directory from minidlna.conf"""
|
||||
line = grep('^media_dir=', CONFIG_PATH)
|
||||
|
||||
print(line[0].split("=")[1])
|
||||
|
||||
|
||||
def subcommand_set_media_dir(arguments):
|
||||
"""Set media directory in minidlna.conf"""
|
||||
line = grep('^media_dir=', CONFIG_PATH)[0]
|
||||
|
||||
new_line = 'media_dir=%s\n' % arguments.dir
|
||||
replace_in_config_file(CONFIG_PATH, line, new_line)
|
||||
if action_utils.service_is_running('minidlna'):
|
||||
action_utils.service_restart('minidlna')
|
||||
|
||||
|
||||
def replace_in_config_file(file_path, pattern, subst):
|
||||
"""
|
||||
Create a temporary minidlna.conf file,
|
||||
replace the media dir config,
|
||||
remove original one and move the temporary file.
|
||||
Preserve permissions as the original file.
|
||||
"""
|
||||
temp_file, temp_file_path = mkstemp()
|
||||
with fdopen(temp_file, 'w') as new_file:
|
||||
with open(file_path) as old_file:
|
||||
for line in old_file:
|
||||
new_file.write(line.replace(pattern, subst))
|
||||
|
||||
old_st_mode = stat(file_path).st_mode
|
||||
remove(file_path)
|
||||
move(temp_file_path, file_path)
|
||||
chmod(file_path, old_st_mode)
|
||||
|
||||
|
||||
def main():
|
||||
"""Parse arguments and perform all duties."""
|
||||
arguments = parse_arguments()
|
||||
|
||||
subcommand = arguments.subcommand.replace('-', '_')
|
||||
subcommand_method = globals()['subcommand_' + subcommand]
|
||||
subcommand_method(arguments)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -150,9 +150,8 @@ def get_monkeysphere_keys(key_id=None):
|
||||
"""Return the list of keys imported into monkeysphere."""
|
||||
try:
|
||||
key_ids = [] if not key_id else [key_id]
|
||||
output = subprocess.check_output(
|
||||
['monkeysphere-host', 'show-keys'] + key_ids,
|
||||
stderr=subprocess.DEVNULL)
|
||||
output = subprocess.check_output(['monkeysphere-host', 'show-keys'] +
|
||||
key_ids, stderr=subprocess.DEVNULL)
|
||||
except subprocess.CalledProcessError:
|
||||
# no keys available
|
||||
return {}
|
||||
@ -288,9 +287,10 @@ def subcommand_host_publish_key(arguments):
|
||||
# setting TMPDIR as workaround for Debian bug #656750
|
||||
proc = subprocess.Popen(
|
||||
['monkeysphere-host', 'publish-keys'] + arguments.key_ids,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=dict(
|
||||
os.environ, TMPDIR='/var/lib/monkeysphere/authentication/tmp/',
|
||||
MONKEYSPHERE_PROMPT='false'))
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
env=dict(os.environ,
|
||||
TMPDIR='/var/lib/monkeysphere/authentication/tmp/',
|
||||
MONKEYSPHERE_PROMPT='false'))
|
||||
output, error = proc.communicate()
|
||||
output, error = output.decode(), error.decode()
|
||||
if proc.returncode != 0:
|
||||
|
||||
79
actions/mumble
Executable file
79
actions/mumble
Executable file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Configure Mumble server.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Return parsed command line arguments as dictionary."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||
|
||||
subparsers.add_parser('create-password',
|
||||
help='Setup mumble superuser password')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def read_from_stdin():
|
||||
"""Read password from stdin"""
|
||||
|
||||
return (''.join(sys.stdin)).strip()
|
||||
|
||||
|
||||
def subcommand_create_password(arguments):
|
||||
"""Save superuser password with murmurd command"""
|
||||
|
||||
password = read_from_stdin()
|
||||
|
||||
cmd = ['murmurd', '-ini', '/etc/mumble-server.ini', '-readsupw']
|
||||
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
|
||||
|
||||
# The exit code of the command above seems to be 1 when successful!
|
||||
# checking if the 'phrase' is included in the error message which
|
||||
# shows that the password is successfully set.
|
||||
out, err = proc.communicate(input=password.encode())
|
||||
out, err = out.decode(), err.decode()
|
||||
|
||||
phrase = "Superuser password set on server"
|
||||
if phrase not in err:
|
||||
print(
|
||||
"Error occured while saving password: %s" % err
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Parse arguments and perform all duties."""
|
||||
arguments = parse_arguments()
|
||||
|
||||
subcommand = arguments.subcommand.replace('-', '_')
|
||||
subcommand_method = globals()['subcommand_' + subcommand]
|
||||
subcommand_method(arguments)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -177,8 +177,8 @@ def subcommand_upgrade(_):
|
||||
'renewed/private_by_serial', 'renewed/reqs_by_serial'
|
||||
]
|
||||
for dir_name in directories_to_create:
|
||||
os.makedirs(
|
||||
os.path.join(pki_dir, dir_name), mode=0o700, exist_ok=True)
|
||||
os.makedirs(os.path.join(pki_dir, dir_name), mode=0o700,
|
||||
exist_ok=True)
|
||||
|
||||
def _move_by_file_extension(file_extension, directory, excluded=None):
|
||||
excluded = excluded or []
|
||||
@ -217,7 +217,6 @@ def _write_server_config():
|
||||
|
||||
def _setup_firewall():
|
||||
"""Add TUN device to internal zone in firewalld."""
|
||||
|
||||
def _configure_interface(interface, operation):
|
||||
"""Add or remove an interface into internal zone."""
|
||||
command = [
|
||||
@ -299,9 +298,10 @@ def subcommand_get_profile(arguments):
|
||||
user_key_string = _read_file(user_key)
|
||||
ca_string = _read_file(CA_CERTIFICATE_PATH)
|
||||
|
||||
profile = CLIENT_CONFIGURATION.format(
|
||||
ca=ca_string, cert=user_certificate_string, key=user_key_string,
|
||||
remote=remote_server)
|
||||
profile = CLIENT_CONFIGURATION.format(ca=ca_string,
|
||||
cert=user_certificate_string,
|
||||
key=user_key_string,
|
||||
remote=remote_server)
|
||||
|
||||
print(profile)
|
||||
|
||||
@ -326,8 +326,8 @@ def _is_non_empty_file(filepath):
|
||||
|
||||
def load_augeas():
|
||||
"""Initialize Augeas."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
|
||||
# shell-script config file lens
|
||||
aug.set('/augeas/load/Simplevars/lens', 'Simplevars.lns')
|
||||
|
||||
@ -349,8 +349,9 @@ def _download_packages(packages):
|
||||
return downloaded_files
|
||||
|
||||
|
||||
def _get_conffile_hashes_from_downloaded_files(
|
||||
packages, downloaded_files, status_hashes, mismatched_hashes):
|
||||
def _get_conffile_hashes_from_downloaded_files(packages, downloaded_files,
|
||||
status_hashes,
|
||||
mismatched_hashes):
|
||||
"""Retrieve the conffile hashes from downloaded .deb files."""
|
||||
new_hashes = defaultdict(dict)
|
||||
new_versions = defaultdict(lambda: None)
|
||||
@ -369,8 +370,9 @@ def _get_conffile_hashes_from_downloaded_files(
|
||||
return new_hashes, new_versions
|
||||
|
||||
|
||||
def _get_conffile_hashes_from_downloaded_file(
|
||||
packages, downloaded_file, status_hashes, mismatched_hashes):
|
||||
def _get_conffile_hashes_from_downloaded_file(packages, downloaded_file,
|
||||
status_hashes,
|
||||
mismatched_hashes):
|
||||
"""Retrieve the conffile hashes from a single downloaded .deb file."""
|
||||
deb_file = apt_inst.DebFile(downloaded_file)
|
||||
|
||||
|
||||
@ -31,13 +31,18 @@ from plinth.modules.pagekite import utils
|
||||
aug = None
|
||||
|
||||
PATHS = {
|
||||
'service_on': os.path.join(utils.CONF_PATH, '*', 'service_on', '*'),
|
||||
'kitename': os.path.join(utils.CONF_PATH, '10_account.rc', 'kitename'),
|
||||
'kitesecret': os.path.join(utils.CONF_PATH, '10_account.rc', 'kitesecret'),
|
||||
'abort_not_configured': os.path.join(utils.CONF_PATH, '10_account.rc',
|
||||
'abort_not_configured'),
|
||||
'defaults': os.path.join(utils.CONF_PATH, '20_frontends.rc', 'defaults'),
|
||||
'frontend': os.path.join(utils.CONF_PATH, '20_frontends.rc', 'frontend'),
|
||||
'service_on':
|
||||
os.path.join(utils.CONF_PATH, '*', 'service_on', '*'),
|
||||
'kitename':
|
||||
os.path.join(utils.CONF_PATH, '10_account.rc', 'kitename'),
|
||||
'kitesecret':
|
||||
os.path.join(utils.CONF_PATH, '10_account.rc', 'kitesecret'),
|
||||
'abort_not_configured':
|
||||
os.path.join(utils.CONF_PATH, '10_account.rc', 'abort_not_configured'),
|
||||
'defaults':
|
||||
os.path.join(utils.CONF_PATH, '20_frontends.rc', 'defaults'),
|
||||
'frontend':
|
||||
os.path.join(utils.CONF_PATH, '20_frontends.rc', 'frontend'),
|
||||
}
|
||||
|
||||
|
||||
@ -50,9 +55,9 @@ def parse_arguments():
|
||||
subparsers.add_parser('start-and-enable', help='Enable PageKite service')
|
||||
subparsers.add_parser('stop-and-disable', help='Disable PageKite service')
|
||||
subparsers.add_parser('restart', help='Restart PageKite service')
|
||||
subparsers.add_parser('is-disabled',
|
||||
help=('Whether PageKite is disabled in the file '
|
||||
'/etc/pagekite.d/10_accounts.rc'))
|
||||
subparsers.add_parser(
|
||||
'is-disabled', help=('Whether PageKite is disabled in the file '
|
||||
'/etc/pagekite.d/10_accounts.rc'))
|
||||
|
||||
# Frontend
|
||||
subparsers.add_parser('get-frontend', help='Get pagekite frontend')
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Configuration helper for power controls.
|
||||
"""
|
||||
|
||||
@ -132,8 +132,8 @@ def subcommand_fix_collections(_):
|
||||
|
||||
def load_augeas():
|
||||
"""Initialize Augeas."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
|
||||
# shell-script config file lens
|
||||
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
|
||||
|
||||
@ -114,10 +114,7 @@ def _create_share(mount_point, windows_filesystem=False):
|
||||
|
||||
# FAT and NTFS partitions don't support setting permissions
|
||||
if not windows_filesystem:
|
||||
shutil.chown(open_share_path, group='freedombox-share')
|
||||
os.chmod(open_share_path, 0o2775)
|
||||
subprocess.check_call(['setfacl', '-Rm', 'g::rwx', open_share_path])
|
||||
subprocess.check_call(['setfacl', '-Rdm', 'g::rwx', open_share_path])
|
||||
_set_open_share_permissions(open_share_path)
|
||||
|
||||
share_name = _create_share_name(mount_point)
|
||||
_define_open_share(share_name, open_share_path, windows_filesystem)
|
||||
@ -166,9 +163,10 @@ def _get_shares():
|
||||
config = configparser.ConfigParser()
|
||||
config.read_string(output.decode())
|
||||
for name in config.sections():
|
||||
mount_point = _get_mount_point(config[name]['path'])
|
||||
path = config[name]['path']
|
||||
mount_point = _get_mount_point(path)
|
||||
mount_point = os.path.normpath(mount_point)
|
||||
shares.append(dict(name=name, mount_point=mount_point))
|
||||
shares.append(dict(name=name, mount_point=mount_point, path=path))
|
||||
|
||||
return shares
|
||||
|
||||
@ -193,6 +191,23 @@ def _make_mounts_readable_by_others(mount_point):
|
||||
os.chmod(dirname, stats.st_mode | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
|
||||
def _set_open_share_permissions(directory):
|
||||
"""Set file and directory permissions for open share."""
|
||||
shutil.chown(directory, group='freedombox-share')
|
||||
os.chmod(directory, 0o2775)
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for subdir in dirs:
|
||||
subdir_path = os.path.join(root, subdir)
|
||||
shutil.chown(subdir_path, group='freedombox-share')
|
||||
os.chmod(subdir_path, 0o2775)
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
shutil.chown(file_path, group='freedombox-share')
|
||||
os.chmod(file_path, 0o0664)
|
||||
subprocess.check_call(['setfacl', '-Rm', 'g::rwX', directory])
|
||||
subprocess.check_call(['setfacl', '-Rdm', 'g::rwX', directory])
|
||||
|
||||
|
||||
def _use_config_file(conf_file):
|
||||
"""Set samba configuration file location."""
|
||||
aug = augeas.Augeas(
|
||||
|
||||
@ -81,8 +81,8 @@ def _update_uwsgi_configuration():
|
||||
|
||||
uwsgi 2.0.15-debian crashes when trying to autoload.
|
||||
"""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/inifile/lens', 'Puppet.lns')
|
||||
aug.set('/augeas/load/inifile/incl[last() + 1]', UWSGI_FILE)
|
||||
aug.load()
|
||||
|
||||
@ -35,12 +35,11 @@ def parse_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||
|
||||
subparsers.add_parser('setup',
|
||||
help='Perform initial setup steps')
|
||||
subparsers.add_parser(
|
||||
'get-config', help='Read and print JSON config to stdout')
|
||||
subparsers.add_parser(
|
||||
'merge-config', help='Merge JSON config from stdin with existing')
|
||||
subparsers.add_parser('setup', help='Perform initial setup steps')
|
||||
subparsers.add_parser('get-config',
|
||||
help='Read and print JSON config to stdout')
|
||||
subparsers.add_parser('merge-config',
|
||||
help='Merge JSON config from stdin with existing')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
@ -58,8 +58,8 @@ def parse_arguments():
|
||||
|
||||
def load_augeas():
|
||||
"""Initialize augeas for this app's configuration file."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
||||
aug.set('/augeas/load/Httpd/incl[last() + 1]', APACHE_CONFIGURATION)
|
||||
aug.load()
|
||||
@ -191,8 +191,9 @@ def _list(aug=None):
|
||||
"""Must contain the line 'Require all granted'."""
|
||||
require = location + '//directive["Require"]'
|
||||
return bool(aug.match(require)) and aug.get(
|
||||
require + '/arg[1]') == 'all' and aug.get(
|
||||
require + '/arg[2]') == 'granted'
|
||||
require +
|
||||
'/arg[1]') == 'all' and aug.get(require +
|
||||
'/arg[2]') == 'granted'
|
||||
|
||||
for share in shares:
|
||||
if share['name'] == name:
|
||||
|
||||
@ -117,8 +117,8 @@ def subcommand_set_keys(arguments):
|
||||
|
||||
def _load_augeas():
|
||||
"""Initialize augeas for this app's configuration file."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Sshd/lens', 'Sshd.lns')
|
||||
aug.set('/augeas/load/Sshd/incl[last() + 1]', '/etc/ssh/sshd_config')
|
||||
aug.load()
|
||||
|
||||
@ -96,11 +96,11 @@ def validate_mountpoint(mountpoint):
|
||||
"""Check that the folder is empty, and create it if it doesn't exist"""
|
||||
if os.path.exists(mountpoint):
|
||||
if _is_mounted(mountpoint):
|
||||
raise AlreadyMountedError(
|
||||
'Mountpoint %s already mounted' % mountpoint)
|
||||
raise AlreadyMountedError('Mountpoint %s already mounted' %
|
||||
mountpoint)
|
||||
if os.listdir(mountpoint) or not os.path.isdir(mountpoint):
|
||||
raise ValueError(
|
||||
'Mountpoint %s is not an empty directory' % mountpoint)
|
||||
raise ValueError('Mountpoint %s is not an empty directory' %
|
||||
mountpoint)
|
||||
else:
|
||||
os.makedirs(mountpoint)
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ Configuration helper for disks manager.
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
@ -51,6 +52,14 @@ def parse_arguments():
|
||||
subparsers.add_parser('usage-info',
|
||||
help='Get information about disk space usage')
|
||||
|
||||
subparser = subparsers.add_parser('validate-directory',
|
||||
help='Validate a directory')
|
||||
subparser.add_argument('--path', help='Path of the directory',
|
||||
required=True)
|
||||
subparser.add_argument('--check-writable', required=False, default=False,
|
||||
action='store_true',
|
||||
help='Check that the directory is writable')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
@ -316,6 +325,19 @@ def subcommand_usage_info(_):
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
|
||||
def subcommand_validate_directory(arguments):
|
||||
"""Validate a directory"""
|
||||
directory = arguments.path
|
||||
if not os.path.exists(directory):
|
||||
print('ValidationError: 1')
|
||||
if not os.path.isdir(directory):
|
||||
print('ValidationError: 2')
|
||||
if not os.access(directory, os.R_OK):
|
||||
print('ValidationError: 3')
|
||||
if arguments.check_writable and not os.access(directory, os.W_OK):
|
||||
print('ValidationError: 4')
|
||||
|
||||
|
||||
def main():
|
||||
"""Parse arguments and perform all duties."""
|
||||
arguments = parse_arguments()
|
||||
|
||||
@ -114,8 +114,8 @@ def subcommand_setup(arguments):
|
||||
def subcommand_autostart(_):
|
||||
"""Automatically start all introducers and storage nodes on system startup.
|
||||
"""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
|
||||
aug.set('/augeas/load/Shellvars/incl[last() + 1]', DEFAULT_FILE)
|
||||
aug.load()
|
||||
@ -157,8 +157,9 @@ def subcommand_create_storage_node(_):
|
||||
if not os.path.exists(os.path.join(tahoe_home, storage_node_name)):
|
||||
subprocess.check_call([
|
||||
'tahoe', 'create-node', '--nickname=\"storage_node\"',
|
||||
'--webport=1234', '--hostname={}'.format(
|
||||
get_configured_domain_name()), storage_node_name
|
||||
'--webport=1234',
|
||||
'--hostname={}'.format(get_configured_domain_name()),
|
||||
storage_node_name
|
||||
])
|
||||
with open(
|
||||
os.path.join(tahoe_home, introducer_name, 'private',
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Set time zones with timedatectl (requires root permission).
|
||||
"""
|
||||
|
||||
@ -529,8 +529,8 @@ def _update_ports():
|
||||
|
||||
def augeas_load():
|
||||
"""Initialize Augeas."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Tor/lens', 'Tor.lns')
|
||||
aug.set('/augeas/load/Tor/incl[last() + 1]',
|
||||
'/etc/tor/instances/plinth/torrc')
|
||||
|
||||
@ -133,8 +133,8 @@ def _run_as_postgres(command, stdin=None, stdout=None):
|
||||
|
||||
def load_augeas():
|
||||
"""Initialize Augeas."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
|
||||
aug.set('/augeas/load/Shellvars/incl[last() + 1]', DEFAULT_FILE)
|
||||
aug.set('/augeas/load/Phpvars/lens', 'Phpvars.lns')
|
||||
|
||||
@ -210,8 +210,8 @@ def configure_ldapscripts():
|
||||
# modify a copy of the config file
|
||||
shutil.copy('/etc/ldapscripts/ldapscripts.conf', LDAPSCRIPTS_CONF)
|
||||
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
|
||||
aug.set('/augeas/load/Shellvars/incl[last() + 1]', LDAPSCRIPTS_CONF)
|
||||
aug.load()
|
||||
|
||||
65
debian/changelog
vendored
65
debian/changelog
vendored
@ -1,3 +1,68 @@
|
||||
plinth (19.23) unstable; urgency=medium
|
||||
|
||||
[ Thomas Vincent ]
|
||||
* Translated using Weblate (French)
|
||||
* Translated using Weblate (French)
|
||||
|
||||
[ Fred ]
|
||||
* Translated using Weblate (French)
|
||||
|
||||
[ Alice Kile ]
|
||||
* show app icons in apps page
|
||||
* use single variable for referencing icon filename
|
||||
* fix formatting issues
|
||||
* fix formatting and template-related issues
|
||||
* properly implement header in app and setup pages
|
||||
* implement responsive layout for app page
|
||||
* fix toggle button html layout and responsive design css
|
||||
* config: fix minor syntax error
|
||||
* fix: implement requested changes
|
||||
|
||||
[ James Valleroy ]
|
||||
* themes: css whitespace minor fixes
|
||||
* samba: Add icon to app page
|
||||
* minidlna: Add managed service and Daemon component
|
||||
* minidlna: Use single action to set media dir and restart
|
||||
* minidlna: Show icon on app page
|
||||
* minidlna: Fix webserver config name
|
||||
* minidlna: Only show shortcut to users in group
|
||||
* mumble: Keep icon_filename in moved view
|
||||
* cockpit: Filter out localhost URLs from displayed access list
|
||||
* users: Use service action to restart share group service
|
||||
* locale: Update translation strings
|
||||
* doc: Fetch latest manual
|
||||
|
||||
[ Veiko Aasa ]
|
||||
* samba: recursively set open share directory permissions
|
||||
* users: Fix functional tests changing the language feature
|
||||
* app: Fix app checkbox status change functional tests
|
||||
* storage: Directory selection form and validator
|
||||
* transmission: New directory selection form
|
||||
|
||||
[ Nektarios Katakis ]
|
||||
* feature: minidlna app
|
||||
* fix: minidlna.conf file permissions after editing
|
||||
* update minidlna svg
|
||||
* run sysctl after installation
|
||||
* mumble: Add option to set SuperUser password
|
||||
* cockpit: extend apps description with access info
|
||||
* cockpit: add list of valid urls to access the app.
|
||||
|
||||
[ /rgb ]
|
||||
* Translated using Weblate (German)
|
||||
* Translated using Weblate (German)
|
||||
|
||||
[ Luis A. Arizmendi ]
|
||||
* Translated using Weblate (Spanish)
|
||||
|
||||
[ adaragao ]
|
||||
* Translated using Weblate (Portuguese)
|
||||
|
||||
[ Michael Breidenbach ]
|
||||
* Translated using Weblate (Swedish)
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Mon, 16 Dec 2019 18:38:46 -0500
|
||||
|
||||
plinth (19.22~bpo10+1) buster-backports; urgency=medium
|
||||
|
||||
* Rebuild for buster-backports.
|
||||
|
||||
5
doc/manual/en/GitWeb.raw.xml
Normal file
5
doc/manual/en/GitWeb.raw.xml
Normal file
File diff suppressed because one or more lines are too long
@ -4,6 +4,12 @@
|
||||
<articleinfo>
|
||||
<title>FreedomBox/Manual</title>
|
||||
<revhistory>
|
||||
<revision>
|
||||
<revnumber>78</revnumber>
|
||||
<date>2019-12-15 19:26:31</date>
|
||||
<authorinitials>Drahtseil</authorinitials>
|
||||
<revremark>Added Gitweb (to the end of the manual, but I have no clue how this is sorted)</revremark>
|
||||
</revision>
|
||||
<revision>
|
||||
<revnumber>77</revnumber>
|
||||
<date>2019-04-30 00:45:49</date>
|
||||
@ -3113,6 +3119,34 @@ echo "newpassword" | su mumble-server -s /bin/sh -c "/usr/sbin/murmurd -ini /etc
|
||||
</itemizedlist>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>Simple Git Hosting (GitWeb)</title>
|
||||
<para>Git is a distributed version-control system for tracking changes in source code during software development. GitWeb provides a web interface to Git repositories. You can browse history and content of source code, use search to find relevant commits and code. You can also clone repositories and upload code changes with a command-line Git client or with multiple available graphical clients. And you can share your code with people around the world. </para>
|
||||
<para>To learn more on how to use Git visit <ulink url="https://git-scm.com/docs/gittutorial">Git tutorial</ulink>. </para>
|
||||
<para><emphasis role="strong">Available since version:</emphasis> 19.19 </para>
|
||||
<section>
|
||||
<title>Managing the repositories</title>
|
||||
<para>After installation of GitWeb, a new repository can be created. It can be marked as <emphasis>private</emphasis> to limit access. </para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Access</title>
|
||||
<para>GitWeb can be accessed after installation e.g. by the web client through <ulink url="https://<my"/> freedombox name>/gitweb </para>
|
||||
</section>
|
||||
<section>
|
||||
<title>HTTP basic auth</title>
|
||||
<para>GitWeb on FreedomBox currently supports HTTP remotes only. To avoid having to enter the password each time you pull/push to the repository, you can edit your remote to include the credentials. </para>
|
||||
<para>
|
||||
<emphasis>Example:</emphasis>
|
||||
<ulink url="https://username@password:my.freedombox.rocks/gitweb/myrepo"/>
|
||||
</para>
|
||||
<para>Your username and password will be encrypted. Someone monitoring the network traffic will notice the domain name only.</para>
|
||||
<para><emphasis role="strong">Note:</emphasis> If using this method, your password will be stored in plain text in the local repository's .git/config file. </para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Mirroring</title>
|
||||
<para>Though your repositories are primarily hosted on your own FreedomBox, you can configure a repository on another Git hosting system like <ulink url="https://wiki.debian.org/FreedomBox/Manual/GitLab#">GitLab</ulink> as a mirror. </para>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>System</title>
|
||||
@ -9956,6 +9990,38 @@ 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 19.23 (2019-12-16)</title>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>minidlna: New app for MiniDLNA (Simple Media Server) </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>apps: Show app icons in app pages </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>apps: Implement responsive layout for app pages </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>samba: Recursively set open share directory permissions </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>transmission: Add directory selection form </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>mumble: Add option to set SuperUser password </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>cockpit: Extend apps description with access info </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>cockpit: Add list of valid urls to access the app </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Update translations for French, German, Spanish, Portuguese, and Swedish </para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>FreedomBox 19.22 (2019-12-02)</title>
|
||||
<itemizedlist>
|
||||
|
||||
@ -9806,6 +9806,38 @@ 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 19.23 (2019-12-16)</title>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>minidlna: New app for MiniDLNA (Simple Media Server) </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>apps: Show app icons in app pages </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>apps: Implement responsive layout for app pages </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>samba: Recursively set open share directory permissions </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>transmission: Add directory selection form </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>mumble: Add option to set SuperUser password </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>cockpit: Extend apps description with access info </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>cockpit: Add list of valid urls to access the app </para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>Update translations for French, German, Spanish, Portuguese, and Swedish </para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>FreedomBox 19.22 (2019-12-02)</title>
|
||||
<itemizedlist>
|
||||
|
||||
@ -107,8 +107,7 @@ def select_domain_name(browser, app_name, domain_name):
|
||||
|
||||
@given('the shadowsocks application is configured')
|
||||
def configure_shadowsocks(browser):
|
||||
application.configure_shadowsocks(browser, 'example.com',
|
||||
'fakepassword')
|
||||
application.configure_shadowsocks(browser, 'example.com', 'fakepassword')
|
||||
|
||||
|
||||
@when(
|
||||
@ -128,8 +127,8 @@ def assert_shadowsocks_configuration(browser, server, password):
|
||||
password) == application.shadowsocks_get_configuration(browser)
|
||||
|
||||
|
||||
@when(
|
||||
parsers.parse('I modify the maximum file size of coquelicot to {size:d}'))
|
||||
@when(parsers.parse('I modify the maximum file size of coquelicot to {size:d}')
|
||||
)
|
||||
def modify_max_file_size(browser, size):
|
||||
application.modify_max_file_size(browser, size)
|
||||
|
||||
@ -372,38 +371,55 @@ def tahoe_given_add_introducer(browser, domain):
|
||||
def tahoe_remove_introducer(browser, domain):
|
||||
application.tahoe_remove_introducer(browser, domain)
|
||||
|
||||
@given('the access rights are set to "only the owner can view or make changes"')
|
||||
|
||||
@given('the access rights are set to "only the owner can view or make changes"'
|
||||
)
|
||||
def radicale_given_owner_only(browser):
|
||||
application.radicale_set_access_rights(browser, 'owner_only')
|
||||
|
||||
@given('the access rights are set to "any user can view, but only the owner can make changes"')
|
||||
|
||||
@given(
|
||||
'the access rights are set to "any user can view, but only the owner can make changes"'
|
||||
)
|
||||
def radicale_given_owner_write(browser):
|
||||
application.radicale_set_access_rights(browser, 'owner_write')
|
||||
|
||||
|
||||
@given('the access rights are set to "any user can view or make changes"')
|
||||
def radicale_given_authenticated(browser):
|
||||
application.radicale_set_access_rights(browser, 'authenticated')
|
||||
|
||||
@when('I change the access rights to "only the owner can view or make changes"')
|
||||
|
||||
@when('I change the access rights to "only the owner can view or make changes"'
|
||||
)
|
||||
def radicale_set_owner_only(browser):
|
||||
application.radicale_set_access_rights(browser, 'owner_only')
|
||||
|
||||
@when('I change the access rights to "any user can view, but only the owner can make changes"')
|
||||
|
||||
@when(
|
||||
'I change the access rights to "any user can view, but only the owner can make changes"'
|
||||
)
|
||||
def radicale_set_owner_write(browser):
|
||||
application.radicale_set_access_rights(browser, 'owner_write')
|
||||
|
||||
|
||||
@when('I change the access rights to "any user can view or make changes"')
|
||||
def radicale_set_authenticated(browser):
|
||||
application.radicale_set_access_rights(browser, 'authenticated')
|
||||
|
||||
|
||||
@then('the access rights should be "only the owner can view or make changes"')
|
||||
def radicale_check_owner_only(browser):
|
||||
assert application.radicale_get_access_rights(browser) == 'owner_only'
|
||||
|
||||
@then('the access rights should be "any user can view, but only the owner can make changes"')
|
||||
|
||||
@then(
|
||||
'the access rights should be "any user can view, but only the owner can make changes"'
|
||||
)
|
||||
def radicale_check_owner_write(browser):
|
||||
assert application.radicale_get_access_rights(browser) == 'owner_write'
|
||||
|
||||
|
||||
@then('the access rights should be "any user can view or make changes"')
|
||||
def radicale_check_authenticated(browser):
|
||||
assert application.radicale_get_access_rights(browser) == 'authenticated'
|
||||
@ -451,8 +467,8 @@ def app_visible_on_front_page(browser, app_name):
|
||||
assert len(shortcuts) == 1
|
||||
|
||||
|
||||
@then(
|
||||
parsers.parse('{app_name:w} app should not be visible on the front page'))
|
||||
@then(parsers.parse('{app_name:w} app should not be visible on the front page')
|
||||
)
|
||||
def app_not_visible_on_front_page(browser, app_name):
|
||||
shortcuts = application.find_on_front_page(browser, app_name)
|
||||
assert len(shortcuts) == 0
|
||||
|
||||
@ -19,7 +19,6 @@ from pytest_bdd import given, parsers, then, when
|
||||
|
||||
from support import config, interface
|
||||
|
||||
|
||||
default_url = config['DEFAULT']['url']
|
||||
|
||||
|
||||
@ -52,8 +51,8 @@ def new_user_does_not_exist(browser, name):
|
||||
@given(parsers.parse('the user {name:w} exists'))
|
||||
def test_user_exists(browser, name):
|
||||
interface.nav_to_module(browser, 'users')
|
||||
user_link = browser.find_link_by_href(
|
||||
'/plinth/sys/users/' + name + '/edit/')
|
||||
user_link = browser.find_link_by_href('/plinth/sys/users/' + name +
|
||||
'/edit/')
|
||||
if not user_link:
|
||||
create_user(browser, name, 'secret123')
|
||||
|
||||
|
||||
@ -61,8 +61,9 @@ def verify_upload_password(browser, password):
|
||||
'I upload the sample local file to coquelicot with password {password:w}'
|
||||
))
|
||||
def coquelicot_upload_file(browser, sample_local_file, password):
|
||||
url = site.upload_file_to_coquelicot(
|
||||
browser, sample_local_file['file_path'], password)
|
||||
url = site.upload_file_to_coquelicot(browser,
|
||||
sample_local_file['file_path'],
|
||||
password)
|
||||
sample_local_file['upload_url'] = url
|
||||
|
||||
|
||||
@ -214,7 +215,8 @@ def syncthing_folder_not_present(browser, folder_name):
|
||||
|
||||
@given(
|
||||
parsers.parse(
|
||||
'folder {folder_path:S} is present as syncthing folder {folder_name:w}'))
|
||||
'folder {folder_path:S} is present as syncthing folder {folder_name:w}'
|
||||
))
|
||||
def syncthing_folder_present(browser, folder_name, folder_path):
|
||||
if not site.syncthing_folder_is_present(browser, folder_name):
|
||||
site.syncthing_add_folder(browser, folder_name, folder_path)
|
||||
|
||||
@ -98,30 +98,40 @@ def is_installed(browser, app_name):
|
||||
return not bool(install_button)
|
||||
|
||||
|
||||
def _change_status(browser, app_name, change_status_to='enabled',
|
||||
checkbox_id=None):
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
checkbox_id = checkbox_id or get_app_checkbox_id(app_name)
|
||||
checkbox = browser.find_by_id(checkbox_id)
|
||||
def _change_app_status(browser, app_name, change_status_to='enabled'):
|
||||
"""Enable or disable application."""
|
||||
button = browser.find_by_id('app-toggle-button')
|
||||
checkbox_id = get_app_checkbox_id(app_name)
|
||||
checkbox = browser.find_by_id(checkbox_id)
|
||||
if button:
|
||||
if checkbox.checked and change_status_to == 'disabled' or (
|
||||
not checkbox.checked and change_status_to == 'enabled'):
|
||||
interface.submit(browser, element=button)
|
||||
else:
|
||||
checkbox.check(
|
||||
) if change_status_to == 'enabled' else checkbox.uncheck()
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
_change_status(browser, app_name, checkbox_id, change_status_to)
|
||||
|
||||
if app_name in apps_with_loaders:
|
||||
wait_for_config_update(browser, app_name)
|
||||
|
||||
|
||||
def _change_status(browser, app_name, checkbox_id, change_status_to='enabled'):
|
||||
"""Change checkbox status."""
|
||||
checkbox = browser.find_by_id(checkbox_id)
|
||||
checkbox.check() if change_status_to == 'enabled' else checkbox.uncheck()
|
||||
interface.submit(browser, form_class='form-configuration')
|
||||
|
||||
if app_name in apps_with_loaders:
|
||||
wait_for_config_update(browser, app_name)
|
||||
|
||||
|
||||
def enable(browser, app_name):
|
||||
_change_status(browser, app_name, 'enabled')
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
_change_app_status(browser, app_name, 'enabled')
|
||||
|
||||
|
||||
def disable(browser, app_name):
|
||||
_change_status(browser, app_name, 'disabled')
|
||||
interface.nav_to_module(browser, get_app_module(app_name))
|
||||
_change_app_status(browser, app_name, 'disabled')
|
||||
|
||||
|
||||
def wait_for_config_update(browser, app_name):
|
||||
@ -270,29 +280,27 @@ def verify_inaccessible_share(browser, name):
|
||||
def enable_mediawiki_public_registrations(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'enabled',
|
||||
checkbox_id='id_enable_public_registrations')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_public_registrations',
|
||||
'enabled')
|
||||
|
||||
|
||||
def disable_mediawiki_public_registrations(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'disabled',
|
||||
checkbox_id='id_enable_public_registrations')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_public_registrations',
|
||||
'disabled')
|
||||
|
||||
|
||||
def enable_mediawiki_private_mode(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'enabled',
|
||||
checkbox_id='id_enable_private_mode')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_private_mode', 'enabled')
|
||||
|
||||
|
||||
def disable_mediawiki_private_mode(browser):
|
||||
"""Enable public registrations in MediaWiki."""
|
||||
interface.nav_to_module(browser, 'mediawiki')
|
||||
_change_status(browser, 'mediawiki', 'disabled',
|
||||
checkbox_id='id_enable_private_mode')
|
||||
_change_status(browser, 'mediawiki', 'id_enable_private_mode', 'disabled')
|
||||
|
||||
|
||||
def set_mediawiki_admin_password(browser, password):
|
||||
@ -305,15 +313,13 @@ def set_mediawiki_admin_password(browser, password):
|
||||
def enable_ejabberd_message_archive_management(browser):
|
||||
"""Enable Message Archive Management in Ejabberd."""
|
||||
interface.nav_to_module(browser, 'ejabberd')
|
||||
_change_status(browser, 'ejabberd', 'enabled',
|
||||
checkbox_id='id_MAM_enabled')
|
||||
_change_status(browser, 'ejabberd', 'id_MAM_enabled', 'enabled')
|
||||
|
||||
|
||||
def disable_ejabberd_message_archive_management(browser):
|
||||
"""Enable Message Archive Management in Ejabberd."""
|
||||
interface.nav_to_module(browser, 'ejabberd')
|
||||
_change_status(browser, 'ejabberd', 'disabled',
|
||||
checkbox_id='id_MAM_enabled')
|
||||
_change_status(browser, 'ejabberd', 'id_MAM_enabled', 'disabled')
|
||||
|
||||
|
||||
def ejabberd_add_contact(browser):
|
||||
@ -401,9 +407,8 @@ def _gitweb_get_repo_url(repo, with_auth):
|
||||
if with_auth:
|
||||
password = config['DEFAULT']['password']
|
||||
|
||||
return '{0}://{1}:{2}@{3}/gitweb/{4}'.format(scheme,
|
||||
config['DEFAULT']['username'],
|
||||
password, url, repo)
|
||||
return '{0}://{1}:{2}@{3}/gitweb/{4}'.format(
|
||||
scheme, config['DEFAULT']['username'], password, url, repo)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
||||
@ -294,8 +294,8 @@ def transmission_remove_all_torrents(browser):
|
||||
def transmission_upload_sample_torrent(browser):
|
||||
"""Upload a sample torrent into transmission."""
|
||||
browser.visit(config['DEFAULT']['url'] + '/transmission')
|
||||
file_path = os.path.join(
|
||||
os.path.dirname(__file__), '..', 'data', 'sample.torrent')
|
||||
file_path = os.path.join(os.path.dirname(__file__), '..', 'data',
|
||||
'sample.torrent')
|
||||
browser.click_link_by_id('toolbar-open')
|
||||
eventually(browser.is_element_not_present_by_css,
|
||||
args=['#upload-container[style="display: none;"]'])
|
||||
@ -396,9 +396,8 @@ def deluge_remove_all_torrents(browser):
|
||||
browser.find_by_id('remove').first.click()
|
||||
|
||||
# Remove window shows up
|
||||
assert eventually(
|
||||
lambda: _deluge_get_active_window_title(browser) == 'Remove Torrent'
|
||||
)
|
||||
assert eventually(lambda: _deluge_get_active_window_title(browser) ==
|
||||
'Remove Torrent')
|
||||
|
||||
_deluge_click_active_window_button(browser, 'Remove With Data')
|
||||
|
||||
@ -434,8 +433,8 @@ def deluge_upload_sample_torrent(browser):
|
||||
eventually(
|
||||
lambda: _deluge_get_active_window_title(browser) == 'Add Torrents')
|
||||
|
||||
file_path = os.path.join(
|
||||
os.path.dirname(__file__), '..', 'data', 'sample.torrent')
|
||||
file_path = os.path.join(os.path.dirname(__file__), '..', 'data',
|
||||
'sample.torrent')
|
||||
|
||||
if browser.find_by_id('fileUploadForm'): # deluge-web 2.x
|
||||
browser.attach_file('file', file_path)
|
||||
@ -443,9 +442,8 @@ def deluge_upload_sample_torrent(browser):
|
||||
browser.find_by_css('button.x-deluge-add-file').first.click()
|
||||
|
||||
# Add from file window appears
|
||||
eventually(
|
||||
lambda: _deluge_get_active_window_title(browser) == 'Add from File'
|
||||
)
|
||||
eventually(lambda: _deluge_get_active_window_title(browser) ==
|
||||
'Add from File')
|
||||
|
||||
# Attach file
|
||||
browser.attach_file('file', file_path)
|
||||
|
||||
@ -95,7 +95,8 @@ def set_language(browser, language_code):
|
||||
|
||||
def check_language(browser, language_code):
|
||||
nav_to_module(browser, 'config')
|
||||
return browser.title == config_page_title_language_map[language_code]
|
||||
return browser.find_by_css('.header-bar').first.find_by_tag(
|
||||
'h2').first.value == config_page_title_language_map[language_code]
|
||||
|
||||
|
||||
def delete_all_snapshots(browser):
|
||||
|
||||
@ -18,4 +18,4 @@
|
||||
Package init file.
|
||||
"""
|
||||
|
||||
__version__ = '19.22'
|
||||
__version__ = '19.23'
|
||||
|
||||
@ -211,7 +211,6 @@ def webserver_disable(name, kind='config', apply_changes=True):
|
||||
|
||||
class WebserverChange(object):
|
||||
"""Context to restart/reload Apache after configuration changes."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the context object state."""
|
||||
self.actions_required = set()
|
||||
@ -425,9 +424,10 @@ def diagnose_url_on_all(url, **kwargs):
|
||||
def diagnose_netcat(host, port, input='', negate=False):
|
||||
"""Run a diagnostic using netcat."""
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
['nc', host, str(port)], stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
process = subprocess.Popen(['nc', host, str(port)],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate(input=input.encode())
|
||||
if process.returncode != 0:
|
||||
result = 'failed'
|
||||
|
||||
@ -114,7 +114,6 @@ class Component:
|
||||
|
||||
def enable(self):
|
||||
"""Run operations to enable the component."""
|
||||
|
||||
def disable(self):
|
||||
"""Run operations to disable the component."""
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Django context processors to provide common data to templates.
|
||||
"""
|
||||
|
||||
@ -23,7 +23,6 @@ from plinth import action_utils, actions, app
|
||||
|
||||
class Daemon(app.LeaderComponent):
|
||||
"""Component to manage a background daemon or any systemd unit."""
|
||||
|
||||
def __init__(self, component_id, unit, strict_check=False):
|
||||
"""Initialize a new daemon component.
|
||||
|
||||
|
||||
@ -83,7 +83,6 @@ class PackageHandler():
|
||||
|
||||
class DBusServer():
|
||||
"""Abstraction over a connection to D-Bus."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the server object."""
|
||||
self.package_handler = None
|
||||
|
||||
@ -43,7 +43,6 @@ class DomainSelectionForm(forms.Form):
|
||||
"""Form for selecting a domain name to be used for
|
||||
distributed federated applications
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@ -84,9 +83,9 @@ class LanguageSelectionFormMixin:
|
||||
plinth_dir = os.path.dirname(plinth.__file__)
|
||||
if language_code == 'en' or os.path.exists(
|
||||
os.path.join(plinth_dir, 'locale', locale_code)):
|
||||
supported_languages.append((language_code,
|
||||
_get_local_name(
|
||||
language_code, language_name)))
|
||||
supported_languages.append(
|
||||
(language_code,
|
||||
_get_local_name(language_code, language_name)))
|
||||
|
||||
self.fields['language'].choices = supported_languages
|
||||
|
||||
@ -106,7 +105,6 @@ class CheckboxSelectMultipleWithReadOnly(forms.widgets.CheckboxSelectMultiple):
|
||||
|
||||
Derived from https://djangosnippets.org/snippets/2786/
|
||||
"""
|
||||
|
||||
def render(self, name, value, attrs=None, choices=(), renderer=None):
|
||||
if value is None:
|
||||
value = []
|
||||
@ -114,8 +112,8 @@ class CheckboxSelectMultipleWithReadOnly(forms.widgets.CheckboxSelectMultiple):
|
||||
output = [u'<ul>']
|
||||
global_readonly = 'readonly' in final_attrs
|
||||
str_values = set([v for v in value])
|
||||
for i, (option_value, option_label) in enumerate(
|
||||
chain(self.choices, choices)):
|
||||
for i, (option_value,
|
||||
option_label) in enumerate(chain(self.choices, choices)):
|
||||
if not global_readonly and 'readonly' in final_attrs:
|
||||
# If the entire group is readonly keep all options readonly
|
||||
del final_attrs['readonly']
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Simple key/value store using Django models
|
||||
"""
|
||||
|
||||
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
@ -41,7 +41,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class SetupMiddleware(MiddlewareMixin):
|
||||
"""Django middleware to show pre-setup message and setup progress."""
|
||||
|
||||
@staticmethod
|
||||
def process_view(request, view_func, view_args, view_kwargs):
|
||||
"""Handle a request as Django middleware request handler."""
|
||||
@ -77,8 +76,8 @@ class SetupMiddleware(MiddlewareMixin):
|
||||
str(exception))
|
||||
error_details = getattr(exception, 'error_details', '')
|
||||
message = _('Error installing application: {string} '
|
||||
'{details}').format(
|
||||
string=error_string, details=error_details)
|
||||
'{details}').format(string=error_string,
|
||||
details=error_details)
|
||||
else:
|
||||
message = _('Error installing application: {error}') \
|
||||
.format(error=exception)
|
||||
@ -96,7 +95,6 @@ class SetupMiddleware(MiddlewareMixin):
|
||||
|
||||
class AdminRequiredMiddleware(MiddlewareMixin):
|
||||
"""Django middleware for authenticating requests for admin areas."""
|
||||
|
||||
@staticmethod
|
||||
def process_view(request, view_func, view_args, view_kwargs):
|
||||
"""Reject non-admin access to views that are private and not marked."""
|
||||
@ -110,7 +108,6 @@ class AdminRequiredMiddleware(MiddlewareMixin):
|
||||
|
||||
class FirstSetupMiddleware(MiddlewareMixin):
|
||||
"""Django middleware to block all interactions before first setup."""
|
||||
|
||||
@staticmethod
|
||||
def process_view(request, view_func, view_args, view_kwargs):
|
||||
"""Block all user interactions when first setup is pending."""
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
#
|
||||
# Generated by Django 1.9 on 2015-12-04 07:27
|
||||
#
|
||||
|
||||
"""
|
||||
Initial Django migration for FreedomBox to create database tables.
|
||||
"""
|
||||
@ -33,8 +32,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Remove the deprecated KVStore entries 'setup_state' and 'firstboot_state',
|
||||
and only use the new entry 'firstboot_completed' instead.
|
||||
@ -63,8 +62,8 @@ def merge_firstboot_finished_fields(apps, schema_editor):
|
||||
firstboot_completed = _object.value
|
||||
|
||||
# Set new 'firstboot_completed' if needed
|
||||
new_firstboot_completed = bool(firstboot_completed or setup_state or
|
||||
firstboot_state)
|
||||
new_firstboot_completed = bool(firstboot_completed or setup_state
|
||||
or firstboot_state)
|
||||
if new_firstboot_completed and not firstboot_completed:
|
||||
obj, created = KVStore.objects.get_or_create(key='firstboot_completed')
|
||||
obj.value = 1
|
||||
|
||||
@ -30,14 +30,17 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='UserProfile',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True,
|
||||
serialize=False, verbose_name='ID')),
|
||||
('language', models.CharField(default=None, max_length=32,
|
||||
null=True)),
|
||||
('user', models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL)),
|
||||
], ),
|
||||
('id',
|
||||
models.AutoField(auto_created=True, primary_key=True,
|
||||
serialize=False, verbose_name='ID')),
|
||||
('language',
|
||||
models.CharField(default=None, max_length=32, null=True)),
|
||||
('user',
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.RunPython(code=insert_users,
|
||||
reverse_code=truncate_user_profile),
|
||||
]
|
||||
|
||||
@ -54,9 +54,9 @@ class ApacheApp(app_module.App):
|
||||
|
||||
freedombox_ports = Firewall(
|
||||
'firewall-plinth',
|
||||
format_lazy(
|
||||
_('{box_name} Web Interface (Plinth)'), box_name=_(
|
||||
cfg.box_name)), ports=['http', 'https'], is_external=True)
|
||||
format_lazy(_('{box_name} Web Interface (Plinth)'),
|
||||
box_name=_(cfg.box_name)), ports=['http', 'https'],
|
||||
is_external=True)
|
||||
self.add(freedombox_ports)
|
||||
|
||||
letsencrypt = LetsEncrypt('letsencrypt-apache', domains='*',
|
||||
|
||||
@ -23,7 +23,6 @@ from plinth import action_utils, actions, app
|
||||
|
||||
class Webserver(app.LeaderComponent):
|
||||
"""Component to enable/disable Apache configuration."""
|
||||
|
||||
def __init__(self, component_id, web_name, kind='config'):
|
||||
"""Initialize the web server component.
|
||||
|
||||
@ -62,7 +61,6 @@ class Webserver(app.LeaderComponent):
|
||||
|
||||
class Uwsgi(app.LeaderComponent):
|
||||
"""Component to enable/disable uWSGI configuration."""
|
||||
|
||||
def __init__(self, component_id, uwsgi_name):
|
||||
"""Initialize the uWSGI component.
|
||||
|
||||
|
||||
@ -14,10 +14,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
URLs for the Apache module.
|
||||
"""
|
||||
|
||||
urlpatterns = [
|
||||
]
|
||||
urlpatterns = []
|
||||
|
||||
@ -45,9 +45,8 @@ def shortcuts(request, **kwargs):
|
||||
# XXX: Get the module (or module name) from shortcut properly.
|
||||
username = str(request.user) if request.user.is_authenticated else None
|
||||
response = get_shortcuts_as_json(username)
|
||||
return HttpResponse(
|
||||
json.dumps(response, cls=DjangoJSONEncoder),
|
||||
content_type='application/json')
|
||||
return HttpResponse(json.dumps(response, cls=DjangoJSONEncoder),
|
||||
content_type='application/json')
|
||||
|
||||
|
||||
def get_shortcuts_as_json(username=None):
|
||||
|
||||
@ -91,9 +91,10 @@ def init():
|
||||
global app
|
||||
app = AvahiApp()
|
||||
if app.is_enabled():
|
||||
domain_added.send_robust(
|
||||
sender='avahi', domain_type='domain-type-local',
|
||||
name=get_hostname() + '.local', services='__all__')
|
||||
domain_added.send_robust(sender='avahi',
|
||||
domain_type='domain-type-local',
|
||||
name=get_hostname() + '.local',
|
||||
services='__all__')
|
||||
app.set_enabled(True)
|
||||
|
||||
post_hostname_change.connect(on_post_hostname_change)
|
||||
|
||||
@ -108,9 +108,8 @@ def _backup_handler(packet, encryption_passphrase=None):
|
||||
arguments = ['create-archive', '--path', packet.path, '--paths'] + paths
|
||||
input_data = ''
|
||||
if encryption_passphrase:
|
||||
input_data = json.dumps({
|
||||
'encryption_passphrase': encryption_passphrase
|
||||
})
|
||||
input_data = json.dumps(
|
||||
{'encryption_passphrase': encryption_passphrase})
|
||||
|
||||
actions.superuser_run('backups', arguments, input=input_data.encode())
|
||||
|
||||
|
||||
@ -82,7 +82,6 @@ def _validate_service(service):
|
||||
|
||||
class BackupError:
|
||||
"""Represent an backup/restore operation error."""
|
||||
|
||||
def __init__(self, error_type, app, hook=None):
|
||||
"""Initialize the error object."""
|
||||
self.error_type = error_type
|
||||
@ -98,7 +97,6 @@ class BackupError:
|
||||
|
||||
class Packet:
|
||||
"""Information passed to a handlers for backup/restore operations."""
|
||||
|
||||
def __init__(self, operation, scope, root, apps=None, path=None):
|
||||
"""Initialize the packet.
|
||||
|
||||
@ -240,7 +238,6 @@ def _install_apps_before_restore(apps):
|
||||
|
||||
class BackupApp:
|
||||
"""A application that can be backed up and its manifest."""
|
||||
|
||||
def __init__(self, name, app):
|
||||
"""Initialize object and load manfiest."""
|
||||
self.name = name
|
||||
@ -365,7 +362,6 @@ def _switch_to_subvolume(subvolume):
|
||||
|
||||
class ServiceHandler:
|
||||
"""Abstraction to help with service shutdown/restart."""
|
||||
|
||||
@staticmethod
|
||||
def create(backup_app, service):
|
||||
service_type = 'system'
|
||||
@ -400,7 +396,6 @@ class ServiceHandler:
|
||||
|
||||
class SystemServiceHandler(ServiceHandler):
|
||||
"""Handle starting and stopping of system services for backup."""
|
||||
|
||||
def __init__(self, backup_app, service):
|
||||
"""Initialize the object."""
|
||||
super().__init__(backup_app, service)
|
||||
@ -420,7 +415,6 @@ class SystemServiceHandler(ServiceHandler):
|
||||
|
||||
class ApacheServiceHandler(ServiceHandler):
|
||||
"""Handle starting and stopping of Apache services for backup."""
|
||||
|
||||
def __init__(self, backup_app, service):
|
||||
"""Initialize the object."""
|
||||
super().__init__(backup_app, service)
|
||||
|
||||
@ -30,7 +30,6 @@ def delete_tmp_backup_file(function):
|
||||
XXX: Implement a better way to delete uploaded files.
|
||||
|
||||
"""
|
||||
|
||||
@functools.wraps(function)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
path = request.session.get(SESSION_PATH_VARIABLE, None)
|
||||
|
||||
@ -59,10 +59,8 @@ KNOWN_ERRORS = [
|
||||
'errors': [
|
||||
'not a valid repository', 'does not exist', 'FileNotFoundError'
|
||||
],
|
||||
'message':
|
||||
_('Repository not found'),
|
||||
'raise_as':
|
||||
errors.BorgRepositoryDoesNotExistError,
|
||||
'message': _('Repository not found'),
|
||||
'raise_as': errors.BorgRepositoryDoesNotExistError,
|
||||
},
|
||||
{
|
||||
'errors': ['passphrase supplied in .* is incorrect'],
|
||||
@ -176,7 +174,6 @@ class BaseBorgRepository(abc.ABC):
|
||||
|
||||
def remove(self):
|
||||
"""Remove a borg repository"""
|
||||
|
||||
def list_archives(self):
|
||||
"""Return list of archives in this repository."""
|
||||
output = self.run(['list-repo', '--path', self.borg_path])
|
||||
@ -241,7 +238,6 @@ class BaseBorgRepository(abc.ABC):
|
||||
|
||||
def get_download_stream(self, archive_name):
|
||||
"""Return an stream of .tar.gz binary data for a backup archive."""
|
||||
|
||||
class BufferedReader(io.BufferedReader):
|
||||
"""Improve performance of buffered binary streaming.
|
||||
|
||||
@ -255,7 +251,6 @@ class BaseBorgRepository(abc.ABC):
|
||||
binary data.
|
||||
|
||||
"""
|
||||
|
||||
def __next__(self):
|
||||
"""Override to call read() instead of readline()."""
|
||||
chunk = self.read(io.DEFAULT_BUFFER_SIZE)
|
||||
|
||||
@ -57,7 +57,6 @@ def _get_backup_app(name):
|
||||
|
||||
class TestBackupApp:
|
||||
"""Test the BackupApp class."""
|
||||
|
||||
@staticmethod
|
||||
def test_run_hook():
|
||||
"""Test running a hook on an application."""
|
||||
@ -82,7 +81,6 @@ class TestBackupApp:
|
||||
@pytest.mark.usefixtures('load_cfg')
|
||||
class TestBackupProcesses:
|
||||
"""Test cases for backup processes"""
|
||||
|
||||
@staticmethod
|
||||
def test_packet_process_manifests():
|
||||
"""Test that directories/files are collected from manifests."""
|
||||
@ -239,7 +237,6 @@ class TestBackupProcesses:
|
||||
|
||||
class TestBackupModule:
|
||||
"""Tests of the backups django module, like views or forms."""
|
||||
|
||||
@staticmethod
|
||||
def test_file_upload():
|
||||
# posting a video should fail
|
||||
|
||||
@ -44,6 +44,8 @@ managed_packages = ['cockpit']
|
||||
|
||||
name = _('Cockpit')
|
||||
|
||||
icon_filename = 'cockpit'
|
||||
|
||||
short_description = _('Server Administration')
|
||||
|
||||
description = [
|
||||
@ -56,7 +58,11 @@ description = [
|
||||
format_lazy(
|
||||
_('It can be accessed by <a href="{users_url}">any user</a> on '
|
||||
'{box_name} belonging to the admin group.'),
|
||||
box_name=_(cfg.box_name), users_url=reverse_lazy('users:index'))
|
||||
box_name=_(cfg.box_name), users_url=reverse_lazy('users:index')),
|
||||
format_lazy(
|
||||
_('Cockpit requires that you access it through a domain name. '
|
||||
'It will not work when accessed using an IP address as part'
|
||||
' of the URL.')),
|
||||
]
|
||||
|
||||
manual_page = 'Cockpit'
|
||||
|
||||
39
plinth/modules/cockpit/templates/cockpit.html
Normal file
39
plinth/modules/cockpit/templates/cockpit.html
Normal file
@ -0,0 +1,39 @@
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
{% endcomment %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block status %}
|
||||
{{ block.super }}
|
||||
|
||||
<h3>{% trans "Access" %}</h3>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Cockpit will only work when accessed using the following URLs.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
{% for url_ in urls %}
|
||||
<li>{{ url_ }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endblock %}
|
||||
@ -20,15 +20,8 @@ URLs for Cockpit module.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from plinth.modules import cockpit
|
||||
from plinth.views import AppView
|
||||
from plinth.modules.cockpit.views import CockpitAppView
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^sys/cockpit/$',
|
||||
AppView.as_view(app_id='cockpit', name=cockpit.name,
|
||||
diagnostics_module_name='cockpit',
|
||||
description=cockpit.description,
|
||||
show_status_block=True, clients=cockpit.clients,
|
||||
manual_page=cockpit.manual_page), name='index'),
|
||||
url(r'^sys/cockpit/$', CockpitAppView.as_view(), name='index'),
|
||||
]
|
||||
|
||||
@ -27,8 +27,8 @@ CONFIG_FILE = '/etc/cockpit/cockpit.conf'
|
||||
|
||||
def load_augeas():
|
||||
"""Initialize Augeas."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/inifile/lens', 'Puppet.lns')
|
||||
aug.set('/augeas/load/inifile/incl[last() + 1]', CONFIG_FILE)
|
||||
aug.load()
|
||||
|
||||
47
plinth/modules/cockpit/views.py
Normal file
47
plinth/modules/cockpit/views.py
Normal file
@ -0,0 +1,47 @@
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
"""
|
||||
Views for the Cockpit module
|
||||
"""
|
||||
from plinth.views import AppView
|
||||
from plinth.modules.cockpit import (
|
||||
name,
|
||||
description,
|
||||
clients,
|
||||
manual_page,
|
||||
icon_filename,
|
||||
)
|
||||
from plinth.modules.cockpit.utils import get_origin_domains, load_augeas
|
||||
|
||||
|
||||
class CockpitAppView(AppView):
|
||||
app_id = 'cockpit'
|
||||
name = name
|
||||
description = description
|
||||
diagnostics_module_name = 'cockpit'
|
||||
show_status_block = True
|
||||
clients = clients
|
||||
manual_page = manual_page
|
||||
template_name = 'cockpit.html'
|
||||
icon_filename = icon_filename
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
urls = get_origin_domains(load_augeas())
|
||||
context['urls'] = [url for url in urls if 'localhost' not in url]
|
||||
|
||||
return context
|
||||
@ -36,6 +36,11 @@ is_essential = True
|
||||
|
||||
name = _('General Configuration')
|
||||
|
||||
description = [
|
||||
_('Here you can set some general configuration options '
|
||||
'like hostname, domain name, webserver home page etc.')
|
||||
]
|
||||
|
||||
depends = ['firewall', 'names']
|
||||
|
||||
manual_page = 'Configure'
|
||||
@ -81,8 +86,8 @@ def get_hostname():
|
||||
|
||||
def _get_home_page_url():
|
||||
"""Get the default application for the domain."""
|
||||
aug = augeas.Augeas(
|
||||
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
||||
conf_file = APACHE_HOMEPAGE_CONFIG if os.path.exists(
|
||||
APACHE_HOMEPAGE_CONFIG) else FREEDOMBOX_APACHE_CONFIG
|
||||
|
||||
@ -67,11 +67,10 @@ class ConfigurationForm(forms.Form):
|
||||
'end with an alphabet or a digit and have as interior '
|
||||
'characters only alphabets, digits and hyphens. Total '
|
||||
'length must be 63 characters or less.'),
|
||||
box_name=ugettext_lazy(cfg.box_name)),
|
||||
validators=[
|
||||
validators.RegexValidator(HOSTNAME_REGEX,
|
||||
ugettext_lazy('Invalid hostname'))
|
||||
], strip=True)
|
||||
box_name=ugettext_lazy(cfg.box_name)), validators=[
|
||||
validators.RegexValidator(HOSTNAME_REGEX,
|
||||
ugettext_lazy('Invalid hostname'))
|
||||
], strip=True)
|
||||
|
||||
domainname = forms.CharField(
|
||||
label=ugettext_lazy('Domain Name'), help_text=format_lazy(
|
||||
@ -83,12 +82,12 @@ class ConfigurationForm(forms.Form):
|
||||
'only alphabets, digits and hyphens. Length of each label '
|
||||
'must be 63 characters or less. Total length of domain name '
|
||||
'must be 253 characters or less.'),
|
||||
box_name=ugettext_lazy(cfg.box_name)),
|
||||
required=False, validators=[
|
||||
validators.RegexValidator(
|
||||
r'^[a-zA-Z0-9]([-a-zA-Z0-9.]{,251}[a-zA-Z0-9])?$',
|
||||
ugettext_lazy('Invalid domain name')), domain_label_validator
|
||||
], strip=True)
|
||||
box_name=ugettext_lazy(cfg.box_name)), required=False, validators=[
|
||||
validators.RegexValidator(
|
||||
r'^[a-zA-Z0-9]([-a-zA-Z0-9.]{,251}[a-zA-Z0-9])?$',
|
||||
ugettext_lazy('Invalid domain name')),
|
||||
domain_label_validator
|
||||
], strip=True)
|
||||
|
||||
homepage = forms.ChoiceField(
|
||||
label=ugettext_lazy('Webserver Home Page'), help_text=format_lazy(
|
||||
|
||||
@ -36,6 +36,7 @@ LOGGER = logging.getLogger(__name__)
|
||||
class ConfigAppView(views.AppView):
|
||||
"""Serve configuration page."""
|
||||
name = config.name
|
||||
description = config.description
|
||||
form_class = ConfigurationForm
|
||||
app_id = 'config'
|
||||
manual_page = config.manual_page
|
||||
|
||||
@ -38,6 +38,8 @@ managed_packages = ['deluged', 'deluge-web']
|
||||
|
||||
name = _('Deluge')
|
||||
|
||||
icon_filename = 'deluge'
|
||||
|
||||
short_description = _('BitTorrent Web Client')
|
||||
|
||||
description = [
|
||||
@ -71,7 +73,7 @@ class DelugeApp(app_module.App):
|
||||
|
||||
shortcut = frontpage.Shortcut('shortcut-deluge', name,
|
||||
short_description=short_description,
|
||||
url='/deluge', icon='deluge',
|
||||
url='/deluge', icon=icon_filename,
|
||||
clients=clients, login_required=True,
|
||||
allowed_groups=[group[0]])
|
||||
self.add(shortcut)
|
||||
|
||||
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