Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions .github/scripts/build-upgradable-app.sh
Original file line number Diff line number Diff line change
@@ -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|<version>.*</version>|<version>$NEXT_APP_VERSION</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 <<EOF
[
{
"id": "$APP_ID",
"releases": [
{
"version": "$NEXT_APP_VERSION",
"minIntSize": 32,
"download": "http://localhost:8080/${APP_ID}-${NEXT_APP_VERSION}.tar.gz",
"licenses": [
"agpl"
],
"isNightly": false,
"rawPlatformVersionSpec": "\u003E=28",
"signature": "$signature",
"signatureDigest": "sha512"
}
],
"certificate": $certificate
}
]
EOF
175 changes: 175 additions & 0 deletions .github/workflows/app-upgrade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 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: "31 32"
php_versions:
required: false
type: string
default: "8.3"

jobs:
create-matrix:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- 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

- 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
5 changes: 5 additions & 0 deletions tests/acceptance/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading