freedombox Debian release 24.22

-----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmcXBsIWHGp2YWxsZXJv
 eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICLYHD/9dEGFq/ILCf8q0lgw3qkyDs8V9
 jlvLx/hsK60RhYcSakWaT8QbjACnFGn5nwAMMxcQWIq3Nc/XSs9NMx+7nbZUVmXK
 ZqWWLU65pfpMP0EE6zpGBq8ZNhnzIdX4jwGhu8KNUQjoaaMLUk7m7DH3sBVeaMIt
 FxrOW2Yso9+R5QOOcpJXUmrg5z9JUeYzd+F0EqyUa4uL3zZsU1ELFShskepXJuTH
 mx1Nkjdcs56Kx40YJlh1qxwBAPsAks4Kpc2KDWQsj7sNvO6gScRAYc0Rr70b8DUl
 g2uPLzFnNV5jp1Ug3lJZoUCwAwBB4Xr736fVl0L6l0mk9AtMqhrArMnvlTsL2571
 0KcqHenMsXbk5+ti/Z9KlAJ/FvvLFqjz43cyinyiVrOIAmkS6Uo0lSJnnwSE5G8B
 uJs+xEKFit3uD4KzSzum4tQe4W1pRGV8DxO1STXZxbSfAGqS9JDuez4O8LkN1+8E
 daP9eWtDhwYoWNihmZ5nSImHrhzCPPryWQmk0F8l7n5lMBAddvICEufLlAl6ngoZ
 j65ye1N38BvcKvMZvUkcTPIaSgjlgtf4+ShM+Qe5c0dE0sV5TnhFXbpVh+SYr4yZ
 zSYg0aTta2INSMyt5Kuv7/fAm3b82hNoRI/ZLlA1JmK9sGGJZEnBROD/qE4BFqsZ
 woEzuyTf1G8yI6nIMA==
 =0v+a
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmcblXEWHGp2YWxsZXJv
 eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICJShD/9CHfCPo03mlOjG/HcpJJdAJBJM
 Zn9o7eABYVGwqdVS0Wqu8yX0X1QcFk/Gj/az0kyf7DjAUTpum9keBmLQNE0Grh+f
 zuDF7XQ54YaqThQo1wyOeycLR4VeGl67ATqG6hilQ6BguJLmtyFJHmGcM6M+Mny1
 HKW5wxZW2Mzk+SkLlwjNy0fULy6KKU/rskCV8GYfH1GBW7l4Kosu5FTQ2lHEliEs
 mrQR7hLwL7wEJRAxlb0EwYVyATm4teol4Z93J03NQ7p+fXTiYA/6eZbRQDq2bOPb
 +nW7j7g8+/a5OmRS2i65tHQqa+DKedc8OA7b3bNjPXFUo8XeBZY/Wvi/e7fa6wR3
 IwFSE4QXeFNEocjdALITOybMsyzhHG5Hh0nA+qec7Udj3FAQrZsQya+7P+hWetyQ
 Yhf3CVV3eqDJNAYRHsdqhCiSHhtZf8KkqeP5gLDpg1EylOUjtn7m7wk8OOAD7JUa
 NLcJwZqfQLBbAFD4PTCWWiIwdc1O2unefcMwlsR86rmi1AQgtign/IOt7z+E+XDy
 Ps9gjg1vUzDwuYXgJTPZv/+bPUgp5W68EWz3eqhIpXmrED7VmO9hE3k6CuVdgqwE
 jBrWG9JV3BAAaTqFsMtiyTFeuV5CbOYwNvc1BGB23I6mXBn3XkSSvyuQ5yWoLuU4
 1tFj/fJgD7vVC9Dxrw==
 =Vpo6
 -----END PGP SIGNATURE-----

Merge tag 'v24.22' into debian/bookworm-backports

freedombox Debian release 24.22

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
James Valleroy 2024-10-25 08:56:03 -04:00
commit 9761c7ba47
195 changed files with 31175 additions and 11848 deletions

View File

@ -0,0 +1,40 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
FROM debian:stable
CMD /sbin/init
USER root:root
ENV DEBIAN_FRONTEND=noninteractive
RUN echo "deb http://deb.debian.org/debian bookworm-backports main" > /etc/apt/sources.list.d/backports.list; echo "deb-src http://deb.debian.org/debian bookworm-backports main" >> /etc/apt/sources.list.d/backports.list
RUN apt-get update
# Update all packages
RUN apt-get dist-upgrade -y
# Install freedombox package so that plint:plinth user/group are created etc.
RUN apt-get install -y freedombox/bookworm-backports
RUN systemctl mask plinth.service
# Don't ask for the secret in first wizard
RUN rm -f /var/lib/plinth/firstboot-wizard-secret
# Dependencies of the freedombox Debian package
RUN apt-get build-dep -y freedombox/bookworm-backports
# Build and test dependencies
RUN apt-get install -y \
build-essential \
parted \
sshpass \
wget
# Install functional test dependencies
ADD https://salsa.debian.org/freedombox-team/freedombox/-/raw/main/plinth/tests/functional/install.sh /usr/src/install.sh
RUN bash /usr/src/install.sh; rm -f /usr/src/install.sh
# Allow daemons to start when container is started
RUN rm -f /usr/sbin/policy-rc.d

View File

@ -25,4 +25,4 @@ RUN apt-get install -y build-essential \
RUN apt-get install -y $(./run --list-dependencies)
# Coverage should know that test_functional.py files are tests
RUN pip3 install --break-system-packages splinter pytest-bdd
RUN pip3 install --break-system-packages splinter

View File

