freedombox Debian release 25.11

-----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmjAEGkWHGp2YWxsZXJv
 eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICOGlD/9iEnOAJFHjTrh4GpD6XnIPo6uc
 /HyqxwS03gPgJ+cFyFgIU4d4fo65JC3DflxriGQkw/mLePQLrN9OzJGUO85KC/M8
 SzYIDx9hLLp3ccWNGbKTySHreRbgzyPp1b+Gg2ApWl3POe7UswbVAyCMFoakW27i
 T1PWKaTb53Pa1s8uNwBbnSqfDy/M7lDf/e5A3+SJ277Aw05EnqYOyNGY6akjUZdj
 xdn296b1edAn2ZGEhsTYONhqO/McSR9ABk+idg2bEpoMiImqysRkl4In/tMjy3js
 W5LlESv2eaL3IHCW2JxDl8WSTGjTyATcD9QSkraVt2WM93KgyPFIQD4HpgKuT8GN
 DUs6D5puAmgKMsLGU32H5dMdGCovGab88W5MoztQLNsfA/zsMCuJCj9JlrxEN7Rt
 4Cl8jxUn4onb1PeoIAH7ohcEabqQOwFwt7APO/rBtN98G8jOho21f/OmKQeQ3Hfo
 Ytqu+YALBlU9N/FSR+GJJxghz//ONodgbmy7MyGdgL/qsiy0IHNg8Y0p+e9sX5Kb
 WWrSihjpw+FcZWDlzNPGSiygYaJfLBU5NRDniW1CUk29pibdV5SuRGdP9/0NjGJc
 8pnZFwasLlDc1sEEq6wUfO9zEhxnizS5/f47YtoiHJzSsdH/+Bm4YuIusVi3wYYn
 iME7d0a5ABmefpWBAw==
 =4rWm
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmjD/XwWHGp2YWxsZXJv
 eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICMejD/9eWnFBEYH/tXqMsJUrgwwv8JoQ
 iFCuGSkx329N26Iu0s0IKz0w7EDxWbGsHxCA9fck96GuZJJFxCAuFmKwc4jCRaCF
 N64Ncm3h5Roz2qR0TFLRfQXVCd84bli1Dnz+nUVfZV6sktmRHQsUw4ZmWX+EEqxm
 A5VrrI5ylAHed7DzUKzFFEWtdUywX8ZiHoEOuvdUnJcOkm2KjThDSxXQw/dVk91k
 KkM89VtggffkrWESUCHGATDapIKIt27CVO6pY4F/zHlXhzpgwTYtyMrsZAbLeWJA
 yvV6/A7QQiFudVNm35EunTgSKXeExM9ctz/x6YgFoxWDFGp1YUvgQyzCUGSJi2ma
 44UpsDL51cnJxZdACakjmJsVLeZL2R+kMhJOYKoZoGQf/DsjYFeRNvVN0Drtu281
 U2Z7CHVyTwCRoGpWw74jfj29LD6JEX4PiFSQ8ItaLYNDoWA65cTrSClkMvwF52SP
 CQbmiuNR5VFjuNDPnpL6eZWYwcQBHcQRpLOrU3Fh5hM6GkVomwWNKbLXz60mYow5
 +9YLwCTz++kDbrkpbLPkyTZDDB1Zxe22tt9TkCnybYry1GalI5t3Rvl+F+sfbADW
 WHxqj1D1rXjt1tBEdbwD9R8cpEf7DyIEPoAGFnjlQOMA9TcyrUWhSAB+a1bKl2m7
 uHqIKtb8Yx7McqgAwA==
 =gG/r
 -----END PGP SIGNATURE-----

Merge tag 'v25.11' into debian/trixie-backports

freedombox Debian release 25.11
This commit is contained in:
James Valleroy 2025-09-12 07:01:04 -04:00
commit 92f02f12de
134 changed files with 107100 additions and 101464 deletions

View File

