mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-04-29 10:10:19 +00:00
Compare commits
145 Commits
7fd47736ad
...
2d2efbdb8c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d2efbdb8c | ||
|
|
acd94255c3 | ||
|
|
4db977ff4e | ||
|
|
448d5d84ed | ||
|
|
41640f5d3c | ||
|
|
5381990e02 | ||
|
|
5a2e8ddc16 | ||
|
|
4d6ddcdcdb | ||
|
|
97a2d68ac6 | ||
|
|
6128d3be16 | ||
|
|
bce25f465f | ||
|
|
71913580db | ||
|
|
eb709f8687 | ||
|
|
88ad08c074 | ||
|
|
9e795ee8d7 | ||
|
|
cfc5d3acab | ||
|
|
212d865b43 | ||
|
|
9a55902f37 | ||
|
|
07845bc960 | ||
|
|
abbc4e7557 | ||
|
|
af70c73f24 | ||
|
|
5ccb332ce6 | ||
|
|
e2047ec3a0 | ||
|
|
a7584b465d | ||
|
|
6fd85e3e46 | ||
|
|
31e7997d2b | ||
|
|
68126c3ec6 | ||
|
|
29ef56b51e | ||
|
|
efe2bccb11 | ||
|
|
64272a2bef | ||
|
|
af892adb5e | ||
|
|
4ab2007c99 | ||
|
|
3c1d801e15 | ||
|
|
e2da29cf25 | ||
|
|
ce62fdb142 | ||
|
|
cad6bc8ca0 | ||
|
|
483f28de83 | ||
|
|
64f1a1c918 | ||
|
|
cdfbff0b6b | ||
|
|
45076cc603 | ||
|
|
f0b1aa34ac | ||
|
|
bced133d90 | ||
|
|
a8e2d4cd69 | ||
|
|
bbbe2cf950 | ||
|
|
168f662a17 | ||
|
|
0d579012d7 | ||
|
|
854916c54c | ||
|
|
fde0a620f9 | ||
|
|
185559b43f | ||
|
|
82d7cd0e8f | ||
|
|
0eca1394c0 | ||
|
|
4371e2475d | ||
|
|
a7ec37dbce | ||
|
|
778c35f2bc | ||
|
|
bf83cb5a5b | ||
|
|
284a384d3a | ||
|
|
af6d1d9a4c | ||
|
|
9a524b331b | ||
|
|
72005d6205 | ||
|
|
4b24fda3f5 | ||
|
|
ad9ebe2301 | ||
|
|
7e7e7a6ccf | ||
|
|
643a06c7cd | ||
|
|
8a7e70aab2 | ||
|
|
11d58134e5 | ||
|
|
bc257af638 | ||
|
|
6ba35df665 | ||
|
|
e4ee756918 | ||
|
|
7f2b49f70c | ||
|
|
36fb92a953 | ||
|
|
57816029e5 | ||
|
|
3be73bad59 | ||
|
|
5e112bd8bf | ||
|
|
68ccb46ecf | ||
|
|
2044fa3e84 | ||
|
|
bc4730c33c | ||
|
|
365c1c3484 | ||
|
|
32fae4c3d3 | ||
|
|
9a16e20fa9 | ||
|
|
03b4a78fd0 | ||
|
|
ac83de6635 | ||
|
|
b1177a82f8 | ||
|
|
77112e9faf | ||
|
|
d9f20b205b | ||
|
|
c0bd1c8280 | ||
|
|
f0a36f11ba | ||
|
|
b18c37a5f6 | ||
|
|
ccf5231569 | ||
|
|
020ef6ae0c | ||
|
|
cc8fa47efe | ||
|
|
59c3b49d8b | ||
|
|
a4cfb824e8 | ||
|
|
59329169e4 | ||
|
|
df7793916c | ||
|
|
0c8cba3a13 | ||
|
|
5da5ef5f96 | ||
|
|
4ae66c034c | ||
|
|
e21ab91b21 | ||
|
|
dd0a0f56a6 | ||
|
|
a7ef60015c | ||
|
|
2208a7b210 | ||
|
|
ff7c3a53a5 | ||
|
|
7d3d930137 | ||
|
|
ad40072267 | ||
|
|
305b1f01f5 | ||
|
|
58ecf9d3e4 | ||
|
|
3cb5d1a936 | ||
|
|
8b9413c719 | ||
|
|
451e582c07 | ||
|
|
043bd44dec | ||
|
|
3558a26b2f | ||
|
|
f689e1b3cf | ||
|
|
7fb41313cd | ||
|
|
156d0b761f | ||
|
|
33d05ef5be | ||
|
|
a1bd01f0c1 | ||
|
|
ea4172a4c2 | ||
|
|
0ee977a6b0 | ||
|
|
6b7e518eed | ||
|
|
196fcea328 | ||
|
|
e37d26abee | ||
|
|
13a575017c | ||
|
|
4e668c8a98 | ||
|
|
367b2d9f79 | ||
|
|
7eac69a1f8 | ||
|
|
d0a73142ac | ||
|
|
48929b9d75 | ||
|
|
f5e487569f | ||
|
|
04ba96a467 | ||
|
|
01cafafcda | ||
|
|
3c5f81ab8c | ||
|
|
57f5105fd0 | ||
|
|
f4b1eb23ac | ||
|
|
b0a841c63a | ||
|
|
0fa77cbe30 | ||
|
|
7988cc737b | ||
|
|
2bb2eaa6ec | ||
|
|
c19d2ab692 | ||
|
|
01da6934be | ||
|
|
6960a57779 | ||
|
|
2237d89745 | ||
|
|
e3b893277c | ||
|
|
6bf95de3bc | ||
|
|
0614b5e509 | ||
|
|
53f7c75d8e |
@ -1,16 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- mode: python -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Set required permissions for user "plinth" to run plinth in dev setup."""
|
||||
|
||||
import pathlib
|
||||
|
||||
content = '''
|
||||
Cmnd_Alias FREEDOMBOX_ACTION_DEV = /usr/share/plinth/actions/actions, /freedombox/actions/actions
|
||||
Defaults!FREEDOMBOX_ACTION_DEV closefrom_override
|
||||
plinth ALL=(ALL:ALL) NOPASSWD:SETENV : FREEDOMBOX_ACTION_DEV
|
||||
fbx ALL=(ALL:ALL) NOPASSWD : ALL
|
||||
'''
|
||||
|
||||
sudoers_file = pathlib.Path('/etc/sudoers.d/01-freedombox-development')
|
||||
sudoers_file.write_text(content)
|
||||
18
HACKING.md
18
HACKING.md
@ -66,7 +66,7 @@ development environment inside a systemd-nspawn container.
|
||||
folder: (This step requires at least 16GB of free disk space)
|
||||
|
||||
```bash
|
||||
host$ ./container up
|
||||
host$ ./container start
|
||||
```
|
||||
|
||||
1. To run unit tests:
|
||||
@ -97,20 +97,20 @@ development environment inside a systemd-nspawn container.
|
||||
1. Using an environment variable.
|
||||
|
||||
```bash
|
||||
host$ DISTRIBUTION=stable ./container up
|
||||
host$ DISTRIBUTION=stable ./container start
|
||||
host$ DISTRIBUTION=stable ./container ssh
|
||||
```
|
||||
|
||||
```bash
|
||||
host$ export DISTRIBUTION=stable
|
||||
host$ ./container up
|
||||
host$ ./container start
|
||||
host$ ./container ssh
|
||||
```
|
||||
|
||||
2. Using the `--distribution` option for each command.
|
||||
|
||||
```bash
|
||||
host$ ./container up --distribution=stable
|
||||
host$ ./container start --distribution=stable
|
||||
host$ ./container ssh --distribution=stable
|
||||
```
|
||||
|
||||
@ -131,7 +131,7 @@ used simultaneously as they all use different disk images.
|
||||
example, to bring up a virtual machine instead of a container run:
|
||||
|
||||
```bash
|
||||
host$ ./container up --machine-type=vm
|
||||
host$ ./container start --machine-type=vm
|
||||
```
|
||||
|
||||
#### Using after Setup
|
||||
@ -164,9 +164,9 @@ Note: This development container has automatic upgrades disabled by default.
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
* Sometimes `host$ ./container destroy && ./container up` doesn't work. In such
|
||||
* Sometimes `host$ ./container destroy && ./container start` doesn't work. In such
|
||||
cases, try to delete the hidden `.container` folder and then `host$
|
||||
./container up`.
|
||||
./container start`.
|
||||
* Not all kinds of changes are automatically updated. Try `guest$ sudo mount -o
|
||||
remount /freedombox`.
|
||||
* I am getting an error that says `lo` is not managed by Network Manager
|
||||
@ -178,7 +178,7 @@ Note: This development container has automatic upgrades disabled by default.
|
||||
```bash
|
||||
host$ sudo touch /etc/NetworkManager/conf.d/10-globally-managed-devices.conf
|
||||
host$ sudo service network-manager restart
|
||||
host$ ./container destroy && ./container up
|
||||
host$ ./container destroy && ./container start
|
||||
```
|
||||
* File/directory not found errors when running tests can be fixed by clearing `__pycache__` directories.
|
||||
|
||||
@ -416,7 +416,7 @@ for more details.
|
||||
### Translating literals (contributing translations)
|
||||
|
||||
The easiest way to start translating is with your browser, by using
|
||||
[Weblate](https://hosted.weblate.org/projects/freedombox/plinth/).
|
||||
[Weblate](https://hosted.weblate.org/projects/freedombox/freedombox/).
|
||||
Your changes will automatically get pushed to the code repository.
|
||||
|
||||
Alternatively, you can directly edit the `.po` file in your language directory
|
||||
|
||||
@ -35,7 +35,7 @@ FreedomBox [Manual](https://wiki.debian.org/FreedomBox/Manual/)'s
|
||||
|
||||
3. Access FreedomBox UI:
|
||||
|
||||
UI should be accessible at http://localhost:8000/plinth
|
||||
UI should be accessible at http://localhost:8000/freedombox
|
||||
|
||||
If you are installing FreedomBox Service (Plinth) for development purposes, see
|
||||
HACKING.md instead.
|
||||
|
||||
8
Makefile
8
Makefile
@ -21,7 +21,8 @@ DISABLED_APPS_TO_REMOVE := \
|
||||
tahoe \
|
||||
mldonkey \
|
||||
i2p \
|
||||
ttrss
|
||||
ttrss \
|
||||
sso
|
||||
|
||||
APP_FILES_TO_REMOVE := $(foreach app,$(DISABLED_APPS_TO_REMOVE),$(ENABLED_APPS_PATH)/$(app))
|
||||
|
||||
@ -101,11 +102,12 @@ install:
|
||||
$(INSTALL) -d $(DESTDIR)$${lib_dir} && \
|
||||
rm -rf $(DESTDIR)$${lib_dir}/plinth $(DESTDIR)$${lib_dir}/plinth*.dist-info && \
|
||||
mv $${temp}/plinth $${temp}/plinth*.dist-info $(DESTDIR)$${lib_dir} && \
|
||||
rm -f $(DESTDIR)$${lib_dir}/plinth*.dist-info/COPYING.md && \
|
||||
rm -f $(DESTDIR)$${lib_dir}/plinth*.dist-info/licenses/COPYING.md && \
|
||||
rm -f $(DESTDIR)$${lib_dir}/plinth*.dist-info/direct_url.json && \
|
||||
$(INSTALL) -D -t $(BIN_DIR) bin/plinth
|
||||
$(INSTALL) -D -t $(LIB_DIR)/freedombox bin/freedombox-privileged
|
||||
$(INSTALL) -D -t $(BIN_DIR) bin/freedombox-cmd
|
||||
$(INSTALL) -D -t $(BIN_DIR) bin/freedombox-change-password
|
||||
|
||||
# Static web server files
|
||||
rm -rf $(STATIC_FILES_DIRECTORY)
|
||||
@ -229,7 +231,7 @@ provision-dev:
|
||||
sshpass bash-completion
|
||||
|
||||
wait-while-first-setup:
|
||||
while [ x$$(curl -k https://localhost/plinth/status/ 2> /dev/null | \
|
||||
while [ x$$(curl -k https://localhost/freedombox/status/ 2> /dev/null | \
|
||||
json_pp 2> /dev/null | grep 'is_first_setup_running' | \
|
||||
tr -d '[:space:]' | cut -d':' -f2 ) != 'xfalse' ] ; do \
|
||||
sleep 1; echo -n .; done
|
||||
|
||||
15
Vagrantfile
vendored
15
Vagrantfile
vendored
@ -6,8 +6,7 @@ require 'etc'
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "freedombox/freedombox-testing-dev"
|
||||
config.vm.network "forwarded_port", guest: 443, host: 4430
|
||||
config.vm.network "forwarded_port", guest: 445, host: 4450
|
||||
config.vm.network "public_network"
|
||||
config.vm.synced_folder ".", "/freedombox", owner: "plinth", group: "plinth"
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.cpus = Etc.nprocessors
|
||||
@ -28,17 +27,15 @@ Vagrant.configure(2) do |config|
|
||||
SHELL
|
||||
config.vm.provision "tests", run: "never", type: "shell", path: "plinth/tests/functional/install.sh"
|
||||
config.vm.post_up_message = "FreedomBox virtual machine is ready
|
||||
for development. Plinth will be available at https://localhost:4430/plinth
|
||||
for development. To get the IP address:
|
||||
$ vagrant ssh
|
||||
$ ip address show
|
||||
|
||||
FreedomBox interface will be available at https://<ip address>/freedombox
|
||||
(with an invalid SSL certificate). To watch logs:
|
||||
$ vagrant ssh
|
||||
$ sudo freedombox-logs
|
||||
"
|
||||
|
||||
config.trigger.after [:up, :resume, :reload] do |trigger|
|
||||
trigger.info = "Set plinth user permissions for development environment"
|
||||
trigger.run_remote = {
|
||||
path: ".vagrant-scripts/plinth-user-permissions.py"
|
||||
}
|
||||
end
|
||||
config.vm.boot_timeout=1200
|
||||
end
|
||||
|
||||
61
bin/freedombox-change-password
Executable file
61
bin/freedombox-change-password
Executable file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Utility to change user password in FreedomBox's Django database.
|
||||
|
||||
Usage:
|
||||
$ freedombox-change-password <username>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import sys
|
||||
|
||||
import plinth.web_framework
|
||||
from plinth.modules.users import privileged
|
||||
|
||||
|
||||
def main():
|
||||
"""Ask for new password, setup Django and update a user's password."""
|
||||
try:
|
||||
plinth.web_framework.init()
|
||||
except Exception:
|
||||
_print('Error initializing Django.')
|
||||
return
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('username',
|
||||
help='Username of the account to change password for')
|
||||
args = parser.parse_args()
|
||||
|
||||
username = args.username
|
||||
password = getpass.getpass('Enter new password: ')
|
||||
|
||||
try:
|
||||
_change_password(username, password)
|
||||
privileged._set_user_password(username, password)
|
||||
privileged._set_samba_user(username, password)
|
||||
_print('Password updated in web interface, LDAP, and samba databases.')
|
||||
except Exception as exception:
|
||||
_print('Error setting password:', str(exception))
|
||||
|
||||
|
||||
def _print(*args, **kwargs):
|
||||
"""Write to stderr."""
|
||||
print(*args, **kwargs, file=sys.stderr)
|
||||
|
||||
|
||||
def _change_password(username: str, password: str):
|
||||
"""Update the password in SQLite database file."""
|
||||
from django.contrib.auth.models import User
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
except User.DoesNotExist:
|
||||
_print('User account does not exist:', username)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -227,6 +227,7 @@ fi
|
||||
|
||||
echo "> In machine: Upgrade packages"
|
||||
apt-get update
|
||||
apt-mark hold freedombox freedombox-doc-en freedombox-doc-es
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -yq --with-new-pkgs upgrade
|
||||
|
||||
# Install requirements for tests if not already installed as root
|
||||
@ -1064,7 +1065,7 @@ Terminal login : sudo machinectl login fbx-{distribution}
|
||||
Open a root shell : sudo machinectl shell fbx-{distribution}
|
||||
Shutdown : {script} stop {options}
|
||||
Destroy : {script} destroy {options}
|
||||
Reset : {script} destroy {options}; {script} up {options}'''
|
||||
Reset : {script} destroy {options}; {script} start {options}'''
|
||||
logger.info(message)
|
||||
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
# Don't redirect for onion sites as it is not needed and leads to
|
||||
# unnecessary warning.
|
||||
RewriteCond %{HTTP_HOST} !^.*\.onion$ [NC]
|
||||
RewriteCond %{REQUEST_URI} !^/freedombox/apache/discover-idp/$ [NC]
|
||||
ReWriteCond %{HTTPS} !=on
|
||||
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
</LocationMatch>
|
||||
|
||||
@ -39,16 +39,16 @@
|
||||
</If>
|
||||
|
||||
##
|
||||
## Redirect traffic on home to /plinth as part of turning the machine
|
||||
## Redirect traffic on home to /freedombox as part of turning the machine
|
||||
## into FreedomBox server. Plinth then acts as a portal to reach all
|
||||
## other services.
|
||||
##
|
||||
<IfFile !/etc/apache2/conf-enabled/freedombox-apache-homepage.conf>
|
||||
RedirectMatch "^/$" "/plinth"
|
||||
RedirectMatch "^/$" "/freedombox"
|
||||
</IfFile>
|
||||
|
||||
##
|
||||
## On all sites, provide FreedomBox on a default path: /plinth
|
||||
## On all sites, provide FreedomBox on a default path: /freedombox
|
||||
##
|
||||
## Requires the following Apache modules to be enabled:
|
||||
## mod_headers
|
||||
@ -56,7 +56,8 @@
|
||||
## mod_proxy_http
|
||||
##
|
||||
<Location /freedombox>
|
||||
ProxyPass http://127.0.0.1:8000/plinth
|
||||
ProxyPass http://127.0.0.1:8000/freedombox
|
||||
ProxyPreserveHost On
|
||||
## Send the scheme from user's request to enable Plinth to redirect
|
||||
## URLs, set cookies, set absolute URLs (if any) properly.
|
||||
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
|
||||
@ -70,7 +71,20 @@
|
||||
RequestHeader unset X-Forwarded-For
|
||||
</Location>
|
||||
<Location /plinth>
|
||||
ProxyPass http://127.0.0.1:8000/plinth
|
||||
ProxyPass http://127.0.0.1:8000/freedombox
|
||||
ProxyPreserveHost On
|
||||
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
|
||||
RequestHeader unset X-Forwarded-For
|
||||
</Location>
|
||||
<Location /.well-known/openid-configuration>
|
||||
ProxyPass http://127.0.0.1:8000/freedombox/o/.well-known/openid-configuration
|
||||
ProxyPreserveHost On
|
||||
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
|
||||
RequestHeader unset X-Forwarded-For
|
||||
</Location>
|
||||
<Location /.well-known/jwks.json>
|
||||
ProxyPass http://127.0.0.1:8000/freedombox/o/.well-known/jwks.json
|
||||
ProxyPreserveHost On
|
||||
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
|
||||
RequestHeader unset X-Forwarded-For
|
||||
</Location>
|
||||
@ -82,7 +96,7 @@
|
||||
<Location ~ ^/favicon\.ico$>
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule /favicon\.ico$ "/plinth/static/theme/img/favicon.ico" [PT]
|
||||
RewriteRule /favicon\.ico$ "/freedombox/static/theme/img/favicon.ico" [PT]
|
||||
</IfModule>
|
||||
</Location>
|
||||
|
||||
|
||||
230
debian/changelog
vendored
230
debian/changelog
vendored
@ -1,3 +1,233 @@
|
||||
freedombox (26.4.2~bpo13+1) trixie-backports; urgency=high
|
||||
|
||||
* Rebuild for trixie-backports.
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Sun, 08 Mar 2026 16:08:34 -0400
|
||||
|
||||
freedombox (26.4.2) unstable; urgency=medium
|
||||
|
||||
[ Jiří Podhorecký ]
|
||||
* Translated using Weblate (Czech)
|
||||
|
||||
[ OwlGale ]
|
||||
* Translated using Weblate (Russian)
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Sun, 08 Mar 2026 15:27:13 -0400
|
||||
|
||||
freedombox (26.4.1) unstable; urgency=high
|
||||
|
||||
[ 大王叫我来巡山 ]
|
||||
* Translated using Weblate (Chinese (Simplified Han script))
|
||||
|
||||
[ Besnik Bleta ]
|
||||
* Translated using Weblate (Albanian)
|
||||
|
||||
[ Burak Yavuz ]
|
||||
* Translated using Weblate (Turkish)
|
||||
|
||||
[ Jiří Podhorecký ]
|
||||
* Translated using Weblate (Czech)
|
||||
|
||||
[ James Valleroy ]
|
||||
* container: Hold freedombox packages during test setup
|
||||
* Vagrantfile: Enable public network for bridged networking
|
||||
* doc: Fetch latest manual
|
||||
|
||||
[ Sunil Mohan Adapa ]
|
||||
* d/control: Trim deps for nocheck build profile (Closes: #1129521)
|
||||
* apache2: Disable pubtkt authentication module
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Sun, 08 Mar 2026 15:09:38 -0400
|
||||
|
||||
freedombox (26.4~bpo13+1) trixie-backports; urgency=medium
|
||||
|
||||
* Rebuild for trixie-backports.
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Fri, 06 Mar 2026 15:34:02 -0500
|
||||
|
||||
freedombox (26.4) unstable; urgency=medium
|
||||
|
||||
[ Joseph Nuthalapati ]
|
||||
* ui: Dismiss notifications without page reload
|
||||
|
||||
[ Sunil Mohan Adapa ]
|
||||
* ui: Refactor notification delete buttons to avoid repeating code
|
||||
* ui: Add animation for notification dismissal
|
||||
* actions, privileged_daemon: Drop some unused global statements
|
||||
* backups: Avoid some repeated text in form help text
|
||||
* backups: Fix issue with Javascript in add remote location form
|
||||
* backups: Show/hide form elements instead of disabling for simplicity
|
||||
* backups: Tweak appearance of add remote location form
|
||||
* backups: tests: Simplify functional test using more classes
|
||||
* backups: Minor refactoring
|
||||
* backups: Simplify handling of migration to SSH keys
|
||||
* backups: Create .ssh folder before creating SSH key
|
||||
* backups: Fix showing proper error for incorrect passphrase
|
||||
* backups: Create a better comment in the generated SSH key file
|
||||
* backups: Fix type checking errors
|
||||
* action_utils: Implement utility to change umask temporarily
|
||||
* quassel: Explicitly set permissions on the domain configuration file
|
||||
* letsencrypt: When copying certificate reset the umask reliably
|
||||
* doc/dev: Set new theme for developer documentation
|
||||
* action_utils: Fix issue with type checking a generator
|
||||
* tests: functional: Increase systemd rate limits for starting units
|
||||
* js: When page load fails during install, show it to user
|
||||
* tests: functional: Fix reloading error page during install/uninstall
|
||||
* locale/de: Fix several translations with HTML links (German)
|
||||
* locale/bg: Fix several translations with HTML links (Bulgarian)
|
||||
* bin: Add tool to change FreedomBox password in Django database
|
||||
* ejabberd: Fix setting up certificates for multiple domains
|
||||
* gitweb: Fix issue with running post init due to missing method
|
||||
* wireguard: Fix format when showing multiple endpoints of the server
|
||||
* wireguard: Fix showing default route setting in server edit form
|
||||
* wireguard: Show status of default route in server information page
|
||||
* wireguard: Accept/use netmask with IP address for server connection
|
||||
* README/HACKING: Update weblate project path to /freedombox
|
||||
* *: Remove some absolute file paths in SVGs
|
||||
* matrixsynapse: Update apache config to proxy Synapse client API
|
||||
* cfg: Drop unused config_dir option
|
||||
* cfg: Drop unused actions_dir option
|
||||
* Vagrantfile: Drop unnecessary sudo configuration for actions
|
||||
* pyproject: Use new format to specify licenses
|
||||
* action_utils: Drop support for link-local IPv6 addresses
|
||||
* debian: Ensure that gbp creates a clean tarball prior to build
|
||||
* syncthing: tests: Fix tests by allowing rapid restarts
|
||||
* web_server: Log requests to WSGI app
|
||||
* *: Update URL base from /plinth to /freedombox
|
||||
* tests: functional: Fix expecting FreedomBox to be home page
|
||||
* web_framework: Allow FreedomBox apps to override templates
|
||||
* templates: Allow building pages without navigation bar and footer
|
||||
* apache: Preserve host header when proxying to service
|
||||
* oidc: New app to implement OpenID Connect Provider
|
||||
* oidc: Style the page for authorizing an OIDC app
|
||||
* apache: Implement protecting apps using OpenID Connect
|
||||
* featherwiki: Use OpenID Connect instead of pubtkt based SSO
|
||||
* syncthing: Use OpenID Connect instead of pubtkt based SSO
|
||||
* searx: Use OpenID Connect instead of pubtkt based SSO
|
||||
* rssbridge: Use OpenID Connect instead of pubtkt based SSO
|
||||
* email: Use OpenID Connect instead of pubtkt based SSO
|
||||
* calibre: Use OpenID Connect instead of pubtkt based SSO
|
||||
* deluge: Use OpenID Connect instead of pubtkt based SSO
|
||||
* gitweb: Use OpenID Connect instead of pubtkt based SSO
|
||||
* tiddlywiki: Use OpenID Connect instead of pubtkt based SSO
|
||||
* wordpress: Use OpenID Connect instead of pubtkt based SSO when private
|
||||
* transmission: Use OpenID Connect instead of pubtkt based SSO
|
||||
* doc/dev: Use OpenID Connect instead of pubtkt based SSO
|
||||
* sharing: Use OpenID Connect instead of pubtkt based SSO
|
||||
* sso: Merge into users module, drop pubtkt related code
|
||||
* apache: Fix diagnosing URLs protected by OpenID Connect
|
||||
|
||||
[ 大王叫我来巡山 ]
|
||||
* Translated using Weblate (Chinese (Simplified Han script))
|
||||
|
||||
[ Burak Yavuz ]
|
||||
* Translated using Weblate (Turkish)
|
||||
|
||||
[ Coucouf ]
|
||||
* Translated using Weblate (French)
|
||||
* Translated using Weblate (French)
|
||||
|
||||
[ Besnik Bleta ]
|
||||
* Translated using Weblate (Albanian)
|
||||
|
||||
[ Dietmar ]
|
||||
* Translated using Weblate (German)
|
||||
|
||||
[ James Valleroy ]
|
||||
* backups: Generate SSH client key if needed
|
||||
* backups: Display SSH public key when adding remote
|
||||
* backups: Copy SSH client public key to remote
|
||||
* backups: Use SSH key instead of password
|
||||
* backups: Use selected SSH credential for remote
|
||||
* backups: Test adding/removing remote location
|
||||
* backups: Arrange form for adding remote location
|
||||
* backups: Migrate to SSH key auth when mounting
|
||||
* Translated using Weblate (Greek)
|
||||
* mumble: murmurd renamed to mumble-server
|
||||
* Translated using Weblate (Tamil)
|
||||
* locale: Update translation strings
|
||||
* doc: Fetch latest manual
|
||||
* apache: Fix check_url test
|
||||
|
||||
[ Frederico Gomes ]
|
||||
* container: Align terminology in printed banner
|
||||
* wireguard: filter .local addresses from showClient view
|
||||
* wireguard: improved server section UX flow
|
||||
* wireguard: show server vpn ip in show client page
|
||||
* wireguard: Fix split tunneling
|
||||
* miniflux: Revert workaround for a packaging bug with DB connection
|
||||
* db: Create a utility to get credentials from dbconfig
|
||||
* miniflux: Get credentials from dbconfig-common directly
|
||||
|
||||
[ Pierfrancesco Passerini ]
|
||||
* Translated using Weblate (Italian)
|
||||
|
||||
[ Daniel Wiik ]
|
||||
* Translated using Weblate (Swedish)
|
||||
* Translated using Weblate (Swedish)
|
||||
|
||||
[ kosagi ]
|
||||
* Translated using Weblate (Catalan)
|
||||
|
||||
[ Jiří Podhorecký ]
|
||||
* Translated using Weblate (Czech)
|
||||
|
||||
[ Isak ]
|
||||
* Translated using Weblate (Swedish)
|
||||
|
||||
[ Βασίλης Χατζηκαμάρης ]
|
||||
* Translated using Weblate (Greek)
|
||||
|
||||
[ Benedek Nagy ]
|
||||
* doc/dev: always have an up-to-date copyright year
|
||||
|
||||
[ தமிழ்நேரம் ]
|
||||
* Translated using Weblate (Tamil)
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Mon, 02 Mar 2026 21:35:46 -0500
|
||||
|
||||
freedombox (26.3) unstable; urgency=medium
|
||||
|
||||
[ Frederico Gomes ]
|
||||
* wireguard: Add 'Start Server' button
|
||||
* docs: Update container script usage
|
||||
* wireguard: Show next available client IP in Add Client form
|
||||
* wireguard: Show server endpoint on main app page
|
||||
|
||||
[ James Valleroy ]
|
||||
* wireguard: Update functional tests to handle Start Server button
|
||||
* lintian: Remove mismatched overrides
|
||||
* Makefile: Fix removing extra license file
|
||||
* debian: Follows policy 4.7.3
|
||||
* debian: Remove default Rules-Requires-Root
|
||||
* debian: Remove preinst script
|
||||
* debian: Update copyright years
|
||||
* locale: Update translation strings
|
||||
* doc: Fetch latest manual
|
||||
|
||||
[ Ettore Atalan ]
|
||||
* Translated using Weblate (German)
|
||||
|
||||
[ Sunil Mohan Adapa ]
|
||||
* debian: Ignore lintian warning: service file missing Install section
|
||||
* wireguard: Remove NM connections when app is uninstalled
|
||||
* ui: Use HTMX to update notifications on partial page updates
|
||||
|
||||
[ Joseph Nuthalapati ]
|
||||
* ui: Add HTMX as a dependency
|
||||
* ui: Use HTMX to eliminate full page reloads
|
||||
|
||||
[ Burak Yavuz ]
|
||||
* Translated using Weblate (Turkish)
|
||||
|
||||
[ Pierfrancesco Passerini ]
|
||||
* Translated using Weblate (Italian)
|
||||
|
||||
[ 大王叫我来巡山 ]
|
||||
* Translated using Weblate (Chinese (Simplified Han script))
|
||||
|
||||
-- James Valleroy <jvalleroy@mailbox.org> Mon, 02 Feb 2026 20:41:30 -0500
|
||||
|
||||
freedombox (26.2~bpo13+1) trixie-backports; urgency=medium
|
||||
|
||||
* Rebuild for trixie-backports.
|
||||
|
||||
48
debian/control
vendored
48
debian/control
vendored
@ -1,6 +1,5 @@
|
||||
Source: freedombox
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: FreedomBox packaging team <freedombox-pkg-team@lists.alioth.debian.org>
|
||||
Uploaders:
|
||||
Tzafrir Cohen <tzafrir@debian.org>,
|
||||
@ -15,52 +14,53 @@ Build-Depends:
|
||||
dblatex,
|
||||
dh-python,
|
||||
docbook-xsl,
|
||||
e2fsprogs,
|
||||
e2fsprogs <!nocheck>,
|
||||
gir1.2-nm-1.0,
|
||||
libjs-bootstrap5,
|
||||
libjs-bootstrap5 <!nocheck>,
|
||||
libjs-htmx <!nocheck>,
|
||||
# Older libjs-bootstrap5 does not have proper dependency on popper.js >= 2.0
|
||||
node-popper2,
|
||||
node-popper2 <!nocheck>,
|
||||
pybuild-plugin-pyproject,
|
||||
python3-all:any,
|
||||
python3-apt,
|
||||
python3-apt <!nocheck>,
|
||||
python3-augeas,
|
||||
python3-bootstrapform,
|
||||
python3-bootstrapform <!nocheck>,
|
||||
python3-build,
|
||||
python3-cherrypy3,
|
||||
python3-configobj,
|
||||
python3-cryptography,
|
||||
python3-configobj <!nocheck>,
|
||||
python3-cryptography <!nocheck>,
|
||||
python3-dbus,
|
||||
python3-django,
|
||||
python3-django-axes,
|
||||
python3-django-captcha,
|
||||
python3-django <!nocheck>,
|
||||
python3-django-axes <!nocheck>,
|
||||
python3-django-captcha <!nocheck>,
|
||||
# Explictly depend on ipware as it is optional dependecy of django-axes
|
||||
python3-django-ipware,
|
||||
python3-django-stronghold,
|
||||
python3-django-ipware <!nocheck>,
|
||||
python3-django-oauth-toolkit <!nocheck>,
|
||||
python3-django-stronghold <!nocheck>,
|
||||
python3-gi,
|
||||
python3-markupsafe,
|
||||
python3-mypy,
|
||||
python3-pampy,
|
||||
python3-mypy <!nocheck>,
|
||||
python3-pampy <!nocheck>,
|
||||
python3-pexpect,
|
||||
python3-pip,
|
||||
python3-psutil,
|
||||
python3-pytest,
|
||||
python3-pytest-cov,
|
||||
python3-pytest-django,
|
||||
python3-pytest-runner,
|
||||
python3-pytest <!nocheck>,
|
||||
python3-pytest-cov <!nocheck>,
|
||||
python3-pytest-django <!nocheck>,
|
||||
python3-pytest-runner <!nocheck>,
|
||||
python3-requests,
|
||||
python3-ruamel.yaml,
|
||||
python3-setuptools,
|
||||
python3-systemd,
|
||||
python3-typeshed,
|
||||
python3-typeshed <!nocheck>,
|
||||
python3-yaml,
|
||||
sshpass,
|
||||
sshpass <!nocheck>,
|
||||
xmlto,
|
||||
xsltproc
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.7.3
|
||||
Homepage: https://salsa.debian.org/freedombox-team/freedombox
|
||||
Vcs-Git: https://salsa.debian.org/freedombox-team/freedombox.git -b debian/trixie-backports
|
||||
Vcs-Browser: https://salsa.debian.org/freedombox-team/freedombox
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: freedombox
|
||||
Breaks:
|
||||
@ -89,6 +89,7 @@ Depends:
|
||||
# For gdbus used to call hooks into service
|
||||
libglib2.0-bin,
|
||||
libjs-bootstrap5,
|
||||
libjs-htmx,
|
||||
lsof,
|
||||
netcat-openbsd,
|
||||
network-manager,
|
||||
@ -108,6 +109,7 @@ Depends:
|
||||
python3-django-captcha,
|
||||
# Explictly depend on ipware as it is optional dependecy of django-axes
|
||||
python3-django-ipware,
|
||||
python3-django-oauth-toolkit,
|
||||
python3-django-stronghold,
|
||||
python3-gi,
|
||||
python3-markupsafe,
|
||||
|
||||
5
debian/copyright
vendored
5
debian/copyright
vendored
@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Source: https://salsa.debian.org/freedombox-team/freedombox
|
||||
|
||||
Files: *
|
||||
Copyright: 2011-2025 FreedomBox Authors
|
||||
Copyright: 2011-2026 FreedomBox Authors
|
||||
License: AGPL-3+
|
||||
|
||||
Files: plinth/modules/jsxc/static/icons/jsxc.png
|
||||
@ -79,6 +79,7 @@ Files: plinth/modules/ejabberd/static/icons/ejabberd.png
|
||||
plinth/modules/rssbridge/static/icons/rssbridge.svg
|
||||
plinth/modules/zoph/static/icons/zoph.png
|
||||
plinth/modules/zoph/static/icons/zoph.svg
|
||||
static/themes/default/img/application.svg
|
||||
static/themes/default/img/network-connection.svg
|
||||
static/themes/default/img/network-connection-vertical.svg
|
||||
static/themes/default/img/network-ethernet.svg
|
||||
@ -358,7 +359,7 @@ License: LGPL-3+ or CC-BY-SA-3.0
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2013 Tzafrir Cohen
|
||||
2013-2024 FreedomBox Authors
|
||||
2013-2026 FreedomBox Authors
|
||||
License: GPL-2+
|
||||
|
||||
License: AGPL-3+
|
||||
|
||||
4
debian/freedombox.lintian-overrides
vendored
4
debian/freedombox.lintian-overrides
vendored
@ -24,3 +24,7 @@ freedombox: package-contains-documentation-outside-usr-share-doc [usr/lib/python
|
||||
# meant for user. However, don't install to /usr/libexec and follow systemd
|
||||
# convention instead.
|
||||
freedombox: executable-in-usr-lib [usr/lib/freedombox/freedombox-privileged]
|
||||
|
||||
# [Install] section is missing for the privileged daemon service because it is
|
||||
# socket activated.
|
||||
freedombox: systemd-service-file-missing-install-key [usr/lib/systemd/system/freedombox-privileged.service]
|
||||
|
||||
43
debian/freedombox.preinst
vendored
43
debian/freedombox.preinst
vendored
@ -1,43 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
upgrade)
|
||||
# Handle removing freedombox-setup-repositories.timer from 20.5.
|
||||
if dpkg --compare-versions "$2" le 20.7; then
|
||||
if [ -x "/usr/bin/deb-systemd-invoke" ]; then
|
||||
deb-systemd-invoke stop freedombox-setup-repositories.timer >/dev/null 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -x "/usr/bin/deb-systemd-helper" ]; then
|
||||
deb-systemd-helper purge freedombox-setup-repositories.timer >/dev/null || true
|
||||
deb-systemd-helper unmask freedombox-setup-repositories.timer >/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
fi
|
||||
|
||||
# Handle removing freedombox-udiskie.service from 20.9.
|
||||
if dpkg --compare-versions "$2" le 20.9; then
|
||||
if [ -x "/usr/bin/deb-systemd-invoke" ]; then
|
||||
deb-systemd-invoke stop freedombox-udiskie.service >/dev/null 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -x "/usr/bin/deb-systemd-helper" ]; then
|
||||
deb-systemd-helper purge freedombox-udiskie.service >/dev/null || true
|
||||
deb-systemd-helper unmask freedombox-udiskie.service >/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
3
debian/gbp.conf
vendored
3
debian/gbp.conf
vendored
@ -1,6 +1,9 @@
|
||||
[DEFAULT]
|
||||
debian-branch = debian/trixie-backports
|
||||
|
||||
[buildpackage]
|
||||
export-dir = ../build-area/
|
||||
|
||||
[dch]
|
||||
git-log = --no-merges
|
||||
multimaint-merge = True
|
||||
|
||||
3
debian/source/lintian-overrides
vendored
3
debian/source/lintian-overrides
vendored
@ -5,7 +5,4 @@
|
||||
very-long-line-length-in-source-file * [doc/manual/*.raw.wiki:*]
|
||||
|
||||
# Misc. files which can't be fixed to have short line lengths.
|
||||
very-long-line-length-in-source-file * [plinth/modules/deluge/tests/data/sample.torrent:*]
|
||||
very-long-line-length-in-source-file * [plinth/modules/transmission/tests/data/sample.torrent:*]
|
||||
very-long-line-length-in-source-file * [doc/visual_design/FreedomBox-Logo.7z:*]
|
||||
very-long-line-length-in-source-file * [COPYING.md:*]
|
||||
|
||||
@ -19,6 +19,7 @@ Install the following Debian packages:
|
||||
|
||||
* python3-sphinx
|
||||
* python3-sphinx-autobuild
|
||||
* python3-sphinx-book-theme
|
||||
* python3-django
|
||||
* python3-django-axes
|
||||
* python3-django-captcha
|
||||
|
||||
122
doc/dev/_static/logo.svg
Normal file
122
doc/dev/_static/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
@ -1,15 +0,0 @@
|
||||
{%- extends "alabaster/layout.html" %}
|
||||
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
{% if show_copyright %}©{{ copyright }} | {% endif %}
|
||||
Licensed under the <a href="https://creativecommons.org/licenses/by-sa/4.0/">
|
||||
CC BY-SA 4.0</a> license
|
||||
{%- if show_source and has_source and sourcename %}
|
||||
{% if show_copyright or theme_show_powered_by %}|{% endif %}
|
||||
<a href="{{ pathto('_sources/' + sourcename, true)|e }}"
|
||||
rel="nofollow">{{ _('Page source') }}</a>
|
||||
{%- endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -15,6 +15,7 @@ list see the documentation: http://www.sphinx-doc.org/en/master/config
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
import django
|
||||
|
||||
@ -26,7 +27,7 @@ django.setup()
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
project = 'FreedomBox'
|
||||
copyright = '2021-2025, FreedomBox Authors'
|
||||
copyright = f'2021-{datetime.now().year}'
|
||||
author = 'FreedomBox Authors'
|
||||
|
||||
# The short X.Y version
|
||||
@ -82,15 +83,23 @@ pygments_style = None
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
html_theme = 'sphinx_book_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
html_theme_options = {
|
||||
'fixed_sidebar': True,
|
||||
'show_related': True,
|
||||
'home_page_in_toc': True,
|
||||
'repository_provider': 'gitlab',
|
||||
'repository_url': 'https://salsa.debian.org/freedombox-team/freedombox/',
|
||||
'use_edit_page_button': True,
|
||||
'use_source_button': True,
|
||||
'use_repository_button': True,
|
||||
'use_issues_button': True,
|
||||
'path_to_docs': 'doc/dev/',
|
||||
'extra_footer': 'Licensed under the <a href="https://creativecommons.org/'
|
||||
'licenses/by-sa/4.0/">CC BY-SA 4.0</a> license.',
|
||||
}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
@ -221,3 +230,4 @@ autodoc_mock_imports = [
|
||||
]
|
||||
|
||||
html_favicon = './_static/favicon.ico'
|
||||
html_logo = './_static/logo.svg'
|
||||
|
||||
@ -291,10 +291,8 @@ file ``transmission-plinth.conf``, add the following.
|
||||
|
||||
<Location /transmission>
|
||||
...
|
||||
Include includes/freedombox-single-sign-on.conf
|
||||
<IfModule mod_auth_pubtkt.c>
|
||||
TKTAuthToken "admin" "bit-torrent"
|
||||
</IfModule>
|
||||
Use AuthOpenIDConnect
|
||||
Use RequireGroup bit-torrent
|
||||
</Location>
|
||||
|
||||
Showing a shortcut in the front page
|
||||
|
||||
@ -12,7 +12,17 @@ In addition to supporting various single board computers and other devices, any
|
||||
|
||||
== Recommended Hardware ==
|
||||
|
||||
On April 22nd, 2019, the !FreedomBox Foundation announced the [[https://freedomboxfoundation.org/buy/|sales]] of the Pioneer Edition !FreedomBox Home Server Kits. This is the recommended pre-installed hardware for all users who don't wish to build their own !FreedomBox by choosing the right components, downloading the image and preparing an SD card with !FreedomBox.
|
||||
=== Libre Crafts FreedomBox ===
|
||||
|
||||
Libre Crafts in an endeavor from the !FreedomBox developers themselves to bring you a powerful !FreedomBox device capable of hosting even the most demanding home server needs. The device is crafted, tested, and delivered to you by !FreedomBox developers. Your purchase helps !FreedomBox development.
|
||||
|
||||
This hardware features a powerful CPU, plenty of main memory, a fast OS disk, ability to add two high capacity hard disk drives, dual multi-gigabit Ethernet ports, all with a low power consumption. Use it to host all your photos, to backup all home devices, as a NAS, as home automation hub, as a desktop computer, and more all at once.
|
||||
|
||||
||<style="text-align: center;"> [[FreedomBox/Hardware/LibreCrafts|{{attachment:libre-crafts.png|Libre Crafts FreedomBox|height=300}}]]<<BR>> [[FreedomBox/Hardware/LibreCrafts|Libre Crafts FreedomBox]] ||
|
||||
|
||||
=== Olimex's FreedomBox Pioneer Edition ===
|
||||
|
||||
On April 22nd, 2019, the !FreedomBox Foundation announced the [[https://freedomboxfoundation.org/buy/|sales]] of the Pioneer Edition !FreedomBox Home Server Kits. This pre-installed hardware is for all users who don't wish to build their own !FreedomBox by choosing the right components, downloading the image and preparing an SD card with !FreedomBox.
|
||||
|
||||
The kit includes all the hardware needed for launching a !FreedomBox home server on an Olimex A20-OLinuXino-LIME2 board. This product provides the perfect combination of open source hardware and free and open source software. By purchasing this product, you also support the !FreedomBox Foundation's efforts to create and promote its free and open source server software.
|
||||
|
||||
|
||||
@ -25,7 +25,8 @@ The [[https://www.olimex.com/Products/OLinuXino/Home-Server/Pioneer-FreedomBox-H
|
||||
* an optional storage add-on for hard disk (HDD) or solid-state drive (SSD)
|
||||
|
||||
=== Recommended Hardware ===
|
||||
This is the hardware recommended for all users who just want a turn-key !FreedomBox, and '''don't''' want to '''build''' their own one.
|
||||
|
||||
This is a hardware recommended for all users who just want a turn-key !FreedomBox, and '''don't''' want to '''build''' their own one.
|
||||
|
||||
(Building your own !FreedomBox means some technical stuff like choosing and buying the right components, downloading the image and preparing the SD card).
|
||||
|
||||
|
||||
@ -8,6 +8,138 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
|
||||
|
||||
The following are the release notes for each !FreedomBox version.
|
||||
|
||||
== FreedomBox 26.4.1 (2026-03-08) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* apache2: Disable pubtkt authentication module
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* container: Hold freedombox packages during test setup
|
||||
* d/control: Trim deps for nocheck build profile
|
||||
* locale: Update translations for Albanian, Chinese (Simplified Han script), Czech, Turkish
|
||||
* Vagrantfile: Enable public network for bridged networking
|
||||
|
||||
== FreedomBox 26.4 (2026-03-02) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* backups: Enable key-based SSH authentication for remote backups
|
||||
* oidc: New app to implement OpenID Connect Provider
|
||||
* apache: Implement protecting apps using OpenID Connect
|
||||
* wireguard: Improve server section user experience flow
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* *: Remove some absolute file paths in SVGs
|
||||
* *: Update URL base from /plinth to /freedombox
|
||||
* README/HACKING: Update weblate project path to /freedombox
|
||||
* Vagrantfile: Drop unnecessary sudo configuration for actions
|
||||
* action_utils: Drop support for link-local IPv6 addresses
|
||||
* action_utils: Fix issue with type checking a generator
|
||||
* action_utils: Implement utility to change umask temporarily
|
||||
* actions, privileged_daemon: Drop some unused global statements
|
||||
* apache: Fix diagnosing URLs protected by OpenID Connect
|
||||
* apache: Preserve host header when proxying to service
|
||||
* backups: Arrange form for adding remote location
|
||||
* backups: Avoid some repeated text in form help text
|
||||
* backups: Copy SSH client public key to remote
|
||||
* backups: Create .ssh folder before creating SSH key
|
||||
* backups: Create a better comment in the generated SSH key file
|
||||
* backups: Display SSH public key when adding remote
|
||||
* backups: Fix issue with Javascript in add remote location form
|
||||
* backups: Fix showing proper error for incorrect passphrase
|
||||
* backups: Fix type checking errors
|
||||
* backups: Generate SSH client key if needed
|
||||
* backups: Migrate to SSH key auth when mounting
|
||||
* backups: Minor refactoring
|
||||
* backups: Show/hide form elements instead of disabling for simplicity
|
||||
* backups: Simplify handling of migration to SSH keys
|
||||
* backups: Test adding/removing remote location
|
||||
* backups: Tweak appearance of add remote location form
|
||||
* backups: Use SSH key instead of password
|
||||
* backups: Use selected SSH credential for remote
|
||||
* backups: tests: Simplify functional test using more classes
|
||||
* bin: Add tool to change !FreedomBox password in Django database
|
||||
* calibre: Use OpenID Connect instead of pubtkt based SSO
|
||||
* cfg: Drop unused actions_dir option
|
||||
* cfg: Drop unused config_dir option
|
||||
* container: Align terminology in printed banner
|
||||
* db: Create a utility to get credentials from dbconfig
|
||||
* debian: Ensure that gbp creates a clean tarball prior to build
|
||||
* deluge: Use OpenID Connect instead of pubtkt based SSO
|
||||
* doc/dev: Set new theme for developer documentation
|
||||
* doc/dev: Use OpenID Connect instead of pubtkt based SSO
|
||||
* doc/dev: always have an up-to-date copyright year
|
||||
* ejabberd: Fix setting up certificates for multiple domains
|
||||
* email: Use OpenID Connect instead of pubtkt based SSO
|
||||
* featherwiki: Use OpenID Connect instead of pubtkt based SSO
|
||||
* gitweb: Fix issue with running post init due to missing method
|
||||
* gitweb: Use OpenID Connect instead of pubtkt based SSO
|
||||
* js: When page load fails during install, show it to user
|
||||
* letsencrypt: When copying certificate reset the umask reliably
|
||||
* locale/bg: Fix several translations with HTML links (Bulgarian)
|
||||
* locale/de: Fix several translations with HTML links (German)
|
||||
* locale: Update translations for Albanian, Catalan, Chinese (Simplified Han script), Czech, French, German, Greek, Italian, Swedish, Tamil, Turkish
|
||||
* matrixsynapse: Update apache config to proxy Synapse client API
|
||||
* miniflux: Get credentials from dbconfig-common directly
|
||||
* miniflux: Revert workaround for a packaging bug with DB connection
|
||||
* mumble: murmurd renamed to mumble-server
|
||||
* oidc: Style the page for authorizing an OIDC app
|
||||
* pyproject: Use new format to specify licenses
|
||||
* quassel: Explicitly set permissions on the domain configuration file
|
||||
* rssbridge: Use OpenID Connect instead of pubtkt based SSO
|
||||
* searx: Use OpenID Connect instead of pubtkt based SSO
|
||||
* sharing: Use OpenID Connect instead of pubtkt based SSO
|
||||
* sso: Merge into users module, drop pubtkt related code
|
||||
* syncthing: Use OpenID Connect instead of pubtkt based SSO
|
||||
* syncthing: tests: Fix tests by allowing rapid restarts
|
||||
* templates: Allow building pages without navigation bar and footer
|
||||
* tests: functional: Fix expecting !FreedomBox to be home page
|
||||
* tests: functional: Fix reloading error page during install/uninstall
|
||||
* tests: functional: Increase systemd rate limits for starting units
|
||||
* tiddlywiki: Use OpenID Connect instead of pubtkt based SSO
|
||||
* transmission: Use OpenID Connect instead of pubtkt based SSO
|
||||
* ui: Add animation for notification dismissal
|
||||
* ui: Dismiss notifications without page reload
|
||||
* ui: Refactor notification delete buttons to avoid repeating code
|
||||
* web_framework: Allow !FreedomBox apps to override templates
|
||||
* web_server: Log requests to WSGI app
|
||||
* wireguard: Accept/use netmask with IP address for server connection
|
||||
* wireguard: Fix format when showing multiple endpoints of the server
|
||||
* wireguard: Fix showing default route setting in server edit form
|
||||
* wireguard: Fix split tunneling
|
||||
* wireguard: Show status of default route in server information page
|
||||
* wireguard: filter .local addresses from showClient view
|
||||
* wireguard: show server vpn ip in show client page
|
||||
* wordpress: Use OpenID Connect instead of pubtkt based SSO when private
|
||||
|
||||
== FreedomBox 26.3 (2026-02-02) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* ui: Use HTMX to eliminate full page reloads
|
||||
* wireguard: Add 'Start Server' button to help with client setup
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* debian: Follows policy 4.7.3
|
||||
* debian: Ignore lintian warning: service file missing Install section
|
||||
* debian: Remove default Rules-Requires-Root
|
||||
* debian: Remove preinst script
|
||||
* debian: Update copyright years
|
||||
* docs: Update container script usage
|
||||
* lintian: Remove mismatched overrides
|
||||
* locale: Update translations for Chinese (Simplified Han script), German, Italian, Turkish
|
||||
* Makefile: Fix removing extra license file
|
||||
* ui: Add HTMX as a dependency
|
||||
* ui: Use HTMX to update notifications on partial page updates
|
||||
* wireguard: Remove NM connections when app is uninstalled
|
||||
* wireguard: Show next available client IP in Add Client form
|
||||
* wireguard: Update functional tests to handle Start Server button
|
||||
* wireguard: Show server endpoint on main app page
|
||||
|
||||
== FreedomBox 26.2 (2026-01-20) ==
|
||||
|
||||
* gitweb: Fix deleting last repo disables app
|
||||
|
||||
@ -20,15 +20,13 @@ The content can be shared publicly or restricted to the users of listed allowed
|
||||
|
||||
=== Setting Up Shares ===
|
||||
|
||||
For the users to access the content through their browser it must exist and have a share. A share is an entry in the Sharing app relating:
|
||||
* the Name (an thereby the URL) with which the users will ask for the content,
|
||||
* the Disk Path of the content to be served and
|
||||
* the sharing mode. On restricted mode, it also has the list of allowed groups.
|
||||
Many shares can coexist in the same server.
|
||||
|
||||
Only admins can create, edit or remove shares. They'll find the Sharing app in the Apps section of !FreedomBox web interface. Sharing app is an easy to use web application with an evident interface.
|
||||
|
||||
Each share has its own sharing mode (public or restricted) setting. Only groups recognized by !FreedomBox service can be combined in the list of allowed groups. Groups created in the CLI won't be offered by the Sharing app.
|
||||
* In !FreedomBox web interface, enable the Sharing App. Only admins can create, edit or remove shares. They'll find the Sharing app in the Apps section of the !FreedomBox web interface. Many shares can coexist in the same server.
|
||||
* Add a new share
|
||||
* Give it a name (an thereby the URL) with which the users will ask for the content. In the example above it would be called ''content_name''.
|
||||
* The Disk Path of the content to be served. This path is relative to ''root'' on your !FreedomBox. For instance ''/var/lib/freedombox/sharing/content_name'' might be a choice.
|
||||
* Sharing mode. On restricted mode, it also has the list of allowed groups. Only groups recognized by !FreedomBox service can be combined in the list of allowed groups. Groups created in the CLI won't be offered by the Sharing app.
|
||||
* Create the directory specified under ''Disk Path'' on your !FreedomBox through ''Cockpit'', ''Nautilus'' or remote login.
|
||||
* Make sure the user, who will provide the content, has write access to that directory for instance by making him the owner of that directory.
|
||||
|
||||
=== Providing/Updating Content ===
|
||||
|
||||
|
||||
BIN
doc/manual/en/images/libre-crafts.png
Normal file
BIN
doc/manual/en/images/libre-crafts.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
@ -11,8 +11,19 @@ Además de soportar varios SBC's (single board computers) y otros dispositivos,
|
||||
|
||||
== Hardware Recomendado ==
|
||||
|
||||
El 22 de Abril de 2019, la ''!FreedomBox Foundation'' anunció que los kits ''Pioneer Edition !FreedomBox Home Server'' salían a la [[https://freedomboxfoundation.org/buy/|venta]]. Este es el hardware preinstalado recomendado para todos los usuarios que no quieran construirse su propia (máquina) !FreedomBox eligiendo los componentes adecuados, descargando la imagen y preparando una tarjeta SD con (el software) !FreedomBox.
|
||||
=== Libre Crafts FreedomBox ===
|
||||
|
||||
Libre Crafts es una iniviativa de los propios desarrolladores de !FreedomBox para proporcionar una !FreedomBox potente capaz de alojar las necesidades más exigentes de un servidor casero.
|
||||
Los propios desarrolladores de !FreedomBox la montan. prueban y entregan. Tu compra ayuda al desarrollo de !FreedomBox.
|
||||
|
||||
Esta máquina lleva un procesador potente, mucha memoria, CPU, un disco de sitema operativo rápido, posibilidad de añador discos duros de alta capacidad, puertos Ethernet multi-gigabit duales, todo ello con bajo consumo.
|
||||
Úsalo para alojar todas tus fotos, las copias de respaldo de tus otros dispositivos, como NAS, como centro de control de domótica, como ordenador de sobremesa, y más, todo a la vez.
|
||||
|
||||
||<style="text-align: center;"> [[FreedomBox/Hardware/LibreCrafts|{{attachment:FreedomBox/libre-crafts.png|FreedomBox de Libre Crafts|height=300}}]]<<BR>> [[FreedomBox/Hardware/LibreCrafts|FreedomBox de Libre Crafts]] ||
|
||||
|
||||
=== Olimex's FreedomBox Pioneer Edition ===
|
||||
|
||||
On April 22nd, 2019, the !FreedomBox Foundation announced the [[https://freedomboxfoundation.org/buy/|sales]] of the Pioneer Edition !FreedomBox Home Server Kits. This pre-installed hardware is for all users who don't wish to build their own !FreedomBox by choosing the right components, downloading the image and preparing an SD card with !FreedomBox.
|
||||
El kit incluye todo el hardware necesario para arrancar un servidor casero !FreedomBox sobre una placa ''Olimex A20-OLinuXino-LIME2''. Este producto proporciona la combinación perfecta de hardware de fuentes abiertas y software libre. Al comprar este producto, soportas también los esfuerzos de la ''!FreedomBox Foundation'' para crear y promover su software de servidor libre.
|
||||
|
||||
||<style="text-align: center;"> [[es/FreedomBox/Hardware/PioneerEdition|{{attachment:FreedomBox/Hardware/pioneer-edition_thumb.jpg|Kits de servidor doméstico FreedomBox edición Pioneer|width=320,height=257}}]]<<BR>> [[es/FreedomBox/Hardware/PioneerEdition|Kits de servidor doméstico FreedomBox edición Pioneer]] ||
|
||||
|
||||
@ -16,7 +16,7 @@ Los servidores caseros !FreedomBox Pioneer Edition los fabrica y vende Olimex, u
|
||||
== Características del Producto ==
|
||||
|
||||
=== HW Recomendado ===
|
||||
Éste es el hardware recomendado para los usuarios que quieran simplemente una !FreedomBox llave en mano, y '''no''' quieran '''construirse''' una.
|
||||
Éste es un hardware recomendado para los usuarios que quieran simplemente una !FreedomBox llave en mano, y '''no''' quieran '''construirse''' una.
|
||||
|
||||
(Construir tu propia !FreedomBox implica algunos tecnicismos como elegir y comprar los componentes adecuados, descargar la imágen y preparar una tarjeta SD).
|
||||
|
||||
|
||||
@ -8,6 +8,138 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
|
||||
|
||||
The following are the release notes for each !FreedomBox version.
|
||||
|
||||
== FreedomBox 26.4.1 (2026-03-08) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* apache2: Disable pubtkt authentication module
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* container: Hold freedombox packages during test setup
|
||||
* d/control: Trim deps for nocheck build profile
|
||||
* locale: Update translations for Albanian, Chinese (Simplified Han script), Czech, Turkish
|
||||
* Vagrantfile: Enable public network for bridged networking
|
||||
|
||||
== FreedomBox 26.4 (2026-03-02) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* backups: Enable key-based SSH authentication for remote backups
|
||||
* oidc: New app to implement OpenID Connect Provider
|
||||
* apache: Implement protecting apps using OpenID Connect
|
||||
* wireguard: Improve server section user experience flow
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* *: Remove some absolute file paths in SVGs
|
||||
* *: Update URL base from /plinth to /freedombox
|
||||
* README/HACKING: Update weblate project path to /freedombox
|
||||
* Vagrantfile: Drop unnecessary sudo configuration for actions
|
||||
* action_utils: Drop support for link-local IPv6 addresses
|
||||
* action_utils: Fix issue with type checking a generator
|
||||
* action_utils: Implement utility to change umask temporarily
|
||||
* actions, privileged_daemon: Drop some unused global statements
|
||||
* apache: Fix diagnosing URLs protected by OpenID Connect
|
||||
* apache: Preserve host header when proxying to service
|
||||
* backups: Arrange form for adding remote location
|
||||
* backups: Avoid some repeated text in form help text
|
||||
* backups: Copy SSH client public key to remote
|
||||
* backups: Create .ssh folder before creating SSH key
|
||||
* backups: Create a better comment in the generated SSH key file
|
||||
* backups: Display SSH public key when adding remote
|
||||
* backups: Fix issue with Javascript in add remote location form
|
||||
* backups: Fix showing proper error for incorrect passphrase
|
||||
* backups: Fix type checking errors
|
||||
* backups: Generate SSH client key if needed
|
||||
* backups: Migrate to SSH key auth when mounting
|
||||
* backups: Minor refactoring
|
||||
* backups: Show/hide form elements instead of disabling for simplicity
|
||||
* backups: Simplify handling of migration to SSH keys
|
||||
* backups: Test adding/removing remote location
|
||||
* backups: Tweak appearance of add remote location form
|
||||
* backups: Use SSH key instead of password
|
||||
* backups: Use selected SSH credential for remote
|
||||
* backups: tests: Simplify functional test using more classes
|
||||
* bin: Add tool to change !FreedomBox password in Django database
|
||||
* calibre: Use OpenID Connect instead of pubtkt based SSO
|
||||
* cfg: Drop unused actions_dir option
|
||||
* cfg: Drop unused config_dir option
|
||||
* container: Align terminology in printed banner
|
||||
* db: Create a utility to get credentials from dbconfig
|
||||
* debian: Ensure that gbp creates a clean tarball prior to build
|
||||
* deluge: Use OpenID Connect instead of pubtkt based SSO
|
||||
* doc/dev: Set new theme for developer documentation
|
||||
* doc/dev: Use OpenID Connect instead of pubtkt based SSO
|
||||
* doc/dev: always have an up-to-date copyright year
|
||||
* ejabberd: Fix setting up certificates for multiple domains
|
||||
* email: Use OpenID Connect instead of pubtkt based SSO
|
||||
* featherwiki: Use OpenID Connect instead of pubtkt based SSO
|
||||
* gitweb: Fix issue with running post init due to missing method
|
||||
* gitweb: Use OpenID Connect instead of pubtkt based SSO
|
||||
* js: When page load fails during install, show it to user
|
||||
* letsencrypt: When copying certificate reset the umask reliably
|
||||
* locale/bg: Fix several translations with HTML links (Bulgarian)
|
||||
* locale/de: Fix several translations with HTML links (German)
|
||||
* locale: Update translations for Albanian, Catalan, Chinese (Simplified Han script), Czech, French, German, Greek, Italian, Swedish, Tamil, Turkish
|
||||
* matrixsynapse: Update apache config to proxy Synapse client API
|
||||
* miniflux: Get credentials from dbconfig-common directly
|
||||
* miniflux: Revert workaround for a packaging bug with DB connection
|
||||
* mumble: murmurd renamed to mumble-server
|
||||
* oidc: Style the page for authorizing an OIDC app
|
||||
* pyproject: Use new format to specify licenses
|
||||
* quassel: Explicitly set permissions on the domain configuration file
|
||||
* rssbridge: Use OpenID Connect instead of pubtkt based SSO
|
||||
* searx: Use OpenID Connect instead of pubtkt based SSO
|
||||
* sharing: Use OpenID Connect instead of pubtkt based SSO
|
||||
* sso: Merge into users module, drop pubtkt related code
|
||||
* syncthing: Use OpenID Connect instead of pubtkt based SSO
|
||||
* syncthing: tests: Fix tests by allowing rapid restarts
|
||||
* templates: Allow building pages without navigation bar and footer
|
||||
* tests: functional: Fix expecting !FreedomBox to be home page
|
||||
* tests: functional: Fix reloading error page during install/uninstall
|
||||
* tests: functional: Increase systemd rate limits for starting units
|
||||
* tiddlywiki: Use OpenID Connect instead of pubtkt based SSO
|
||||
* transmission: Use OpenID Connect instead of pubtkt based SSO
|
||||
* ui: Add animation for notification dismissal
|
||||
* ui: Dismiss notifications without page reload
|
||||
* ui: Refactor notification delete buttons to avoid repeating code
|
||||
* web_framework: Allow !FreedomBox apps to override templates
|
||||
* web_server: Log requests to WSGI app
|
||||
* wireguard: Accept/use netmask with IP address for server connection
|
||||
* wireguard: Fix format when showing multiple endpoints of the server
|
||||
* wireguard: Fix showing default route setting in server edit form
|
||||
* wireguard: Fix split tunneling
|
||||
* wireguard: Show status of default route in server information page
|
||||
* wireguard: filter .local addresses from showClient view
|
||||
* wireguard: show server vpn ip in show client page
|
||||
* wordpress: Use OpenID Connect instead of pubtkt based SSO when private
|
||||
|
||||
== FreedomBox 26.3 (2026-02-02) ==
|
||||
|
||||
=== Highlights ===
|
||||
|
||||
* ui: Use HTMX to eliminate full page reloads
|
||||
* wireguard: Add 'Start Server' button to help with client setup
|
||||
|
||||
=== Other Changes ===
|
||||
|
||||
* debian: Follows policy 4.7.3
|
||||
* debian: Ignore lintian warning: service file missing Install section
|
||||
* debian: Remove default Rules-Requires-Root
|
||||
* debian: Remove preinst script
|
||||
* debian: Update copyright years
|
||||
* docs: Update container script usage
|
||||
* lintian: Remove mismatched overrides
|
||||
* locale: Update translations for Chinese (Simplified Han script), German, Italian, Turkish
|
||||
* Makefile: Fix removing extra license file
|
||||
* ui: Add HTMX as a dependency
|
||||
* ui: Use HTMX to update notifications on partial page updates
|
||||
* wireguard: Remove NM connections when app is uninstalled
|
||||
* wireguard: Show next available client IP in Add Client form
|
||||
* wireguard: Update functional tests to handle Start Server button
|
||||
* wireguard: Show server endpoint on main app page
|
||||
|
||||
== FreedomBox 26.2 (2026-01-20) ==
|
||||
|
||||
* gitweb: Fix deleting last repo disables app
|
||||
|
||||
@ -19,15 +19,15 @@ El contenido se puede compartir públicamente o restringido a usuarios de una li
|
||||
|
||||
=== Editando comparticiones ===
|
||||
|
||||
Para que los usuarios accedan al contenido mediante su navegador debe existir y tener una compartición. Una compartición es una entrada en la aplicación Sharing que relaciona:
|
||||
* El Nombre (y por tanto la URL) que usarán los usuarios para solicitar el contenido,
|
||||
* el Ruta de acceso al contenido a servir y
|
||||
* el modo de compartición. Si es restringido, también contendrá la lista de grupos autorizados.
|
||||
En el mismo servidor pueden coexistir múltiples comparticiones.
|
||||
Cada compartición tiene su propio ajuste de modo de compartición (pública o restringida). Sólo los grupos que reconoce el servicio !FreedomBox se pueden combinar en la lista de grupos autorizados. La aplicación ''Sharing'' no ofrecerá los grupos creados en el interfaz de línea de órdenes.
|
||||
|
||||
Sólo los administradores pueden crear, editar o eliminar comparticiones. Encontrarán la aplicación ''Sharing'' en la sección Aplicacions del interfaz web de !FreedomBox. La aplicación ''Sharing'' es una aplicación web fácil de usar y con un interfaz evidente.
|
||||
|
||||
Cada compartición tiene su priopio ajuste de modo de compartición (pública o restrigida). Sólo los grupos que reconoce el servicio !FreedomBox se pueden combinar en la lista de grupos autorizados. La aplicación ''Sharing'' no ofrecerá los grupos creados en el interfaz de línea de órdenes.
|
||||
* In el interfaz web de !FreedomBox, habilita la App ''Sharing''. Sólo los administradores pueden crear, editar o eliminar comparticiones. Encontrarán la aplicación ''Sharing'' en la sección Aplicaciones del interfaz web de !FreedomBox. En el mismo servidor pueden coexistir múltiples comparticiones.
|
||||
* Añadir una nueva compartición:
|
||||
* Dale un nombre (y por tanto la URL) que usarán los usuarios para solicitar el contenido, En el ejemplo anterior se llamaría ''nombre del contenido''.
|
||||
* La Ruta completa de acceso al contenido a servir. Por ejemplo ''/var/lib/freedombox/sharing/nombre_del_contenido''.
|
||||
* El modo de compartición. Si es restringido, también contendrá la lista de grupos autorizados. Solo los grupos reconocidos por el servicio !FreedomBox se pueden combinar en la lista de grupos autorizados. La app no ofrecerá los grupos creados sólo en la línea de órdenes.
|
||||
* Crea el directorio especificado en ''Ruta de Disco'' en !FreedomBox mediante ''Cockpit'', ''Nautilus'' o ingreso remoto.
|
||||
* Asegúrate de que el usuario que proporcione el contenido tiene permiso para escribir en el directorio, por ejemplo, haciéndole dueño del directorio.
|
||||
|
||||
=== Provisionar/actualizar el contenido ===
|
||||
|
||||
|
||||
BIN
doc/manual/es/images/libre-crafts.png
Normal file
BIN
doc/manual/es/images/libre-crafts.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
@ -73,8 +73,8 @@
|
||||
<para>
|
||||
This the URL fragment under which Plinth will provide its services.
|
||||
Plinth is shipped with a default value of
|
||||
<filename>/plinth</filename>. This means that Plinth will be
|
||||
available as http://localhost:8000/plinth by default.
|
||||
<filename>/freedombox</filename>. This means that Plinth will be
|
||||
available as http://localhost:8000/freedombox by default.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@ -194,7 +194,7 @@
|
||||
<synopsis>$ plinth --server_dir='/myurl'</synopsis>
|
||||
<para>
|
||||
Run Plinth with the '/myurl' prefix. Note that Apache forwards requests
|
||||
to '/plinth' by default, so /myurl is not accessible outside of your
|
||||
to '/freedombox' by default, so /myurl is not accessible outside of your
|
||||
FreedomBox without adapting the apache configuration.
|
||||
</para>
|
||||
</example>
|
||||
|
||||
@ -13,7 +13,7 @@ from pathlib import Path
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
BASE_URL = 'https://wiki.debian.org/'
|
||||
LOCAL_BASE = '/plinth/help/manual/{lang}/'
|
||||
LOCAL_BASE = '/freedombox/help/manual/{lang}/'
|
||||
ICONS_DIR = 'icons'
|
||||
|
||||
DEFAULT_LANGUAGE = 'en'
|
||||
@ -624,21 +624,21 @@ def resolve_url(url, context):
|
||||
|
||||
Locally available page in default language => shortcut to local copy:
|
||||
>>> resolve_url('FreedomBox/Contribute', {'language': '', 'title': ''})
|
||||
'/plinth/help/manual/en/Contribute#'
|
||||
'/freedombox/help/manual/en/Contribute#'
|
||||
|
||||
Translated available page => shortcut to local copy:
|
||||
>>> resolve_url('es/FreedomBox/Contribute', {'language': '', 'title': ''})
|
||||
'/plinth/help/manual/es/Contribute#'
|
||||
'/freedombox/help/manual/es/Contribute#'
|
||||
|
||||
Available page in default language refferred as translated => shortcut to
|
||||
local copy:
|
||||
>>> resolve_url('en/FreedomBox/Contribute', {'language': '', 'title': ''})
|
||||
'/plinth/help/manual/en/Contribute#'
|
||||
'/freedombox/help/manual/en/Contribute#'
|
||||
|
||||
Unrecognized language => handle considering it as default language:
|
||||
>>> resolve_url('missing/FreedomBox/Contribute', {'language': '', \
|
||||
'title': ''})
|
||||
'/plinth/help/manual/en/Contribute#'
|
||||
'/freedombox/help/manual/en/Contribute#'
|
||||
"""
|
||||
|
||||
# Process first all easy, straight forward cases:
|
||||
@ -1191,11 +1191,11 @@ from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>')
|
||||
[Paragraph([PlainText('a')]), Paragraph([PlainText('b ')])]
|
||||
>>> parse_wiki('{{{#!wiki caution\\n\\nOnce some other app is set as the \
|
||||
home page, you can only navigate to the !FreedomBox Service (Plinth) by \
|
||||
typing https://myfreedombox.rocks/plinth/ into the browser. <<BR>>\\n\
|
||||
typing https://myfreedombox.rocks/freedombox/ into the browser. <<BR>>\\n\
|
||||
''/freedombox'' can also be used as an alias to ''/plinth''\\n}}}')
|
||||
[Admonition('caution', [Paragraph([PlainText('Once some other app is set \
|
||||
as the home page, you can only navigate to the FreedomBox Service (Plinth) by \
|
||||
typing '), Url('https://myfreedombox.rocks/plinth/'), PlainText(' into the \
|
||||
typing '), Url('https://myfreedombox.rocks/freedombox/'), PlainText(' into the \
|
||||
browser. ')]), Paragraph([PlainText('/freedombox can also be used as an alias \
|
||||
to /plinth ')])])]
|
||||
|
||||
@ -1761,7 +1761,7 @@ Features introduction</ulink>'
|
||||
|
||||
>>> generate_inner_docbook([Link('../../Contribute', \
|
||||
[PlainText('Contribute')])], context={'title': 'FreedomBox/Manual/Hardware'})
|
||||
'<ulink url="/plinth/help/manual/en/Contribute#">\
|
||||
'<ulink url="/freedombox/help/manual/en/Contribute#">\
|
||||
Contribute</ulink>'
|
||||
|
||||
>>> generate_inner_docbook([Link('/Code', \
|
||||
@ -1772,9 +1772,9 @@ Code</ulink>'
|
||||
>>> generate_inner_docbook([Link('DebianBug:1234', [PlainText('Bug')])])
|
||||
'<ulink url="https://bugs.debian.org/1234#">Bug</ulink>'
|
||||
|
||||
>>> generate_inner_docbook([Link('DebianPkg:plinth', \
|
||||
>>> generate_inner_docbook([Link('DebianPkg:freedombox', \
|
||||
[PlainText('Plinth')])])
|
||||
'<ulink url="https://packages.debian.org/plinth#">Plinth</ulink>'
|
||||
'<ulink url="https://packages.debian.org/freedombox#">Plinth</ulink>'
|
||||
|
||||
>>> generate_inner_docbook([Link('AliothList:freedombox-discuss', \
|
||||
[PlainText('Discuss')])])
|
||||
@ -1911,7 +1911,7 @@ PlainText(' on it. ')])])
|
||||
'<para>An alternative to downloading these images is to \
|
||||
<ulink url="https://wiki.debian.org/InstallingDebianOn/TI/BeagleBone#">\
|
||||
install Debian</ulink> on the BeagleBone and then \
|
||||
<ulink url="/plinth/help/manual/en/Debian#">install \
|
||||
<ulink url="/freedombox/help/manual/en/Debian#">install \
|
||||
FreedomBox</ulink> on it. </para>'
|
||||
|
||||
>>> generate_inner_docbook([Paragraph([PlainText('After Roundcube is \
|
||||
|
||||
@ -3,4 +3,4 @@
|
||||
Package init file.
|
||||
"""
|
||||
|
||||
__version__ = '26.2'
|
||||
__version__ = '26.4.1'
|
||||
|
||||
@ -10,6 +10,7 @@ import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from typing import Generator
|
||||
|
||||
import augeas
|
||||
|
||||
@ -370,12 +371,13 @@ def get_addresses() -> list[dict[str, str | bool]]:
|
||||
'url_address': hostname
|
||||
})
|
||||
|
||||
# XXX: When a hostname is resolved to IPv6 address, it may likely
|
||||
# be link-local address. Link local IPv6 addresses are valid only
|
||||
# for a given link and need to be scoped with interface name such
|
||||
# as '%eth0' to work. Tools such as curl don't seem to handle
|
||||
# When a hostname is resolved to IPv6 address, it may likely be link-local
|
||||
# address. Link local IPv6 addresses are valid only for a given link and
|
||||
# need to be scoped with interface name such as '%eth0' to work. Browsers
|
||||
# refused to implement support for link-local addresses (with zone IDs) in
|
||||
# URLs due to platform specific parsing rules and other implementation
|
||||
# difficulties. mod_auth_openidc does not support them either.
|
||||
# this correctly.
|
||||
# addresses.append({'kind': '6', 'address': hostname, 'numeric': False})
|
||||
|
||||
return addresses
|
||||
|
||||
@ -397,13 +399,15 @@ def get_ip_addresses() -> list[dict[str, str | bool]]:
|
||||
}
|
||||
|
||||
if address['kind'] == '6' and address['numeric']:
|
||||
if address['scope'] != 'link':
|
||||
address['url_address'] = '[{0}]'.format(address['address'])
|
||||
else:
|
||||
address['url_address'] = '[{0}%{1}]'.format(
|
||||
address['url_address'], address['interface'])
|
||||
address['url_address'] = '[{0}]'.format(address['address'])
|
||||
|
||||
addresses.append(address)
|
||||
if address['scope'] != 'link':
|
||||
# Do not include link local addresses. Browsers refused to
|
||||
# implement support for link-local addresses (with zone IDs) in
|
||||
# URLs due to platform specific parsing rules and other
|
||||
# implementation difficulties. mod_auth_openidc does not support
|
||||
# them either.
|
||||
addresses.append(address)
|
||||
|
||||
return addresses
|
||||
|
||||
@ -838,3 +842,13 @@ def run(command, **kwargs):
|
||||
raise exception
|
||||
|
||||
return process
|
||||
|
||||
|
||||
@contextmanager
|
||||
def umask(mask) -> Generator:
|
||||
"""Set the umask temporarily for a operation and then revert it."""
|
||||
old_umask = os.umask(mask)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
|
||||
@ -369,7 +369,6 @@ class JSONEncoder(json.JSONEncoder):
|
||||
|
||||
def _setup_thread_storage():
|
||||
"""Setup collection of stdout/stderr from any process in this thread."""
|
||||
global thread_storage
|
||||
thread_storage.stdout = b''
|
||||
thread_storage.stderr = b''
|
||||
|
||||
@ -380,14 +379,12 @@ def _clear_thread_storage():
|
||||
Python documentation is silent on whether thread local storage will be
|
||||
cleaned up after a thread terminates.
|
||||
"""
|
||||
global thread_storage
|
||||
thread_storage.stdout = None
|
||||
thread_storage.stderr = None
|
||||
|
||||
|
||||
def get_return_value_from_exception(exception):
|
||||
"""Return the value to return from server when an exception is raised."""
|
||||
global thread_storage
|
||||
return_value = {
|
||||
'result': 'exception',
|
||||
'exception': {
|
||||
|
||||
@ -12,13 +12,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# [Path] section
|
||||
file_root = '/usr/share/plinth'
|
||||
config_dir = '/etc/plinth'
|
||||
data_dir = '/var/lib/plinth'
|
||||
custom_static_dir = '/var/www/plinth/custom/static'
|
||||
store_file = data_dir + '/plinth.sqlite3'
|
||||
actions_dir = '/usr/share/plinth/actions'
|
||||
doc_dir = '/usr/share/freedombox'
|
||||
server_dir = '/plinth'
|
||||
server_dir = '/freedombox'
|
||||
|
||||
# [Network] section
|
||||
host = '127.0.0.1'
|
||||
@ -111,11 +109,9 @@ def read_file(config_path):
|
||||
|
||||
config_items = (
|
||||
('Path', 'file_root', 'string'),
|
||||
('Path', 'config_dir', 'string'),
|
||||
('Path', 'data_dir', 'string'),
|
||||
('Path', 'custom_static_dir', 'string'),
|
||||
('Path', 'store_file', 'string'),
|
||||
('Path', 'actions_dir', 'string'),
|
||||
('Path', 'doc_dir', 'string'),
|
||||
('Path', 'server_dir', 'string'),
|
||||
('Network', 'host', 'string'),
|
||||
|
||||
@ -64,10 +64,10 @@ def fixture_load_cfg():
|
||||
"""Load test configuration."""
|
||||
from plinth import cfg
|
||||
|
||||
keys = ('file_root', 'config_dir', 'data_dir', 'custom_static_dir',
|
||||
'store_file', 'actions_dir', 'doc_dir', 'server_dir', 'host',
|
||||
'port', 'use_x_forwarded_for', 'use_x_forwarded_host',
|
||||
'secure_proxy_ssl_header', 'box_name', 'develop')
|
||||
keys = ('file_root', 'data_dir', 'custom_static_dir', 'store_file',
|
||||
'doc_dir', 'server_dir', 'host', 'port', 'use_x_forwarded_for',
|
||||
'use_x_forwarded_host', 'secure_proxy_ssl_header', 'box_name',
|
||||
'develop')
|
||||
saved_state = {}
|
||||
for key in keys:
|
||||
saved_state[key] = getattr(cfg, key)
|
||||
|
||||
40
plinth/db/dbconfig.py
Normal file
40
plinth/db/dbconfig.py
Normal file
@ -0,0 +1,40 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Utilities for parsing dbconfig-common files with Augeas."""
|
||||
|
||||
import pathlib
|
||||
|
||||
import augeas
|
||||
|
||||
|
||||
def get_credentials(dbconfig_path: str) -> dict[str, str]:
|
||||
"""Parse dbconfig-common file with Augeas Shellvars lens."""
|
||||
if not pathlib.Path(dbconfig_path).is_file():
|
||||
raise FileNotFoundError(f'DB config not found: {dbconfig_path}')
|
||||
|
||||
aug = _load_augeas(dbconfig_path)
|
||||
|
||||
required = ['dbc_dbuser', 'dbc_dbpass', 'dbc_dbname']
|
||||
credentials = {}
|
||||
for key in required + ['dbc_dbserver']:
|
||||
credentials[key] = aug.get(key).strip('\'"')
|
||||
|
||||
if not all(credentials.get(key) for key in required):
|
||||
raise ValueError('Missing required dbconfig-common credentials')
|
||||
|
||||
return {
|
||||
'user': credentials['dbc_dbuser'],
|
||||
'password': credentials['dbc_dbpass'],
|
||||
'database': credentials['dbc_dbname'],
|
||||
'host': credentials['dbc_dbserver'] or 'localhost'
|
||||
}
|
||||
|
||||
|
||||
def _load_augeas(config_path: str):
|
||||
"""Initialize Augeas."""
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
pathstr = str(config_path)
|
||||
aug.transform('Shellvars', pathstr)
|
||||
aug.set('/augeas/context', f'/files{pathstr}')
|
||||
aug.load()
|
||||
return aug
|
||||
@ -1,5 +1,3 @@
|
||||
[Path]
|
||||
file_root = %(parent_parent_dir)s
|
||||
config_dir = %(file_root)s/data/etc/plinth
|
||||
actions_dir = %(file_root)s/actions
|
||||
doc_dir = %(file_root)s/doc
|
||||
|
||||
@ -10,7 +10,7 @@ translating the PO file from your language directory.
|
||||
Introducing yourself is important since some work may have been done
|
||||
already on Debian translators discussion lists and Weblate
|
||||
localization platform.
|
||||
https://hosted.weblate.org/projects/freedombox/plinth/
|
||||
https://hosted.weblate.org/projects/freedombox/freedombox/
|
||||
https://www.debian.org/MailingLists/subscribe
|
||||
|
||||
## Wiki: translators landing page
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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
@ -4,6 +4,7 @@ Common Django middleware.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from django import urls
|
||||
from django.conf import settings
|
||||
@ -58,7 +59,9 @@ class SetupMiddleware(MiddlewareMixin):
|
||||
except urls.Resolver404:
|
||||
return
|
||||
|
||||
if not resolver_match.namespaces or not len(resolver_match.namespaces):
|
||||
non_app_namespaces = {'oauth2_provider'}
|
||||
if (not resolver_match.namespaces or not len(resolver_match.namespaces)
|
||||
or (set(resolver_match.namespaces) & non_app_namespaces)):
|
||||
# Requested URL does not belong to any application
|
||||
return
|
||||
|
||||
@ -99,10 +102,17 @@ class AdminRequiredMiddleware(MiddlewareMixin):
|
||||
hasattr(view_func, 'IS_NON_ADMIN'):
|
||||
return
|
||||
|
||||
if not is_user_admin(request):
|
||||
if not AdminRequiredMiddleware.check_user_group(
|
||||
view_func, request):
|
||||
raise PermissionDenied
|
||||
public_urls = settings.STRONGHOLD_PUBLIC_URLS
|
||||
if any(re.match(url, request.path_info) for url in public_urls):
|
||||
return
|
||||
|
||||
if is_user_admin(request):
|
||||
return
|
||||
|
||||
if AdminRequiredMiddleware.check_user_group(view_func, request):
|
||||
return
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
class FirstSetupMiddleware(MiddlewareMixin):
|
||||
|
||||
@ -1,16 +1,79 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""FreedomBox app for Apache server."""
|
||||
"""FreedomBox app for Apache server.
|
||||
|
||||
This module implements a mechanism for protecting various URL paths using
|
||||
FreedomBox's OpenID Connect implementation as Identity Provider and
|
||||
mod_auth_oidc as Relying Party. The following is a simplified description of
|
||||
how the flow works:
|
||||
|
||||
- User accesses the URL /foo using a browser. /foo is a URL path which is
|
||||
protected by this module's OpenID Connect SSO (using AuthType
|
||||
openid-connect).
|
||||
|
||||
- mod_auth_opendic seizes control and checks for authorization. Since this is
|
||||
the first visit, it starts the authentication/authorization process. It first
|
||||
redirects the browser to provider discovery URL
|
||||
/freedombox/apache/discover-idp/.
|
||||
|
||||
- This URL selects and Identity Provider based on incoming URL's host header.
|
||||
It will select https://mydomain.example/freedombox/o as IDP if the original
|
||||
URL is https://mydomain.example/foo. Or https://freedombox.local/freedombox/o
|
||||
if the original URL is https://freedombox.local/foo. After selection it will
|
||||
redirect the browser back to /apache/oidc/callback with the selected IDP in
|
||||
the GET parameters.
|
||||
|
||||
- /apache/oidc/callback is controlled by mod_auth_openidc which receives the
|
||||
IDP selection. It will then query the IDP for further information such as
|
||||
authorization URL, token URL, supported scopes and claims. This is done using
|
||||
a backend call to /freedombox/o/.well-known/openid-configuration.
|
||||
|
||||
- After determining the authorization end point (/freedombox/o/authorize/) from
|
||||
the metadata, mod_auth_openidc will start the authentication/authorization
|
||||
process by redirecting the browser to the URL.
|
||||
|
||||
- FreedomBox shows login page if the user is not already logged in. User logs
|
||||
in.
|
||||
|
||||
- FreedomBox will show a page asking the user to authorize the application to
|
||||
access information such as name and email. In case of Apache's
|
||||
mod_auth_openidc, this is skipped.
|
||||
|
||||
- FreedomBox will redirect back to /apache/oidc/callback after various checks.
|
||||
This request will contain authorization grant token and OIDC claims in
|
||||
parameters.
|
||||
|
||||
- mod_auth_openidc connects using back channel HTTP call to token endpoint
|
||||
(/freedombox/o/token/) with the authorization grant token and then obtains
|
||||
access token and refresh token. OIDC claims are checked using client_secret
|
||||
known only to FreedomBox IDP and mod_auth_openidc.
|
||||
|
||||
- The OIDC claims contains username as part of 'sub' claim. This is exported as
|
||||
REMOTE_USER header. 'freedombox_groups' contains the list of groups that
|
||||
FreedomBox account is part of. These, along with 'Require claim' Apache
|
||||
configuration directives, are used to determine if the user should get access
|
||||
to /foo path or not.
|
||||
|
||||
- The application providing /foo will have access to information such username
|
||||
and groups as part of REMOTE_USER and other OIDC_* environment variables.
|
||||
|
||||
- mod_auth_openidc also sets cookies that ensure that the whole process is not
|
||||
repeated when a second request for the path /foo is received.
|
||||
"""
|
||||
|
||||
import ipaddress
|
||||
import os
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth import action_utils
|
||||
from plinth import app as app_module
|
||||
from plinth import cfg
|
||||
from plinth.config import DropinConfigs
|
||||
from plinth.daemon import Daemon, RelatedDaemon
|
||||
from plinth.modules import names
|
||||
from plinth.modules.firewall.components import Firewall
|
||||
from plinth.modules.letsencrypt.components import LetsEncrypt
|
||||
from plinth.modules.oidc.components import OpenIDConnect
|
||||
from plinth.package import Packages
|
||||
from plinth.signals import domain_added, domain_removed
|
||||
from plinth.utils import format_lazy, is_valid_user_name
|
||||
@ -23,7 +86,7 @@ class ApacheApp(app_module.App):
|
||||
|
||||
app_id = 'apache'
|
||||
|
||||
_version = 14
|
||||
_version = 16
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Create components for the app."""
|
||||
@ -34,11 +97,13 @@ class ApacheApp(app_module.App):
|
||||
self.add(info)
|
||||
|
||||
packages = Packages('packages-apache', [
|
||||
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3'
|
||||
'apache2', 'php-fpm', 'ssl-cert', 'uwsgi', 'uwsgi-plugin-python3',
|
||||
'libapache2-mod-auth-openidc'
|
||||
])
|
||||
self.add(packages)
|
||||
|
||||
dropin_configs = DropinConfigs('dropin-configs-apache', [
|
||||
'/etc/apache2/conf-available/10-freedombox.conf',
|
||||
'/etc/apache2/conf-available/php-fpm-freedombox.conf',
|
||||
'/etc/fail2ban/jail.d/apache-auth-freedombox.conf',
|
||||
])
|
||||
@ -59,6 +124,13 @@ class ApacheApp(app_module.App):
|
||||
daemons=['apache2'], reload_daemons=True)
|
||||
self.add(letsencrypt)
|
||||
|
||||
openidconnect = OpenIDConnect(
|
||||
'openidconnect-apache', 'apache',
|
||||
_('Web app protected by FreedomBox'),
|
||||
redirect_uris=['https://{domain}/apache/oidc/callback'],
|
||||
skip_authorization=True)
|
||||
self.add(openidconnect)
|
||||
|
||||
daemon = Daemon('daemon-apache', 'apache2')
|
||||
self.add(daemon)
|
||||
|
||||
@ -78,6 +150,43 @@ class ApacheApp(app_module.App):
|
||||
self.enable()
|
||||
|
||||
|
||||
def validate_host(hostname: str):
|
||||
"""Check whether we are allowed to be called by a given name.
|
||||
|
||||
This is to prevent DNS rebinding attacks and other poor consequences in the
|
||||
OpenID Connect protoctol.
|
||||
"""
|
||||
if hostname in ('localhost', 'ip6-localhost', 'ip6-loopback'):
|
||||
return
|
||||
|
||||
if hostname == action_utils.get_hostname():
|
||||
return
|
||||
|
||||
if hostname in names.components.DomainName.list_names():
|
||||
return
|
||||
|
||||
try:
|
||||
ipaddress.ip_address(hostname)
|
||||
return
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
raise ValueError(f'Server not configured to be called as {hostname}')
|
||||
|
||||
|
||||
def setup_oidc_client(netloc: str, hostname: str):
|
||||
"""Setup OpenID Connect client configuration.
|
||||
|
||||
netloc is hostname or IP address along with port as parsed by
|
||||
urllib.parse.urlparse() method from a URL.
|
||||
"""
|
||||
validate_host(hostname)
|
||||
|
||||
oidc = app_module.App.get('apache').get_component('openidconnect-apache')
|
||||
privileged.setup_oidc_client(netloc, oidc.client_id,
|
||||
oidc.get_client_secret())
|
||||
|
||||
|
||||
def _on_domain_added(sender, domain_type, name='', description='',
|
||||
services=None, **kwargs):
|
||||
"""Add site specific configuration for a domain."""
|
||||
|
||||
@ -301,7 +301,19 @@ def check_url(url: str, kind: str | None = None,
|
||||
wrapper: str | None = None,
|
||||
expected_output: str | None = None) -> bool:
|
||||
"""Check whether a URL is accessible."""
|
||||
command = ['curl', '--location', '-f', '-w', '%{response_code}']
|
||||
# When testing a URL with cURL, following any redirections with --location.
|
||||
# During those follows, store cookies that have been set and use them for
|
||||
# later requests. mod_auth_openidc will set a cookie 'x_csrf' to prevent
|
||||
# CSRF attacks and expect this cookie to sent back to it in later requests.
|
||||
# If this cookie is not present, it will refuse to perform OIDC Discovery
|
||||
# process resulting 404 errors and diagnostic failures for domains that
|
||||
# have not been visited by a user recently. --cookie '' means the cURL will
|
||||
# use an in-process cookie-jar for storing and retrieving cookies without
|
||||
# writing to a file on the disk.
|
||||
command = [
|
||||
'curl', '--location', '--cookie', '', '--fail', '--write-out',
|
||||
'%{response_code}'
|
||||
]
|
||||
|
||||
if kind == '6':
|
||||
# extract zone index
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
## SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##
|
||||
## DO NOT EDIT. If you do, FreedomBox will not automatically upgrade.
|
||||
##
|
||||
## Apache configuration managed by FreedomBox. If customization is needed,
|
||||
## create a new configuration file with higher priority and override directives.
|
||||
##
|
||||
|
||||
##
|
||||
## Macro to protect directories or locations (potentially backed by application
|
||||
## proxies) with OpenID Connect. To use the macro add 'Use AuthOpenIDConnect'.
|
||||
## By default, the visitor will need to be part of the 'admin' group to be able
|
||||
## to access the resource. Add additional groups using 'Use RequireGroup
|
||||
## <group>'. To debug OpenID Connect related communication add 'LogLevel
|
||||
## auth_openidc:debug'.
|
||||
##
|
||||
<Macro AuthOpenIDConnect>
|
||||
<IfModule mod_auth_openidc.c>
|
||||
AuthType openid-connect
|
||||
Require claim freedombox_groups:admin
|
||||
</IfModule>
|
||||
</Macro>
|
||||
|
||||
<Macro RequireGroup $group>
|
||||
<IfModule mod_auth_openidc.c>
|
||||
Require claim freedombox_groups:$group
|
||||
</IfModule>
|
||||
</Macro>
|
||||
@ -2,12 +2,22 @@
|
||||
"""Configure Apache web server."""
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import urllib.parse
|
||||
|
||||
from plinth import action_utils
|
||||
from plinth.actions import privileged
|
||||
import augeas
|
||||
|
||||
from plinth import action_utils, utils
|
||||
from plinth.actions import privileged, secret_str
|
||||
|
||||
openidc_config_path = pathlib.Path(
|
||||
'/etc/apache2/conf-available/freedombox-openidc.conf')
|
||||
metadata_dir_path = pathlib.Path(
|
||||
'/var/cache/apache2/mod_auth_openidc/metadata/')
|
||||
|
||||
|
||||
def _get_sort_key_of_version(version):
|
||||
@ -70,6 +80,8 @@ def setup(old_version: int):
|
||||
action_utils.run(['make-ssl-cert', 'generate-default-snakeoil'],
|
||||
check=True)
|
||||
|
||||
_setup_oidc_config()
|
||||
|
||||
with action_utils.WebserverChange() as webserver:
|
||||
# Disable mod_php as we have switched to mod_fcgi + php-fpm. Disable
|
||||
# before switching away from mpm_prefork otherwise switching fails due
|
||||
@ -114,8 +126,9 @@ def setup(old_version: int):
|
||||
webserver.enable('headers', kind='module')
|
||||
|
||||
# Various modules for authentication/authorization
|
||||
webserver.enable('auth_openidc', kind='module')
|
||||
webserver.enable('authnz_ldap', kind='module')
|
||||
webserver.enable('auth_pubtkt', kind='module')
|
||||
webserver.disable('auth_pubtkt', kind='module')
|
||||
|
||||
# enable some critical modules to avoid restart while installing
|
||||
# FreedomBox applications.
|
||||
@ -135,9 +148,11 @@ def setup(old_version: int):
|
||||
webserver.enable('dav', kind='module')
|
||||
webserver.enable('dav_fs', kind='module')
|
||||
|
||||
# setup freedombox site
|
||||
# setup freedombox configuration
|
||||
webserver.enable('10-freedombox', kind='config')
|
||||
webserver.enable('freedombox', kind='config')
|
||||
webserver.enable('freedombox-tls', kind='config')
|
||||
webserver.enable('freedombox-openidc.conf', kind='config')
|
||||
|
||||
# enable serving Debian javascript libraries
|
||||
webserver.enable('javascript-common', kind='config')
|
||||
@ -151,6 +166,119 @@ def setup(old_version: int):
|
||||
webserver.enable('freedombox-default', kind='site')
|
||||
|
||||
|
||||
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.transform('Httpd', str(openidc_config_path))
|
||||
aug.set('/augeas/context', '/files' + str(openidc_config_path))
|
||||
aug.load()
|
||||
|
||||
return aug
|
||||
|
||||
|
||||
def _get_mod_openidc_passphrase() -> str:
|
||||
"""Read existing mod-auth-openidc passphase.
|
||||
|
||||
Instead of generating a new passphrase every time, use existing one. If the
|
||||
passphrase changes, all the existing sessions will be logged out and users
|
||||
will have login to apps again.
|
||||
"""
|
||||
aug = _load_augeas()
|
||||
for directive in aug.match('*/directive'):
|
||||
if aug.get(directive) == 'OIDCCryptoPassphrase':
|
||||
return aug.get(directive + '/arg')
|
||||
|
||||
# Does not exist already, generate new
|
||||
return utils.generate_password(size=64)
|
||||
|
||||
|
||||
@privileged
|
||||
def setup_oidc_client(netloc: str, client_id: str, client_secret: secret_str):
|
||||
"""Setup client ID and secret for provided domain.
|
||||
|
||||
netloc is hostname or IP address along with port as parsed by
|
||||
urllib.parse.urlparse() method from a URL.
|
||||
"""
|
||||
issuer = f'{netloc}/freedombox/o'
|
||||
issuer_quoted = urllib.parse.quote_plus(issuer)
|
||||
client_path = metadata_dir_path / f'{issuer_quoted}.client'
|
||||
if client_path.exists():
|
||||
try:
|
||||
current_data = json.loads(client_path.read_text())
|
||||
if (current_data['client_id'] == client_id
|
||||
and current_data['client_secret'] == client_secret):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
client_configuration = {
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret
|
||||
}
|
||||
previous_umask = os.umask(0o077)
|
||||
try:
|
||||
client_path.write_text(json.dumps(client_configuration))
|
||||
finally:
|
||||
os.umask(previous_umask)
|
||||
|
||||
shutil.chown(client_path, 'www-data', 'www-data')
|
||||
|
||||
|
||||
def _setup_oidc_config():
|
||||
"""Setup Apache as a OpenID Connect Relying Party.
|
||||
|
||||
Ensure that auth_openidc module's metadata directory is created. It will be
|
||||
used to store provider-specific configuration. Since FreedomBox will be
|
||||
configured with multiple domains and some of them may not be accessible due
|
||||
to the access method, we need to configure a separate IDP for each domain.
|
||||
This is also because auth_openidc does not allow IDP configuration with
|
||||
relative URLs.
|
||||
|
||||
Keep the metadata directory and configuration file unreadable by non-admin
|
||||
users since they contain module's crypto secret and OIDC client secret.
|
||||
"""
|
||||
metadata_dir_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
metadata_dir_path.mkdir(mode=0o700, exist_ok=True)
|
||||
shutil.chown(metadata_dir_path.parent, 'www-data', 'www-data')
|
||||
shutil.chown(metadata_dir_path, 'www-data', 'www-data')
|
||||
|
||||
# XXX: Default cache type is 'shm' or shared memory. This is lost when
|
||||
# Apache is restarted and users/apps will have to reauthenticate. Improve
|
||||
# this by using file (in tmpfs), redis, or memache caches.
|
||||
|
||||
passphrase = _get_mod_openidc_passphrase()
|
||||
configuration = f'''##
|
||||
## OpenID Connect related configuration
|
||||
##
|
||||
<IfModule mod_auth_openidc.c>
|
||||
OIDCCryptoPassphrase {passphrase}
|
||||
OIDCMetadataDir {str(metadata_dir_path)}
|
||||
# Use relative URL to redirect to the same origin as the resource
|
||||
OIDCDiscoverURL /freedombox/apache/discover-idp/
|
||||
OIDCSSLValidateServer Off
|
||||
OIDCProviderMetadataRefreshInterval 86400
|
||||
|
||||
# Use relative URL to return to the original domain
|
||||
OIDCRedirectURI /apache/oidc/callback
|
||||
OIDCRemoteUserClaim sub
|
||||
|
||||
# The redirect URI must always be under a location protected by
|
||||
# mod_openidc.
|
||||
<Location /apache>
|
||||
AuthType openid-connect
|
||||
# Checking audience is not necessary, but we need to check some claim.
|
||||
Require claim aud:apache
|
||||
</Location>
|
||||
</IfModule>
|
||||
'''
|
||||
previous_umask = os.umask(0o077)
|
||||
try:
|
||||
openidc_config_path.write_text(configuration)
|
||||
finally:
|
||||
os.umask(previous_umask)
|
||||
|
||||
|
||||
# TODO: Check that the (name, kind) is a managed by FreedomBox before
|
||||
# performing operation.
|
||||
@privileged
|
||||
|
||||
@ -473,7 +473,8 @@ def test_diagnose_url(get_addresses, check):
|
||||
def test_check_url(run):
|
||||
"""Test checking whether a URL is accessible."""
|
||||
url = 'http://localhost/test'
|
||||
basic_command = ['curl', '--location', '-f', '-w', '%{response_code}']
|
||||
basic_command = ['curl', '--location', '--cookie', '', '--fail',
|
||||
'--write-out', '%{response_code}']
|
||||
extra_args = {'env': None, 'check': True, 'stdout': -1, 'stderr': -1}
|
||||
|
||||
# Basic
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
URLs for the Apache module.
|
||||
"""
|
||||
"""URLs for the Apache module."""
|
||||
|
||||
urlpatterns: list = []
|
||||
from django.urls import re_path
|
||||
from stronghold.decorators import public
|
||||
|
||||
from .views import DiscoverIDPView
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^apache/discover-idp/$', public(DiscoverIDPView.as_view()),
|
||||
name='discover-idp'),
|
||||
]
|
||||
|
||||
71
plinth/modules/apache/views.py
Normal file
71
plinth/modules/apache/views.py
Normal file
@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Views for the Apache app."""
|
||||
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
from django.http import (HttpResponseBadRequest, HttpResponseRedirect,
|
||||
HttpResponseServerError)
|
||||
from django.views import View
|
||||
|
||||
from . import setup_oidc_client, validate_host
|
||||
|
||||
# By default 'openid' scope already included by mod_auth_openidc
|
||||
OIDC_SCOPES = 'email freedombox_groups'
|
||||
|
||||
|
||||
class DiscoverIDPView(View):
|
||||
"""A view called by auth_openidc Apache module to find the IDP.
|
||||
|
||||
According to documentation for auth_openidc: an Issuer selection can be
|
||||
passed back to the callback URL as in:
|
||||
<callback-url>?iss=[${issuer}|${domain}|${e-mail-style-account-name}]
|
||||
[parameters][&login_hint=<login-hint>][&scopes=<scopes>]
|
||||
[&auth_request_params=<params>]
|
||||
|
||||
where the <iss> parameter contains the URL-encoded issuer value of the
|
||||
selected Provider (or...), [parameters] contains the additional parameters
|
||||
that were passed in on the discovery request (e.g.
|
||||
target_link_uri=<url>&x_csrf=<x_csrf>&method=<method>&scopes=<scopes>)
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
"""Redirect back to auth_openidc module after selecting a IDP."""
|
||||
target_link_uri = request.GET.get('target_link_uri', '')
|
||||
method = request.GET.get('method', 'get')
|
||||
x_csrf = request.GET.get('x_csrf', '')
|
||||
oidc_callback = request.GET.get('oidc_callback')
|
||||
|
||||
if method != 'get':
|
||||
return HttpResponseBadRequest(f'Cannot handle "{method}" method')
|
||||
|
||||
oidc_callback_parts = urlparse(oidc_callback)
|
||||
request_host = request.META['HTTP_HOST']
|
||||
if request_host != oidc_callback_parts.netloc:
|
||||
return HttpResponseBadRequest(
|
||||
f'Cannot redirect from {request_host} to a different host '
|
||||
f'{oidc_callback_parts.netloc}')
|
||||
|
||||
try:
|
||||
validate_host(oidc_callback_parts.hostname)
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest(
|
||||
f'Accessed using unknown domain {request_host}. Please add '
|
||||
'the domain to list of configured domains.')
|
||||
|
||||
try:
|
||||
setup_oidc_client(oidc_callback_parts.netloc,
|
||||
oidc_callback_parts.hostname)
|
||||
except ValueError:
|
||||
return HttpResponseServerError(
|
||||
f'Server not configured to called as {request_host}')
|
||||
|
||||
url = '/apache/oidc/callback'
|
||||
params = {
|
||||
'iss': f'https://{request_host}/freedombox/o',
|
||||
'target_link_uri': target_link_uri,
|
||||
'method': method,
|
||||
'x_csrf': x_csrf,
|
||||
'scopes': OIDC_SCOPES,
|
||||
}
|
||||
params = urlencode(params)
|
||||
return HttpResponseRedirect(f'{url}?{params}')
|
||||
@ -1,12 +1,14 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""FreedomBox app to manage backup archives."""
|
||||
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from django.utils.text import get_valid_filename
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -14,9 +16,10 @@ from django.utils.translation import gettext_noop
|
||||
|
||||
from plinth import app as app_module
|
||||
from plinth import cfg, glib, menu
|
||||
from plinth.modules.names.components import DomainName
|
||||
from plinth.package import Packages
|
||||
|
||||
from . import api, manifest, privileged
|
||||
from . import api, errors, manifest, privileged
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -132,9 +135,77 @@ def restore_from_upload(path, app_ids=None):
|
||||
create_subvolume=False, backup_file=path)
|
||||
|
||||
|
||||
def get_known_hosts_path():
|
||||
def get_known_hosts_path() -> Path:
|
||||
"""Return the path to the known hosts file."""
|
||||
return pathlib.Path(cfg.data_dir) / '.ssh' / 'known_hosts'
|
||||
return Path(cfg.data_dir) / '.ssh' / 'known_hosts'
|
||||
|
||||
|
||||
def get_ssh_client_auth_key_paths() -> tuple[Path, Path]:
|
||||
"""Return the paths to the SSH client public key and private key."""
|
||||
key_path = Path(cfg.data_dir) / '.ssh' / 'id_ed25519'
|
||||
pubkey_path = key_path.with_suffix('.pub')
|
||||
return pubkey_path, key_path
|
||||
|
||||
|
||||
def generate_ssh_client_auth_key():
|
||||
"""Generate SSH client authentication keypair, if needed."""
|
||||
_, key_path = get_ssh_client_auth_key_paths()
|
||||
key_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if key_path.exists():
|
||||
logger.info('SSH client key %s for FreedomBox service already exists',
|
||||
key_path)
|
||||
return
|
||||
|
||||
domain_name = DomainName.list_names()[0]
|
||||
logger.info('Generating SSH client key %s for FreedomBox service',
|
||||
key_path)
|
||||
subprocess.run([
|
||||
'ssh-keygen', '-t', 'ed25519', '-N', '', '-C',
|
||||
f'freedombox@{domain_name}', '-f',
|
||||
str(key_path)
|
||||
], stdout=subprocess.DEVNULL, check=True)
|
||||
|
||||
|
||||
def get_ssh_client_public_key() -> str:
|
||||
"""Get SSH client public key for FreedomBox service."""
|
||||
pubkey_path, _ = get_ssh_client_auth_key_paths()
|
||||
with pubkey_path.open('r') as pubkey_file:
|
||||
pubkey = pubkey_file.read()
|
||||
|
||||
return pubkey
|
||||
|
||||
|
||||
def copy_ssh_client_public_key(pubkey_path: str, hostname: str, username: str,
|
||||
password: str):
|
||||
"""Copy the SSH client public key to the remote server.
|
||||
|
||||
Returns whether the copy was successful, and any error message.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
env['SSHPASS'] = str(password)
|
||||
with raise_ssh_error():
|
||||
try:
|
||||
subprocess.run([
|
||||
'sshpass', '-e', 'ssh-copy-id', '-i', pubkey_path,
|
||||
f'{username}@{hostname}'
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True,
|
||||
env=env)
|
||||
logger.info("Copied SSH client public key to remote host's "
|
||||
"authorized keys.")
|
||||
except subprocess.CalledProcessError as exception:
|
||||
logger.warning('Failed to copy SSH client public key: %s',
|
||||
exception.stderr)
|
||||
raise
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def raise_ssh_error() -> collections.abc.Generator[None]:
|
||||
"""Convert subprocess error to SshError."""
|
||||
try:
|
||||
yield
|
||||
except subprocess.CalledProcessError as exception:
|
||||
raise errors.SshError(exception.returncode, exception.cmd,
|
||||
exception.output, exception.stderr)
|
||||
|
||||
|
||||
def is_ssh_hostkey_verified(hostname):
|
||||
|
||||
@ -1,18 +1,24 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import subprocess
|
||||
|
||||
from plinth.errors import PlinthError
|
||||
|
||||
|
||||
class BorgError(PlinthError):
|
||||
"""Generic borg errors"""
|
||||
"""Generic borg errors."""
|
||||
|
||||
|
||||
class BorgRepositoryDoesNotExistError(BorgError):
|
||||
"""Borg access to a repository works but the repository does not exist"""
|
||||
"""Borg access to a repository works but the repository does not exist."""
|
||||
|
||||
|
||||
class SshError(subprocess.CalledProcessError):
|
||||
"""Error when running an SSH command."""
|
||||
|
||||
|
||||
class SshfsError(PlinthError):
|
||||
"""Generic sshfs errors"""
|
||||
"""Generic sshfs errors."""
|
||||
|
||||
|
||||
class BorgRepositoryExists(BorgError):
|
||||
|
||||
@ -184,7 +184,7 @@ class EncryptedBackupsMixin(forms.Form):
|
||||
choices=[('repokey', _('Key in Repository')), ('none', _('None'))])
|
||||
encryption_passphrase = forms.CharField(
|
||||
label=_('Passphrase'),
|
||||
help_text=_('Passphrase; Only needed when using encryption.'),
|
||||
help_text=_('Only needed when using encryption.'),
|
||||
widget=forms.PasswordInput(), required=False)
|
||||
confirm_encryption_passphrase = forms.CharField(
|
||||
label=_('Confirm Passphrase'), help_text=_('Repeat the passphrase.'),
|
||||
@ -251,13 +251,30 @@ class AddRemoteRepositoryForm(EncryptedBackupsMixin, forms.Form):
|
||||
help_text=_('Path of a new or existing repository. Example: '
|
||||
'<i>user@host:~/path/to/repo/</i>'),
|
||||
validators=[repository_validator])
|
||||
ssh_auth_type = forms.ChoiceField(
|
||||
label=_('SSH Authentication Type'),
|
||||
help_text=_('Choose how to authenticate to the remote SSH server.'),
|
||||
widget=forms.RadioSelect(),
|
||||
choices=[('key_auth', _('Key-based Authentication')),
|
||||
('password_auth', _('Password-based Authentication'))])
|
||||
ssh_password = forms.CharField(
|
||||
label=_('SSH server password'), strip=True,
|
||||
help_text=_('Password of the SSH Server.<br />'
|
||||
'SSH key-based authentication is not yet possible.'),
|
||||
widget=forms.PasswordInput(), required=False)
|
||||
label=_('SSH server password'), widget=forms.PasswordInput(),
|
||||
strip=True, help_text=_('Required for password-based authentication.'),
|
||||
required=False)
|
||||
|
||||
field_order = ['repository', 'ssh_password'] + encryption_fields
|
||||
field_order = ['repository', 'ssh_auth_type', 'ssh_password'
|
||||
] + encryption_fields
|
||||
|
||||
def clean(self):
|
||||
"""Perform additional checks on form data."""
|
||||
super().clean()
|
||||
ssh_password = self.cleaned_data.get('ssh_password')
|
||||
if self.cleaned_data.get(
|
||||
'ssh_auth_type') == 'password_auth' and not ssh_password:
|
||||
raise forms.ValidationError(
|
||||
_('SSH password is needed for password-based authentication.'))
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def clean_repository(self):
|
||||
"""Validate repository form field."""
|
||||
|
||||
@ -49,7 +49,7 @@ KNOWN_ERRORS = [
|
||||
'raise_as': errors.BorgRepositoryDoesNotExistError,
|
||||
},
|
||||
{
|
||||
'errors': ['passphrase supplied in .* is incorrect'],
|
||||
'errors': ['[Pp]assphrase supplied in .* is incorrect'],
|
||||
'message': _('Incorrect encryption passphrase'),
|
||||
'raise_as': errors.BorgError,
|
||||
},
|
||||
@ -131,8 +131,7 @@ def _reraise_known_errors(err):
|
||||
|
||||
@reraise_known_errors
|
||||
@privileged
|
||||
def mount(mountpoint: str, remote_path: str, ssh_keyfile: str | None = None,
|
||||
password: secret_str | None = None,
|
||||
def mount(mountpoint: str, remote_path: str, ssh_keyfile: str,
|
||||
user_known_hosts_file: str = '/dev/null'):
|
||||
"""Mount a remote ssh path via sshfs."""
|
||||
try:
|
||||
@ -156,16 +155,9 @@ def mount(mountpoint: str, remote_path: str, ssh_keyfile: str | None = None,
|
||||
'sshfs', remote_path, mountpoint, '-o',
|
||||
f'UserKnownHostsFile={user_known_hosts_file}', '-o',
|
||||
'StrictHostKeyChecking=yes', '-o', 'reconnect', '-o',
|
||||
'ServerAliveInterval=15', '-o', 'ServerAliveCountMax=3'
|
||||
'ServerAliveInterval=15', '-o', 'ServerAliveCountMax=3', '-o',
|
||||
'IdentityFile=' + ssh_keyfile
|
||||
]
|
||||
if ssh_keyfile:
|
||||
cmd += ['-o', 'IdentityFile=' + ssh_keyfile]
|
||||
else:
|
||||
if not password:
|
||||
raise ValueError('mount requires either a password or ssh_keyfile')
|
||||
cmd += ['-o', 'password_stdin']
|
||||
input_ = password.encode()
|
||||
|
||||
action_utils.run(cmd, check=True, timeout=TIMEOUT, input=input_)
|
||||
|
||||
|
||||
|
||||
@ -13,7 +13,9 @@ from django.utils.translation import gettext_lazy as _
|
||||
from plinth import cfg
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import (_backup_handler, api, errors, get_known_hosts_path, privileged,
|
||||
from . import (_backup_handler, api, copy_ssh_client_public_key, errors,
|
||||
generate_ssh_client_auth_key, get_known_hosts_path,
|
||||
get_ssh_client_auth_key_paths, privileged, raise_ssh_error,
|
||||
restore_archive_handler, split_path, store)
|
||||
from .schedule import Schedule
|
||||
|
||||
@ -143,6 +145,9 @@ class BaseBorgRepository(abc.ABC):
|
||||
privileged.delete_archive(archive_path,
|
||||
self._get_encryption_passpharse())
|
||||
|
||||
def migrate_credentials(self) -> None:
|
||||
"""Migrate any credentials."""
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize / create a borg repository."""
|
||||
encryption = 'none'
|
||||
@ -315,11 +320,27 @@ class SshBorgRepository(BaseBorgRepository):
|
||||
self._umount_ignore_errors()
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
def hostname(self) -> str:
|
||||
"""Return hostname from the remote path."""
|
||||
_, hostname, _ = split_path(self._path)
|
||||
return hostname.split('%')[0] # XXX: Likely incorrect to split
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""Return username from the remote path."""
|
||||
username, _, _ = split_path(self._path)
|
||||
return username
|
||||
|
||||
@property
|
||||
def ssh_password(self) -> str | None:
|
||||
"""Return SSH password if it is stored, otherwise None."""
|
||||
return self.credentials.get('ssh_password')
|
||||
|
||||
@property
|
||||
def ssh_keyfile(self) -> str | None:
|
||||
"""Return path to SSH client key if stored, otherwise None."""
|
||||
return self.credentials.get('ssh_keyfile')
|
||||
|
||||
@property
|
||||
def _mountpoint(self):
|
||||
"""Return the local mount point where repository is to be mounted."""
|
||||
@ -330,8 +351,22 @@ class SshBorgRepository(BaseBorgRepository):
|
||||
"""Return whether remote path is mounted locally."""
|
||||
return privileged.is_mounted(self._mountpoint)
|
||||
|
||||
def migrate_credentials(self) -> None:
|
||||
"""Add SSH keyfile credential and delete stored password."""
|
||||
if not self.ssh_password:
|
||||
return
|
||||
|
||||
pubkey_path, keyfile_path = get_ssh_client_auth_key_paths()
|
||||
generate_ssh_client_auth_key()
|
||||
copy_ssh_client_public_key(str(pubkey_path), self.hostname,
|
||||
self.username, self.ssh_password)
|
||||
self.credentials['ssh_keyfile'] = str(keyfile_path)
|
||||
self.credentials.pop('ssh_password', None)
|
||||
self.save()
|
||||
|
||||
def initialize(self):
|
||||
"""Initialize the repository after mounting the target directory."""
|
||||
self.migrate_credentials()
|
||||
self._ensure_remote_directory()
|
||||
self.mount()
|
||||
super().initialize()
|
||||
@ -341,17 +376,11 @@ class SshBorgRepository(BaseBorgRepository):
|
||||
if self.is_mounted:
|
||||
return
|
||||
|
||||
self.migrate_credentials()
|
||||
known_hosts_path = get_known_hosts_path()
|
||||
kwargs = {'user_known_hosts_file': str(known_hosts_path)}
|
||||
if 'ssh_password' in self.credentials and self.credentials[
|
||||
'ssh_password']:
|
||||
kwargs['password'] = self.credentials['ssh_password']
|
||||
|
||||
if 'ssh_keyfile' in self.credentials and self.credentials[
|
||||
'ssh_keyfile']:
|
||||
kwargs['ssh_keyfile'] = self.credentials['ssh_keyfile']
|
||||
|
||||
privileged.mount(self._mountpoint, self._path, **kwargs)
|
||||
privileged.mount(self._mountpoint, self._path,
|
||||
ssh_keyfile=self.credentials['ssh_keyfile'],
|
||||
user_known_hosts_file=str(known_hosts_path))
|
||||
|
||||
def umount(self):
|
||||
"""Unmount the remote path that was mounted locally using sshfs."""
|
||||
@ -391,16 +420,16 @@ class SshBorgRepository(BaseBorgRepository):
|
||||
if dir_path[0] == '~':
|
||||
dir_path = '.' + dir_path[1:]
|
||||
|
||||
password = self.credentials['ssh_password']
|
||||
|
||||
# Ensure remote directory exists, check contents
|
||||
env = {'SSHPASS': password}
|
||||
known_hosts_path = str(get_known_hosts_path())
|
||||
subprocess.run([
|
||||
'sshpass', '-e', 'ssh', '-o',
|
||||
f'UserKnownHostsFile={known_hosts_path}', f'{username}@{hostname}',
|
||||
'mkdir', '-p', dir_path
|
||||
], check=True, env=env)
|
||||
with raise_ssh_error():
|
||||
subprocess.run([
|
||||
'ssh', '-i',
|
||||
str(self.ssh_keyfile), '-o',
|
||||
f'UserKnownHostsFile={known_hosts_path}', '-o',
|
||||
'BatchMode=yes', f'{username}@{hostname}', 'mkdir', '-p',
|
||||
dir_path
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
||||
|
||||
|
||||
def get_repositories():
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
/**
|
||||
* @licstart The following is the entire license notice for the JavaScript
|
||||
* code in this page.
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* @licend The above is the entire license notice for the JavaScript code
|
||||
* in this page.
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const keyAuth = document.getElementById('id_ssh_auth_type_0');
|
||||
const passwordAuth = document.getElementById('id_ssh_auth_type_1');
|
||||
const sshPasswordField = document.getElementById('id_ssh_password');
|
||||
const encryptionType = document.getElementById('id_encryption');
|
||||
const encryptionPassphraseField = document.getElementById('id_encryption_passphrase');
|
||||
const encryptionConfirmPassphraseField = document.getElementById('id_confirm_encryption_passphrase');
|
||||
|
||||
function handleAuthTypeChange() {
|
||||
if (passwordAuth.checked) {
|
||||
sshPasswordField.parentElement.parentElement.style.display = 'block';
|
||||
} else {
|
||||
sshPasswordField.parentElement.parentElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function handleEncryptionTypeChange() {
|
||||
let display = 'none';
|
||||
if (encryptionType.value === "repokey") {
|
||||
display = 'block';
|
||||
}
|
||||
|
||||
encryptionPassphraseField.parentElement.parentElement.style.display = display;
|
||||
encryptionConfirmPassphraseField.parentElement.parentElement.style.display = display;
|
||||
}
|
||||
|
||||
keyAuth.addEventListener('change', handleAuthTypeChange);
|
||||
passwordAuth.addEventListener('change', handleAuthTypeChange);
|
||||
encryptionType.addEventListener('change', handleEncryptionTypeChange);
|
||||
|
||||
handleAuthTypeChange();
|
||||
handleEncryptionTypeChange();
|
||||
});
|
||||
@ -5,15 +5,125 @@
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block page_js %}
|
||||
<script type="text/javascript" src="{% static 'backups/backups_add_remote_repository.js' %}"
|
||||
defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<form class="form" method="post">
|
||||
<form class="form form-add-remote-repository" method="post">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
<a class="close" data-dismiss="alert">×</a>
|
||||
{% for non_field_error in form.non_field_errors %}
|
||||
{{ non_field_error }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form|bootstrap }}
|
||||
<div class="form-group{% if form.repository.errors %}
|
||||
has-error{% endif %}">
|
||||
<label class="control-label fs-4 fw-medium" for="id_repository">
|
||||
{{ form.repository.label }}
|
||||
</label>
|
||||
<div>
|
||||
<input type="text" name="repository" class="form-control"
|
||||
required id="id_repository">
|
||||
{% for error in form.repository.errors %}
|
||||
<span class="help-block {{ form.error_css_class }}">{{ error }}</span>
|
||||
{% endfor %}
|
||||
<p class="help-block">
|
||||
{{ form.repository.help_text|safe }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group{% if form.ssh_auth_type.errors %}
|
||||
has-error{% endif %}">
|
||||
<label class="control-label fs-4 fw-medium">
|
||||
{{ form.ssh_auth_type.label }}
|
||||
</label>
|
||||
<p class="help-block">
|
||||
{{ form.ssh_auth_type.help_text|safe }}
|
||||
</p>
|
||||
{% for error in form.ssh_auth_type.errors %}
|
||||
<span class="help-block {{ form.error_css_class }}">{{ error }}</span>
|
||||
{% endfor %}
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ form.ssh_auth_type.0.tag }}
|
||||
{{ form.ssh_auth_type.0.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
The following SSH client public key must be added to the
|
||||
authorized keys list on the remote machine for {{ box_name }} to
|
||||
be able to connect to the remote machine:
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<pre>{{ ssh_client_public_key }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ form.ssh_auth_type.1.tag }}
|
||||
{{ form.ssh_auth_type.1.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
{{ box_name }} service will attempt to connect using the provided
|
||||
password. If successful, then the public key will be automatically
|
||||
added to the authorized keys list, so that future connections do
|
||||
not need the password.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% with _=form.ssh_password|add_input_classes %}
|
||||
{% include "bootstrapform/field.html" with field=form.ssh_password %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group{% if form.encryption.errors %}
|
||||
has-error{% endif %}">
|
||||
<label class="control-label fs-4 fw-medium" for="id_encryption">
|
||||
{{ form.encryption.label }}
|
||||
</label>
|
||||
<div>
|
||||
<select name="encryption" class="form-control" id="id_encryption">
|
||||
<option value="repokey">{{ form.encryption.0.choice_label }}</option>
|
||||
<option value="none">{{ form.encryption.1.choice_label }}</option>
|
||||
</select>
|
||||
{% for error in form.encryption.errors %}
|
||||
<span class="help-block {{ form.error_css_class }}">{{ error }}</span>
|
||||
{% endfor %}
|
||||
<p class="help-block">
|
||||
{{ form.encryption.help_text|safe }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with _=form.encryption_passphrase|add_input_classes %}
|
||||
{% include "bootstrapform/field.html" with field=form.encryption_passphrase %}
|
||||
{% endwith %}
|
||||
|
||||
{% with _=form.confirm_encryption_passphrase|add_input_classes %}
|
||||
{% include "bootstrapform/field.html" with field=form.confirm_encryption_passphrase %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning d-flex align-items-center" role="alert">
|
||||
<div class="me-2">
|
||||
@ -24,8 +134,8 @@
|
||||
{% blocktrans trimmed %}
|
||||
The credentials for this repository are stored on your {{ box_name }}.
|
||||
<br />
|
||||
To restore a backup on a new {{ box_name }} you need the SSH credentials
|
||||
and, if chosen, the encryption passphrase.
|
||||
To restore a backup on a new {{ box_name }} you need the SSH
|
||||
credentials and, if chosen, the encryption passphrase.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table" id="archives-list">
|
||||
<div class="table-responsive repository" data-repository-name="{{ repository.name }}">
|
||||
<table class="table" class="archives-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
@ -23,7 +23,7 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{{ repository.name }}
|
||||
<span class="repository-name">{{ repository.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
|
||||
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