@ -1,66 +1,27 @@
---
# SPDX-License-Identifier: AGPL-3.0-or-later
.app-server:
stage: functional-tests
dependencies: []
except:
- $GITLAB_USER_LOGIN == "weblate"
script:
- BUILD_JOB_ID=$(curl -s "https://salsa.debian.org/api/v4/projects/$CI_PROJECT_ID/pipelines/$CI_PIPELINE_ID/jobs?scope[]=success" | jq -r '.[] | select(.name==env.BUILD_JOB_NAME) | .id')
- export AWS_DEFAULT_REGION=us-east-1
- |
aws lambda invoke --function-name launch_app_server --payload '{"launch_template_name": "'"$LAUNCH_TEMPLATE_NAME"'", "instance_name": "'"$INSTANCE_NAME"'", "ci_project_id": "'"$CI_PROJECT_ID"'", "build_job_id": "'"$BUILD_JOB_ID"'"}' response.json
- echo "APP_SERVER_IP_1=$(jq -r '.app_server_ip' response.json)" >> app-servers.env
- echo "INSTANCE_ID_1=$(jq -r '.instance_id' response.json)" >> app-servers.env
- |
aws lambda invoke --function-name launch_app_server --payload '{"launch_template_name": "'"$LAUNCH_TEMPLATE_NAME"'", "instance_name": "'"$INSTANCE_NAME"'", "ci_project_id": "'"$CI_PROJECT_ID"'", "build_job_id": "'"$BUILD_JOB_ID"'"}' response.json
- echo "APP_SERVER_IP_2=$(jq -r '.app_server_ip' response.json)" >> app-servers.env
- echo "INSTANCE_ID_2=$(jq -r '.instance_id' response.json)" >> app-servers.env
tags:
- functional-tests
artifacts:
reports:
dotenv: app-servers.env
.run-functional-tests:
stage: functional-tests
timeout: 3h
needs: []
dependencies: []
tags:
- functional-tests
timeout: 10h
# Need to find another way of running the cleanup step even on failure
allow_failure: true
when: delayed
# Wait for the app-server to come up. Saves time for the CI runners.
start_in: 3 minutes
except:
- $GITLAB_USER_LOGIN == "weblate"
before_script:
- apt-get update
- apt-get install -y sudo curl wget
- ./plinth/tests/functional/install.sh
- adduser tester --gecos "First Last,RoomNumber,WorkPhone,HomePhone" --disabled-password && echo "tester:password" | chpasswd
- sudo -u plinth ./run --develop > plinth.log 2>&1 &
- 'echo -e "Cmnd_Alias FREEDOMBOX_ACTION_DEV = /usr/share/plinth/actions/actions, `pwd`/actions/actions\nDefaults!FREEDOMBOX_ACTION_DEV closefrom_override\nplinth ALL=(ALL:ALL) NOPASSWD:SETENV : FREEDOMBOX_ACTION_DEV\n" > /etc/sudoers.d/01-freedombox-development'
- while ! grep -q "Setup thread finished" plinth.log; do sleep 1; echo -n .; done
script:
- cp -r . /home/tester/freedombox && chown -R tester:tester /home/tester/freedombox
- |
sudo APP_SERVER_URL_1="https://$APP_SERVER_IP_1" APP_SERVER_URL_2="https://$APP_SERVER_IP_2" -u tester bash -c \
'cd /home/tester/freedombox && py.test-3 -v --durations=10 --include-functional --splinter-headless -n 2 --dist=loadscope --template=html1/index.html --report=functional-tests.html'
after_script:
- echo "INSTANCE_ID_1=$INSTANCE_ID_1" >> app-servers.env
- echo "INSTANCE_ID_2=$INSTANCE_ID_2" >> app-servers.env
- cp /home/tester/freedombox/functional-tests.html .
- cp -r /home/tester/freedombox/screenshots/ .
- FREDOMBOX_URL=https://localhost FREEDOMBOX_SSH_PORT=22 FREEDOMBOX_SAMBA_PORT=445 pytest -v --durations=10 --include-functional --splinter-headless --instafail --template=html1/index.html --report=functional-tests.html
artifacts:
when: always
reports:
dotenv: app-servers.env
paths:
- functional-tests.html
- screenshots/
# Does not run if the previous job times out or is cancelled
.terminate-app-server:
stage: functional-tests
script:
- export AWS_DEFAULT_REGION=us-east-1
- |
aws lambda invoke --function-name terminate_app_server --payload '{"instance_id": "'"$INSTANCE_ID_1"'"}' response.json
aws lambda invoke --function-name terminate_app_server --payload '{"instance_id": "'"$INSTANCE_ID_2"'"}' response.json
tags:
- functional-tests
- plinth.log

View File

@ -0,0 +1,21 @@
concurrent = 1
check_interval = 0
[[runners]]
name = "freedombox-functional"
url = "https://salsa.debian.org"
token = "<insert-server-provided-token-here>"
executor = "custom"
builds_dir = "/freedombox"
cache_dir = "/cache"
[runners.custom]
prepare_exec = "/var/lib/fbx-functional/bin/prepare.sh"
prepare_exec_timeout = 1200
run_exec = "/var/lib/fbx-functional/bin/run.sh"
cleanup_exec = "/var/lib/fbx-functional/bin/cleanup.sh"
cleanup_exec_timeout = 1200
graceful_kill_timeout = 200
force_kill_timeout = 200

View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
This directory contains a custom driver for Gitlab-CI Runner. This is used to
run functional tests.
Based on https://docs.gitlab.com/runner/executors/custom_examples/lxd.html under
Expat license.

View File

@ -0,0 +1,4 @@
#!/usr/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
CONTAINER_ID="runner-$CUSTOM_ENV_CI_RUNNER_ID-project-$CUSTOM_ENV_CI_PROJECT_ID-concurrent-$CUSTOM_ENV_CI_CONCURRENT_PROJECT_ID-$CUSTOM_ENV_CI_JOB_ID"

View File

@ -0,0 +1,10 @@
#!/usr/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${current_dir}/base.sh # Get variables from base.
echo "Deleting container $CONTAINER_ID"
podman container stop "$CONTAINER_ID"
podman container rm -f "$CONTAINER_ID"

View File

