mirror of
https://github.com/nextcloud/nextcloudpi.git
synced 2026-01-10 15:12:01 -03:30
commit
f550440a84
8
.github/workflows/build-lxd.yml
vendored
8
.github/workflows/build-lxd.yml
vendored
@ -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:
|
||||
|
||||
4
.github/workflows/build-sd-images.yml
vendored
4
.github/workflows/build-sd-images.yml
vendored
@ -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
|
||||
|
||||
4
.github/workflows/publish-image.yml
vendored
4
.github/workflows/publish-image.yml
vendored
@ -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
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -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}}
|
||||
|
||||
5
.github/workflows/vm-tests.yml
vendored
5
.github/workflows/vm-tests.yml
vendored
@ -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:"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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+" )
|
||||
|
||||
|
||||
7
bin/ncp/CONFIG/ncp-community.sh
Normal file
7
bin/ncp/CONFIG/ncp-community.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configure various settings for community participation
|
||||
|
||||
install() { :; }
|
||||
|
||||
configure() { :; }
|
||||
@ -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
43
build/armbian/Vagrantfile
vendored
Normal 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
|
||||
@ -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
|
||||
|
||||
30
build/build-SD-armbian-vagrant.sh
Executable file
30
build/build-SD-armbian-vagrant.sh
Executable 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
|
||||
@ -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
|
||||
|
||||
14
changelog.md
14
changelog.md
@ -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
|
||||
|
||||
@ -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?}"
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
{
|
||||
"id": "VER",
|
||||
"name": "Version",
|
||||
"value": "27.0.1"
|
||||
"value": "27.1.3"
|
||||
},
|
||||
{
|
||||
"id": "MAXFILESIZE",
|
||||
|
||||
35
etc/ncp-config.d/ncp-community.cfg
Normal file
35
etc/ncp-config.d/ncp-community.cfg
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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
16
generate_cohortes.sh
Executable 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'
|
||||
14
install.sh
14
install.sh
@ -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
7
ncp-app/.eslintrc.js
Normal 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
3
ncp-app/.gitattributes
vendored
Normal 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
9
ncp-app/.gitignore
vendored
Normal 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.*
|
||||
20
ncp-app/.php_cs-fixer.dist.php
Normal file
20
ncp-app/.php_cs-fixer.dist.php
Normal 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
164
ncp-app/Makefile
Normal 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
|
||||
@ -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>
|
||||
|
||||
@ -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
5
ncp-app/babel.config.js
Normal 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
38
ncp-app/composer.json
Normal 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
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
38
ncp-app/css/admin.css
Normal 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
6
ncp-app/js-src/script.js
Normal 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;
|
||||
2
ncp-app/js/nextcloudpi-admin.js
Normal file
2
ncp-app/js/nextcloudpi-admin.js
Normal file
File diff suppressed because one or more lines are too long
1
ncp-app/js/nextcloudpi-admin.js.map
Normal file
1
ncp-app/js/nextcloudpi-admin.js.map
Normal file
File diff suppressed because one or more lines are too long
16
ncp-app/lib/AppInfo/Application.php
Normal file
16
ncp-app/lib/AppInfo/Application.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace OCA\NextCloudPi\Controller;
|
||||
namespace OCA\NextcloudPi\Controller;
|
||||
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
|
||||
46
ncp-app/lib/Controller/SettingsController.php
Normal file
46
ncp-app/lib/Controller/SettingsController.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
11
ncp-app/lib/Exceptions/InvalidSettingsException.php
Normal file
11
ncp-app/lib/Exceptions/InvalidSettingsException.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\NextcloudPi\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidSettingsException extends Exception {
|
||||
}
|
||||
|
||||
?>
|
||||
11
ncp-app/lib/Exceptions/SaveSettingsException.php
Normal file
11
ncp-app/lib/Exceptions/SaveSettingsException.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\NextcloudPi\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SaveSettingsException extends Exception {
|
||||
}
|
||||
|
||||
?>
|
||||
105
ncp-app/lib/Service/SettingsService.php
Normal file
105
ncp-app/lib/Service/SettingsService.php
Normal 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];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
59
ncp-app/lib/Settings/AdminSettings.php
Normal file
59
ncp-app/lib/Settings/AdminSettings.php
Normal 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
BIN
ncp-app/ncp.tar.gz
Normal file
Binary file not shown.
16325
ncp-app/package-lock.json
generated
Normal file
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
43
ncp-app/package.json
Normal 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
38
ncp-app/psalm.xml
Normal 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
66
ncp-app/src/main-admin.js
Normal 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");
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
5
ncp-app/stylelint.config.js
Normal file
5
ncp-app/stylelint.config.js
Normal 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
|
||||
42
ncp-app/templates/admin.php
Normal file
42
ncp-app/templates/admin.php
Normal 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
12
ncp-app/webpack.config.js
Normal 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')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
54
ncp.sh
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
#
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
robotframework
|
||||
selenium
|
||||
robotframework
|
||||
@ -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)
|
||||
|
||||
@ -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
65
updates/1.53.0sh
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user