diff --git a/.github/scripts/build-upgradable-app.sh b/.github/scripts/build-upgradable-app.sh new file mode 100644 index 000000000..4735b7f17 --- /dev/null +++ b/.github/scripts/build-upgradable-app.sh @@ -0,0 +1,207 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 Jankari Tech Pvt. Ltd. +# SPDX-License-Identifier: AGPL-3.0-or-later + +# This script is used to build the upgradable integration_openproject app. It performs the following steps: +# 1. Copy the build files to a separate folder named publish, excluding unnecessary files and directories. +# 2. Get the current version of the app and update it to a new version by incrementing the major version number. +# 3. Sign the app files using self-signed certificate. +# 4. Archive the app into a .tar.gz file. +# 5. Sign the archive. +# NOTE: Before running this script, ensure that the Nextcloud instance is running and integration_openproject app is built. + +# Required environment variables: +# 1. NEXTCLOUD_PATH (Absolute path to Nextcloud where occ command is available, e.g. /var/www/html) +# 2. INTEGRATION_OPENPROJECT_DIR (Absolute path to the directory containing the integration_openproject repository, e.g. /var/www/html/build-app-shared) + +set -e -o pipefail + +# helper functions +log_error() { + echo -e "\e[31m$1\e[0m" +} + +log_info() { + echo -e "\e[37m$1\e[0m" +} + +log_success() { + echo -e "\e[32m$1\e[0m" +} + +if [[ -z "$NEXTCLOUD_PATH" ]] || [[ -z "$INTEGRATION_OPENPROJECT_DIR" ]]; then + log_error "Missing required environment variables: NEXTCLOUD_PATH, INTEGRATION_OPENPROJECT_DIR" + exit 1 +fi + +APP_ID=integration_openproject +cd "$INTEGRATION_OPENPROJECT_DIR" + +if [[ ! -d "$INTEGRATION_OPENPROJECT_DIR/$APP_ID" ]]; then + log_error "Folder does not exist: $INTEGRATION_OPENPROJECT_DIR/$APP_ID" + exit 1 +fi + +mkdir -p publish + +# copy app files to a separate folder +log_info "Copying necessary app files to publish directory..." +rsync -a \ +--exclude=server \ +--exclude=dev \ +--exclude=.git \ +--exclude=appinfo/signature.json \ +--exclude='*.swp' \ +--exclude=build \ +--exclude=.gitignore \ +--exclude=.travis.yml \ +--exclude=.scrutinizer.yml \ +--exclude=CONTRIBUTING.md \ +--exclude=composer.phar \ +--exclude=js/node_modules \ +--exclude=node_modules \ +--exclude=src \ +--exclude=translationfiles \ +--exclude='webpack.*' \ +--exclude=stylelint.config.js \ +--exclude=.eslintrc.js \ +--exclude=.github \ +--exclude=.gitlab-ci.yml \ +--exclude=crowdin.yml \ +--exclude=tools \ +--exclude=.tx \ +--exclude=.l10nignore \ +--exclude=l10n/.tx \ +--exclude=l10n/l10n.pl \ +--exclude=l10n/templates \ +--exclude='l10n/*.sh' \ +--exclude='l10n/[a-z][a-z]' \ +--exclude='l10n/[a-z][a-z]_[A-Z][A-Z]' \ +--exclude=l10n/no-php \ +--exclude=makefile \ +--exclude=screenshots \ +--exclude='phpunit*xml' \ +--exclude=tests \ +--exclude=ci \ +--exclude=vendor/bin \ +$APP_ID publish/ + +cd publish + +# get current version of integration_openproject and update to new version +current_version=$(php ${NEXTCLOUD_PATH}/occ app:list --output=json | jq -r ".enabled.$APP_ID") || { log_error "Failed to get current version of $APP_ID app."; exit 1; } +IFS=. read -r a b c <<< "$current_version" +NEXT_APP_VERSION="$((a+1)).$b.$c" + +# Save the new tag to a file for later use in the workflow +echo "$NEXT_APP_VERSION" > "${APP_ID}_new_version.txt" + +# update version in info.xml +sed -i "s|.*|$NEXT_APP_VERSION|" "integration_openproject/appinfo/info.xml" + +##################### +# Signing the app # +##################### +# https://nextcloudappstore.readthedocs.io/en/latest/developer.html#obtaining-a-certificate +# Check if openssl exists, otherwise install it +if ! command -v openssl >/dev/null 2>&1; then + echo "OpenSSL not found. Installing..." + apt update && apt install -y openssl || { + echo "Failed to install OpenSSL." + exit 1 + } +fi +log_info "Generating app.key and app.crt..." +openssl req -x509 -newkey rsa:4096 -sha256 -nodes \ + -keyout app.key \ + -out app.crt \ + -days 3650 \ + -subj "/CN=$APP_ID" \ + -addext "basicConstraints=CA:FALSE" \ + -addext "keyUsage=digitalSignature" \ + -addext "extendedKeyUsage=codeSigning" + +if [[ ! -s app.key || ! -s app.crt ]]; then + log_error "Failed to generate app signing certificate and key: app.key or app.crt not found." + exit 1 +fi + +log_info "Adding the generated certificate to Nextcloud's root.crt..." +nextcloud_root_crt="${NEXTCLOUD_PATH}/resources/codesigning/root.crt" +if [[ -f ${nextcloud_root_crt} ]]; then + echo "" >> ${nextcloud_root_crt} + cat app.crt >> ${nextcloud_root_crt} +else + log_error "Nextcloud's root.crt not found at ${nextcloud_root_crt}." + exit 1 +fi + +# fix permissions for signing +chown www-data app.key +chown www-data app.crt +chown -R www-data $APP_ID + +# Sign the app +# need full path for signing +log_info "Signing the app files..." +php ${NEXTCLOUD_PATH}/occ integrity:sign-app \ + --privateKey=${INTEGRATION_OPENPROJECT_DIR}/publish/app.key \ + --certificate=${INTEGRATION_OPENPROJECT_DIR}/publish/app.crt \ + --path=${INTEGRATION_OPENPROJECT_DIR}/publish/$APP_ID || { log_error "Failed to sign app."; exit 1; } + +# Archive the app +tar -czf $APP_ID-$NEXT_APP_VERSION.tar.gz $APP_ID +if [[ ! -f $APP_ID-$NEXT_APP_VERSION.tar.gz ]]; then + log_error "Failed to archive the app. Archive file $APP_ID-$NEXT_APP_VERSION.tar.gz not found." + exit 1 +fi +log_success "App archived into $APP_ID-$NEXT_APP_VERSION.tar.gz." + +##################### +# Sign the archive # +##################### +log_info "Signing the app archive..." +openssl dgst -sha512 -sign app.key $APP_ID-$NEXT_APP_VERSION.tar.gz \ + | openssl base64 \ + | tee ${INTEGRATION_OPENPROJECT_DIR}/publish/sign.txt + +if [[ ! -s ${INTEGRATION_OPENPROJECT_DIR}/publish/sign.txt ]]; then + log_error "Failed to sign the app archive. Signature file sign.txt is empty or not found." + exit 1 +else + log_success "App archive signed successfully." +fi + +log_success "Upgradable app built successfully." + +# prepare apps.json file +if [[ ! -f ${INTEGRATION_OPENPROJECT_DIR}/publish/${APP_ID}/appinfo/signature.json ]]; then + echo "Signature file not found at ${INTEGRATION_OPENPROJECT_DIR}/publish/${APP_ID}/appinfo/signature.json." + exit 1 +fi +certificate=$(jq '.certificate' "${INTEGRATION_OPENPROJECT_DIR}/publish/${APP_ID}/appinfo/signature.json") +signature=$(tr -d '\n' < "${INTEGRATION_OPENPROJECT_DIR}/publish/sign.txt") + +# Create apps.json with the required structure +cat > apps.json </dev/null) +if [[ -z "$jobs" ]]; then + log_error "❌ No jobs found in below JSON:" + log_info "$NEEDS_JSON" exit 1 fi -jobs_informations=$(echo "$response" | jq '.jobs[:-1]') -jobs_conclusions=$(echo "$jobs_informations" | jq -r '.[].conclusion') +results=$(echo "$NEEDS_JSON" | jq -r '.[].result' 2>/dev/null) -workflow_status="Success" -if [[ " ${jobs_conclusions[*]} " == *"failure"* ]]; then - workflow_status="Failure" -elif [[ " ${jobs_conclusions[*]} " == *"cancelled"* ]]; then - workflow_status="Cancelled" -elif [[ " ${jobs_conclusions[*]} " == *"skipped"* ]]; then - workflow_status="Skipped" +workflow_status="✅ Success" +if [[ "${results[*]}" == *"failure"* ]]; then + workflow_status="❌ Failure" +elif [[ "${results[*]}" == *"cancelled"* ]]; then + workflow_status="⚠️ Cancelled" +elif [[ "${results[*]}" == *"skipped"* ]]; then + workflow_status="⚠️ Skipped" fi log_info "Sending report to the element chat...." +payload=$(cat <NC-Nightly-${BRANCH_NAME}