@ -114,6 +114,26 @@ development environment inside a systemd-nspawn container.
host$ ./container ssh --distribution=stable
```
#### Using KVM Virtual Machine
The `./container` script, shipped with FreedomBox source code, can manage the
development environment inside a KVM-based virtual machine using libvirt. This
is an alternative to using containers described above (but the name of the
script is still 'container' even when managing virtual machines). Some hardware,
such as additional disks, can be better tested with virtual machine than with
containers.
Containers and a virtual machines of the same or different distribution all be
used simultaneously as they all use different disk images.
1. To use virtual machines instead of container, append the option
'--machine-type=vm' to all the ./container commands described above. For
example, to bring up a virtual machine instead of a container run:
```bash
host$ ./container up --machine-type=vm
```
#### Using after Setup
After logging into the container, the source code is available in `/freedombox`

View File

@ -58,7 +58,11 @@ ROOT_DATA_FILES := $(shell find data -type f $(FIND_ARGS))
MODULE_DATA_FILES := $(shell find $(wildcard plinth/modules/*/data) -type f $(FIND_ARGS))
update-translations:
cd plinth; $(DJANGO_ADMIN) makemessages --all --domain django --keep-pot --verbosity=1
$(DJANGO_ADMIN) makemessages --all --domain django --keep-pot \
--verbosity=1 --ignore conftest.py --ignore doc --ignore build \
--ignore htmlcov --ignore screenshots --ignore debian --ignore \
actions --ignore preseed --ignore static --ignore data \
--settings plinth.settings --pythonpath .
configure:
# Nothing to do
@ -99,9 +103,7 @@ install:
rm -f $(DESTDIR)$${lib_dir}/plinth*.dist-info/direct_url.json && \
$(INSTALL) -D -t $(BIN_DIR) bin/plinth
$(INSTALL) -D -t $(BIN_DIR) bin/freedombox-privileged
# Actions
$(INSTALL) -D -t $(DESTDIR)/usr/share/plinth/actions actions/actions
$(INSTALL) -D -t $(BIN_DIR) bin/freedombox-cmd
# Static web server files
rm -rf $(STATIC_FILES_DIRECTORY)
@ -136,7 +138,7 @@ check-tests-cov:
# Code quality checking using flake8
check-code:
$(PYTHON) -m flake8 plinth actions/actions container
$(PYTHON) -m flake8 plinth container
# Static type checking using mypy
check-type:

View File

@ -1,7 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
from plinth.actions import privileged_main
if __name__ == '__main__':
privileged_main()

6
bin/freedombox-cmd Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
import plinth.privileged_daemon
plinth.privileged_daemon.client_main()

View File

@ -160,8 +160,10 @@ from typing import Callable
from urllib.request import urlopen
URLS_AMD64 = {
'oldstable': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
'amd64/bookworm/freedombox-bookworm_all-amd64.img.xz',
'stable': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
'amd64/bookworm/freedombox-bookworm_all-amd64.img.xz',
'amd64/trixie/freedombox-trixie_all-amd64.img.xz',
'testing': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
'amd64/testing/freedombox-testing_dev_all-amd64.img.xz',
'unstable': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
@ -169,8 +171,10 @@ URLS_AMD64 = {
}
URLS_ARM64 = {
'oldstable': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
'arm64/bookworm/freedombox-bookworm_all-arm64.img.xz',
'stable': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
'arm64/bookworm/freedombox-bookworm_all-arm64.img.xz',
'arm64/trixie/freedombox-trixie_all-arm64.img.xz',
'testing': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
'arm64/testing/freedombox-testing_dev_all-arm64.img.xz',
'unstable': 'https://ftp.freedombox.org/pub/freedombox/hardware/'
@ -221,7 +225,7 @@ mount -o remount /freedombox
if [[ "{distribution}" == "stable" && ! -e $BACKPORTS_SOURCES_LIST ]]
then
echo "> In machine: Enable backports"
/freedombox/actions/actions upgrades activate_backports --no-args
/freedombox/bin/freedombox-cmd upgrades activate_backports --no-args
fi
echo "> In machine: Upgrade packages"
@ -293,7 +297,7 @@ LIBVIRT_DOMAIN_XML_TEMPLATE = '''<domain type="kvm">
<access mode="shared"/>
</memoryBacking>
<vcpu placement="static">{cpus}</vcpu>
<os>
<os {firmware}>
<type arch="x86_64" machine="pc-q35-7.2">hvm</type>
<boot dev="hd"/>
</os>
@ -777,7 +781,8 @@ def _get_partition_info(image_file: pathlib.Path) -> tuple[str, str]:
return partition_table_type, last_partition_number
def _resize_disk_image(image_file: pathlib.Path, new_size: str):
def _resize_disk_image(image_file: pathlib.Path, new_size: str,
distribution: str):
"""Resize the disk image if has not already been."""
if new_size[-1] != 'G':
raise ValueError(f'Invalid size: {new_size}')
@ -820,10 +825,11 @@ def _resize_disk_image(image_file: pathlib.Path, new_size: str):
with tempfile.TemporaryDirectory(
dir=_get_work_directory().resolve()) as mount_point:
subprocess.run(['sudo', 'mount', partition, mount_point], check=True)
time.sleep(2) # Mount may trigger some re-balancing
subprocess.run(
['sudo', 'btrfs', 'filesystem', 'resize', 'max', mount_point],
check=True, stdout=subprocess.DEVNULL)
_update_fsid(mount_point, partition, old_fsid, new_fsid)
_update_fsid(mount_point, partition, old_fsid, new_fsid, distribution)
subprocess.run(['sudo', 'umount', mount_point], check=True)
subprocess.run(
@ -841,17 +847,26 @@ def _get_fsid(partition: str) -> str:
def _update_fsid(mount_point: str, partition: str, old_fsid: str,
new_fsid: str):
new_fsid: str, distribution: str):
"""After changing btrfs fsid, run grub-install to keep image bootable."""
is_efi = (platform.machine() in ('aarch64', 'arm64')
or distribution != 'oldstable')
# Guess the loopback device for the filesystem
matches = re.match(r'^/dev/mapper/loop(\d+)p\d+$', partition)
matches = re.match(r'^/dev/mapper/loop(\d+)p(\d+)$', partition)
assert matches
loop_device = f'/dev/loop{matches[1]}'
if is_efi:
efi_device = f'/dev/mapper/loop{matches[1]}p{int(matches[2]) - 1}'
# Mount /dev, /proc and run grub commands in chroot
grub_args = []
if platform.machine() in ('aarch64', 'arm64'):
grub_args += ['--no-nvram']
elif distribution != 'oldstable':
grub_args += [
'--target=x86_64-efi', '--no-nvram', '--uefi-secure-boot'
]
subprocess.run(
['sudo', 'mount', '-o', 'bind', '/dev/', f'{mount_point}/dev'],
@ -859,6 +874,11 @@ def _update_fsid(mount_point: str, partition: str, old_fsid: str,
subprocess.run(
['sudo', 'mount', '-o', 'bind', '/proc/', f'{mount_point}/proc'],
check=True)
if is_efi:
subprocess.run(
['sudo', 'mount', efi_device, f'{mount_point}/boot/efi'],
check=True)
subprocess.run(['sudo', 'chroot', mount_point, 'update-grub'], check=True)
subprocess.run(
['sudo', 'chroot', mount_point, 'grub-install', loop_device] +
@ -867,6 +887,10 @@ def _update_fsid(mount_point: str, partition: str, old_fsid: str,
'sudo', 'chroot', mount_point, 'sed', '-ie',
f's|UUID={old_fsid}|UUID={new_fsid}|', '/etc/fstab'
], check=True)
if is_efi:
subprocess.run(['sudo', 'umount', f'{mount_point}/boot/efi'],
check=True)
subprocess.run(['sudo', 'umount', f'{mount_point}/proc'], check=True)
subprocess.run(['sudo', 'umount', f'{mount_point}/dev'], check=True)
@ -903,11 +927,7 @@ def _setup_users(image_file: pathlib.Path):
str(gid), 'plinth'], stdout=subprocess.DEVNULL)
logger.info('In image: Setting up sudo for users "fbx" and "plinth"')
sudo_config = 'Cmnd_Alias FREEDOMBOX_ACTION_DEV = /usr/share/plinth/' \
'actions/actions, /freedombox/actions/actions\n' \
'Defaults!FREEDOMBOX_ACTION_DEV closefrom_override\n' \
'plinth ALL=(ALL:ALL) NOPASSWD:SETENV : FREEDOMBOX_ACTION_DEV\n' \
'fbx ALL=(ALL:ALL) NOPASSWD : ALL\n'
sudo_config = 'fbx ALL=(ALL:ALL) NOPASSWD : ALL\n'
_runc(image_file, ['tee', '/etc/sudoers.d/01-freedombox-development'],
input=sudo_config.encode(), stdout=subprocess.DEVNULL)
@ -1357,9 +1377,11 @@ class VM(Machine):
pass
qcow_image = self._create_qcow_image()
firmware = 'firmware="efi"' if self.distribution != 'oldstable' else ''
domain_xml = LIBVIRT_DOMAIN_XML_TEMPLATE.format(
domain_name=self.machine_name, memory_mib='2048', cpus='4',
image_file=qcow_image, source_dir=_get_project_folder())
firmware=firmware, image_file=qcow_image,
source_dir=_get_project_folder())
with tempfile.NamedTemporaryFile() as file_handle:
file_handle.write(domain_xml.encode())
@ -1390,7 +1412,7 @@ class VM(Machine):
logger.info('Running `virsh destroy %s`', self.machine_name)
try:
self._virsh(['undefine', self.machine_name],
self._virsh(['undefine', '--nvram', self.machine_name],
stdout=subprocess.DEVNULL)
except subprocess.CalledProcessError:
pass
@ -1399,7 +1421,7 @@ class VM(Machine):
"""Remove all traces of the VM from the host."""
logger.info('Running `virsh undefine %s`', self.machine_name)
try:
self._virsh(['undefine', self.machine_name],
self._virsh(['undefine', '--nvram', self.machine_name],
stdout=subprocess.DEVNULL)
except subprocess.CalledProcessError:
pass
@ -1470,7 +1492,8 @@ def subcommand_up(arguments: argparse.Namespace):
_verify_dependencies()
image_file = _download_disk_image(arguments.distribution,
arguments.hkp_client)
_resize_disk_image(image_file, arguments.image_size)
_resize_disk_image(image_file, arguments.image_size,
arguments.distribution)
_setup_image(image_file)
machine.setup()
machine.launch()

View File

@ -1,11 +1,3 @@
#
# Allow plinth user to run plinth action scripts with superuser privileges
# without needing a password.
#
Cmnd_Alias FREEDOMBOX_ACTION = /usr/share/plinth/actions/actions
Defaults!FREEDOMBOX_ACTION closefrom_override
plinth ALL=(ALL:ALL) NOPASSWD:FREEDOMBOX_ACTION
#
# On FreedomBox, allow all users in the 'admin' LDAP group to execute
# commands as root.

View File

@ -13,7 +13,8 @@ TimeoutSec=300s
User=root
Group=root
NotifyAccess=main
PrivateTmp=yes
# Uploaded files in /var/tmp/ are shared with FreedomBox web service.
#PrivateTmp=yes
Restart=on-failure
# Don't restart too fast
RestartSec=1

79
debian/changelog vendored
View File

@ -1,3 +1,82 @@
freedombox (25.11) unstable; urgency=medium
[ Coucouf ]
* Translated using Weblate (French)
[ Burak Yavuz ]
* Translated using Weblate (Turkish)
[ 大王叫我来巡山 ]
* Translated using Weblate (Chinese (Simplified Han script))
[ Максим Горпиніч ]
* Translated using Weblate (Ukrainian)
[ Sunil Mohan Adapa ]
* homeassistant: Fix typo in description
* HACKING.md: Mention using virtual machines instead of containers
* ui: Fix missing semicolon in JS file
* tests: functional: Fix incorrect skipping of install tests
* diagnostics: Fix notification severity when skipping tests
* container: Add support for Trixie as stable distribution
* d/control: Remove libpam-abl as a recommendation
* Makefile, settings: Use full paths in pot files
* container: Pass --nvram option to virsh undefine
* syncthing: Update Android clients to Syncthing-Fork
* web_server: Restart in development mode only for source code changes
* upgrades: Don't allow needs-restart to restart privileged daemon
* privileged: Don't isolate /var/tmp/ for privileged daemon
* email: Fix Thunderbird auto configuration failure
* action_utils: Implement a utility to run a command as different user
* gitweb: Use pathlib API more
* gitweb: Don't use privileged action feature to run as different user
* storage: Don't use privileged action feature to run as different user
* actions: Drop feature to run privileged action as another user
* actions: Simplify raw output code in privileged methods
* actions, backup: Implement raw output for privileged daemon
* privileged_daemon: Introduce a command line client for the API
* actions: Drop sudo based privileged actions
* actions: Framework for capturing stdout/stderr in privileged daemon
* actions, backups: Fix tests depending on sudo based actions
* privileged_daemon: Log only to journal and not console
* dynamicdns: Handle showing errors from GnuDIP
* upgrades: Overhaul detection of distribution
* package: Refresh apt cache if sources list is newer
* package: Don't consider uninstalled packages as available
* matrixsynapse, upgrades: Install select packages from unstable
[ Joseph Nuthalapati ]
* power: logind config to ignore laptop lid close
* backups: Trim translatable string
* l10n: Fix formatted strings for errors/exceptions
* l10n: Fix broken Italian translation
* l10n: Fix a broken string in Russian translation
[ ikmaak ]
* Translated using Weblate (Dutch)
[ Dietmar ]
* Translated using Weblate (German)
* Translated using Weblate (Italian)
[ Roman Akimov ]
* Translated using Weblate (Russian)
[ 109247019824 ]
* Translated using Weblate (Bulgarian)
[ Veiko Aasa ]
* samba: Update client list
[ Jiří Podhorecký ]
* Translated using Weblate (Czech)
[ James Valleroy ]
* locale: Update translation strings
* doc: Fetch latest manual
-- James Valleroy <jvalleroy@mailbox.org> Mon, 08 Sep 2025 20:27:54 -0400
freedombox (25.10~bpo13+1) trixie-backports; urgency=medium
* debian: Set the branch for trixie-backports

2
debian/control vendored
View File

@ -134,8 +134,6 @@ Recommends:
libnss-mdns,
# Resolve current hostname without /etc/hosts
libnss-myhostname,
# Block repeated failed PAM login attempts
libpam-abl,
# Priority: standard
locales,
# Precompiled data for all locales

View File

@ -14,13 +14,15 @@
=== What is User Websites? ===
User websites is a standard location for webservers to allow host users to expose static files on the filesystem as a website to the local network and/or the internet according to the network and firewall setup.
User websites is a feature that allows any (even non-admin) user on a !FreedomBox to host their own website simply by copying files to well known location in their home directory on the !FreedomBox server. The URL for the website will look like `https://mydomain.example/~myusername/`. The website will be available on the local network and/or the internet according to the network and firewall setup. If the copied files are HTML pages, they will show up as a website. If they are other types of files such as photos or documents. A list of those files is shown and a visitor will be able to view or download them.
The standard webserver in !FreedomBox is Apache and this is implemented by means of a specific Apache module.
Apache is the web server used in !FreedomBox and this feature is implemented using an Apache module.
=== Screenshot ===
/* Add when/if an interface is made for FreedomBox */
{{attachment:user-websites-folder.png|User Website copied to FreedomBox using GNOME File Browser}}
{{attachment:user-websites-browser.png|User Website accessed using a browser}}
=== Using User Websites ===
@ -30,9 +32,8 @@ To serve documents, place the files in the designated directory in a !FreedomBox
This directory is: '''public_html'''
Thus the absolute path for the directory of a user named fbx with home directory in /home/fbx will be '''/home/fbx/public_html'''.
User websites will serve documents placed in this directory when requests for documents with the URI path "~fbx" are received. For the the `example.org` domain thus a request for the document `example.org/~fbx/index.html` will transfer the file in `/home/fbx/public_html/index.html`.
Thus, the absolute path for the directory of a user named ''fbx'' with home directory in ''/home/fbx'' will be '''/home/fbx/public_html'''.
The User Websites feature will serve documents placed in this directory when requests for documents with the URI path ''~fbx'' are received. For example, if `mydomain.example` is your domain then a request for the URL `https://mydomain.example/~fbx/photo.jpg` will display the file in `/home/fbx/public_html/photo.jpg`. If a file named ''index.html'' is placed in the directory, it will shown when no file name is provided in the URL. So, the URL `https://mydomain.example/~fbx/` will show the HTML page `/home/fbx/public_html/index.html`
=== Creating public_html folder and uploading documents ===
@ -44,7 +45,7 @@ Linux standard desktop file managers use to support remote filesystem access thr
* Gnome's Nautilus:
1. To lauch Nautilus you can seek its archive icon, or search ether its name or the word "file".
1. At the bottom of the left pane you'll find an option "+ Other locations".
1. It leads you to a list of locations. Find "`freedombox SFTP server`" (english literal for all desktop languages). Click on it.
1. It leads you to a list of locations. Find "`freedombox SFTP server`" (english literal for all desktop languages). Click on it. If you don't find this, you can instead type `sftp://username@freedombox.local` in the address bar.
1. The first time you'll be asked for your user and password. Enter your !FreedomBox user and its password. The dialog will also offer you some options to remember it for some time.
* Plasma file manager AKA Dolphin:
1. Click on the location bar at the top of the window.