@ -0,0 +1,48 @@
#!/usr/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${current_dir}/base.sh # Get variables from base.
set -eo pipefail
# trap any error, and mark it as a system failure.
trap "exit $SYSTEM_FAILURE_EXIT_CODE" ERR
start_container () {
if podman container exists "$CONTAINER_ID" ; then
echo 'Found old container, deleting'
podman container stop "$CONTAINER_ID"
podman container rm -f "$CONTAINER_ID"
fi
podman pull registry.salsa.debian.org/freedombox-team/freedombox:functional-tests-stable
podman run --name "$CONTAINER_ID" --systemd=always \
--privileged \
--cap-add=SYS_ADMIN --cap-add=NET_ADMIN --cap-add=MKNOD \
--detach registry.salsa.debian.org/freedombox-team/freedombox:functional-tests-stable /sbin/init
if podman exec "$CONTAINER_ID" systemctl is-system-running --wait; then
echo 'Container started.'
else
echo 'Container started degraded.'
fi
}
install_dependencies () {
podman exec "$CONTAINER_ID" /usr/bin/bash <<EOF
set -eo pipefail
echo 'Package: *' > /etc/apt/preferences.d/unstable
echo 'Pin: release a=unstable' >> /etc/apt/preferences.d/unstable
echo 'Pin-Priority: 400' >> /etc/apt/preferences.d/unstable
echo 'deb http://deb.debian.org/debian unstable main' > /etc/apt/sources.list.d/unstable.list
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y gitlab-runner git-lfs
EOF
}
echo "Running in $CONTAINER_ID"
start_container
install_dependencies

View File

@ -0,0 +1,14 @@
#!/usr/bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
set -eo pipefail
current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source ${current_dir}/base.sh # Get variables from base.
podman exec --interactive "$CONTAINER_ID" /bin/bash < "${1}"
if [ $? -ne 0 ]; then
# Exit using the variable, to make the build as failure in GitLab
# CI.
exit $BUILD_FAILURE_EXIT_CODE
fi

11
.ci/update-container-image.sh Executable file
View File

@ -0,0 +1,11 @@
#! /bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
CONTAINER=$1
CONTAINER="${CONTAINER:-gitlabci}"
podman login registry.salsa.debian.org
# Build and upload a new image to the container registry
podman build -t registry.salsa.debian.org/freedombox-team/freedombox:${CONTAINER} -f .ci/Containerfile.${CONTAINER} .
podman push registry.salsa.debian.org/freedombox-team/freedombox:${CONTAINER}

View File

@ -1,8 +0,0 @@
#! /bin/bash
# SPDX-License-Identifier: AGPL-3.0-or-later
docker login registry.salsa.debian.org
# Build and upload a new image to the container registry
DOCKER_BUILDKIT=1 docker build -t registry.salsa.debian.org/freedombox-team/freedombox:gitlabci -f .ci/Dockerfile.gitlabci .
docker push registry.salsa.debian.org/freedombox-team/freedombox:gitlabci

View File

@ -47,63 +47,9 @@ doc-tests:
script:
- make check-doc
.app-server-stable:
variables:
LAUNCH_TEMPLATE_NAME: functional-tests-stable
INSTANCE_NAME: app-server-stable
BUILD_JOB_NAME: build-backports
extends: .app-server
.run-functional-tests-stable:
needs:
- job: app-server-stable
artifacts: true
run-functional-tests-stable:
extends: .run-functional-tests
.terminate-app-server-stable:
needs:
- job: run-functional-tests-stable
artifacts: true
extends: .terminate-app-server
.app-server-testing:
variables:
LAUNCH_TEMPLATE_NAME: functional-tests-testing
INSTANCE_NAME: app-server-testing
BUILD_JOB_NAME: build
extends: .app-server
.run-functional-tests-testing:
needs:
- job: app-server-testing
artifacts: true
extends: .run-functional-tests
.terminate-app-server-testing:
needs:
- job: run-functional-tests-testing
artifacts: true
extends: .terminate-app-server
.app-server-unstable:
variables:
LAUNCH_TEMPLATE_NAME: functional-tests-unstable
INSTANCE_NAME: app-server-unstable
BUILD_JOB_NAME: build
extends: .app-server
.run-functional-tests-unstable:
needs:
- job: app-server-unstable
artifacts: true
extends: .run-functional-tests
.terminate-app-server-unstable:
needs:
- job: run-functional-tests-unstable
artifacts: true
extends: .terminate-app-server
extract-source:
extends: .provisioning-extract-source

View File

@ -72,15 +72,18 @@ RedirectMatch "^/$" "/plinth"
## Enable strict sandboxing enabled with some exceptions:
## - Allow running Javascript.
## - Allow popups as sometimes we use <a target=_blank>
## - Allow popups to have different sandbox requirements as we launch apps' web
## clients.
## - Allow forms to support configuration forms.
## -
## - Allow policies to treat same origin differently from other origins
## - Allow downloads such as backup tarballs.
##
## Disable browser guessing of MIME types. FreedoBox already sets good content
## types for all the common file types.
##
<LocationMatch "^/(plinth|freedombox)">
Header set Referrer-Policy 'same-origin'
Header set Content-Security-Policy "font-src 'self'; frame-src 'none'; img-src 'self'; manifest-src 'none'; media-src 'none'; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self'; default-src 'self'; base-uri 'none'; sandbox allow-scripts allow-popups allow-forms allow-same-origin allow-downloads; form-action 'self'; frame-ancestors 'none'; block-all-mixed-content;"
Header set Content-Security-Policy "font-src 'self'; frame-src 'none'; img-src 'self'; manifest-src 'none'; media-src 'none'; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self'; default-src 'self'; base-uri 'none'; sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-same-origin allow-downloads; form-action 'self'; frame-ancestors 'none'; block-all-mixed-content;"
Header set X-Content-Type-Options 'nosniff'
</LocationMatch>

81
debian/changelog vendored
View File