Status: ${workflow_status}" +} +EOF +) + send_message_to_room_response=$(curl -s -XPOST "$ELEMENT_CHAT_URL/_matrix/client/r0/rooms/%21$ELEMENT_ROOM_ID/send/m.room.message?access_token=$NIGHTLY_CI_USER_TOKEN" \ - -d ' - { - "msgtype": "m.text", - "body": "", - "format": "org.matrix.custom.html", - "formatted_body": "NC-Nightly-'$BRANCH_NAME'

Status: '$workflow_status'" - } - ' + -d "$payload" ) if [[ "$send_message_to_room_response" != *"event_id"* ]]; then @@ -59,4 +84,4 @@ if [[ "$send_message_to_room_response" != *"event_id"* ]]; then exit 1 fi -log_success "Notification of the nightly build has been sent to Element chat (OpenProject + Nextcloud)" +log_success "Notification of the nightly build has been sent to Element chat (OpenProject + Nextcloud)" \ No newline at end of file diff --git a/.github/workflows/app-upgrade.yml b/.github/workflows/app-upgrade.yml new file mode 100644 index 000000000..4c7109e34 --- /dev/null +++ b/.github/workflows/app-upgrade.yml @@ -0,0 +1,178 @@ +# SPDX-FileCopyrightText: 2026 Jankari Tech Pvt. Ltd. +# SPDX-License-Identifier: AGPL-3.0-or-later + +name: App Upgrade + +on: + workflow_call: + inputs: + branch: + required: true + type: string + nextcloud_versions: + required: false + type: string + default: 'master' + php_versions: + required: false + type: string + default: '8.3' + +jobs: + create-matrix: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ inputs.branch }} + + - name: Create matrix + id: create-matrix + env: + NEXTCLOUD_VERSIONS: ${{ inputs.nextcloud_versions }} + PHP_VERSIONS: ${{ inputs.php_versions }} + DEFAULT_PHP_VERSION: '8.3' + DEFAULT_DATABASE: 'mysql' + run: | + MATRIX=$(./.github/scripts/generate-matrix.sh) + echo "matrix={\"include\": [$MATRIX]}" >> $GITHUB_OUTPUT + outputs: + matrix: ${{ steps.create-matrix.outputs.matrix }} + + upgrade-test: + name: Upgrade Testing + needs: create-matrix + runs-on: ubuntu-latest + strategy: + matrix: ${{ fromJson(needs.create-matrix.outputs.matrix) }} + + services: + nextcloud: + image: ghcr.io/juliusknorr/nextcloud-dev-php${{ format('{0}{1}', matrix.phpVersionMajor,matrix.phpVersionMinor) }}:master + env: + SQL: ${{ matrix.database }} + SERVER_BRANCH: ${{ matrix.nextcloudVersion }} + NEXTCLOUD_AUTOINSTALL: "Yes" + NEXTCLOUD_AUTOINSTALL_APPS: oidc groupfolders user_oidc integration_openproject + NEXTCLOUD_AUTOINSTALL_APPS_WAIT_TIME: 60 + WITH_REDIS: "YES" + ports: + - 80:80 + options: --name=nextcloud + volumes: + - ${{ github.workspace }}/extra-apps:/var/www/html/apps-shared + - ${{ github.workspace }}/build-app:/var/www/html/build-app-shared + + database-mysql: + image: ghcr.io/nextcloud/continuous-integration-mariadb-10.5:latest + env: + MYSQL_ROOT_PASSWORD: 'nextcloud' + MYSQL_PASSWORD: 'nextcloud' + MYSQL_USER: 'nextcloud' + MYSQL_DATABASE: 'nextcloud' + + redis: + image: ghcr.io/nextcloud/continuous-integration-redis:latest + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + path: integration_openproject + ref: ${{ inputs.branch }} + + - name: Setup PHP ${{ format('{0}.{1}', matrix.phpVersionMajor,matrix.phpVersionMinor) }} + uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d + with: + php-version: ${{ format('{0}.{1}', matrix.phpVersionMajor,matrix.phpVersionMinor) }} + tools: composer, phpunit + extensions: intl, gd, sqlite3 + + - name: Wait for Nextcloud server to be ready + run: | + if ! timeout 5m bash -c ' + until curl -s -f http://localhost/status.php | grep '"'"'"installed":true'"'"'; do + echo "[INFO] Waiting for server to be ready..." + sleep 10 + done + '; then + echo "[ERROR] Server not ready within 5 minutes." + exit 1 + fi + + # activity app cannot be installed using occ command + - name: Setup activity app + run: | + # fix permissions for folder extra-apps + sudo chown $USER:$USER extra-apps + cd extra-apps + git clone https://github.com/nextcloud/activity.git --depth 1 --branch ${{ matrix.nextcloudVersion }} + # Enable activity apps + if [ "${{matrix.nextcloudVersion}}" == "master" ]; then + # enable app even if it is not compatible with the master branch + docker exec nextcloud /bin/bash -c 'occ a:e -f activity' + else + docker exec nextcloud /bin/bash -c 'occ a:e activity' + fi + + - name: Build upgradable integration_openproject app + run: | + make -C integration_openproject + # fix permissions for build-app folder + sudo chown -R $USER:$USER build-app + cp -R integration_openproject build-app + # run the build script inside the nextcloud container + docker exec nextcloud /bin/bash -c "\ + cd build-app-shared && \ + INTEGRATION_OPENPROJECT_DIR=/var/www/html/build-app-shared \ + NEXTCLOUD_PATH=/var/www/html \ + source integration_openproject/.github/scripts/build-upgradable-app.sh" + cd build-app/publish + echo "NEXT_APP_VERSION=$(cat integration_openproject_new_version.txt)" >> $GITHUB_ENV + + - name: Update integration_openproject app + run: | + # Start local appstore server + docker exec nextcloud /bin/bash -c "cd build-app-shared && php -S localhost:8080 -t publish &" + + # Wait for local appstore server to be ready + docker exec nextcloud /bin/bash -c ' + for i in $(seq 1 5); do + if curl -sS http://localhost:8080 > /dev/null; then + exit 0 + fi + sleep 2 + done + echo "Failed to run local appstore at localhost:8080" + exit 1 + ' + + # Clear appstore cache (force re-fetch) + docker exec nextcloud /bin/bash -c "echo '' > /var/www/html/data/appdata_*/appstore/apps.json" + + # Disable share rate limit protection + docker exec nextcloud /bin/bash -c "php occ config:system:set ratelimit.protection.enabled --value false --type bool" + + # Configure local appstore server + docker exec nextcloud /bin/bash -c "php occ config:system:set appstoreurl --value 'http://localhost:8080'" + docker exec nextcloud /bin/bash -c "php occ config:system:set allow_local_remote_servers --value true" + + # Update app + docker exec nextcloud /bin/bash -c "php occ app:update --allow-unstable integration_openproject" + + # Verify update + if docker exec nextcloud /bin/bash -c "php occ app:list | grep -q 'integration_openproject.*$NEXT_APP_VERSION'"; then + echo "App updated successfully to version $NEXT_APP_VERSION" + else + echo "App not updated to version $NEXT_APP_VERSION" + exit 1 + fi + + - name: API Tests + working-directory: integration_openproject + env: + NEXTCLOUD_BASE_URL: http://localhost + run: | + make api-test \ No newline at end of file diff --git a/.github/workflows/nighlty-ci-master.yml b/.github/workflows/nighlty-ci-master.yml index b2a691155..2d0ab0d56 100644 --- a/.github/workflows/nighlty-ci-master.yml +++ b/.github/workflows/nighlty-ci-master.yml @@ -15,3 +15,27 @@ jobs: secrets: inherit with: branch: master + + app-upgrade-test: + uses: ./.github/workflows/app-upgrade.yml + with: + branch: master + nextcloud_versions: '33' + + notify-nightly-report: + needs: [builds, app-upgrade-test] + if: ${{ always() }} + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + + - name: Notify Element (nightly builds) + env: + ELEMENT_CHAT_URL: ${{ secrets.ELEMENT_CHAT_URL }} + ELEMENT_ROOM_ID: ${{ secrets.ELEMENT_ROOM_ID }} + NIGHTLY_CI_USER_TOKEN: ${{ secrets.NIGHTLY_CI_USER_TOKEN }} + BRANCH_NAME: master + NEEDS_JSON: ${{ toJson(needs) }} + run: | + ./.github/scripts/notify-to-element.sh \ No newline at end of file diff --git a/.github/workflows/nighlty-ci-release-2-branch.yml b/.github/workflows/nighlty-ci-release-2-branch.yml index 159007a07..f3abc9bd7 100644 --- a/.github/workflows/nighlty-ci-release-2-branch.yml +++ b/.github/workflows/nighlty-ci-release-2-branch.yml @@ -15,4 +15,28 @@ jobs: secrets: inherit with: branch: release/2.11 - nextcloud_versions: "31 32" \ No newline at end of file + nextcloud_versions: "31 32" + + app-upgrade-test: + uses: ./.github/workflows/app-upgrade.yml + with: + branch: release/2.11 + nextcloud_versions: "31 32" + + notify-nightly-report: + needs: [builds, app-upgrade-test] + if: ${{ always() }} + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + + - name: Notify Element (nightly builds) + env: + ELEMENT_CHAT_URL: ${{ secrets.ELEMENT_CHAT_URL }} + ELEMENT_ROOM_ID: ${{ secrets.ELEMENT_ROOM_ID }} + NIGHTLY_CI_USER_TOKEN: ${{ secrets.NIGHTLY_CI_USER_TOKEN }} + BRANCH_NAME: release/2.11 + NEEDS_JSON: ${{ toJson(needs) }} + run: | + ./.github/scripts/notify-to-element.sh \ No newline at end of file diff --git a/.github/workflows/nighty-ci-release-3-branch.yml b/.github/workflows/nighty-ci-release-3-branch.yml index 568a1555a..a8e9b99d9 100644 --- a/.github/workflows/nighty-ci-release-3-branch.yml +++ b/.github/workflows/nighty-ci-release-3-branch.yml @@ -15,4 +15,28 @@ jobs: secrets: inherit with: branch: release/3.0 - nextcloud_versions: "33" \ No newline at end of file + nextcloud_versions: "33 master" + + app-upgrade-test: + uses: ./.github/workflows/app-upgrade.yml + with: + branch: release/3.0 + nextcloud_versions: "33" + + notify-nightly-report: + needs: [builds, app-upgrade-test] + if: ${{ always() }} + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + + - name: Notify Element (nightly builds) + env: + ELEMENT_CHAT_URL: ${{ secrets.ELEMENT_CHAT_URL }} + ELEMENT_ROOM_ID: ${{ secrets.ELEMENT_ROOM_ID }} + NIGHTLY_CI_USER_TOKEN: ${{ secrets.NIGHTLY_CI_USER_TOKEN }} + BRANCH_NAME: release/3.0 + NEEDS_JSON: ${{ toJson(needs) }} + run: | + ./.github/scripts/notify-to-element.sh \ No newline at end of file diff --git a/.github/workflows/shared_workflow.yml b/.github/workflows/shared_workflow.yml index 9b3d8d7fa..debe6f3e1 100644 --- a/.github/workflows/shared_workflow.yml +++ b/.github/workflows/shared_workflow.yml @@ -326,26 +326,4 @@ jobs: - name: API Tests env: NEXTCLOUD_BASE_URL: http://localhost - run: make api-test - - notify-nightly-report: - needs: - - js-lint-unit - - api-phpunit-tests - if: ${{ always() && github.event_name == 'schedule' }} - runs-on: ubuntu-22.04 - steps: - - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - - - name: Notify nightly report to element - env: - ELEMENT_CHAT_URL: ${{ secrets.ELEMENT_CHAT_URL }} - ELEMENT_ROOM_ID: ${{ secrets.ELEMENT_ROOM_ID }} - NIGHTLY_CI_USER_TOKEN: ${{ secrets.NIGHTLY_CI_USER_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO_OWNER: nextcloud - REPO_NAME: integration_openproject - RUN_ID: ${{ github.run_id }} - BRANCH_NAME: ${{ inputs.branch }} - run: ./.github/scripts/notify-to-element.sh + run: make api-test \ No newline at end of file diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index 1ddd42031..6a57747a9 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -272,6 +272,11 @@ private function deleteUserDataFromDocker(string $user): void { echo "'docker' command not found. Skipping user data deletion.\n"; return; } + // Skip if Nextcloud Docker container does not exist + exec("docker ps --format \"{{.Names}}\"", $containers); + if (!in_array('nextcloud', $containers)) { + return; + } $firstChar = substr($user, 0, 1); $restChars = substr($user, 1);