Merge pull request #1836 from nextcloud/devel

Changes from v1.53.0
This commit is contained in:
Tobias Knöppler 2023-11-15 00:53:22 +01:00 committed by GitHub
commit f550440a84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 20499 additions and 87 deletions

View File

@ -197,6 +197,10 @@ jobs:
nictype: bridged
type: nic
EOF
- name: Checkout code
uses: actions/checkout@v3
with:
ref: "${{ env.VERSION }}"
- name: Setup Firefox
uses: browser-actions/setup-firefox@latest
- name: Setup GeckoDriver
@ -205,10 +209,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Selenium
run: pip install selenium
- name: Checkout code
uses: actions/checkout@v3
with:
ref: "${{ env.VERSION }}"
- name: download LXD image from artifact store
uses: actions/download-artifact@v3
with:

View File

@ -64,7 +64,6 @@ jobs:
continue-on-error: true
run: |
set -ex
export LIB_TAG=master
export IMG="NextCloudPi_${{ inputs.board_name }}_${VERSION//\//_}.img"
[[ "${{ github.ref_protected }}" == true ]] || export DBG=x
@ -83,7 +82,6 @@ jobs:
echo -e "${LOG_CICD} Cleanup armbian build leftovers..."
sudo rm -rf armbian/ tmp/ output/
export LIB_TAG=master
export IMG="NextCloudPi_${{ inputs.board_name }}_${VERSION//\//_}.img"
[[ "${{ github.ref_protected }}" == true ]] || export DBG=x
@ -179,6 +177,8 @@ jobs:
sudo rm -rf raspbian_root
. ./build/buildlib.sh
mount_raspbian "ncp.img"
sudo cat raspbian_root/etc/machine-id
sudo systemd-id128 new | sudo tee ./raspbian_root/etc/machine-id
sudo wget -nv https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static -O raspbian_root/usr/bin/qemu-aarch64-static
sudo wget -nv https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static -O raspbian_root/usr/bin/qemu-arm-static
sudo chmod +x raspbian_root/usr/bin/qemu-{arm,aarch64}-static

View File