@ -1,3 +1,84 @@
freedombox (24.22) unstable; urgency=medium
[ Sunil Mohan Adapa ]
* apache2: Allow popups to have different sandbox policy
* firstboot: Improve the setup complete page with more setups
* firstboot: Hide navigation toggler in mobile layouts
* firstboot: Make logo image responsive during first setup
* firstboot: Show spinner instead of message during first setup
* css: Fix height of navbar in mobile layout during first boot
* css: Navbar styling fixes in mobile layout
* upgrades: Remove step upgrade during first setup
* networks: Remove first boot steps for connectivity/topology
* upgrades: Show notification to remind user to run updates manually
* first_boot: Allow the next steps page to be revisited
* first_boot: Add notification for next steps after first setup
* wordpress: tests: functional: Fix tests on Trixie
* calibre: tests: functional: Fix occasional failure in add book test
* ci: Generalize script to update container, switch to podman
* ci: Dockerfile: Drop obsolete dependency on pytest-bdd
* ci: Rename Dockerfiles to Containerfiles
* ci: Add docker container for functional-tests:stable
* ci: Add gitlab runner configuration
* ci: Add a custom driver for gitlab runner for podman
* .gitlab-ci.yml: Update for new infrastructure
* ci: Update functional test timeout to 10h
* *: tags: Adjust tags and style
* context_processors: Use active menu urls to decide what to highlight
* help, system: Stop using submenu.sorted_items
* context_processors: Stop adding unused 'submenu' to context
* tags: css: Minor styling cleanups
* tags: js: Minor fixes and refactoring
* tests: functional: Create utility to set user preferred locale
* tags: Localization fixes
* tests: functional: Add package for printing test failures instantly
* ci: Enable showing test failures immediately as they fail
* help: tests: Fix tests failing due to tags related changes
* *: Remove unused imports to fix flake8 errors
* nextcloud: Fix install failure due to PrivateTmp=yes
* utils: Improve safe formatter by handling more cases
* operation: Use safe formatter for translating messages
* middleware: Show translated error messages when operation completes
* setup: Translate errors when installing/updating/repairing apps
[ gallegonovato ]
* Translated using Weblate (Spanish)
[ Burak Yavuz ]
* Translated using Weblate (Turkish)
[ 大王叫我来巡山 ]
* Translated using Weblate (Chinese (Simplified Han script))
[ 109247019824 ]
* Translated using Weblate (Bulgarian)
* Translated using Weblate (Bulgarian)
* Translated using Weblate (Bulgarian)
[ Besnik Bleta ]
* Translated using Weblate (Albanian)
* Translated using Weblate (Albanian)
[ Veiko Aasa ]
* syncthing: Fix app setup in Debian testing
* ssh: Start server after nslcd service
[ Joseph Nuthalapati ]
* backups: Use new utility for handling file uploads
* *: Implements tags for apps
[ Jiří Podhorecký ]
* Translated using Weblate (Czech)
[ Ihor Hordiichuk ]
* Translated using Weblate (Ukrainian)
[ James Valleroy ]
* locale: Update translation strings
* doc: Fetch latest manual
-- James Valleroy <jvalleroy@mailbox.org> Mon, 21 Oct 2024 20:42:43 -0400
freedombox (24.21~bpo12+1) bookworm-backports; urgency=medium
* Rebuild for bookworm-backports.

View File

@ -8,6 +8,58 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
The following are the release notes for each !FreedomBox version.
== FreedomBox 24.22 (2024-10-21) ==
=== Highlights ===
* Add tags for apps
=== Other Changes ===
* .gitlab-ci.yml: Update for new infrastructure
* Remove unused imports to fix flake8 errors
* apache2: Allow popups to have different sandbox policy
* backups: Use new utility for handling file uploads
* calibre: tests: functional: Fix occasional failure in add book test
* ci: Add a custom driver for gitlab runner for podman
* ci: Add docker container for functional-tests:stable
* ci: Add gitlab runner configuration
* ci: Dockerfile: Drop obsolete dependency on pytest-bdd
* ci: Enable showing test failures immediately as they fail
* ci: Generalize script to update container, switch to podman
* ci: Rename Dockerfiles to Containerfiles
* ci: Update functional test timeout to 10h
* context_processors: Stop adding unused 'submenu' to context
* context_processors: Use active menu urls to decide what to highlight
* css: Fix height of navbar in mobile layout during first boot
* css: Navbar styling fixes in mobile layout
* first_boot: Add notification for next steps after first setup
* first_boot: Allow the next steps page to be revisited
* firstboot: Hide navigation toggler in mobile layouts
* firstboot: Improve the setup complete page with more setups
* firstboot: Make logo image responsive during first setup
* firstboot: Show spinner instead of message during first setup
* help, system: Stop using submenu.sorted_items
* help: tests: Fix tests failing due to tags related changes
* locale: Update translations for Albanian, Bulgarian, Chinese (Simplified Han script), Czech, Spanish, Turkish, Ukrainian
* middleware: Show translated error messages when operation completes
* networks: Remove first boot steps for connectivity/topology
* nextcloud: Fix install failure due to !PrivateTmp=yes
* operation: Use safe formatter for translating messages
* setup: Translate errors when installing/updating/repairing apps
* ssh: Start server after nslcd service
* syncthing: Fix app setup in Debian testing
* tags: Adjust tags and style
* tags: Localization fixes
* tags: css: Minor styling cleanups
* tags: js: Minor fixes and refactoring
* tests: functional: Add package for printing test failures instantly
* tests: functional: Create utility to set user preferred locale
* upgrades: Remove step upgrade during first setup
* upgrades: Show notification to remind user to run updates manually
* utils: Improve safe formatter by handling more cases
* wordpress: tests: functional: Fix tests on Trixie
== FreedomBox 24.21 (2024-10-07) ==
=== Highlights ===

View File

@ -8,6 +8,58 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
The following are the release notes for each !FreedomBox version.
== FreedomBox 24.22 (2024-10-21) ==
=== Highlights ===
* Add tags for apps
=== Other Changes ===
* .gitlab-ci.yml: Update for new infrastructure
* Remove unused imports to fix flake8 errors
* apache2: Allow popups to have different sandbox policy
* backups: Use new utility for handling file uploads
* calibre: tests: functional: Fix occasional failure in add book test
* ci: Add a custom driver for gitlab runner for podman
* ci: Add docker container for functional-tests:stable
* ci: Add gitlab runner configuration
* ci: Dockerfile: Drop obsolete dependency on pytest-bdd
* ci: Enable showing test failures immediately as they fail
* ci: Generalize script to update container, switch to podman
* ci: Rename Dockerfiles to Containerfiles
* ci: Update functional test timeout to 10h
* context_processors: Stop adding unused 'submenu' to context
* context_processors: Use active menu urls to decide what to highlight
* css: Fix height of navbar in mobile layout during first boot
* css: Navbar styling fixes in mobile layout
* first_boot: Add notification for next steps after first setup
* first_boot: Allow the next steps page to be revisited
* firstboot: Hide navigation toggler in mobile layouts
* firstboot: Improve the setup complete page with more setups
* firstboot: Make logo image responsive during first setup
* firstboot: Show spinner instead of message during first setup
* help, system: Stop using submenu.sorted_items
* help: tests: Fix tests failing due to tags related changes
* locale: Update translations for Albanian, Bulgarian, Chinese (Simplified Han script), Czech, Spanish, Turkish, Ukrainian
* middleware: Show translated error messages when operation completes
* networks: Remove first boot steps for connectivity/topology
* nextcloud: Fix install failure due to !PrivateTmp=yes
* operation: Use safe formatter for translating messages
* setup: Translate errors when installing/updating/repairing apps
* ssh: Start server after nslcd service
* syncthing: Fix app setup in Debian testing
* tags: Adjust tags and style
* tags: Localization fixes
* tags: css: Minor styling cleanups
* tags: js: Minor fixes and refactoring
* tests: functional: Add package for printing test failures instantly
* tests: functional: Create utility to set user preferred locale
* upgrades: Remove step upgrade during first setup
* upgrades: Show notification to remind user to run updates manually
* utils: Improve safe formatter by handling more cases
* wordpress: tests: functional: Fix tests on Trixie
== FreedomBox 24.21 (2024-10-07) ==
=== Highlights ===