View File

@ -44,36 +44,11 @@ It is important to verify the images you have downloaded to ensure that the file
* First open a terminal and import the public keys of the !FreedomBox developers who built the images:
{{{
$ gpg --keyserver keyserver.ubuntu.com --recv-keys BCBEBD57A11F70B23782BC5736C361440C9BC971
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
# This is the FreedomBox CI server's key
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
# This is the new FreedomBox CI server's key
$ gpg --keyserver keyserver.ubuntu.com --recv-keys D4B069124FCF43AA1FCD7FBC2ACFC1E15AF82D8C
}}}
* Next, verify the fingerprint of the public keys:
{{{
$ gpg --fingerprint BCBEBD57A11F70B23782BC5736C361440C9BC971
pub 4096R/0C9BC971 2011-11-12
Key fingerprint = BCBE BD57 A11F 70B2 3782 BC57 36C3 6144 0C9B C971
uid Sunil Mohan Adapa <sunil@medhas.org>
sub 4096R/4C1D4B57 2011-11-12
$ gpg --fingerprint 7D6ADB750F91085589484BE677C0C75E7B650808
pub 4096R/7B650808 2015-06-07 [expires: 2020-06-05]
Key fingerprint = 7D6A DB75 0F91 0855 8948 4BE6 77C0 C75E 7B65 0808
uid James Valleroy <jvalleroy@mailbox.org>
uid James Valleroy <jvalleroy@freedombox.org>
sub 4096R/25D22BF4 2015-06-07 [expires: 2020-06-05]
sub 4096R/DDA11207 2015-07-03 [expires: 2020-07-01]
sub 2048R/2A624357 2015-12-22
$ gpg --fingerprint 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
pub rsa4096 2018-06-06 [SC]
013D 86D8 BA32 EAB4 A669 1BF8 5D41 53D6 FE18 8FC8
uid [ unknown] FreedomBox CI (Continuous Integration server) <admin@freedombox.org>
sub rsa4096 2018-06-06 [E]
$ gpg --fingerprint D4B069124FCF43AA1FCD7FBC2ACFC1E15AF82D8C
pub rsa4096 2022-03-09 [SC]
D4B0 6912 4FCF 43AA 1FCD 7FBC 2ACF C1E1 5AF8 2D8C
@ -250,6 +225,37 @@ dd if=temp/usr/lib/u-boot/A20-OLinuXino-Lime2/u-boot-sunxi-with-spl.bin of=<lime
The resulting image will have the modified u-boot in it.
=== Old Signing Keys ===
Some very old disk images of !FreedomBox have been signed by different GPG keys than the one listed above. Those signatures are still valid and can be verified using the older keys.
{{{
$ gpg --keyserver keyserver.ubuntu.com --recv-keys BCBEBD57A11F70B23782BC5736C361440C9BC971
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
# This is the FreedomBox CI server's key
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
$ gpg --fingerprint BCBEBD57A11F70B23782BC5736C361440C9BC971
pub 4096R/0C9BC971 2011-11-12
Key fingerprint = BCBE BD57 A11F 70B2 3782 BC57 36C3 6144 0C9B C971
uid Sunil Mohan Adapa <sunil@medhas.org>
sub 4096R/4C1D4B57 2011-11-12
$ gpg --fingerprint 7D6ADB750F91085589484BE677C0C75E7B650808
pub 4096R/7B650808 2015-06-07 [expires: 2020-06-05]
Key fingerprint = 7D6A DB75 0F91 0855 8948 4BE6 77C0 C75E 7B65 0808
uid James Valleroy <jvalleroy@mailbox.org>
uid James Valleroy <jvalleroy@freedombox.org>
sub 4096R/25D22BF4 2015-06-07 [expires: 2020-06-05]
sub 4096R/DDA11207 2015-07-03 [expires: 2020-07-01]
sub 2048R/2A624357 2015-12-22
$ gpg --fingerprint 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
pub rsa4096 2018-06-06 [SC]
013D 86D8 BA32 EAB4 A669 1BF8 5D41 53D6 FE18 8FC8
uid [ unknown] FreedomBox CI (Continuous Integration server) <admin@freedombox.org>
sub rsa4096 2018-06-06 [E]
}}}
## END_INCLUDE
<<Include(FreedomBox/Portal)>>

View File

@ -16,6 +16,12 @@ Dynamic DNS service providers assist in working around a problem. First they pr
For this to work, every time you connect to the Internet, you will have to tell your Dynamic DNS provider what your current IP address is. Hence you need special software on your server to perform this operation. The Dynamic DNS function in !FreedomBox will allow users without a static public IP address to push the current public IP address to a Dynamic DNS Server. This allows you to expose services on !FreedomBox, such as ownCloud, to the Internet.
=== A Free Domain for your FreedomBox ===
You can get a free domain name for your !FreedomBox using the free Dynamic DNS service provided by the !FreedomBox community at https://ddns.freedombox.org . With this service, your domain name will look like ''myaccount.fbx.one'' or ''myaccount.freedombox.rocks'' where ''myaccount'' is the account you created on the service.
The service also provides you with free unlimited number of subdomains. For this, login to the account, go to ''Change Settings'', check the option ''Wild Card'', and click ''Save Changes''. After this you can add any number of subdomains like ''foo.myaccount.fbx.one'' to your !FreedomBox in ''System -> Names -> Add Domain (regular)''. TLS certificates (for HTTPS) will be automatically obtained by !FreedomBox using Let's Encrypt for these subdomains. Subdomains are especially useful for applications that require being hosted on a separate domain, such as [[FreedomBox/Manual/HomeAssistant|Home Assistant]].
=== GnuDIP vs. Update URL ===
There are two main mechanism to notify the Dynamic DNS server of your new IP address; using the ''GnuDIP'' protocol and using the ''Update URL'' mechanism.
@ -27,7 +33,6 @@ On the other hand, the GnuDIP protocol will only transport a salted MD5 value of
=== Using the GnuDIP protocol ===
1. Register an account with any Dynamic DNS service provider. A free service provided by the !FreedomBox community is available at https://ddns.freedombox.org .
1. In !FreedomBox UI, enable the Dynamic DNS Service.
1. Select ''GnuDIP'' as ''Service type'', enter your Dynamic DNS service provider address (for example, ddns.freedombox.org) into ''GnuDIP Server Address'' field.

View File

@ -72,6 +72,7 @@ You might want to configure your wiki with different [[https://feather.wiki/?pag
=== External links ===
* Website: https://feather.wiki
* Source code for Feather Wiki: https://codeberg.org/Alamantus/FeatherWiki
## END_INCLUDE

View File

@ -0,0 +1,70 @@
#language en
##TAG:TRANSLATION-HEADER-START
~- [[FreedomBox/Manual/HomeAssistant|English]] - [[es/FreedomBox/Manual/HomeAssistant|Español]] - [[DebianWiki/EditorGuide#translation|(+)]]-~
##TAG:TRANSLATION-HEADER-END
<<TableOfContents()>>
## BEGIN_INCLUDE
== Home Assistant ==
||<tablestyle="float: right;"> {{attachment:homeassistant.png|Home Assistant icon}} ||
'''Available since''': version 25.10
Home Assistant is a home automation hub with emphasis on local control and privacy. It integrates with thousands of devices including smart bulbs, alarms, presence sensors, door bells, thermostats, irrigation timers, energy monitors, etc.
Please note that Home Assistant is installed and run inside a container provided by the Home Assistant project. Security, quality, privacy and legal reviews are done by the upstream project and not by Debian/FreedomBox. Updates are performed following an independent cycle.
Home Assistant app is considered experimental in !FreedomBox as it is fairly new to our ecosystem. You may face issues working with it or the app may undergo major breaking changes.
=== Hardware ===
Home Assistant can detect, configure, and use various devices on the local network. For example, if a device is connected using Wi-Fi or LAN to the same network as !FreedomBox, Home Assistant can detect, configure, and use the device. Other home automation protocols such as Thread, !ZigBee, and Z-Wave are also supported but require additional hardware to be connected to your !FreedomBox. For example, if you have a door sensor that speaks !ZigBee, you need to attach a !ZigBee USB dongle to your !FreedomBox. Home Assistant can then detect and use the door sensor on the !ZigBee network.
Home Assistant is a comprehensive solution for your home automation needs supporting thousands of devices. You can check whether a device is supported by Home Assistant by visiting the [[https://www.home-assistant.io/integrations/|integrations]] page. Other devices which are not listed may also be supported when they are similar to supported devices. It is recommended that you purchase hardware that advertises support for Home Assistant.
Many home automation devices connect to or send data to their manufacturers' servers. This can be a serious violation of your home's privacy. These devices may also stop working when their manufacturer goes bankrupt, decides to close the product line, or forces you to purchase newer models. It is strongly recommended that you purchase hardware that works locally with a home automation hub (in this case Home Assistant) without connecting to manufacturer's servers. These are listed under the categories "Local Push" or "Local Polling" in the Home Assistant's integrations page.
=== Setup ===
After the app is installed, Home Assistant web interface must be setup. An administrator account is created at this time. Home Assistant maintains its own user accounts separate from user accounts created on !FreedomBox.
{{attachment:homeassistant-setup-step-1.png|Setup - Step 1}}
{{attachment:homeassistant-setup-step-2.png|Setup - Step 2}}
{{attachment:homeassistant-setup-step-3.png|Setup - Step 3}}
{{attachment:homeassistant-setup-step-4.png|Setup - Step 4}}
{{attachment:homeassistant-setup-step-5.png|Setup - Step 5}}
When new USB dongles are added to make Home Assistant talk to devices on networks like Thread, !ZigBee, and Z-Wave, then setup operation must be re-run on the app. This operation is available in the Extras menu drop down with gears icon in the Home Assistant app page. Setup must also be re-run when the hardware is removed. Otherwise, Home Assistant service will fail to start.
=== Usage ===
Home Assistant provides a fully functional web application and a mobile application. Simply login and start using:
{{attachment:homeassistant-login.png|Login}}
{{attachment:homeassistant-ui.png|Web UI}}
{{attachment:homeassistant-app.png|Mobile App}}
=== External links ===
* Website: https://www.home-assistant.io/
* Source code for Home Assistant: https://github.com/home-assistant/core
* Source code for Home Assistant container image: https://github.com/home-assistant/docker
## END_INCLUDE
Back to [[FreedomBox/Features|Features introduction]] or [[FreedomBox/Manual|manual]] pages.
<<Include(FreedomBox/Portal)>>
----
CategoryFreedomBox

View File

@ -39,7 +39,8 @@ If you are a Nextcloud user share how this is done.
* Upstream Project: https://nextcloud.com/
* Upstream documentation: https://nextcloud.com/support/
* Upstream support forum: https://help.nextcloud.com/
* The source code for the container image used by !FreedomBox: https://github.com/nextcloud/docker
* Source code for Nextcloud: https://download.nextcloud.com/server/releases/
* Source code for the container image: https://github.com/nextcloud/docker (includes links to source code for other components)
* Debian Nextcloud wiki: https://wiki.debian.org/Nextcloud
{{{#!wiki comment

View File

@ -8,6 +8,52 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
The following are the release notes for each !FreedomBox version.
== FreedomBox 25.11 (2025-09-08) ==
=== Highlights ===
* email: Fix Thunderbird auto configuration failure
* matrixsynapse, upgrades: Install select packages from unstable
=== Other Changes ===
* action_utils: Implement a utility to run a command as different user
* actions, backup: Implement raw output for privileged daemon
* actions, backups: Fix tests depending on sudo based actions
* actions: Drop feature to run privileged action as another user
* actions: Drop sudo based privileged actions
* actions: Framework for capturing stdout/stderr in privileged daemon
* actions: Simplify raw output code in privileged methods
* backups: Trim translatable string
* container: Add support for Trixie as stable distribution
* container: Pass --nvram option to virsh undefine
* d/control: Remove libpam-abl as a recommendation
* diagnostics: Fix notification severity when skipping tests
* dynamicdns: Handle showing errors from GnuDIP
* gitweb: Don't use privileged action feature to run as different user
* gitweb: Use pathlib API more
* HACKING.md: Mention using virtual machines instead of containers
* homeassistant: Fix typo in description
* l10n: Fix a broken string in Russian translation
* l10n: Fix broken Italian translation
* l10n: Fix formatted strings for errors/exceptions
* locale: Update translations for Bulgarian, Chinese (Simplified Han script), Czech, Dutch, French, German, Italian, Russian, Turkish, Ukrainian
* Makefile, settings: Use full paths in pot files
* package: Don't consider uninstalled packages as available
* package: Refresh apt cache if sources list is newer
* power: logind config to ignore laptop lid close
* privileged: Don't isolate /var/tmp/ for privileged daemon
* privileged_daemon: Introduce a command line client for the API
* privileged_daemon: Log only to journal and not console
* samba: Update client list
* storage: Don't use privileged action feature to run as different user
* syncthing: Update Android clients to Syncthing-Fork
* tests: functional: Fix incorrect skipping of install tests
* ui: Fix missing semicolon in JS file
* upgrades: Don't allow needs-restart to restart privileged daemon
* upgrades: Overhaul detection of distribution
* web_server: Restart in development mode only for source code changes
== FreedomBox 25.10 (2025-08-18) ==
=== Highlights ===

View File

@ -71,6 +71,7 @@ You can also have a custom image as the favicon for each !TiddlyWiki. Using a di
* Website: https://tiddlywiki.com
* Grok !TiddlyWiki (online e-book): https://groktiddlywiki.com/read/
* Source code for TiddlyWiki: https://github.com/TiddlyWiki/TiddlyWiki5
## END_INCLUDE

View File

@ -20,7 +20,9 @@ Note that once the updates start, it may take a long time to complete. During au
Although updates are done every day for security reasons, latest features of !FreedomBox will not propagate to all the users. The following information should help you understand how new features become available to users.
'''Stable Users''': This category of users include users who bought the [[FreedomBox/Hardware/PioneerEdition|FreedomBox Pioneer Edition]], installed !FreedomBox on a [[FreedomBox/Hardware/Debian|Debian]] stable distribution or users who downloaded the ''stable'' images from [[https://freedombox.org|freedombox.org]]. As a general rule, only security updates to various packages are provided to these users. One exception to this rule is where !FreedomBox service itself is updated when a release gains high confidence from developers. This means that latest !FreedomBox features may become available to these users although after a few days of delay compared to ''testing'' users. If an app is available only in ''testing'' distribution but not in ''stable'' distribution, then that app will show up in the web interface but will not be installable by ''stable'' users. Some apps are also provided an exception to the rule of "security updates only" when the app is severely broken otherwise. Every two years, a major release of Debian stable happens with the latest versions of all the software packages and !FreedomBox developers will attempt to upgrade these users to the new release without requiring manual intervention. See the sections on upgrade to next stable release below.
'''Stable Users''': This category of users include users who bought the [[FreedomBox/Hardware/PioneerEdition|FreedomBox Pioneer Edition]], installed !FreedomBox on a [[FreedomBox/Hardware/Debian|Debian]] stable distribution or users who downloaded the ''stable'' images from [[https://freedombox.org|freedombox.org]]. If an app is available only in ''testing'' distribution but not in ''stable'' distribution, then that app will show up in the web interface but will not be installable by ''stable'' users. Every two years, a major release of Debian stable happens with the latest versions of all the software packages and !FreedomBox developers will attempt to upgrade these users to the new release without requiring manual intervention. See the sections on upgrade to next stable release below.
As a general rule, only security updates to various packages are provided to these users. One exception to this rule is when ''frequent feature updates'' (recommended) option is enabled. When this option is enabled, !FreedomBox service itself is updated when a release gains high confidence from developers. This means that latest !FreedomBox features may become available to these users (after a few days of delay compared to ''testing'' users). Some apps (currently only Matrix Synapse) are also provided as an exception to the rule of "security updates only" when the app is severely broken or unavailable otherwise. Technically, when this option is enabled, after thorough testing, a select list of packages may be installed from ''backports'' or ''unstable'' repositories of Debian.
'''Testing Users''': This category of users include users who installed !FreedomBox on a [[FreedomBox/Hardware/Debian|Debian]] ''testing'' distribution or users who downloaded the ''testing'' images from [[https://freedombox.org|freedombox.org]]. Users who use Debian ''testing'' are likely to face occasional disruption in the services and may even need manual intervention to fix the issue. As a general rule, these users receive all the latest features and security updates to all the installed packages. Every two weeks, a new version of !FreedomBox is released with all the latest features and fixes. These releases will reach ''testing'' users approximately 2-3 days after the release.

View File

@ -24,6 +24,7 @@
<<Include(FreedomBox/Manual/Email, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/FeatherWiki, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/GitWeb, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/HomeAssistant, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/Ikiwiki, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/Infinoted, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/Janus, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -41,35 +41,11 @@ Es importante verificar las imágenes que has descargado para asegurar que el fi
* Primero abre un terminal e importa las claves publicas de los desarrolladores de !FreedomBox que construyeron las imágenes:
{{{
$ gpg --keyserver keyserver.ubuntu.com --recv-keys BCBEBD57A11F70B23782BC5736C361440C9BC971
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
# Esta es la clave del servidor de integración contínua de FreedomBox
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
# Esta es la clave del uevo servidor de integración contínua de FreedomBox
# Esta es la clave del nuevo servidor de integración contínua de FreedomBox
$ gpg --keyserver keyserver.ubuntu.com --recv-keys D4B069124FCF43AA1FCD7FBC2ACFC1E15AF82D8C
}}}
* A continuación, verifica la huella de las claves públicas:
{{{
$ gpg --fingerprint BCBEBD57A11F70B23782BC5736C361440C9BC971
pub 4096R/0C9BC971 2011-11-12
Key fingerprint = BCBE BD57 A11F 70B2 3782 BC57 36C3 6144 0C9B C971
uid Sunil Mohan Adapa <sunil@medhas.org>
sub 4096R/4C1D4B57 2011-11-12
$ gpg --fingerprint 7D6ADB750F91085589484BE677C0C75E7B650808
pub 4096R/7B650808 2015-06-07 [expires: 2020-06-05]
Key fingerprint = 7D6A DB75 0F91 0855 8948 4BE6 77C0 C75E 7B65 0808
uid James Valleroy <jvalleroy@mailbox.org>
uid James Valleroy <jvalleroy@freedombox.org>
sub 4096R/25D22BF4 2015-06-07 [expires: 2020-06-05]
sub 4096R/DDA11207 2015-07-03 [expires: 2020-07-01]
sub 2048R/2A624357 2015-12-22
$ gpg --fingerprint 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
pub rsa4096 2018-06-06 [SC]
013D 86D8 BA32 EAB4 A669 1BF8 5D41 53D6 FE18 8FC8
uid [ unknown] FreedomBox CI (Continuous Integration server) <admin@freedombox.org>
sub rsa4096 2018-06-06 [E]
$ gpg --fingerprint D4B069124FCF43AA1FCD7FBC2ACFC1E15AF82D8C
pub rsa4096 2022-03-09 [SC]
@ -202,7 +178,7 @@ cd <directorio_fuente>
1. El código fuente de cualquier paquete se puede ver y buscar usando el interfaz web de [[https://sources.debian.org/|sources.debian.org]]. Por ejemplo, mira el paquete [[https://sources.debian.org/src/plinth/|plinth]].
1. El código fuente y el binario precompilado de cualquier version de un paquete, incluyendo versiones antigüas, se pueden obtener de [[https://snapshot.debian.org/|snapshot.debian.org]]. Por ejemplo, mira el paquete [[https://snapshot.debian.org/package/plinth/|plinth]].
1. El código fuente y el binario precompilado de cualquier version de un paquete, incluyendo versiones antiguas, se pueden obtener de [[https://snapshot.debian.org/|snapshot.debian.org]]. Por ejemplo, mira el paquete [[https://snapshot.debian.org/package/plinth/|plinth]].
1. También puedes obtener los enlaces a la web del proyecto original, al control de versiones del proyecto original, al control de versiones de Debian, registro de cambios, etc. desde la página de control Debian para el proyecto en [[https://tracker.debian.org/|tracker.debian.org]]. Por ejemplo, mira la página de control para el paquete [[https://tracker.debian.org/pkg/plinth|plinth]].
@ -244,6 +220,37 @@ dd if=temp/usr/lib/u-boot/A20-OLinuXino-Lime2/u-boot-sunxi-with-spl.bin of=<lime
La imagen resultante tendrá el u-boot modificado.
=== Claves de firma antiguas ===
Algunas imágenes de disco de !FreedomBox muy antiguas se firmaron con claves GPG diferentes a las listadas anteriormente. Esas firmas siguen valiendo y se pueden verificar empleando las claves antiguas.
{{{
$ gpg --keyserver keyserver.ubuntu.com --recv-keys BCBEBD57A11F70B23782BC5736C361440C9BC971
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
# Esta es la clave del servidor de integración contínua de FreedomBox
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
$ gpg --fingerprint BCBEBD57A11F70B23782BC5736C361440C9BC971
pub 4096R/0C9BC971 2011-11-12
Key fingerprint = BCBE BD57 A11F 70B2 3782 BC57 36C3 6144 0C9B C971
uid Sunil Mohan Adapa <sunil@medhas.org>
sub 4096R/4C1D4B57 2011-11-12
$ gpg --fingerprint 7D6ADB750F91085589484BE677C0C75E7B650808
pub 4096R/7B650808 2015-06-07 [expires: 2020-06-05]
Key fingerprint = 7D6A DB75 0F91 0855 8948 4BE6 77C0 C75E 7B65 0808
uid James Valleroy <jvalleroy@mailbox.org>
uid James Valleroy <jvalleroy@freedombox.org>
sub 4096R/25D22BF4 2015-06-07 [expires: 2020-06-05]
sub 4096R/DDA11207 2015-07-03 [expires: 2020-07-01]
sub 2048R/2A624357 2015-12-22
$ gpg --fingerprint 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8
pub rsa4096 2018-06-06 [SC]
013D 86D8 BA32 EAB4 A669 1BF8 5D41 53D6 FE18 8FC8
uid [ unknown] FreedomBox CI (Continuous Integration server) <admin@freedombox.org>
sub rsa4096 2018-06-06 [E]
}}}
## END_INCLUDE

View File

@ -0,0 +1,69 @@
#language es
<<Include(FreedomBox/Manual/HomeAssistant, ,from="^##TAG:TRANSLATION-HEADER-START",to="^##TAG:TRANSLATION-HEADER-END")>>
<<TableOfContents()>>
## BEGIN_INCLUDE
== Home Assistant ==
||<tablestyle="float: right;"> {{attachment:FreedomBox/Manual/HomeAssistant/homeassistant.png|icono de Home Assistant}} ||
'''Disponible desde''': versión 25.10
Home Assistant es un centro de automatización del hogar con énfasis en control local y privacidad. Se integra con centenas de dispositivos inteligentes, incluyendo bombillas, alarmas, sensores de presencia, timbres, termostatos, temporizadores de riego, monitores de energía, etc.
Observa que Home Assistant se instala y ejecuta dentro de un contenedor proporcionado por el proyecto Home Assistant. Las revisiones de seguridad, calidad, privacidad y legislación las hace el proyecto en origen, ni Debian ni FreedomBox. Las actualizaciones también siguen un ciclo independiente.
La app Home Assistant se considera experimental en !FreedomBox, ya que es nueva en nuestro ecosistema. Podrías experimentar problemas y la propia app podría sufrir cambios importantes o disruptivos.
=== Hardware ===
Home Assistant puede detectar, configurar, y usar varios dispositivos de la red local. Por ejemplo, si un dispositivo se conecta mediante Wi-Fi o LAN a la misma red que !FreedomBox, Home Assistant puede detectarlo, configurarlo, y usarlo. También se soportan otros protocolos de automatización como Thread, !ZigBee, y Z-Wave, pero requieren hardware adicional para conectarlos a tu !FreedomBox. Por ejemplo, si tienes un sensor de puerta que habla !ZigBee, necesitas conectar un adaptador !ZigBee USB a tu !FreedomBox. Home Assistant podrá entonces detectar y usar el sensor de puerta en la red de !ZigBee.
Home Assistant es una solución completa si tu automatización del hogar necesita soportar miles de dispositivos. Puedes consultar si Home Assistant soporta un dispositivo visitando su página de [[https://www.home-assistant.io/integrations/|integraciones]]. Otros dispositivos similares a los soportados podrían estarlo aunque no figuren en la lista. Se recomienda comprar hardware etiquetado como compatible con Home Assistant.
Muchos dispositivos de automatización del hogar se conectan a los servidores de los fabricantes y les envían tus datos. Esto pserious ede suponer una violación muy seria de la privacidad de tu hogar. Estos dispositivos podrían también dejar de funcionar si su fabricante quebrara, decidiera descontinuar la línea de producto, o te forzara a comprar modelos más modernos. Se recomienda decididamente que compres hardware que funcione localmente con un centro de automatización del hogar (en este caso Home Assistant) sin conectarse a los servidores del fabricante. En la página de [[https://www.home-assistant.io/integrations/|integraciones]] de Home Assistant, estos dispositivos aparecen listados en las categorías "Local Push" o "Local Polling".
=== Arranque ===
Tras instalar la app hay que levantar el interfaz web de Home Assistant. Con ello se crea una cuenta de administrador. Home Assistant mantiene su propias cuentas de usuario diferentes de las de la !FreedomBox.
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-setup-step-1.png|Setup - Step 1}}
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-setup-step-2.png|Setup - Step 2}}
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-setup-step-3.png|Setup - Step 3}}
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-setup-step-4.png|Setup - Step 4}}
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-setup-step-5.png|Setup - Step 5}}
Al añadir los adaptadores USB para que Home Assistant hable con los dispositivos de redes como Thread, !ZigBee, hay que reejecutar la operación en la app. Esta operación está en el menú desplegable Extras con un icono de tuercas en la página de la app de Home Assistant. También hay que reiniciar al quitar hardware. Si no, Home Assistant fallará al iniciarse.
=== Uso ===
Home Assistant proporciona unas aplicaciones para web y movil totalmente functionales. Simplemente ingresa:
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-login.png|Login}}
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-ui.png|Web UI}}
{{attachment:FreedomBox/Manual/HomeAssistant/homeassistant-app.png|Mobile App}}
=== Enlaces externos ===
* Sitio web: https://www.home-assistant.io
* Código fuente de Home Assistant: https://github.com/home-assistant/core
* Código fuente de la imágen del contenedor Home: https://github.com/home-assistant/docker
## END_INCLUDE
Volver a la [[es/FreedomBox/Features|descripción de Funcionalidades]] o a las páginas del [[es/FreedomBox/Manual|manual]].
<<Include(es/FreedomBox/Portal)>>
----
CategoryFreedomBox

View File

@ -24,6 +24,7 @@
<<Include(FreedomBox/Manual/Email, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/FeatherWiki, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/GitWeb, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/HomeAssistant, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/Ikiwiki, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/Infinoted, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(FreedomBox/Manual/Janus, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>

View File

@ -8,6 +8,52 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
The following are the release notes for each !FreedomBox version.
== FreedomBox 25.11 (2025-09-08) ==
=== Highlights ===
* email: Fix Thunderbird auto configuration failure
* matrixsynapse, upgrades: Install select packages from unstable
=== Other Changes ===
* action_utils: Implement a utility to run a command as different user
* actions, backup: Implement raw output for privileged daemon
* actions, backups: Fix tests depending on sudo based actions
* actions: Drop feature to run privileged action as another user
* actions: Drop sudo based privileged actions
* actions: Framework for capturing stdout/stderr in privileged daemon
* actions: Simplify raw output code in privileged methods
* backups: Trim translatable string
* container: Add support for Trixie as stable distribution
* container: Pass --nvram option to virsh undefine
* d/control: Remove libpam-abl as a recommendation
* diagnostics: Fix notification severity when skipping tests
* dynamicdns: Handle showing errors from GnuDIP
* gitweb: Don't use privileged action feature to run as different user
* gitweb: Use pathlib API more
* HACKING.md: Mention using virtual machines instead of containers
* homeassistant: Fix typo in description
* l10n: Fix a broken string in Russian translation
* l10n: Fix broken Italian translation
* l10n: Fix formatted strings for errors/exceptions
* locale: Update translations for Bulgarian, Chinese (Simplified Han script), Czech, Dutch, French, German, Italian, Russian, Turkish, Ukrainian
* Makefile, settings: Use full paths in pot files
* package: Don't consider uninstalled packages as available
* package: Refresh apt cache if sources list is newer
* power: logind config to ignore laptop lid close
* privileged: Don't isolate /var/tmp/ for privileged daemon
* privileged_daemon: Introduce a command line client for the API
* privileged_daemon: Log only to journal and not console
* samba: Update client list
* storage: Don't use privileged action feature to run as different user
* syncthing: Update Android clients to Syncthing-Fork
* tests: functional: Fix incorrect skipping of install tests
* ui: Fix missing semicolon in JS file
* upgrades: Don't allow needs-restart to restart privileged daemon
* upgrades: Overhaul detection of distribution
* web_server: Restart in development mode only for source code changes
== FreedomBox 25.10 (2025-08-18) ==
=== Highlights ===

View File

@ -22,6 +22,7 @@
<<Include(es/FreedomBox/Manual/ejabberd, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(es/FreedomBox/Manual/Email, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(es/FreedomBox/Manual/GitWeb, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(es/FreedomBox/Manual/HomeAssistant, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(es/FreedomBox/Manual/Ikiwiki, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(es/FreedomBox/Manual/Infinoted, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>
<<Include(es/FreedomBox/Manual/Janus, , from="## BEGIN_INCLUDE", to="## END_INCLUDE")>>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -3,4 +3,4 @@
Package init file.
"""
__version__ = '25.10'
__version__ = '25.11'

View File

@ -13,6 +13,8 @@ from contextlib import contextmanager
import augeas
from . import actions
logger = logging.getLogger(__name__)
UWSGI_ENABLED_PATH = '/etc/uwsgi/apps-enabled/{config_name}.ini'
@ -788,3 +790,34 @@ def move_uploaded_file(source: str | pathlib.Path,
shutil.move(source, destination)
shutil.chown(destination, user, group)
destination.chmod(permissions)
def run_as_user(command, username, **kwargs):
"""Run a command as another user.
Uses 'runuser' which is similar to 'su'. Creates PAM session unlike
setpriv. Sets real/effective uid/gid and resets the environment.
"""
command = ['runuser', '--user', username, '--'] + command
return run(command, **kwargs)
def run(command, **kwargs):
"""Run subprocess.run but capture stdout and stderr in thread storage."""
collect_stdout = ('stdout' not in kwargs)
collect_stderr = ('stderr' not in kwargs)
if collect_stdout:
kwargs['stdout'] = subprocess.PIPE
if collect_stderr:
kwargs['stderr'] = subprocess.PIPE
process = subprocess.run(command, **kwargs)
if collect_stdout and actions.thread_storage:
actions.thread_storage.stdout += process.stdout
if collect_stderr and actions.thread_storage:
actions.thread_storage.stderr += process.stderr
return process

View File

@ -1,31 +1,27 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Framework to run specified actions with elevated privileges."""
import argparse
import functools
import importlib
import inspect
import io
import json
import logging
import os
import pathlib
import socket
import subprocess
import sys
import threading
import traceback
import types
import typing
from plinth import cfg, log, module_loader
EXIT_SYNTAX = 10
EXIT_PERM = 20
from plinth import cfg, module_loader
logger = logging.getLogger(__name__)
socket_path = '/run/freedombox/privileged.socket'
thread_storage = None
# An alias for 'str' to mark some strings as sensitive. Sensitive strings are
# not logged. Use 'type secret_str = str' when Python 3.11 support is no longer
@ -71,29 +67,17 @@ def privileged(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if getattr(func, '_skip_privileged_call', False):
return func(*args, **kwargs)
module_name = _get_privileged_action_module_name(func)
action_name = func.__name__
return _run_privileged_method(func, module_name, action_name, args,
kwargs)
return run_privileged_method(func, module_name, action_name, args,
kwargs)
return wrapper
def _run_privileged_method(func, module_name, action_name, args, kwargs):
"""Execute a privileged method either using a server or sudo."""
try:
return _run_privileged_method_on_server(func, module_name, action_name,
list(args), dict(kwargs))
except (
NotImplementedError, # For raw_output and run_as_user flags
FileNotFoundError, # When the .socket file is not present
ConnectionRefusedError, # When is daemon not running
ConnectionResetError # When daemon fails permission check
):
return _run_privileged_method_as_process(func, module_name,
action_name, args, kwargs)
def _read_from_server(client_socket: socket.socket) -> bytes:
"""Read everything from a socket and return the data."""
response = b''
@ -124,28 +108,41 @@ def _request_to_server(request: dict) -> socket.socket:
return client_socket
def _run_privileged_method_on_server(func, module_name, action_name, args,
kwargs):
def run_privileged_method(func, module_name, action_name, args, kwargs):
"""Execute a privileged method using a server."""
run_as_user = kwargs.pop('_run_as_user', None)
run_in_background = kwargs.pop('_run_in_background', False)
raw_output = kwargs.pop('_raw_output', False)
log_error = kwargs.pop('_log_error', True)
if raw_output or run_as_user:
raise NotImplementedError('Not yet implemented')
_log_action(func, module_name, action_name, args, kwargs, run_as_user,
run_in_background, is_server=True)
if func:
_log_action(func, module_name, action_name, args, kwargs,
run_in_background)
request = {
'module': module_name,
'action': action_name,
'args': args,
'kwargs': kwargs
'kwargs': kwargs,
}
if raw_output:
request['raw_output'] = raw_output
client_socket = _request_to_server(request)
if raw_output:
def _reader_func():
while True:
chunk = client_socket.recv(4096)
if chunk:
yield chunk
else:
break
client_socket.close()
return _reader_func()
args = (func, module_name, action_name, args, kwargs, log_error,
client_socket)
if not run_in_background:
@ -173,8 +170,8 @@ def _wait_for_server_response(func, module_name, action_name, args, kwargs,
module = importlib.import_module(return_value['exception']['module'])
exception_class = getattr(module, return_value['exception']['name'])
exception = exception_class(*return_value['exception']['args'])
exception.stdout = b''
exception.stderr = b''
exception.stdout = return_value['exception']['stdout'].encode()
exception.stderr = return_value['exception']['stderr'].encode()
def _get_html_message():
"""Return an HTML format error that can be shown in messages."""
@ -188,9 +185,10 @@ def _wait_for_server_response(func, module_name, action_name, args, kwargs,
return_value['exception']['name'], exception_args,
stdout, stderr, traceback)
exception.get_html_message = _get_html_message
if func:
exception.get_html_message = _get_html_message
if log_error:
if log_error and func:
formatted_args = _format_args(func, args, kwargs)
exception_args, stdout, stderr, traceback = _format_error(
exception, return_value)
@ -202,119 +200,49 @@ def _wait_for_server_response(func, module_name, action_name, args, kwargs,
raise exception
def _run_privileged_method_as_process(func, module_name, action_name, args,
kwargs):
"""Execute the privileged method in a sub-process with sudo."""
run_as_user = kwargs.pop('_run_as_user', None)
run_in_background = kwargs.pop('_run_in_background', False)
raw_output = kwargs.pop('_raw_output', False)
log_error = kwargs.pop('_log_error', True)
class ProcessBufferedReader(io.BufferedReader):
"""Improve performance of buffered binary streaming.
read_fd, write_fd = os.pipe()
os.set_inheritable(write_fd, True)
Read from the stdout of a process and close the process after reading is
completed.
# Prepare the command
command = ['sudo', '--non-interactive', '--close-from', str(write_fd + 1)]
if run_as_user:
command += ['--user', run_as_user]
Iteration calls __next__ over the BufferedReader holding process.stdout.
However, this seems to call readline() which looks for \n in binary data
which leads to short unpredictably sized chunks which in turn lead to
severe performance degradation. So, overwrite this and call read() which is
better geared for handling binary data.
"""
if cfg.develop:
command += [f'PYTHONPATH={cfg.file_root}']
def __init__(self, process, extra_cleanup_func=None, *args, **kwargs):
"""Store the process object."""
super().__init__(process.stdout, *args, **kwargs)
self.process = process
self.extra_cleanup_func = extra_cleanup_func
command += [
os.path.join(cfg.actions_dir, 'actions'), module_name, action_name,
'--write-fd',
str(write_fd)
]
def __next__(self):
"""Override to call read() instead of readline()."""
chunk = self.read(io.DEFAULT_BUFFER_SIZE)
if not chunk:
self._cleanup_func()
proc_kwargs = {
'stdin': subprocess.PIPE,
'stdout': subprocess.PIPE,
'stderr': subprocess.PIPE,
'shell': False,
'pass_fds': [write_fd],
}
if cfg.develop:
# In development mode pass on local pythonpath to access Plinth
proc_kwargs['env'] = {'PYTHONPATH': cfg.file_root}
raise StopIteration
_log_action(func, module_name, action_name, args, kwargs, run_as_user,
run_in_background, is_server=False)
return chunk
proc = subprocess.Popen(command, **proc_kwargs)
os.close(write_fd)
def _cleanup_func(self):
"""After the process has been read from, cleanup the process."""
try:
if self.process.stdout:
self.process.stdout.close()
if raw_output:
input_ = json.dumps({'args': args, 'kwargs': kwargs}).encode()
return proc, read_fd, input_
if self.process.stderr:
self.process.stderr.close()
buffers = []
# XXX: Use async to avoid creating a thread.
read_thread = threading.Thread(target=_thread_reader,
args=(read_fd, buffers))
read_thread.start()
wait_args = (func, module_name, action_name, args, kwargs, log_error, proc,
command, read_fd, read_thread, buffers)
if not run_in_background:
return _wait_for_return(*wait_args)
wait_thread = threading.Thread(target=_wait_for_return, args=wait_args)
wait_thread.start()
def _wait_for_return(func, module_name, action_name, args, kwargs, log_error,
proc, command, read_fd, read_thread, buffers):
"""Communicate with the subprocess and wait for its return."""
json_args = json.dumps({'args': args, 'kwargs': kwargs})
stdout, stderr = proc.communicate(input=json_args.encode())
read_thread.join()
if proc.returncode != 0:
logger.error('Error executing command - %s, %s, %s', command, stdout,
stderr)
raise subprocess.CalledProcessError(proc.returncode, command)
try:
return_value = json.loads(b''.join(buffers))
except json.JSONDecodeError:
logger.error('Error decoding action return value %s..%s(*%s, **%s)',
module_name, action_name, args, kwargs)
raise
if return_value['result'] == 'success':
return return_value['return']
module = importlib.import_module(return_value['exception']['module'])
exception_class = getattr(module, return_value['exception']['name'])
exception = exception_class(*return_value['exception']['args'])
exception.stdout = stdout
exception.stderr = stderr
def _get_html_message():
"""Return an HTML format error that can be shown in messages."""
from django.utils.html import format_html
formatted_args = _format_args(func, args, kwargs)
exception_args, stdout, stderr, traceback = _format_error(
exception, return_value)
return format_html('Error running action: {}..{}({}): {}({})\n{}{}{}',
module_name, action_name, formatted_args,
return_value['exception']['name'], exception_args,
stdout, stderr, traceback)
exception.get_html_message = _get_html_message
if log_error:
formatted_args = _format_args(func, args, kwargs)
exception_args, stdout, stderr, traceback = _format_error(
exception, return_value)
logger.error('Error running action %s..%s(%s): %s(%s)\n'
'%s%s%s', module_name, action_name, formatted_args,
return_value['exception']['name'], exception_args, stdout,
stderr, traceback)
raise exception
self.process.wait(30)
if self.extra_cleanup_func:
self.extra_cleanup_func()
except Exception:
logger.exception('Closing process failed after raw output')
def _format_args(func, args, kwargs):
@ -376,18 +304,6 @@ def _format_error(exception, return_value):
return (exception_args, stdout, stderr, traceback)
def _thread_reader(read_fd, buffers):
"""Read from the pipe in a separate thread."""
while True:
buffer = os.read(read_fd, 10240)
if not buffer:
break
buffers.append(buffer)
os.close(read_fd)
def _check_privileged_action_arguments(func):
"""Check that a privileged action has well defined types."""
argspec = inspect.getfullargspec(func)
@ -422,14 +338,10 @@ def _get_privileged_action_module_name(func):
return module_name.rpartition('.')[2]
def _log_action(func, module_name, action_name, args, kwargs, run_as_user,
run_in_background, is_server):
def _log_action(func, module_name, action_name, args, kwargs,
run_in_background):
"""Log an action in a compact format."""
if is_server:
prompt = '»'
else:
prompt = f'({run_as_user})$' if run_as_user else '#'
prompt = '»'
suffix = '&' if run_in_background else ''
formatted_args = _format_args(func, args, kwargs)
logger.info('%s %s..%s(%s) %s', prompt, module_name, action_name,
@ -449,47 +361,49 @@ class JSONEncoder(json.JSONEncoder):
return super().default(obj)
def privileged_main():
"""Parse arguments for the program spawned as a privileged action."""
log.action_init()
parser = argparse.ArgumentParser()
parser.add_argument('module', help='Module to trigger action in')
parser.add_argument('action', help='Action to trigger in module')
parser.add_argument('--write-fd', type=int, default=1,
help='File descriptor to write output to')
parser.add_argument('--no-args', default=False, action='store_true',
help='Do not read arguments from stdin')
args = parser.parse_args()
try:
try:
arguments = {'args': [], 'kwargs': {}}
if not args.no_args:
input_ = sys.stdin.read()
if input_:
arguments = json.loads(input_)
except json.JSONDecodeError as exception:
raise SyntaxError('Arguments on stdin not JSON.') from exception
return_value = _privileged_call(args.module, args.action, arguments)
with os.fdopen(args.write_fd, 'w') as write_file_handle:
write_file_handle.write(json.dumps(return_value, cls=JSONEncoder))
except PermissionError as exception:
logger.error(exception.args[0])
sys.exit(EXIT_PERM)
except SyntaxError as exception:
logger.error(exception.args[0])
sys.exit(EXIT_SYNTAX)
except TypeError as exception:
logger.error(exception.args[0])
sys.exit(EXIT_SYNTAX)
except Exception as exception:
logger.exception(exception)
sys.exit(1)
def _setup_thread_storage():
"""Setup collection of stdout/stderr from any process in this thread."""
global thread_storage
thread_storage = threading.local()
thread_storage.stdout = b''
thread_storage.stderr = b''
def privileged_handle_json_request(request_string: str) -> str:
def _clear_thread_storage():
"""Cleanup memory used for stdout/stderr from processes in this thread.
Python documentation is silent on whether thread local storage will be
cleaned up after a thread terminates.
"""
global thread_storage
if thread_storage:
thread_storage.stdout = None
thread_storage.stderr = None
thread_storage = None
def get_return_value_from_exception(exception):
"""Return the value to return from server when an exception is raised."""
return_value = {
'result': 'exception',
'exception': {
'module': type(exception).__module__,
'name': type(exception).__name__,
'args': exception.args,
'traceback': traceback.format_tb(exception.__traceback__),
'stdout': '',
'stderr': ''
}
}
if thread_storage:
return_value['exception']['stdout'] = thread_storage.stdout.decode()
return_value['exception']['stderr'] = thread_storage.stderr.decode()
return return_value
def privileged_handle_json_request(
request_string: str) -> str | io.BufferedReader:
"""Parse arguments for the program spawned as a privileged action."""
def _parse_request() -> dict:
@ -509,6 +423,10 @@ def privileged_handle_json_request(request_string: str) -> str:
raise TypeError(f'Parameter "{parameter}" must be of type'
f'{expected_type.__name__}')
if 'raw_output' in request and not isinstance(request['raw_output'],
bool):
raise TypeError('Incorrect "raw_output" parameter')
return request
try:
@ -516,23 +434,25 @@ def privileged_handle_json_request(request_string: str) -> str:
logger.info('Received request for %s..%s(..)', request['module'],
request['action'])
arguments = {'args': request['args'], 'kwargs': request['kwargs']}
_setup_thread_storage()
return_value = _privileged_call(request['module'], request['action'],
arguments)
if isinstance(return_value, io.BufferedReader):
raw_output = request.get('raw_output', False)
if not raw_output:
raise TypeError('Invalid call to raw output API.')
return return_value
except (PermissionError, SyntaxError, TypeError, Exception) as exception:
if isinstance(exception, (PermissionError, SyntaxError, TypeError)):
logger.error(exception.args[0])
else:
logger.exception(exception)
return_value = {
'result': 'exception',
'exception': {
'module': type(exception).__module__,
'name': type(exception).__name__,
'args': exception.args,
'traceback': traceback.format_tb(exception.__traceback__)
}
}
return_value = get_return_value_from_exception(exception)
_clear_thread_storage()
return json.dumps(return_value, cls=JSONEncoder)
@ -577,17 +497,12 @@ def _privileged_call(module_name, action_name, arguments):
try:
return_values = func(*arguments['args'], **arguments['kwargs'])
if isinstance(return_values, io.BufferedReader):
return return_values
return_value = {'result': 'success', 'return': return_values}
except Exception as exception:
return_value = {
'result': 'exception',
'exception': {
'module': type(exception).__module__,
'name': type(exception).__name__,
'args': exception.args,
'traceback': traceback.format_tb(exception.__traceback__)
}
}
return_value = get_return_value_from_exception(exception)
return return_value

View File

@ -6,6 +6,7 @@ pytest configuration for all tests.
import importlib
import os
import pathlib
import subprocess
from unittest.mock import patch
import pytest
@ -110,17 +111,6 @@ def fixture_needs_sudo():
pytest.skip('Needs sudo command installed.')
@pytest.fixture(name='no_privileged_server', scope='module')
def fixture_no_privileged__server():
"""Don't setup for and run privileged methods on server.
Tests on using privileged daemon are not yet implemented.
"""
with patch('plinth.actions._run_privileged_method_on_server') as mock:
mock.side_effect = NotImplementedError
yield
@pytest.fixture(scope='session')
def splinter_selenium_implicit_wait():
"""Disable implicit waiting."""
@ -163,9 +153,10 @@ def fixture_mock_privileged(request):
privileged_modules_to_mock = request.module.privileged_modules_to_mock
except AttributeError:
raise AttributeError(
'mock_privileged fixture requires "privileged_module_to_mock" '
'mock_privileged fixture requires "privileged_modules_to_mock" '
'attribute at module level')
mocked_methods = []
for module_name in privileged_modules_to_mock:
module = importlib.import_module(module_name)
for name, member in module.__dict__.items():
@ -176,19 +167,32 @@ def fixture_mock_privileged(request):
if not getattr(member, '_privileged', False):
continue
setattr(wrapped, '_original_wrapper', member)
module.__dict__[name] = wrapped
while getattr(wrapped, '__wrapped__', None) and getattr(
wrapped, '_privileged', True):
wrapped = getattr(wrapped, '__wrapped__', None)
setattr(wrapped, '_skip_privileged_call', True)
mocked_methods.append(wrapped)
yield
for module_name in privileged_modules_to_mock:
module = importlib.import_module(module_name)
for name, member in module.__dict__.items():
wrapper = getattr(member, '_original_wrapper', None)
if not callable(member) or not wrapper:
continue
for mocked_method in mocked_methods:
try:
delattr(mocked_method, '_skip_privileged_call')
except AttributeError:
pass
module.__dict__[name] = wrapper
@pytest.fixture(name='mock_run_as_user')
def fixture_mock_run_as_user():
"""A fixture to override action_utils.run_as_user."""
def _bypass_runuser(*args, username, **kwargs):
return subprocess.run(*args, **kwargs)
with patch('plinth.action_utils.run_as_user') as mock:
mock.side_effect = _bypass_runuser
yield
@pytest.fixture(name='splinter_screenshot_dir', scope='session')

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

View File

@ -3,7 +3,6 @@
Setup logging for the application.
"""
import importlib
import logging
import logging.config
import warnings
@ -77,7 +76,8 @@ def action_init():
_capture_warnings()
configuration = get_configuration()
del configuration['handlers']['console']['formatter']
# Don't log to console
configuration['root']['handlers'] = ['journal']
logging.config.dictConfig(configuration)
@ -118,10 +118,13 @@ def get_configuration():
'console': {
'class': 'logging.StreamHandler',
'formatter': 'color'
},
'journal': {
'class': 'systemd.journal.JournalHandler'
}
},
'root': {
'handlers': ['console'],
'handlers': ['console', 'journal'],
'level': default_level or ('DEBUG' if cfg.develop else 'INFO')
},
'loggers': {
@ -134,14 +137,4 @@ def get_configuration():
}
}
try:
importlib.import_module('systemd.journal')
except ModuleNotFoundError:
pass
else:
configuration['handlers']['journal'] = {
'class': 'systemd.journal.JournalHandler'
}
configuration['root']['handlers'].append('journal')
return configuration

View File

@ -12,7 +12,7 @@ import tarfile
from django.utils.translation import gettext_lazy as _
from plinth import action_utils
from plinth import action_utils, actions
from plinth import app as app_module
from plinth import module_loader
from plinth.actions import privileged, secret_str
@ -118,8 +118,8 @@ def reraise_known_errors(privileged_func):
def _reraise_known_errors(err):
"""Look whether the caught error is known and reraise it accordingly"""
stdout = getattr(err, 'stdout', b'').decode()
stderr = getattr(err, 'stderr', b'').decode()
stdout = (getattr(err, 'stdout') or b'').decode()
stderr = (getattr(err, 'stderr') or b'').decode()
caught_error = str((err, err.args, stdout, stderr))
for known_error in KNOWN_ERRORS:
for error in known_error['errors']:
@ -146,12 +146,12 @@ def mount(mountpoint: str, remote_path: str, ssh_keyfile: str | None = None,
# 'reconnect', 'ServerAliveInternal' and 'ServerAliveCountMax' allow the
# client (FreedomBox) to keep control of the SSH connection even when the
# SSH server misbehaves. Without these options, other commands such as
# '/usr/share/plinth/actions/actions storage usage_info --no-args', 'df',
# '/usr/share/plinth/actions/actions sshfs is_mounted --no-args', or
# 'mountpoint' might block indefinitely (even when manually invoked from
# the command line). This situation has some lateral effects, causing major
# system instability in the course of ~11 days, and leaving the system in
# such state that the only solution is a reboot.
# 'freedombox-cmd storage usage_info --no-args', 'df', 'freedombox-cmd
# sshfs is_mounted --no-args', or 'mountpoint' might block indefinitely
# (even when manually invoked from the command line). This situation has
# some lateral effects, causing major system instability in the course of
# ~11 days, and leaving the system in such state that the only solution is
# a reboot.
cmd = [
'sshfs', remote_path, mountpoint, '-o',
f'UserKnownHostsFile={user_known_hosts_file}', '-o',
@ -166,14 +166,14 @@ def mount(mountpoint: str, remote_path: str, ssh_keyfile: str | None = None,
cmd += ['-o', 'password_stdin']
input_ = password.encode()
subprocess.run(cmd, check=True, timeout=TIMEOUT, input=input_)
action_utils.run(cmd, check=True, timeout=TIMEOUT, input=input_)
@reraise_known_errors
@privileged
def umount(mountpoint: str):
"""Unmount a mountpoint."""
subprocess.run(['umount', mountpoint], check=True)
action_utils.run(['umount', mountpoint], check=True)
def _validate_mountpoint(mountpoint):
@ -340,8 +340,11 @@ def _extract(archive_path, destination, encryption_passphrase, locations=None):
@privileged
def export_tar(path: str, encryption_passphrase: secret_str | None = None):
"""Export archive contents as tar stream on stdout."""
_run(['borg', 'export-tar', path, '-', '--tar-filter=gzip'],
encryption_passphrase)
env = _get_env(encryption_passphrase)
process = subprocess.Popen(
['borg', 'export-tar', path, '-', '--tar-filter=gzip'], env=env,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
return actions.ProcessBufferedReader(process)
def _read_archive_file(archive, filepath, encryption_passphrase):
@ -512,4 +515,4 @@ def _get_env(encryption_passphrase: str | None = None):
def _run(cmd, encryption_passphrase=None, check=True, **kwargs):
"""Wrap the command with extra encryption passphrase handling."""
env = _get_env(encryption_passphrase)
return subprocess.run(cmd, check=check, env=env, **kwargs)
return action_utils.run(cmd, check=check, env=env, **kwargs)

View File

@ -3,7 +3,6 @@
import abc
import datetime
import io
import logging
import os
import subprocess
@ -177,56 +176,9 @@ class BaseBorgRepository(abc.ABC):
def get_download_stream(self, archive_name):
"""Return an stream of .tar.gz binary data for a backup archive."""
class BufferedReader(io.BufferedReader):
"""Improve performance of buffered binary streaming.
Django simply returns the iterator as a response for the WSGI app.
CherryPy then iterates over this iterator and writes to HTTP
response. This calls __next__ over the BufferedReader that is
process.stdout. However, this seems to call readline() which looks
for \n in binary data which leads to short unpredictably sized
chunks which in turn lead to severe performance degradation. So,
overwrite this and call read() which is better geared for handling
binary data.
"""
def __next__(self):
"""Override to call read() instead of readline()."""
chunk = self.read(io.DEFAULT_BUFFER_SIZE)
if not chunk:
if getattr(self, 'cleanup_func'):
self.cleanup_func()
raise StopIteration
return chunk
proc, read_fd, input_ = privileged.export_tar(
self._get_archive_path(archive_name),
self._get_encryption_passpharse(), _raw_output=True)
# Write the method request with args to the process
proc.stdin.write(input_)
proc.stdin.close()
def _cleanup_func():
"""After the process has been read from, cleanup the process."""
try:
if proc.stdout:
proc.stdout.close()
if proc.stderr:
proc.stderr.close()
proc.wait(30)
os.close(read_fd)
except Exception:
logger.exception('Closing process failed after download')
reader = BufferedReader(proc.stdout)
reader.cleanup_func = _cleanup_func
reader = privileged.export_tar(self._get_archive_path(archive_name),
self._get_encryption_passpharse(),
_raw_output=True)
return reader
def _get_archive_path(self, archive_name):

View File

@ -14,7 +14,7 @@
<h3>{{ title }}</h3>
<p>
{% blocktrans %}
{% blocktrans trimmed %}
Upload a backup file downloaded from another {{ box_name }} to restore its
contents. You can choose the apps you wish to restore after uploading a
backup file.

View File

@ -16,7 +16,9 @@ from plinth.modules.backups.repository import BorgRepository, SshBorgRepository
from plinth.tests import config as test_config
pytestmark = pytest.mark.usefixtures('needs_root', 'needs_borg', 'load_cfg',
'no_privileged_server')
'mock_privileged')
privileged_modules_to_mock = ['plinth.modules.backups.privileged']
# try to access a non-existing url and a URL that exists but does not
# grant access

View File

@ -11,7 +11,7 @@ from plinth.modules.apache.components import Webserver
from plinth.modules.backups.components import BackupRestore
from plinth.modules.firewall.components import (Firewall,
FirewallLocalProtection)
from plinth.modules.upgrades import get_current_release
from plinth.modules.upgrades.utils import get_current_release
from plinth.modules.users import add_user_to_share_group
from plinth.modules.users.components import UsersAndGroups
from plinth.package import Packages

View File

@ -292,7 +292,8 @@ def _run_diagnostics():
severity = 'error'
else:
for check in app_data['diagnosis']:
if check.result != Result.PASSED:
if check.result not in (Result.PASSED, Result.NOT_DONE,
Result.SKIPPED):
apps_with_issues.add(app_id)
issue_count += 1
if check.result != Result.WARNING:

View File

@ -271,8 +271,8 @@ def set_status(domain, result, ip_address, error=None):
'domain': domain['domain'],
'result': result,
'ip_address': ip_address,
'error_code': error.__class__.__name__ if error else None,
'error_message': error.args[0] if error and error.args else None,
'error_code': str(error.__class__.__name__) if error else None,
'error_message': str(error.args[0]) if error and error.args else None,
'timestamp': int(time.time()),
}
kvstore.set('dynamicdns_status', json.dumps(status))

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