@ -64,7 +64,7 @@ jobs:
success=false
for i in {1..5}
do
body="$(hub release show -f "%b" "${VERSION}")"
body="$(gh release view --json body "${VERSION}" | jq -r '.body')"
if ! [[ "$body" =~ .*'**Checksums:**'.* ]]
then
@ -80,7 +80,7 @@ jobs:
\`\`\`"
gh release edit "${VERSION?}" -n "$body"
if hub release show -f "%b" "${VERSION}" | grep "$checksum"
if gh release view --json body "${VERSION}" | jq -r '.body' | grep "$checksum"
then
success=true
break

View File

@ -100,7 +100,7 @@ jobs:
# TODO: Fix 32bit armbian images
odroidxu4:
# if: ${{ inputs.sd-images || github.event_name != 'workflow_dispatch' }}
# if: ${{ inputs.sd-images || ( github.event_name != 'workflow_dispatch' && !startsWith(github.ref_name, 'docker-') ) }}
if: ${{ false }}
uses: ./.github/workflows/build-sd-images.yml
with:
@ -126,7 +126,7 @@ jobs:
secrets: inherit
# TODO: Fix 32bit armbian images
bananapi:
#if: ${{ inputs.sd-images || github.event_name != 'workflow_dispatch' }}
# if: ${{ inputs.sd-images || ( github.event_name != 'workflow_dispatch' && !startsWith(github.ref_name, 'docker-') ) }}
if: ${{ false }}
uses: ./.github/workflows/build-sd-images.yml
with:
@ -391,7 +391,7 @@ jobs:
"
hub release create --draft -F - "${{ env.VERSION }}" <<EOF
gh release create --draft -F - "${{ env.VERSION }}" <<EOF
${subject:-No message found}
${body:+${body}${separator}}

View File

@ -145,7 +145,8 @@ jobs:
setup-ssh-port-forwarding "$SERVER_ADDRESS"
echo "Run integration tests"
test-ncp-instance -a -f "$SNAPSHOT_ID" -b "${VERSION}" "root@${SERVER_ADDRESS}" "localhost" "8443" "9443" || {
ssh -o "StrictHostKeyChecking=no" -o "UserKnownHostsFile=/dev/null" "root@${SERVER_ADDRESS}" cat /usr/local/etc/instance.cfg
test-ncp-instance -a -f "$SNAPSHOT_ID" -b "${VERSION}" --systemtest-args "--skip-update-test" "root@${SERVER_ADDRESS}" "localhost" "8443" "9443" || {
echo "Integration tests failed"
echo "Here are the last lines of ncp-install.log:"
@ -217,7 +218,7 @@ jobs:
echo "Run integration tests"
setup-ssh-port-forwarding "$SERVER_ADDRESS"
test-ncp-instance -a -f "$SNAPSHOT_ID" -b "${VERSION}" "root@${SERVER_ADDRESS}" "localhost" "8443" "9443" || {
test-ncp-instance -a -f "$SNAPSHOT_ID" -b "${VERSION}" --systemtest-args "--skip-update-test" "root@${SERVER_ADDRESS}" "localhost" "8443" "9443" || {
echo "Integration tests failed"
echo "Here are the last lines of ncp-install.log:"

View File

@ -2,19 +2,35 @@
# update latest available version in /var/run/.ncp-latest-version
TMPDIR="$( mktemp -d /tmp/ncp-check.XXXXXX || ( echo "Failed to create temp dir. Exiting" >&2; exit 1 ) )"
trap "rm -rf \"${TMPDIR}\"; exit 0" 0 1 2 3 15
TEMPDIR="$( mktemp -d /tmp/ncp-check.XXXXXX || ( echo "Failed to create temp dir. Exiting" >&2; exit 1 ) )"
trap "rm -rf \"${TEMPDIR}\"; exit 0" 0 1 2 3 15
BRANCH="master"
{ [[ -f /.dockerenv ]] || [[ -f /.docker-image ]] || [[ "$DOCKERBUILD" == 1 ]]; } && BRANCH="docker-stable"
git clone -b "$BRANCH" --depth 20 -q --bare https://github.com/nextcloud/nextcloudpi.git "$TMPDIR" || {
git clone -b "$BRANCH" --depth 20 -q --bare https://github.com/nextcloud/nextcloudpi.git "$TEMPDIR" || {
echo "The git clone command failed: No connectivity to https://github.com ?" >&2
exit 1
}
cd "$TMPDIR" || exit 1
cd "$TEMPDIR" || exit 1
VER=$( git describe --always --tags | grep -oP "v\d+\.\d+\.\d+" )
canary="$(. /usr/local/etc/library.sh; find_app_param ncp-community.sh CANARY)"
# TODO: Remove temporary canary override after staged rollouts test
canary="no"
[[ "$canary" != "yes" ]] && [[ -f "/usr/local/etc/instance.cfg" ]] && {
cohorte_id="$(jq .cohorteId /usr/local/etc/instance.cfg)"
[[ -f "./staged_rollouts/${VER}.txt" ]] && {
grep "^${cohorte_id}$" "./staged_rollouts/${VER}.txt" || {
echo "Skipping version $VER - cohorte ${cohorte_id} not yet active"
cd / || exit 1
exit 0
}
}
}
grep -qP "v\d+\.\d+\.\d+" <<< "$VER" && { # check format
echo "$VER" > /var/run/.ncp-latest-version

View File

@ -3,7 +3,7 @@
# this script runs at startup to provide an unique random passwords for each instance
source /usr/local/etc/library.sh
set -x
## redis provisioning
CFG=/var/www/nextcloud/config/config.php
@ -72,4 +72,15 @@ if needs_decrypt; then
apache2ctl -k graceful
fi
[[ -f /usr/local/etc/instance.cfg ]] || {
cohorte_id=$((RANDOM % 100))
cat > /usr/local/etc/instance.cfg <<EOF
{
"cohorteId": ${cohorte_id}
}
EOF
cat /usr/local/etc/instance.cfg
}
exit 0

View File

@ -16,32 +16,32 @@ DIR="$( cd "$( dirname "$BACKUPFILE" )" &>/dev/null && pwd )" #abspath
[[ -f "$BACKUPFILE" ]] || { echo "$BACKUPFILE not found" ; exit 1; }
[[ "$DIR" =~ "$NCDIR".* ]] && { echo "Refusing to restore from $NCDIR"; exit 1; }
TMPDIR="$( mktemp -d "$( dirname "$BACKUPFILE" )"/ncp-restore.XXXXXX )" || { echo "Failed to create temp dir" >&2; exit 1; }
[[ "$(stat -fc%T "${TMPDIR}")" =~ ext|btrfs|zfs ]] || { echo "Can only restore from ext/btrfs/zfs filesystems (found '$(stat -fc%T "${TMPDIR}")" >&2; exit 1; }
TEMPDIR="$( mktemp -d "$( dirname "$BACKUPFILE" )"/ncp-restore.XXXXXX )" || { echo "Failed to create temp dir" >&2; exit 1; }
[[ "$(stat -fc%T "${TEMPDIR}")" =~ ext|btrfs|zfs ]] || { echo "Can only restore from ext/btrfs/zfs filesystems (found '$(stat -fc%T "${TEMPDIR}")" >&2; exit 1; }
TMPDIR="$( cd "$TMPDIR" &>/dev/null && pwd )" || { echo "$TMPDIR not found"; exit 1; } #abspath
cleanup(){ local RET=$?; echo "Cleanup..."; rm -rf "${TMPDIR}"; trap "" EXIT; exit $RET; }
TEMPDIR="$( cd "$TEMPDIR" &>/dev/null && pwd )" || { echo "$TEMPDIR not found"; exit 1; } #abspath
cleanup(){ local RET=$?; echo "Cleanup..."; rm -rf "${TEMPDIR}"; trap "" EXIT; exit $RET; }
trap cleanup INT TERM HUP ERR EXIT
rm -rf "$TMPDIR" && mkdir -p "$TMPDIR"
rm -rf "$TEMPDIR" && mkdir -p "$TEMPDIR"
[[ "$BACKUPFILE" =~ .*".tar.gz" ]] && compress_arg="-I pigz"
# CHECK FREE SPACE IN $TMPDIR
# CHECK FREE SPACE IN $TEMPDIR
echo "check free space..." # allow at least ~100 extra MiB
extractedsize=$(tar $compress_arg -tvf "$BACKUPFILE" | awk '{s+=$3} END{printf "%.0f", (s/1024)}') # Size of extracted files in "KB"
size=$(($extractedsize + 100*1024))
free=$( df "$TMPDIR" | tail -1 | awk '{ print $4 }' )
free=$( df "$TEMPDIR" | tail -1 | awk '{ print $4 }' )
[ $size -ge $free ] && {
echo "free space check failed. Need $size KB in $TMPDIR";
echo "free space check failed. Need $size KB in $TEMPDIR";
exit 1;
}
# EXTRACT FILES
echo "extracting backup file $BACKUPFILE..."
tar $compress_arg -xf "$BACKUPFILE" -C "$TMPDIR" || exit 1
tar $compress_arg -xf "$BACKUPFILE" -C "$TEMPDIR" || exit 1
## SANITY CHECKS
[[ -d "$TMPDIR"/nextcloud ]] && [[ -f "$( ls "$TMPDIR"/nextcloud-sqlbkp_*.bak 2>/dev/null )" ]] || {
[[ -d "$TEMPDIR"/nextcloud ]] && [[ -f "$( ls "$TEMPDIR"/nextcloud-sqlbkp_*.bak 2>/dev/null )" ]] || {
echo "invalid backup file. Abort"
exit 1
}
@ -54,7 +54,7 @@ echo "restore files..."
mv -T "$NCDIR/data" "$DATA_BKP_DIR/"
}
rm -rf "$NCDIR"
mv -T "$TMPDIR"/nextcloud "$NCDIR" || { echo "Error restoring base files"; exit 1; }
mv -T "$TEMPDIR"/nextcloud "$NCDIR" || { echo "Error restoring base files"; exit 1; }
if [[ -n "$DATA_BKP_DIR" ]]
then
@ -91,7 +91,7 @@ EOFMYSQL
# shellcheck disable=SC2181
[ $? -ne 0 ] && { echo "Error configuring nextcloud database"; exit 1; }
mysql -u root nextcloud < "$TMPDIR"/nextcloud-sqlbkp_*.bak || { echo "Error restoring nextcloud database"; exit 1; }
mysql -u root nextcloud < "$TEMPDIR"/nextcloud-sqlbkp_*.bak || { echo "Error restoring nextcloud database"; exit 1; }
## RESTORE DATADIR
@ -105,7 +105,7 @@ cd "$NCDIR"
### INCLUDEDATA=yes situation
NUMFILES=2
if [[ $( ls "$TMPDIR" | wc -l ) -eq $NUMFILES ]]; then
if [[ $( ls "$TEMPDIR" | wc -l ) -eq $NUMFILES ]]; then
[[ -e "$DATADIR" ]] && {
bk_target="$DATADIR-$( date '+%FT%s' )"
@ -121,7 +121,7 @@ if [[ $( ls "$TMPDIR" | wc -l ) -eq $NUMFILES ]]; then
btrfs subvolume create "$DATADIR" || exit 1
}
chown www-data: "$DATADIR"
TMPDATA="$TMPDIR/$( basename "$DATADIR" )"
TMPDATA="$TEMPDIR/$( basename "$DATADIR" )"
mv "$TMPDATA"/* "$TMPDATA"/.[!.]* "$DATADIR" || exit 1
rmdir "$TMPDATA" || exit 1

View File

@ -19,18 +19,18 @@ fi
BRANCH="${1:-master}"
[[ "$BRANCH" != "master" ]] && echo "INFO: updating to development branch '$BRANCH'"
TMPDIR="$( mktemp -d /tmp/ncp-update.XXXXXX || ( echo "Failed to create temp dir. Exiting" >&2; exit 1 ) )"
trap 'cd /; rm -rf "${TMPDIR}"' EXIT
TEMPDIR="$( mktemp -d /tmp/ncp-update.XXXXXX || ( echo "Failed to create temp dir. Exiting" >&2; exit 1 ) )"
trap 'cd /; rm -rf "${TEMPDIR}"' EXIT
echo -e "Downloading updates"
git clone --depth 20 -b "$BRANCH" -q https://github.com/nextcloud/nextcloudpi.git "$TMPDIR" || {
git clone --depth 20 -b "$BRANCH" -q https://github.com/nextcloud/nextcloudpi.git "$TEMPDIR" || {
echo "No internet connectivity"
exit 1
}
# shellcheck disable=SC2164
[[ -f /.ncp-image ]] || {
cd "$TMPDIR" # update locally during build
cd "$TEMPDIR" # update locally during build
[[ -z "$2" ]] || {
git fetch origin "$2" || {
@ -44,7 +44,7 @@ fi
echo -e "Performing updates"
./update.sh || exit $?
cd "$TMPDIR" || exit 1
cd "$TEMPDIR" || exit 1
git describe --always --tags --abbrev=0 2>/dev/null | grep -qoP "v\d+\.\d+\.\d+" || git fetch --unshallow --tags -q
VER=$( git describe --always --tags --abbrev=0 | grep -oP "v\d+\.\d+\.\d+" )

View File

@ -0,0 +1,7 @@
#!/bin/bash
# Configure various settings for community participation
install() { :; }
configure() { :; }

View File

@ -14,8 +14,8 @@ install()
apt-get update
apt-get install --no-install-recommends -y make gcc libc-dev
local TMPDIR="$( mktemp -d /tmp/noip.XXXXXX )"
cd "$TMPDIR"
local TEMPDIR="$( mktemp -d /tmp/noip.XXXXXX )"
cd "$TEMPDIR"
wget -O- --content-disposition https://github.com/nachoparker/noip-DDNS/archive/master/latest.tar.gz \
| tar -xz \
|| return 1
@ -41,7 +41,7 @@ EOF
chmod +x /etc/init.d/noip2
cd -
rm -r "$TMPDIR"
rm -r "$TEMPDIR"
update-rc.d noip2 defaults
update-rc.d noip2 disable

43
build/armbian/Vagrantfile vendored Normal file
View File

@ -0,0 +1,43 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
#
# Vagrantfile for the NCP Debian VM
#
# Instructions: vagrant up; vagrant ssh
#
# Notes: User/Pass is ubnt/ubnt.
# $HOME is accessible as /external. CWD is accessible as /cwd
#
Vagrant.configure("2") do |config|
vmname = "NCP Ubuntu VM"
config.vm.box = "generic/ubuntu2204"
config.vm.box_check_update = true
config.vm.hostname = "ncp-vm"
config.vm.define "ncp-vm"
config.vm.provider :libvirt do |libvirt|
libvirt.default_prefix = ""
libvirt.cpus = 6
libvirt.memory = 4096
end
config.vm.synced_folder '.', '/vagrant', disabled: true
$script = <<-SHELL
sudo su
set -ex
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git ca-certificates
cd /tmp/nextcloudpi
./build/build-SD-armbian.sh "${BOARD_ID?}" "${BOARD_NAME?}"
mv /tmp/nextcloudpi/armbian/output/*.img /tmp/ncp-out/
SHELL
# Provision the VM
config.vm.provision "shell", inline: $script, env: {"BOARD_ID" => ENV['BOARD_ID'], "BOARD_NAME" => ENV['BOARD_NAME']}
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.synced_folder "../..", "/tmp/nextcloudpi", type: "rsync", rsync__exclude: ["/armbian/", ".venv/", "/cache/", "/raspbian_root/", "/output/", "/tmp/", "/.idea/"]
config.vm.synced_folder "../../output", "/tmp/ncp-out", type: "sshfs"
end

View File

@ -12,6 +12,8 @@
set -e
echo 'Running user script...'
ARMBIAN_RELEASE=$1
LINUXFAMILY=$2
BOARD=$3
@ -29,7 +31,9 @@ echo -e "\nInstalling NextCloudPi"
hostname -F /etc/hostname # fix 'sudo resolve host' errors
CODE_DIR="$(pwd)" bash install.sh
CODE_DIR="$(pwd)" DBG=x bash install.sh
echo -e "\nPostinstall..."
run_app_unsafe post-inst.sh
# disable SSH by default, it can be enabled through ncp-web

View File

@ -0,0 +1,30 @@
#!/bin/bash
# Batch creation of NextCloudPi Armbian based images
#
# Copyleft 2023 by Tobias Knöppler
# GPL licensed (see end of file) * Use at your own risk!
#
# Usage: sudo ./build-SD-armbian-vagrant.sh <board_code> [<board_name>]
#
[[ "$UID" == 0 ]] || {
echo "This script needs to be run as root. Try sudo"
exit 1
}
set -ex
export BOARD_ID="${1?}"
export BOARD_NAME="${2:-$1}"
vagrant plugin list | grep vagrant-libvirt || vagrant plugin install vagrant-libvirt
vagrant plugin list | grep vagrant-sshfs || vagrant plugin install vagrant-sshfs
export VAGRANT_DEFAULT_PROVIDER=libvirt
vagrant box list | grep generic/ubuntu2204 || vagrant box add --provider libvirt generic/ubuntu2204
cd "$(dirname "$0")/armbian"
mkdir -p "../../output"
trap 'echo "Cleaning up vagrant..."; vagrant halt; vagrant destroy -f' EXIT
BOARD_ID="$BOARD_ID" BOARD_NAME="$BOARD_NAME" vagrant up --provider=libvirt
exit 0

View File

@ -30,8 +30,11 @@ source etc/library.sh # sets RELEASE
prepare_dirs # tmp cache output
# get latest armbian
[[ -d armbian ]] || git clone https://github.com/armbian/build armbian
( cd armbian && git pull --ff-only --tags && git checkout v23.02 )
[[ -d armbian ]] || git clone --depth 1 https://github.com/armbian/build armbian
#( cd armbian && git pull --ff-only --tags && git checkout v23.02 )
#sed -i -e '/export rootfs_size=/s/du -sm/du --apparent-size -sm/' armbian/lib/functions/image/partitioning.sh
#sed -i -e '/export rootfs_size_mib=/s/du -sm/du --apparent-size -sm/' armbian/lib/functions/main/rootfs-image.sh
#sed -i -e 's/du /du --apparent-size /' armbian/lib/functions/image/rootfs-to-image.sh
# add NCP modifications
mkdir -p armbian/userpatches armbian/userpatches/overlay
@ -48,7 +51,6 @@ cat > "$CONF" <<EOF
BOARD="$BOARD"
BRANCH=current
RELEASE=${RELEASE%%-security}
KERNEL_ONLY=no
KERNEL_CONFIGURE=prebuilt
BUILD_DESKTOP=no
BUILD_MINIMAL=yes
@ -68,10 +70,10 @@ EXTRA_CONF=build/armbian/"config-$BOARD".conf
# build
rm -rf armbian/output/images
mkdir -p armbian/userpatches
sed -e '/docker.*run/s/-it//' armbian/config/templates/config-docker.conf > armbian/userpatches/config-docker.conf
docker pull "ghcr.io/armbian/build:$(cut -d"." -f1-2 < armbian/VERSION)-$(dpkg --print-architecture)" \
|| docker pull "ghcr.io/armbian/build:latest-$(dpkg --print-architecture)"
sudo armbian/compile.sh docker ncp
#sed -e '/docker.*run/s/-it//' armbian/config/templates/config-docker.conf > armbian/userpatches/config-docker.conf
#docker pull "ghcr.io/armbian/build:$(cut -d"." -f1-2 < armbian/VERSION)-$(dpkg --print-architecture)" \
# || docker pull "ghcr.io/armbian/build:latest-$(dpkg --print-architecture)"
armbian/compile.sh ncp
rm "$CONF"
# pack image

View File

@ -1,5 +1,19 @@
# NextcloudPi Changelog
## [v1.53.0](https://github.com/nextcloud/nextcloudpi/tree/v1.53.0) (2023-11-15) Staged Rollouts and NC 27.1.3
### Changes
- Add support for Nextcloud 27.1.3
- Add support for staged rollouts
- Add admin notification and community participation options
- Add NCP section in Nextcloud settings
### Development related
- Use latest armbian build stack (armbian next)
- Support latest Selenium version in integration tests
## [v1.52.4](https://github.com/nextcloud/nextcloudpi/tree/v1.52.4) (2023-07-31) Fix error in NCP updater
### Fixes

View File

@ -212,6 +212,30 @@ function find_app_param_num()
}
function get_app_params() {
local script="${1?}"
local cfg_file="${CFGDIR}/${script%.sh}.cfg"
[[ -f "$cfg_file" ]] && {
local cfg="$( cat "$cfg_file" )"
local param_count="$(jq ".params | length" <<<"$cfg")"
local i=0
local json="{"
while [[ $i -lt $param_count ]]
do
param_id="$(jq -r ".params[$i].id" <<<"$cfg")"
param_val="$(jq -r ".params[$i].value" <<<"$cfg")"
json="${json}"$'\n'" \"${param_id}\": \"${param_val}\""
i=$((i+1))
[[ $i -lt $param_count ]] && json="${json},"
done
json="${json}"$'\n'"}"
echo "$json"
return 0
}
return 1
}
install_template() {
local template="${1?}"
local target="${2?}"

View File

@ -9,7 +9,7 @@
{
"id": "VER",
"name": "Version",
"value": "27.0.1"
"value": "27.1.3"
},
{
"id": "MAXFILESIZE",

View File

@ -0,0 +1,35 @@
{
"id": "ncp-community",
"name": "NCP Community",
"title": "NCP Community Settings",
"description": "Configure various NCP community options",
"info": "",
"infotitle": "",
"params": [
{
"id": "CANARY",
"name": "Enable canary (testing) channel for updates",
"value": "no",
"type": "bool"
},
{
"id": "ADMIN_NOTIFICATIONS",
"name": "Enable notifications about changes in NCP",
"value": "yes",
"type": "bool"
},
{
"id": "USAGE_SURVEYS",
"name": "Help me improve NCP by participating in occasional usage surveys",
"value": "no",
"type": "bool"
},
{
"id": "NOTIFICATION_ACCOUNTS",
"name": "Limit notifications to these accounts",
"value": "",
"suggest": "Comma separated list of nextcloud accounts",
"type": "text"
}
]
}

View File

@ -1,5 +1,5 @@
{
"nextcloud_version": "27.0.1",
"nextcloud_version": "27.1.3",
"php_version": "8.1",
"release": "bullseye"
}

16
generate_cohortes.sh Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
#min=$((101-${1:-50}))
all=({100..1})
cohortes=()
for i in "${all[@]:0:${1?}}"
do
cohorte_id=$((RANDOM % i))
while [[ " ${cohortes[*]} " =~ .*" ${cohorte_id} ".* ]]
do
cohorte_id=$((cohorte_id+1))
done
cohortes+=($cohorte_id)
done
echo "${cohortes[*]}" | tr ' ' $'\n'

View File

@ -14,8 +14,8 @@ BRANCH="${BRANCH:-master}"
set -e$DBG
TMPDIR="$(mktemp -d /tmp/nextcloudpi.XXXXXX || (echo "Failed to create temp dir. Exiting" >&2 ; exit 1) )"
trap "rm -rf \"${TMPDIR}\"" 0 1 2 3 15
TEMPDIR="$(mktemp -d /tmp/nextcloudpi.XXXXXX || (echo "Failed to create temp dir. Exiting" >&2 ; exit 1) )"
trap "rm -rf \"${TEMPDIR}\"" 0 1 2 3 15
[[ ${EUID} -ne 0 ]] && {
printf "Must be run as root. Try 'sudo $0'\n"
@ -35,10 +35,12 @@ apt-get install --no-install-recommends -y git ca-certificates sudo lsb-release
# get install code
if [[ "${CODE_DIR}" == "" ]]; then
echo "Getting build code..."
CODE_DIR="${TMPDIR}"/nextcloudpi
git clone -b "${BRANCH}" https://github.com/nextcloud/nextcloudpi.git "${CODE_DIR}"
CODE_DIR_TMP="${TEMPDIR}"/nextcloudpi
git clone -b "${BRANCH}" https://github.com/nextcloud/nextcloudpi.git "${CODE_DIR_TMP}"
cd "$CODE_DIR_TMP"
else
cd "${CODE_DIR}"
fi
cd "${CODE_DIR}"
# install NCP
echo -e "\nInstalling NextCloudPi..."
@ -82,7 +84,7 @@ rm /.ncp-image
[[ "${CODE_DIR}" != "" ]] || bash /usr/local/bin/ncp-provisioning.sh
cd -
rm -rf "${TMPDIR}"
rm -rf "${TEMPDIR}"
IP="$(get_ip)"

7
ncp-app/.eslintrc.js Normal file
View File

@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = {
extends: [
'@nextcloud',
]
}

3
ncp-app/.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
# SPDX-License-Identifier: AGPL-3.0-or-later
/js/* binary

9
ncp-app/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
# SPDX-License-Identifier: AGPL-3.0-or-later
.idea
*.iml
/vendor/
/build/
node_modules/
/.php_cs.cache
js/*hot-update.*

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
require_once './vendor/autoload.php';
use Nextcloud\CodingStandard\Config;
$config = new Config();
$config
->getFinder()
->ignoreVCSIgnored(true)
->notPath('build')
->notPath('l10n')
->notPath('src')
->notPath('vendor')
->in(__DIR__);
return $config;

164
ncp-app/Makefile Normal file
View File

@ -0,0 +1,164 @@
# SPDX-FileCopyrightText: Bernhard Posselt <dev@bernhard-posselt.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
# Generic Makefile for building and packaging a Nextcloud app which uses npm and
# Composer.
#
# Dependencies:
# * make
# * which
# * curl: used if phpunit and composer are not installed to fetch them from the web
# * tar: for building the archive
# * npm: for building and testing everything JS
#
# If no composer.json is in the app root directory, the Composer step
# will be skipped. The same goes for the package.json which can be located in
# the app root or the js/ directory.
#
# The npm command by launches the npm build script:
#
# npm run build
#
# The npm test command launches the npm test script:
#
# npm run test
#
# The idea behind this is to be completely testing and build tool agnostic. All
# build tools and additional package managers should be installed locally in
# your project, since this won't pollute people's global namespace.
#
# The following npm scripts in your package.json install and update the bower
# and npm dependencies and use gulp as build system (notice how everything is
# run from the node_modules folder):
#
# "scripts": {
# "test": "node node_modules/gulp-cli/bin/gulp.js karma",
# "prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
# "build": "node node_modules/gulp-cli/bin/gulp.js"
# },
app_name=$(notdir $(CURDIR))
build_tools_directory=$(CURDIR)/build/tools
source_build_directory=$(CURDIR)/build/artifacts/source
source_package_name=$(source_build_directory)/$(app_name)
appstore_build_directory=$(CURDIR)/build/artifacts
appstore_package_name=$(appstore_build_directory)/$(app_name)
npm=$(shell which npm 2> /dev/null)
composer=$(shell which composer 2> /dev/null)
all: build
# Fetches the PHP and JS dependencies and compiles the JS. If no composer.json
# is present, the composer step is skipped, if no package.json or js/package.json
# is present, the npm step is skipped
.PHONY: build
build:
ifneq (,$(wildcard $(CURDIR)/composer.json))
make composer
endif
ifneq (,$(wildcard $(CURDIR)/package.json))
make npm
endif
ifneq (,$(wildcard $(CURDIR)/js/package.json))
make npm
endif
cp js-src/*.js js/
# Installs and updates the composer dependencies. If composer is not installed
# a copy is fetched from the web
.PHONY: composer
composer:
ifeq (, $(composer))
@echo "No composer command available, downloading a copy from the web"
mkdir -p $(build_tools_directory)
curl -sS https://getcomposer.org/installer | php
mv composer.phar $(build_tools_directory)
php $(build_tools_directory)/composer.phar install --prefer-dist
else
composer install --prefer-dist
endif
# Installs npm dependencies
.PHONY: npm
npm:
ifeq (,$(wildcard $(CURDIR)/package.json))
cd js && $(npm) run build
else
npm run build
endif
# Removes the appstore build
.PHONY: clean
clean:
rm -rf ./build
# Same as clean but also removes dependencies installed by composer, bower and
# npm
.PHONY: distclean
distclean: clean
rm -rf vendor
rm -rf node_modules
rm -rf js/vendor
rm -rf js/node_modules
# Builds the source and appstore package
.PHONY: dist
dist:
make source
make appstore
# Builds the source package
.PHONY: source
source:
rm -rf $(source_build_directory)
mkdir -p $(source_build_directory)
tar cvzf $(source_package_name).tar.gz \
--exclude-vcs \
--exclude="../$(app_name)/build" \
--exclude="../$(app_name)/js/node_modules" \
--exclude="../$(app_name)/node_modules" \
--exclude="../$(app_name)/*.log" \
--exclude="../$(app_name)/js/*.log" \
../$(app_name) \
# Builds the source package for the app store, ignores php tests, js tests
# and build related folders that are unnecessary for an appstore release
.PHONY: appstore
appstore:
rm -rf $(appstore_build_directory)
mkdir -p $(appstore_build_directory)
tar cvzf $(appstore_package_name).tar.gz \
--exclude-vcs \
--exclude="../$(app_name)/build" \
--exclude="../$(app_name)/tests" \
--exclude="../$(app_name)/Makefile" \
--exclude="../$(app_name)/*.log" \
--exclude="../$(app_name)/phpunit*xml" \
--exclude="../$(app_name)/composer.*" \
--exclude="../$(app_name)/node_modules" \
--exclude="../$(app_name)/js/node_modules" \
--exclude="../$(app_name)/js/tests" \
--exclude="../$(app_name)/js/test" \
--exclude="../$(app_name)/js/*.log" \
--exclude="../$(app_name)/js/package.json" \
--exclude="../$(app_name)/js/bower.json" \
--exclude="../$(app_name)/js/karma.*" \
--exclude="../$(app_name)/js/protractor.*" \
--exclude="../$(app_name)/package.json" \
--exclude="../$(app_name)/bower.json" \
--exclude="../$(app_name)/karma.*" \
--exclude="../$(app_name)/protractor\.*" \
--exclude="../$(app_name)/.*" \
--exclude="../$(app_name)/js/.*" \
--exclude="../$(app_name)/webpack.config.js" \
--exclude="../$(app_name)/stylelint.config.js" \
--exclude="../$(app_name)/CHANGELOG.md" \
--exclude="../$(app_name)/README.md" \
--exclude="../$(app_name)/package-lock.json" \
--exclude="../$(app_name)/LICENSES" \
../$(app_name) \
.PHONY: test
test: composer
$(CURDIR)/vendor/phpunit/phpunit/phpunit -c phpunit.xml
$(CURDIR)/vendor/phpunit/phpunit/phpunit -c phpunit.integration.xml

View File

@ -2,17 +2,17 @@
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>nextcloudpi</id>
<name>NextCloudPi</name>
<name>NextcloudPi</name>
<summary>Nextcloud management tools</summary>
<description><![CDATA[NextCloudPi features a preconfigured Nextcloud instance and a complete set of tools around it for easy management.]]></description>
<version>0.0.1</version>
<version>0.0.2</version>
<licence>agpl</licence>
<author mail="nachoparker@ownyourbits.com" homepage="https://ownyourbits.com">nachoparker</author>
<namespace>NextCloudPi</namespace>
<namespace>NextcloudPi</namespace>
<category>tools</category>
<bugs>https://github.com/nextcloud/nextcloudpi/issues</bugs>
<dependencies>
<nextcloud min-version="14" max-version="27"/>
<nextcloud min-version="22" max-version="27"/>
</dependencies>
<navigations>
<navigation>
@ -20,4 +20,7 @@
<route>nextcloudpi.page.index</route>
</navigation>
</navigations>
<settings>
<admin>OCA\NextcloudPi\Settings\AdminSettings</admin>
</settings>
</info>

View File

@ -2,13 +2,14 @@
/**
* Create your routes in here. The name is the lowercase name of the controller
* without the controller part, the stuff after the hash is the method.
* e.g. page#index -> OCA\NextCloudPi\Controller\PageController->index()
* e.g. page#index -> OCA\NextcloudPi\Controller\PageController->index()
*
* The controller class has to be registered in the application.php file since
* it's instantiated in there
*/
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
]
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'settings#save', 'url' => '/api/settings', 'verb' => 'POST']
]
];

5
ncp-app/babel.config.js Normal file
View File

@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
const babelConfig = require('@nextcloud/babel-config')
module.exports = babelConfig

38
ncp-app/composer.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "nextcloud/nextcloudpi",
"description": "NextcloudPi App",
"type": "project",
"license": "AGPL-3.0-or-later",
"authors": [
{
"name": "Tobias Knöppler"
}
],
"require-dev": {
"phpunit/phpunit": "^9",
"sabre/dav": "^4.1",
"sabre/xml": "^2.2",
"symfony/event-dispatcher": "^5.3.11",
"christophwurst/nextcloud": "dev-master@dev",
"psalm/phar": "^4.10",
"nextcloud/coding-standard": "^1.0"
},
"scripts": {
"lint": "find nc-app/nextcloudpi -name \\\\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar --threads=1",
"psalm:update-baseline": "psalm.phar --threads=1 --update-baseline",
"psalm:update-baseline:force": "psalm.phar --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml",
"psalm:clear": "psalm.phar --clear-cache && psalm --clear-global-cache",
"psalm:fix": "psalm.phar --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true
},
"platform": {
"php": "7.4"
}
}
}

2839
ncp-app/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

38
ncp-app/css/admin.css Normal file
View File

@ -0,0 +1,38 @@
#nextcloudpi li {
display: flex;
flex-direction: row;
justify-content: start;
margin-left: 2em;
}
#nextcloudpi li div:first-of-type {
width: 10em;
white-space: nowrap;
}
#nextcloudpi li * {
line-height: 36px;
}
#nextcloudpi hidden {
display: none;
}
#nextcloudpi .error-message {
padding: 1em;
color: red;
border-width: 1px;
border-color: red;
border-style: dashed;
}
#nextcloudpi input[name="notificationAccounts"] {
width: 27em;
}
.divider {
height: .5em;
border-top-width: 1px;
border-top-style: dotted;
border-top-color: var(--color-main-text);
}

6
ncp-app/js-src/script.js Normal file
View File

@ -0,0 +1,6 @@
// open the NCP web panel
var url = window.location.protocol + '//' + window.location.hostname + ':4443';
if ( !window.open( url, '_blank' ) ) // try to open in a new tab first
window.location.href = url;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\NextcloudPi\AppInfo;
use OCP\AppFramework\App;
class Application extends App {
public const APP_ID = 'nextcloudpi';
public function __construct() {
parent::__construct(self::APP_ID);
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace OCA\NextCloudPi\Controller;
namespace OCA\NextcloudPi\Controller;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace OCA\NextcloudPi\Controller;
use OCA\NextcloudPi\Exceptions\InvalidSettingsException;
use OCA\NextcloudPi\Exceptions\SaveSettingsException;
use OCA\NextcloudPi\Service\SettingsService;
use OCP\IRequest;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Controller;
class SettingsController extends Controller {
/** @var SettingsService */
private $service;
/**
* SettingsController constructor
* @param SettingsService $service
*/
public function __construct(SettingsService $service) {
$this->service = $service;
}
/**
* @NoCSRFRequired
* @CORS
*
* @param array $settings
*/
public function save(array $settings): JSONResponse {
try {
$this->service->saveSettings($settings);
return new JSONResponse([]);
} catch(InvalidSettingsException $e) {
return new JSONResponse(["error" => $e->getMessage()], Http::STATUS_BAD_REQUEST);
} catch(SaveSettingsException $e) {
return new JSONResponse(["error" => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace OCA\NextcloudPi\Exceptions;
use Exception;
class InvalidSettingsException extends Exception {
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace OCA\NextcloudPi\Exceptions;
use Exception;
class SaveSettingsException extends Exception {
}
?>

View File

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace OCA\NextcloudPi\Service;
use OCA\NextcloudPi\Exceptions\InvalidSettingsException;
use OCA\NextcloudPi\Exceptions\SaveSettingsException;
use Psr\Log\LoggerInterface;
class SettingsService {
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
/**
* @param $name string of the config
* @param array $defaults Default value to use if the config can't be loaded
* @return array
*/
public function getConfig(string $name, array $defaults): array
{
[$ret, $config_str, $stderr] = $this->runCommand( "bash -c \"sudo /home/www/ncp-app-bridge.sh config $name\"");
$config = null;
if ($ret == 0) {
try {
$config = json_decode($config_str, true, 512, JSON_THROW_ON_ERROR);
} catch (\Exception $e) {
$this->logger->error($e);
}
}
if ($config == null) {
$this->logger->error("Failed to retrieve ncp config (exit code: $ret)");
return $defaults;
}
return $config;
}
/**
* @param $name string of the config
* @param string $defaults Default value to use if the file can't be loaded
* @return string
*/
public function getFileContent(string $name, string $defaults): string
{
[$ret, $file_contents, $stderr] = $this->runCommand( "bash -c \"sudo /home/www/ncp-app-bridge.sh file $name\"");
if ($ret != 0) {
return $defaults;
}
return $file_contents;
}
/**
* @throws InvalidSettingsException
* @throws SaveSettingsException
*/
public function saveSettings(array $settings) {
$parseBool = function ($val): string {
return $val ? "yes" : "no";
};
$identityFn = function ($val) {
return $val;
};
$settings_map = [
"canary" => ["ncp-community", "CANARY", $parseBool],
"adminNotifications" => ["ncp-community", "ADMIN_NOTIFICATIONS", $parseBool],
"usageSurveys" => ["ncp-community", "USAGE_SURVEYS", $parseBool],
"notificationAccounts" => ["ncp-community", "NOTIFICATION_ACCOUNTS", $identityFn]
];
foreach ($settings as $k => $value) {
[$cfgName, $fieldName, $fn] = $settings_map[$k];
if ($cfgName == null || $fieldName == null) {
throw new InvalidSettingsException("key error for '$k'");
}
$parsed = $fn($value);
$cmd = "bash -c \"sudo /home/www/ncp-app-bridge.sh config '$cfgName' '$fieldName=$parsed'\"";
[$ret, $stdout, $stderr] = $this->runCommand($cmd);
if ($ret !== 0) {
throw new SaveSettingsException(
"Failed to save NCP settings '$cfgName/$fieldName': \n error output from command:\n\n$cmd"
. str_replace("\n", "\n> ", $stderr));
}
}
}
private function runCommand(string $cmd): array {
$descriptorSpec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"]
];
$proc = proc_open($cmd, $descriptorSpec, $pipes, "/home/www-data", null);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
return [proc_close($proc), $stdout, $stderr];
}
}
?>

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace OCA\NextcloudPi\Settings;
use OCA\NextcloudPi\Service\SettingsService;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Settings\ISettings;
class AdminSettings implements ISettings {
/** @var SettingsService */
private $service;
/**
* AdminSettings constructor
* @param SettingsService $service
*/
public function __construct(SettingsService $service) {
$this->service = $service;
}
/**
* @return TemplateResponse
*/
public function getForm() {
$ncp_config = $this->service->getConfig("ncp",
["nextcloud_version" => "unknown", "php_version" => "unknown", "release" => "unknown"]);
$community_config = $this->service->getConfig("ncp-community",
[
"CANARY" => 'no',
"USAGE_SURVEYS" => 'no',
"ADMIN_NOTIFICATIONS" => 'no',
"NOTIFICATION_ACCOUNTS" => ""
]);
$ncp_version = trim($this->service->getFileContent("ncp-version", "unknown"));
return new TemplateResponse('nextcloudpi', 'admin', [
'community' => $community_config,
'ncp' => $ncp_config,
'ncp_version' => $ncp_version
]);
}
/**
* @return string
*/
public function getSection() {
return "server";
}
/**
* @return int
*/
public function getPriority() {
return 1;
}
}
?>

BIN
ncp-app/ncp.tar.gz Normal file

Binary file not shown.

16325
ncp-app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
ncp-app/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "nextcloudpi",
"description": "NextcloudPi App",
"version": "0.0.1",
"author": "Tobias Knöppler <tobias@knoeppler.net>",
"contributors": [],
"bugs": {
"url": "https://github.com/nextcloud/nextcloudpi"
},
"license": "agpl",
"private": true,
"scripts": {
"build": "webpack --node-env production --progress",
"dev": "webpack --node-env development --progress",
"watch": "webpack --node-env development --progress --watch",
"serve": "webpack --node-env development serve --progress",
"lint": "eslint --ext .js,.vue src",
"lint:fix": "eslint --ext .js,.vue src --fix",
"stylelint": "stylelint css/*.css css/*.scss ../../src/**/*.scss src/**/*.vue",
"stylelint:fix": "stylelint css/*.css css/*.scss ../../src/**/*.scss src/**/*.vue --fix"
},
"dependencies": {
"@nextcloud/axios": "^1.10.0",
"@nextcloud/dialogs": "^3.1.4",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^5.4.0",
"vue": "^2.7.0"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": "^16.0.0",
"npm": "^7.0.0 || ^8.0.0"
},
"devDependencies": {
"@nextcloud/babel-config": "^1.0.0",
"@nextcloud/browserslist-config": "^2.2.0",
"@nextcloud/eslint-config": "^8.0.0",
"@nextcloud/stylelint-config": "^2.1.2",
"@nextcloud/webpack-vue-config": "^5.2.1"
}
}

38
ncp-app/psalm.xml Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorBaseline="tests/psalm-baseline.xml"
>
<!--
SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
SPDX-License-Identifier: CC0-1.0
-->
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<extraFiles>
<directory name="vendor" />
<ignoreFiles>
<directory name="vendor/phpunit/php-code-coverage" />
<directory name="vendor/psalm" />
</ignoreFiles>
</extraFiles>
<issueHandlers>
<UndefinedDocblockClass>
<errorLevel type="suppress">
<referencedClass name="OC\AppFramework\OCS\BaseResponse"/>
<referencedClass name="Doctrine\DBAL\Schema\Schema" />
<referencedClass name="Doctrine\DBAL\Schema\SchemaException" />
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
<referencedClass name="Doctrine\DBAL\Schema\Table" />
</errorLevel>
</UndefinedDocblockClass>
</issueHandlers>
</psalm>

66
ncp-app/src/main-admin.js Normal file
View File

@ -0,0 +1,66 @@
/**
* SPDX-FileCopyrightText: 2018 John Molakvoæ <skjnldsv@protonmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import {generateFilePath, generateUrl} from '@nextcloud/router'
import axios from '@nextcloud/axios'
//
// import Vue from 'vue'
// import App from './App.vue'
// eslint-disable-next-line
__webpack_public_path__ = generateFilePath(appName, '', 'js/')
//
// Vue.mixin({ methods: { t, n } })
//
// export default new Vue({
// el: '#nextcloudpi',
// render: h => h(App),
// })
async function saveSettings() {
let settings = collectSettings();
console.log("Saving nextcloudpi settings: ", settings);
try {
let response = await axios.post(generateUrl('/apps/nextcloudpi/api/settings'), {settings: settings})
console.log("Saving ncp settings succeeded")
return {success: true, error: null}
} catch (e) {
// console.log("axios failure: ", arguments)
console.error(e)
let errMsg = e.response.data.error;
throw Error(`${errMsg ? errMsg : e.message} (HTTP ${e.response.status})`)
}
}
function collectSettings() {
let settings = {};
document.querySelectorAll("#nextcloudpi input").forEach(element => {
if (element.type === "checkbox") {
settings[element.name] = element.checked;
} else {
settings[element.name] = element.value;
}
});
return settings;
}
window.addEventListener('load', () => {
console.log("Listening to ncp settings changes");
let errorBox = document.querySelector("#nextcloudpi .error-message");
document.querySelectorAll("#nextcloudpi input").forEach(element => {
element.addEventListener("change", async () => {
saveSettings()
.then(() => {
errorBox.classList.add("hidden");
})
.catch(e => {
console.error(e);
errorBox.innerText = "Failed to save NextcloudPi settings: " + e.message;
errorBox.classList.remove("hidden");
})
})
})
})

View File

@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: Nextcloud contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
const stylelintConfig = require('@nextcloud/stylelint-config')
module.exports = stylelintConfig

View File

@ -0,0 +1,42 @@
<?php
script('nextcloudpi', 'nextcloudpi-admin');
style('nextcloudpi', 'admin');
?>
<div id="nextcloudpi" class="section">
<h2>NextcloudPi</h2>
<h3>System Info</h3>
<ul>
<li>
<div>NCP Version:</div><div><?php echo $_["ncp_version"] ?></div>
</li>
<li>
<div>PHP Version:</div><div><?php echo $_["ncp"]["php_version"] ?></div>
</li>
<li>
<div>Debian Release:</div><div><?php echo $_["ncp"]["release"] ?></div>
</li>
</ul>
<h3>Settings</h3>
<ul>
<li>
<input name="canary" type="checkbox" <?php echo $_['community']['CANARY'] === 'yes' ? ' checked="checked"' : ''; ?>"/>
<label for="canary">Enable updates from canary (testing) channel</label>
</li>
<li>
<input name="adminNotifications" type="checkbox" <?php echo $_['community']['ADMIN_NOTIFICATIONS'] === 'yes' ? ' checked="checked"' : ''; ?>"/>
<label for="adminNotifications">Enable notifications about relevant changes in NCP</label>
</li>
<li>
<input name="usageSurveys" type="checkbox" <?php echo $_['community']['USAGE_SURVEYS'] === 'yes' ? ' checked="checked"' : ''; ?>"/>
<label for="usageSurveys">Enable notifications for surveys that help to improve NCP</label>
</li>
<li>
<div>Accounts to notify:</div>
<input type="text" name="notificationAccounts"
placeholder="comma separated list of accounts. Default is: all admins"
value="<?php echo $_['community']['NOTIFICATION_ACCOUNTS']; ?>"/>
</li>
</ul>
<p class="error-message hidden"></p>
</div>

12
ncp-app/webpack.config.js Normal file
View File

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: Tobias Knöppler <tobias@knoeppler.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
const path = require('path')
const webpackConfig = require('@nextcloud/webpack-vue-config')
module.exports = {...webpackConfig,
...{
entry: {
admin: path.join(__dirname, 'src/main-admin')
}
}
}

View File

@ -136,7 +136,7 @@ HTML;
<div id="header-left">
<a href="https://nextcloudpi.com" id="nextcloudpi" target="_blank" tabindex="1">
<div class="logo-icon">
<h1 class="hidden-visually">NextCloudPi</h1>
<h1 class="hidden-visually">NextcloudPi</h1>
</div>
</a>
<a id=versionlink target="_blank" href="https://github.com/nextcloud/nextcloudpi/blob/master/changelog.md">

54
ncp.sh
View File

@ -176,7 +176,59 @@ grep -q '[\\&#;`|*?~<>^()[{}$&]' <<< "$*" && exit 1
tar $pigz -tf "$file" data &>/dev/null
EOF
chmod 700 /home/www/ncp-backup-launcher.sh
echo "www-data ALL = NOPASSWD: /home/www/ncp-launcher.sh , /home/www/ncp-backup-launcher.sh, /sbin/halt, /sbin/reboot" >> /etc/sudoers
cat > /home/www/ncp-app-bridge.sh <<'EOF'
#!/bin/bash
set -e
grep -q '[\\&#;`|*?~<>^()[{}$&]' <<< "$*" && exit 1
action="${1?}"
[[ "$action" == "config" ]] && {
config_type="${2?}"
arg="${3}"
[[ -z "$arg" ]] || {
key="${arg%=*}"
val="${arg#*=}"
}
if [[ "$config_type" == "ncp" ]]
then
config_path="/usr/local/etc/ncp.cfg"
elif [[ "$config_type" == "ncp-community" ]]
then
. /usr/local/etc/library.sh
[[ -z "${key}" ]] || {
set_app_param ncp-community.sh "${key}" "${val}"
}
get_app_params ncp-community.sh
exit $?
else
echo "ERROR: Invalid config name '${config_type}'" >&2
exit 1
fi
[[ -z "${key}" ]] || {
cfg="$(jq ".${key} = \"${val}\"" <"$config_path")"
echo "$cfg" > "$config_path"
}
cat "$config_path"
exit 0
}
[[ "$action" == "file" ]] && {
file="${2?}"
if [[ "$file" == "ncp-version" ]]
then
cat /usr/local/etc/ncp-version
else
echo "ERROR: Invalid file '${file}'" >&2
exit 1
fi
exit 0
}
EOF
chmod 700 /home/www/ncp-app-bridge.sh
echo "www-data ALL = NOPASSWD: /home/www/ncp-launcher.sh , /home/www/ncp-backup-launcher.sh, /home/www/ncp-app-bridge.sh, /sbin/halt, /sbin/reboot" >> /etc/sudoers
# NCP AUTO TRUSTED DOMAIN
mkdir -p /usr/lib/systemd/system