View File

@ -3,4 +3,4 @@
Package init file.
"""
__version__ = '24.21'
__version__ = '24.22'

View File

@ -434,7 +434,7 @@ class Info(FollowerComponent):
def __init__(self, app_id, version, is_essential=False, depends=None,
name=None, icon=None, icon_filename=None,
short_description=None, description=None, manual_page=None,
clients=None, donation_url=None):
clients=None, donation_url=None, tags=None):
"""Store the basic properties of an app as a component.
Each app must contain at least one component of this type to provide
@ -504,6 +504,9 @@ class Info(FollowerComponent):
'donation_url' is a link to a webpage that describes how to
donate to the upstream project.
'tags' is a list of tags that describe the app. Tags help users to find
similar apps or alternatives and discover use cases.
"""
self.component_id = app_id + '-info'
self.app_id = app_id
@ -518,9 +521,32 @@ class Info(FollowerComponent):
self.manual_page = manual_page
self.clients = clients
self.donation_url = donation_url
self._tags = tags or []
if clients:
clients_module.validate(clients)
@property
def tags(self) -> list[str]:
"""Return a list of untranslated tags on the app.
These can only be retrieved after Django has been configured.
"""
# Store untranslated original strings instead of proxy objects
from django.utils.translation import override
with override(language=None):
return [str(tag) for tag in self._tags]
@classmethod
def list_tags(self) -> list[str]:
"""Return a list of untranslated tags."""
tags: set[str] = set()
from django.utils.translation import override
with override(language=None):
for app in App.list():
tags.update((str(tag) for tag in app.info.tags))
return list(tags)
class EnableState(LeaderComponent):
"""A component to hold the enable state of an app using a simple flag.
@ -626,8 +652,8 @@ def _initialize_module(module_name, module):
for app_class in app_classes:
app_class()
except Exception as exception:
logger.exception('Exception while running init for %s: %s', module,
exception)
logger.exception('Exception while running init for module %s: %s',
module_name, exception)
if cfg.develop:
raise

View File