View File

@ -163,13 +163,13 @@ if __name__ == "__main__":
sys.exit(2)
arg_timeout = 120
options = Options()
options = webdriver.FirefoxOptions()
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
sys.exit(2)
elif opt == '--no-gui':
options.headless = True
options.add_argument("-headless")
elif opt in ('-t', '--timeout'):
arg_timeout = int(arg)
else:

View File

@ -11,18 +11,20 @@ Use at your own risk!
More at https://ownyourbits.com
"""
import json
import sys
import time
import urllib
import os
import getopt
import configparser
import signal
import re
import time
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.firefox.options import Options
from selenium.common.exceptions import NoSuchElementException, WebDriverException, TimeoutException
@ -48,14 +50,14 @@ class TestFailed(Exception):
class Test:
title = "test"
title = "test"
def new(self, title):
self.title = title
print("[check] " + "{:16}".format(title), end=' ', flush = True)
def check(self, expression, msg=None):
if expression:
if expression and not isinstance(expression, Exception):
print(tc.green + "ok" + tc.normal)
self.log("ok")
else:
@ -66,7 +68,6 @@ class Test:
exc_args.append(expression)
if msg is not None:
exc_args.append(msg)
raise TestFailed(*exc_args)
def report(self, title, expression, msg=None):
@ -112,6 +113,13 @@ class VisibilityOfElementLocatedByAnyLocator:
class ConfigTestFailure(Exception):
pass
def is_admin_notifications_checkbox(item: WebElement):
try:
input_item = item.find_element(By.TAG_NAME, "input")
return input_item.get_attribute("name") == "adminNotifications"
except:
return False
def test_nextcloud(IP: str, nc_port: str, driver: WebDriver):
""" Login and assert admin page checks"""
@ -132,12 +140,15 @@ def test_nextcloud(IP: str, nc_port: str, driver: WebDriver):
try:
driver.find_element(By.ID, "submit").click()
except NoSuchElementException:
pass
try:
driver.find_element(By.CSS_SELECTOR, ".login-form button[type=submit]").click()
except NoSuchElementException:
pass
test.report("password", "Wrong password" not in driver.page_source, msg="Failed to login with provided password")
test.new("settings config")
wait = WebDriverWait(driver, 30)
wait = WebDriverWait(driver, 60)
try:
wait.until(VisibilityOfElementLocatedByAnyLocator([(By.CSS_SELECTOR, "#security-warning-state-ok"),
(By.CSS_SELECTOR, "#security-warning-state-warning"),
@ -154,10 +165,11 @@ def test_nextcloud(IP: str, nc_port: str, driver: WebDriver):
infos = driver.find_elements(By.CSS_SELECTOR, "#postsetupchecks > .info > li")
for info in infos:
if re.match(r'.*Your installation has no default phone region set.*', info.text):
if re.match(r'.*Your installation has no default phone region set.*', info.text) \
or re.match(r'The PHP module "imagick" is not enabled', info.text):
continue
else:
print('text', info.text)
php_modules = info.find_elements(By.CSS_SELECTOR, "li")
if len(php_modules) != 1:
raise ConfigTestFailure(f"Could not find the list of php modules within the info message "
@ -174,6 +186,88 @@ def test_nextcloud(IP: str, nc_port: str, driver: WebDriver):
except Exception as e:
test.check(e)
try:
overlay_close_btn = driver.find_element(By.CLASS_NAME, "modal-container__close")
if overlay_close_btn.is_displayed():
overlay_close_btn.click()
time.sleep(3)
except NoSuchElementException:
pass
test.new("admin section (1)")
try:
driver.get(f"https://{IP}:{nc_port}/index.php/settings/admin")
except Exception as e:
test.check(e, msg=f"{tc.red}error:{tc.normal} unable to reach {tc.yellow + IP + tc.normal}")
old_admin_notifications_value = None
list_items = driver.find_elements(By.CSS_SELECTOR, "#nextcloudpi li")
try:
wait.until(lambda drv: drv.find_element(By.ID, "nextcloudpi").is_displayed())
expected = {
"ncp_version": False,
"php_version": False,
"debian_release": False,
"canary": False,
"admin_notifications": False,
# "usage_surveys": False,
"notification_accounts": False
}
version_re = re.compile(r'^(v\d+\.\d+\.\d+)$')
with (Path(__file__).parent / '../etc/ncp.cfg').open('r') as cfg_file:
ncp_cfg = json.load(cfg_file)
for li in list_items:
try:
inp = li.find_element(By.TAG_NAME, "input")
inp_name = inp.get_attribute("name")
inp_value = inp.get_attribute("value") if inp.get_attribute("type") != "checkbox" else inp.is_selected()
if inp_name == "canary":
expected["canary"] = True
elif inp_name == "adminNotifications":
old_admin_notifications_value = inp_value
expected["admin_notifications"] = True
elif inp_name == "usageSurveys":
expected["usage_surveys"] = True
elif inp_name == "notificationAccounts":
expected["notification_accounts"] = True
except:
divs = li.find_elements(By.TAG_NAME, "div")
if 'ncp version' in divs[0].text.lower() and version_re.match(divs[1].text):
expected['ncp_version'] = True
elif 'php version' in divs[0].text.lower() and divs[1].text == ncp_cfg['php_version']:
expected['php_version'] = True
elif 'debian release' in divs[0].text.lower() and divs[1].text == ncp_cfg['release']:
expected['debian_release'] = True
failed = list(map(lambda item: item[0], filter(lambda item: not item[1], expected.items())))
test.check(len(failed) == 0, f"checks failed for admin section: [{', '.join(failed)}]")
except Exception as e:
test.check(e)
test.new("admin section (2)")
wait = WebDriverWait(driver, 10)
try:
li = next(filter(is_admin_notifications_checkbox, list_items))
li.find_element(By.TAG_NAME, "input").click()
time.sleep(15)
wait.until(lambda drv: drv.find_element(By.CSS_SELECTOR, "#nextcloudpi .error-message:not(.hidden)"))
error_box = driver.find_element(By.CSS_SELECTOR, "#nextcloudpi .error-message")
test.check(False, str(error_box.text))
except Exception as e:
if isinstance(e, TestFailed):
raise e
test.check(True)
test.new("admin section (3)")
try:
driver.refresh()
except Exception as e:
test.check(e, msg=f"{tc.red}error:{tc.normal} unable to reach {tc.yellow + IP + tc.normal}")
try:
list_items = driver.find_elements(By.CSS_SELECTOR, "#nextcloudpi li")
li = next(filter(is_admin_notifications_checkbox, list_items))
test.check(li.find_element(By.TAG_NAME, "input").is_selected() != old_admin_notifications_value,
"Toggling admin notifications didn't work")
except Exception as e:
test.check(e)
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
@ -185,7 +279,7 @@ if __name__ == "__main__":
usage()
sys.exit(2)
options = Options()
options = webdriver.FirefoxOptions()
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
@ -194,7 +288,7 @@ if __name__ == "__main__":
if os.path.exists(test_cfg):
os.unlink(test_cfg)
elif opt == '--no-gui':
options.headless = True
options.add_argument("-headless")
else:
usage()
sys.exit(2)
@ -231,13 +325,17 @@ if __name__ == "__main__":
print("---------------------------")
driver = webdriver.Firefox(options=options)
failed=False
try:
test_nextcloud(IP, nc_port, driver)
except Exception as e:
print(e)
print(traceback.format_exc())
failed=True
finally:
driver.close()
if failed:
sys.exit(1)
# License
#

View File

@ -1,2 +1,2 @@
robotframework
selenium
robotframework

View File

@ -11,6 +11,8 @@ Use at your own risk!
More at https://ownyourbits.com
"""
import json
import subprocess
pre_cmd = []
@ -18,7 +20,8 @@ import sys
import getopt
import os
import signal
from subprocess import run, getstatusoutput, PIPE
from subprocess import run, getstatusoutput, PIPE, CompletedProcess
from typing import Optional
processes_must_be_running = [
'apache2',
@ -174,23 +177,95 @@ def signal_handler(sig, frame):
sys.exit(0)
class ProcessExecutionException(Exception):
pass
def test_autoupdates():
def handle_error(r: CompletedProcess) -> CompletedProcess:
if r.returncode != 0:
print(f"{tc.red}error{tc.normal}\n{r.stdout.decode('utf-8') if r.stdout else ''}\n{r.stderr.decode('utf-8') if r.stderr else ''}"
f" -- command failed: '{' '.join(r.args)}'")
raise ProcessExecutionException()
return CompletedProcess(r.args,
r.returncode,
r.stdout.decode('utf-8') if r.stdout else '',
r.stderr.decode('utf-8') if r.stderr else '')
def set_cohorte_id(cohorte_id: int) -> CompletedProcess:
proc = subprocess.Popen(pre_cmd + ['cat', '/usr/local/etc/instance.cfg'], stdout=subprocess.PIPE, shell=False)
#handle_error(run(pre_cmd + ['cat', '/usr/local/etc/instance.cfg'], stdout=subprocess.STDOUT, stderr=subprocess.STDOUT))
#r = handle_error(run(pre_cmd + ['cat', '/usr/local/etc/instance.cfg'], stdout=PIPE, stderr=PIPE))
(out, err) = proc.communicate()
if proc.returncode != 0:
raise ProcessExecutionException()
try:
instance_cfg = json.loads(out)
except json.decoder.JSONDecodeError as e:
print(f"{tc.red}error{tc.normal} /usr/local/etc/instance.cfg could not be parsed, was: {out}\n{err}")
print(f"Command: '{' '.join(pre_cmd + ['cat', '/usr/local/etc/instance.cfg'])}'")
raise e
instance_cfg['cohorteId'] = cohorte_id
return handle_error(run(pre_cmd + ['bash', '-c', f'echo \'{json.dumps(instance_cfg)}\' > /usr/local/etc/instance.cfg'], stdout=PIPE, stderr=PIPE))
print(f"[updates] {tc.brown}staged rollouts{tc.normal}", end=' ')
try:
result = handle_error(run(pre_cmd + ['cat', '/usr/local/etc/ncp-version'], stdout=PIPE, stderr=PIPE))
if 'v99.99.99' in result.stdout:
print(f"{tc.yellow}skipped{tc.normal} (already updated to v99.99.99)")
return True
handle_error(run(pre_cmd + ['rm', '-f', '/var/run/.ncp-latest-version']))
handle_error(run(pre_cmd + ['sed', '-i', 's|BRANCH="master"|BRANCH="testing/staged-rollouts-1"|', '/usr/local/bin/ncp-check-version'], stdout=PIPE, stderr=PIPE))
set_cohorte_id(1)
result = run(pre_cmd + ['test', '-f', '/var/run/.ncp-latest-version'], stdout=PIPE, stderr=PIPE)
if result.returncode == 0:
result = handle_error(run(pre_cmd + ['cat', '/var/run/.ncp-latest-version'], stdout=PIPE, stderr=PIPE))
if 'v99.99.99' in result.stdout:
print(f"{tc.red}error{tc.normal} Auto update to v99.99.99 was unexpectedly not prevented by disabled cohorte id")
return False
set_cohorte_id(99)
handle_error(run(pre_cmd + ['/usr/local/bin/ncp-check-version'], stdout=PIPE, stderr=PIPE))
result = handle_error(run(pre_cmd + ['cat', '/var/run/.ncp-latest-version'], stdout=PIPE, stderr=PIPE))
if 'v99.99.99' not in result.stdout:
print(f"{tc.red}error{tc.normal} Expected latest detected version to be v99.99.99, was {result.stdout}")
return False
handle_error(run(pre_cmd + ['/usr/local/bin/ncp-test-updates']))
handle_error(run(pre_cmd + ['ncp-update', 'testing/staged-rollouts-1'], stdout=PIPE, stderr=PIPE))
result = handle_error(run(pre_cmd + ['cat', '/usr/local/etc/v99.99.99.success'], stdout=PIPE, stderr=PIPE))
if 'updated' not in result.stdout:
print(f"{tc.red}error{tc.normal} update to v99.99.99 did not succeed")
return False
print(f"{tc.green}ok{tc.normal}")
except ProcessExecutionException:
return False
return True
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
# parse options
try:
opts, args = getopt.getopt(sys.argv[1:], 'h', ['help', 'no-ping', 'non-interactive'])
opts, args = getopt.getopt(sys.argv[1:], 'h', ['help', 'no-ping', 'non-interactive', 'skip-update-test'])
except getopt.GetoptError:
usage()
sys.exit(2)
skip_ping = False
interactive = True
skip_update_test = False
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
sys.exit(2)
elif opt == '--skip-update-test':
print("Skipping update test")
skip_update_test = True
elif opt == '--no-ping':
skip_ping = True
elif opt == '--non-interactive':
@ -256,7 +331,7 @@ if __name__ == "__main__":
tc.yellow + ssh_cmd + tc.normal + "...")
binaries_must_be_installed = binaries_must_be_installed + binaries_no_docker
pre_cmd = ['ssh', '-o UserKnownHostsFile=/dev/null' , '-o PasswordAuthentication=no',
'-o StrictHostKeyChecking=no', '-o ConnectTimeout=1', ssh_cmd[4:]]
'-o StrictHostKeyChecking=no', '-o ConnectTimeout=10', ssh_cmd[4:]]
if not skip_ping:
at_char = ssh_cmd.index('@')
@ -282,8 +357,9 @@ if __name__ == "__main__":
files1_result = check_files_exist(files_must_exist)
files2_result = check_files_dont_exist(files_must_not_exist)
notify_push_result = check_notify_push()
update_test_result = True if skip_update_test else test_autoupdates()
if running_result and install_result and files1_result and files2_result and notify_push_result:
if running_result and install_result and files1_result and files2_result and notify_push_result and update_test_result:
sys.exit(0)
else:
sys.exit(1)