@ -8,7 +8,7 @@ import re
from django.utils.translation import gettext as _
from django.utils.translation import gettext_noop
from plinth import cfg, menu
from plinth import cfg
from plinth.utils import is_user_admin
@ -27,10 +27,11 @@ def common(request):
request, user=request.user)
slash_indices = [match.start() for match in re.finditer('/', request.path)]
active_menu_urls = [request.path[:index + 1] for index in slash_indices]
active_menu_urls = [
request.path[:index + 1] for index in slash_indices[2:]
] # Ignore the first two slashes '/plinth/apps/'
return {
'cfg': cfg,
'submenu': menu.main_menu.active_item(request),
'active_menu_urls': active_menu_urls,
'box_name': _(cfg.box_name),
'user_is_admin': is_user_admin(request, True),

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

@ -32,10 +32,10 @@ def _collect_operations_results(request, app):
operations = operation_module.manager.collect_results(app.app_id)
for operation in operations:
if operation.exception:
views.messages_error(request, operation.message,
views.messages_error(request, operation.translated_message,
operation.exception)
else:
messages.success(request, operation.message)
messages.success(request, operation.translated_message)
class SetupMiddleware(MiddlewareMixin):

View File

@ -3,7 +3,6 @@
Discover, load and manage FreedomBox applications.
"""
import collections
import importlib
import logging
import pathlib
@ -16,7 +15,7 @@ from plinth.signals import pre_module_loading
logger = logging.getLogger(__name__)
loaded_modules = collections.OrderedDict()
loaded_modules = dict()
_modules_to_load = None
@ -36,8 +35,8 @@ def load_modules():
for module_import_path in get_modules_to_load():
module_name = module_import_path.split('.')[-1]
try:
loaded_modules[module_name] = importlib.import_module(
module_import_path)
module = importlib.import_module(module_import_path)
loaded_modules[module_name] = module
except Exception as exception:
logger.exception('Could not import %s: %s', module_import_path,
exception)

View File

@ -4,9 +4,8 @@ Decorators for the backup views.
"""
import functools
import os
from . import SESSION_PATH_VARIABLE
from . import privileged, SESSION_PATH_VARIABLE
def delete_tmp_backup_file(function):
@ -15,12 +14,12 @@ def delete_tmp_backup_file(function):
XXX: Implement a better way to delete uploaded files.
"""
@functools.wraps(function)
def wrapper(request, *args, **kwargs):
path = request.session.get(SESSION_PATH_VARIABLE, None)
if path:
if os.path.isfile(path):
os.remove(path)
privileged.remove_uploaded_archive(path)
del request.session[SESSION_PATH_VARIABLE]
return function(request, *args, **kwargs)

View File

@ -8,11 +8,13 @@ import re
import subprocess
import tarfile
from plinth import action_utils
from plinth.actions import privileged, secret_str
from plinth.utils import Version
TIMEOUT = 30
BACKUPS_DATA_PATH = pathlib.Path('/var/lib/plinth/backups-data/')
BACKUPS_UPLOAD_PATH = pathlib.Path('/var/lib/freedombox/backups-upload/')
MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/'
@ -143,6 +145,24 @@ def list_repo(path: str,
return json.loads(process.stdout.decode())
@privileged
def add_uploaded_archive(file_name: str, temporary_file_path: str):
"""Store an archive uploaded by the user."""
BACKUPS_UPLOAD_PATH.mkdir(parents=True, exist_ok=True)
action_utils.move_uploaded_file(temporary_file_path, BACKUPS_UPLOAD_PATH,
file_name, allow_overwrite=True,
permissions=0o600)
@privileged
def remove_uploaded_archive(file_path: str):
"""Delete the archive uploaded by the user."""
resolved_file_path = pathlib.Path(file_path).resolve()
if (resolved_file_path.is_relative_to(BACKUPS_UPLOAD_PATH)
and resolved_file_path.is_file()):
resolved_file_path.unlink()
def _get_borg_version():
"""Return the version of borgbackup."""
process = _run(['borg', '--version'], stdout=subprocess.PIPE)

View File

@ -5,7 +5,6 @@ Views for the backups app.
import logging
import os
import tempfile
from datetime import datetime
from urllib.parse import unquote
@ -190,11 +189,13 @@ class UploadArchiveView(SuccessMessageMixin, FormView):
return context
def form_valid(self, form):
"""store uploaded file."""
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
self.request.session[SESSION_PATH_VARIABLE] = tmp_file.name
for chunk in self.request.FILES['backups-file'].chunks():
tmp_file.write(chunk)
"""Store uploaded file."""
uploaded_file = self.request.FILES['backups-file']
# Hold on to Django's uploaded file. It will be used by other views.
privileged.add_uploaded_archive(uploaded_file.name,
uploaded_file.temporary_file_path())
self.request.session[SESSION_PATH_VARIABLE] = str(
privileged.BACKUPS_UPLOAD_PATH / uploaded_file.name)
return super().form_valid(form)

View File

@ -58,7 +58,7 @@ class BepastyApp(app_module.App):
icon_filename='bepasty',
short_description=_('File & Snippet Sharing'),
description=_description, manual_page='bepasty',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-bepasty', info.name,

View File

@ -19,3 +19,5 @@ backup = {
},
'services': ['uwsgi'],
}
tags = [_('File sharing'), _('Pastebin')]

View File

@ -56,7 +56,7 @@ class CalibreApp(app_module.App):
name=_('calibre'), icon_filename='calibre',
short_description=_('E-book Library'),
description=_description, manual_page='Calibre',
clients=manifest.clients,
clients=manifest.clients, tags=manifest.tags,
donation_url='https://calibre-ebook.com/donate')
self.add(info)

View File

@ -16,3 +16,5 @@ backup = {
},
'services': ['calibre-server-freedombox']
}
tags = [_('Ebook'), _('Library'), _('Ebook reader')]

View File

@ -105,8 +105,17 @@ def _visit_library(browser, name):
functional.eventually(_service_available)
functional.eventually(browser.find_by_css,
args=[f'.calibre-push-button[data-lid="{name}"]'])
def _library_available():
"""Refresh until the expected library is available."""
available = browser.find_by_css(
f'.calibre-push-button[data-lid="{name}"]')
if not available:
time.sleep(0.5)
functional.visit(browser, '/calibre')
return available
functional.eventually(_library_available)
link = browser.find_by_css(f'.calibre-push-button[data-lid="{name}"]')
if not link:
raise ValueError('Library not found')

View File

@ -51,7 +51,8 @@ class CoturnApp(app_module.App):
info = app_module.Info(app_id=self.app_id, version=self._version,
name=_('Coturn'), icon_filename='coturn',
short_description=_('VoIP Helper'),
description=_description, manual_page='Coturn')
description=_description, manual_page='Coturn',
tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-coturn', info.name, info.short_description,

View File

@ -1,3 +1,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
from django.utils.translation import gettext_lazy as _
backup = {'secrets': {'directories': ['/etc/coturn']}, 'services': ['coturn']}
tags = [_('Video conference'), _('STUN'), _('TURN')]

View File

@ -47,7 +47,8 @@ class DelugeApp(app_module.App):
short_description=_('BitTorrent Web Client'),
description=_description, manual_page='Deluge',
clients=manifest.clients,
donation_url='https://www.patreon.com/deluge_cas')
donation_url='https://www.patreon.com/deluge_cas',
tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-deluge', info.name, info.short_description,

View File

@ -17,3 +17,5 @@ backup = {
},
'services': ['deluged', 'deluge-web']
}
tags = [_('File sharing'), _('BitTorrent'), _('Web client'), _('P2P')]

View File

@ -56,11 +56,13 @@ class EjabberdApp(app_module.App):
"""Create components for the app."""
super().__init__()
info = app_module.Info(
app_id=self.app_id, version=self._version, depends=['coturn'],
name=_('ejabberd'), icon_filename='ejabberd',
short_description=_('Chat Server'), description=_description,
manual_page='ejabberd', clients=manifest.clients)
info = app_module.Info(app_id=self.app_id, version=self._version,
depends=['coturn'], name=_('ejabberd'),
icon_filename='ejabberd',
short_description=_('Chat Server'),
description=_description,
manual_page='ejabberd',
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-ejabberd', info.name,

View File

@ -119,3 +119,10 @@ backup = {
},
'services': ['ejabberd']
}
tags = [
_('Encrypted messaging'),
_('Audio chat'),
_('Video chat'),
_('XMPP'),
]

View File

@ -63,7 +63,7 @@ class EmailApp(plinth.app.App):
icon_filename='email',
short_description=_('Email Server'),
description=_description, manual_page='Email',
clients=manifest.clients,
clients=manifest.clients, tags=manifest.tags,
donation_url='https://rspamd.com/support.html')
self.add(info)

View File

@ -78,3 +78,5 @@ backup = {
},
'services': ['postfix', 'dovecot', 'rspamd']
}
tags = [_('Email server'), _('IMAP'), _('Spam control')]

View File

@ -61,7 +61,7 @@ class FeatherWikiApp(app_module.App):
short_description=_('Personal Notebooks'),
description=_description,
manual_page='FeatherWiki',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-featherwiki', info.name,

View File

@ -14,3 +14,5 @@ clients = [{
}]
backup = {'data': {'directories': [str(wiki_dir)]}}
tags = [_('Wiki'), _('Note taking'), _('Website'), _('Quine'), _('Non-Debian')]

View File

@ -8,6 +8,7 @@ import os
import sys
from django.urls import reverse
from django.utils.translation import gettext_noop
from plinth import app as app_module
from plinth import cfg
@ -19,12 +20,6 @@ first_boot_steps = [
'url': 'first_boot:welcome',
'order': 0
},
{
# TODO: Rename this, or merge with 'firstboot_completed'.
'id': 'firstboot_complete',
'url': 'first_boot:complete',
'order': 10
}
]
_all_first_boot_steps = None
@ -54,8 +49,38 @@ class FirstBootApp(app_module.App):
def setup(self, old_version):
"""Install and configure the app."""
super().setup(old_version)
if not old_version:
self._show_next_steps_notification()
self.enable()
def _show_next_steps_notification(self):
"""After first setup, show notification for next steps."""
from plinth.notification import Notification
title = gettext_noop('Setup complete! Next steps:')
message = gettext_noop(
'Initial setup has been completed. Perform the next steps to make '
'your {box_name} operational.')
data = {
'app_name': 'translate:' + gettext_noop('Next steps'),
'app_icon': 'fa-arrow-right',
'box_name': 'translate:' + cfg.box_name
}
actions = [{
'type': 'link',
'class': 'primary',
'text': gettext_noop('See next steps'),
'url': 'first_boot:complete'
}, {
'type': 'dismiss'
}]
Notification.update_or_create(id='first-boot-complete',
app_id='first_boot', severity='info',
title=title, message=message,
actions=actions, data=data,
group='admin', dismissed=False)
def _clear_first_boot_steps(sender, module_name, **kwargs):
"""Flush the cache of first boot steps so it is recreated."""
@ -96,9 +121,9 @@ def _get_steps():
def next_step():
"""Return the resolved next first boot step URL required to go to.
If there are no more step remaining, return index page.
If there are no more step remaining, return 'complete' page.
"""
return next_step_or_none() or 'index'
return next_step_or_none() or 'first_boot:complete'
def next_step_or_none():

View File

@ -20,6 +20,7 @@ LOGGER = logging.getLogger(__name__)
class FirstBootMiddleware(MiddlewareMixin):
"""Forward to firstboot page if firstboot isn't finished yet."""
@staticmethod
def process_request(request):
"""Handle a request as Django middleware request handler."""
@ -46,11 +47,11 @@ class FirstBootMiddleware(MiddlewareMixin):
# If user requests a step other than the welcome step, verify that they
# indeed completed the secret verification by looking at the session.
if (user_requests_firstboot and
not request.path.startswith(reverse('first_boot:welcome')) and
first_boot.firstboot_wizard_secret_exists() and
not request.session.get('firstboot_secret_provided', False) and
not is_user_admin(request)):
if (user_requests_firstboot
and not request.path.startswith(reverse('first_boot:welcome'))
and first_boot.firstboot_wizard_secret_exists()
and not request.session.get('firstboot_secret_provided', False)
and not is_user_admin(request)):
return HttpResponseRedirect(reverse('first_boot:welcome'))
# Redirect to first boot if requesting normal page and first
@ -63,7 +64,7 @@ class FirstBootMiddleware(MiddlewareMixin):
# No more steps in first boot
first_boot.set_completed()
# Redirect to index page if request firstboot after it is
# Redirect to 'complete' page if user requested firstboot after it is
# finished.
if firstboot_completed and user_requests_firstboot:
return HttpResponseRedirect(reverse('index'))
return HttpResponseRedirect(reverse('first_boot:complete'))

View File

@ -12,6 +12,15 @@ no-brand
{% block mainmenu_left_collapse %}
{% endblock %}
{% block notifications_dropdown %}
{% endblock %}
{% block mainmenu_toggler %}
{% if user.is_authenticated %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block mainmenu_right %}
{% if user.is_authenticated %}
{% include "firstboot_navbar.html" %}

View File

@ -8,27 +8,79 @@
{% block content %}
<h2>{% trans "Setup Complete!" %}</h2>
<h2>{% trans "Setup Complete! Next Steps:" %}</h2>
<p>
{% blocktrans trimmed %}
Without any apps, your {{ box_name }} cannot do very much.
{% endblocktrans %}
</p>
<ol class='next-steps'>
<li>
<div class="app-icon fa fa-refresh"></div>
<div class="step-text">
{% url 'upgrades:index' as upgrades_url %}
{% blocktrans trimmed %}
Automatic <a href="{{ upgrades_url }}" target="_blank">software
update</a> runs daily by default. For the first time, manually run
it now.
{% endblocktrans %}
<form class="form" method="post" target="_blank"
action="{% url 'upgrades:upgrade' %}">
{% csrf_token %}
<input type="submit" class="btn btn-default"
value="{% trans "Update now" %}"/>
</form>
</div>
</li>
<div class="text-center">
<a class="btn btn-lg btn-primary" href="{% url 'apps' %}">
{% trans "Install Apps" %}</a>
</div>
<li>
<div class="app-icon fa fa-eye-slash"></div>
<div class="step-text">
{% url 'privacy:index' as privacy_url %}
{% blocktrans trimmed %}
Review <a href="{{ privacy_url }}" target="_blank">privacy options</a>.
{% endblocktrans %}
</div>
</li>
<p>
<p>
{% url 'networks:index' as networks_url %}
{% blocktrans trimmed %}
You may want to check the <a href="{{ networks_url }}">network
setup</a> and modify it if necessary.
{% endblocktrans %}
</p>
</p>
<li>
<div class="app-icon fa fa-signal"></div>
<div class="step-text">
{% url 'networks:index' as networks_url %}
{% blocktrans trimmed %}
Review and setup <a href="{{ networks_url }}" target="_blank">network
connections</a>. Change the default Wi-Fi password, if applicable.
{% endblocktrans %}
</div>
</li>
<li>
<div class="app-icon fa fa-tags"></div>
<div class="step-text">
{% url 'names:index' as names_url %}
{% blocktrans trimmed %}
Configure a <a href="{{ names_url }}" target="_blank">domain name</a>.
{% endblocktrans %}
</div>
</li>
<li>
<div class="app-icon fa fa-files-o"></div>
<div class="step-text">
{% url 'backups:index' as backups_url %}
{% blocktrans trimmed %}
Configure and schedule remote
<a href="{{ backups_url }}" target="_blank">backups</a>.
{% endblocktrans %}
</div>
</li>
<li>
<div class="app-icon fa fa-th"></div>
<div class="step-text">
{% url 'apps' as apps_url %}
{% blocktrans trimmed %}
Put {{ box_name }} to use by installing
<a href="{{ apps_url }}" target="_blank">apps</a>.
{% endblocktrans %}
</div>
</li>
</ul>
{% endblock %}

View File

@ -31,22 +31,12 @@ class WelcomeView(FormView):
class CompleteView(TemplateView):
"""Show summary after all firstboot setup is done.
After viewing this page the firstboot module can't be accessed anymore.
"""
"""Show next steps after all firstboot wizard steps are done."""
template_name = 'firstboot_complete.html'
def get(self, request, *args, **kwargs):
"""Mark as done as soon as page is served."""
response = super().get(self, request, *args, **kwargs)
first_boot.mark_step_done('firstboot_complete')
return response
def get_context_data(self, **kwargs):
"""Add network connections to context list."""
context = super().get_context_data(**kwargs)
context['title'] = _('Setup Complete')
context['firstboot_complete'] = True
return context

View File

@ -48,7 +48,7 @@ class GitwebApp(app_module.App):
name=_('Gitweb'), icon_filename='gitweb',
short_description=_('Simple Git Hosting'),
description=_description, manual_page='GitWeb',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-gitweb', info.name, info.short_description,

View File

@ -33,3 +33,5 @@ clients = [
]
backup = {'data': {'directories': [GIT_REPO_PATH]}}
tags = [_('Git hosting'), _('Version control'), _('Developer tool')]

View File

@ -18,8 +18,12 @@ import pytest
from django import urls
from django.conf import settings
from django.http import Http404
from django.urls import re_path
from plinth import cfg, module_loader
from plinth import cfg
from plinth import menu as menu_module
from plinth import module_loader
from plinth.modules import help as help_module
from plinth.modules.help import views
# For all tests, use plinth.urls instead of urls configured for testing
@ -35,8 +39,12 @@ def _is_page(response):
@pytest.fixture(autouse=True, scope='module')
def fixture_app_urls():
"""Make sure app's URLs are part of plinth.urls."""
urls = [
re_path(r'^apps/$', views.index, name='apps'),
re_path(r'^system/$', views.index, name='system')
]
with patch('plinth.module_loader._modules_to_load', new=[]) as modules, \
patch('plinth.urls.urlpatterns', new=[]):
patch('plinth.urls.urlpatterns', new=urls):
modules.append('plinth.modules.help')
module_loader.include_urls()
yield
@ -48,6 +56,13 @@ def fixture_developer_configuration():
cfg.read_file(cfg.get_develop_config_path())
@pytest.fixture(name='menu', autouse=True)
def fixture_menu():
"""Initialized menu module."""
menu_module.init()
help_module.HelpApp() # Create all menu components
@pytest.mark.parametrize("view_name, view", (
('feedback', views.feedback),
('support', views.support),

View File

@ -16,7 +16,7 @@ from django.urls import reverse
from django.utils.translation import get_language_from_request
from django.utils.translation import gettext as _
from plinth import __version__, cfg
from plinth import __version__, cfg, menu
from plinth.modules.upgrades import views as upgrades_views
from . import privileged
@ -24,8 +24,11 @@ from . import privileged
def index(request):
"""Serve the index page"""
return TemplateResponse(request, 'help_index.html',
{'title': _('Documentation and FAQ')})
menu_items = menu.main_menu.active_item(request).sorted_items()
return TemplateResponse(request, 'help_index.html', {
'title': _('Documentation and FAQ'),
'menu_items': menu_items
})
def contribute(request):

View File

@ -52,7 +52,7 @@ class I2PApp(app_module.App):
name=_('I2P'), icon_filename='i2p',
short_description=_('Anonymity Network'),
description=_description, manual_page='I2P',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-i2p', info.name, info.short_description,

View File

@ -39,3 +39,5 @@ backup = {
},
'services': ['i2p']
}
tags = [_('Anonymity network'), _('Censorship resistance')]

View File

@ -45,7 +45,7 @@ class IkiwikiApp(app_module.App):
name=_('ikiwiki'), icon_filename='ikiwiki',
short_description=_('Wiki and Blog'),
description=_description, manual_page='Ikiwiki',
clients=manifest.clients,
clients=manifest.clients, tags=manifest.tags,
donation_url='https://ikiwiki.info/tipjar/')
self.add(info)

View File

@ -11,3 +11,5 @@ clients = [{
}]
backup = {'data': {'directories': ['/var/lib/ikiwiki/', '/var/www/ikiwiki/']}}
tags = [_('Wiki'), _('Blog'), _('Website')]

View File

@ -42,7 +42,7 @@ class InfinotedApp(app_module.App):
short_description=_('Gobby Server'),
description=_description,
manual_page='Infinoted',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-infinoted', info.name,

View File

@ -42,3 +42,5 @@ backup = {
},
'services': ['infinoted']
}
tags = [_('Note taking'), _('Collaborative editing'), _('Gobby')]

View File

@ -43,7 +43,7 @@ class JanusApp(app_module.App):
icon_filename='janus',
short_description=_('Video Room'),
description=_description, manual_page='Janus',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-janus', info.name, info.short_description,

View File

@ -12,3 +12,5 @@ clients = [{
}]
backup: dict = {}
tags = [_('Video conference'), _('WebRTC'), _('Web conference')]

View File

@ -38,7 +38,7 @@ class JSXCApp(app_module.App):
name=_('JSXC'), icon_filename='jsxc',
short_description=_('Chat Client'),
description=_description, manual_page='JSXC',
clients=manifest.clients)
clients=manifest.clients, tags=manifest.tags)
self.add(info)
menu_item = menu.Menu('menu-jsxc', info.name, info.short_description,

View File

@ -12,3 +12,5 @@ clients = [{
}]
backup: dict = {}
tags = [_('Web chat'), _('XMPP'), _('Client')]

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