View File

@ -151,7 +151,8 @@ chmod 770 /var/www/ncp-web
# install NC app
rm -rf /var/www/ncp-app
cp -r ncp-app /var/www/
mkdir -p /var/www/ncp-app
cp -r ncp-app/{appinfo,css,img,js,lib,templates} /var/www/ncp-app/
# install ncp-previewgenerator
rm -rf /var/www/ncp-previewgenerator

65
updates/1.53.0sh Normal file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env bash
[[ -f /usr/local/etc/instance.cfg ]] || {
cohorte_id=$((RANDOM % 100))
cat > /usr/local/etc/instance.cfg <<EOF
{
"cohorteId": ${cohorte_id}
}
EOF
}
cat > /home/www/ncp-app-bridge.sh <<'EOF'
#!/bin/bash
set -e
grep -q '[\\&#;`|*?~<>^()[{}$&]' <<< "$*" && exit 1
action="${1?}"
[[ "$action" == "config" ]] && {
config_type="${2?}"
arg="${3}"
[[ -z "$arg" ]] || {
key="${arg%=*}"
val="${arg#*=}"
}
if [[ "$config_type" == "ncp" ]]
then
config_path="/usr/local/etc/ncp.cfg"
elif [[ "$config_type" == "ncp-community" ]]
then
. /usr/local/etc/library.sh
[[ -z "${key}" ]] || {
set_app_param ncp-community.sh "${key}" "${val}"
}
get_app_params ncp-community.sh
exit $?
else
echo "ERROR: Invalid config name '${config_type}'" >&2
exit 1
fi
[[ -z "${key}" ]] || {
cfg="$(jq ".${key} = \"${val}\"" <"$config_path")"
echo "$cfg" > "$config_path"
}
cat "$config_path"
exit 0
}
[[ "$action" == "file" ]] && {
file="${2?}"
if [[ "$file" == "ncp-version" ]]
then
cat /usr/local/etc/ncp-version
else
echo "ERROR: Invalid file '${file}'" >&2
exit 1
fi
exit 0
}
EOF
chmod 700 /home/www/ncp-app-bridge.sh
sed -i 's|www-data ALL = NOPASSWD: .*|www-data ALL = NOPASSWD: /home/www/ncp-launcher.sh , /home/www/ncp-backup-launcher.sh, /home/www/ncp-app-bridge.sh, /sbin/halt, /sbin/reboot|' /etc/sudoers
ncc upgrade