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
77 changes: 51 additions & 26 deletions .github/scripts/notify-to-element.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# SPDX-FileCopyrightText: 2023-2024 Jankari Tech Pvt. Ltd.
# SPDX-License-Identifier: AGPL-3.0-or-later

set -e

# helper functions
log_error() {
echo -e "\e[31m$1\e[0m"
Expand All @@ -15,42 +17,65 @@ log_success() {
echo -e "\e[32m$1\e[0m"
}

log_info "Fetching all workflow jobs....."
required_vars=(
ELEMENT_CHAT_URL
ELEMENT_ROOM_ID
NIGHTLY_CI_USER_TOKEN
GITHUB_REPOSITORY
GITHUB_RUN_ID
BRANCH_NAME
NEEDS_JSON
)

response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/actions/runs/$RUN_ID/jobs?per_page=50")
for var in "${required_vars[@]}"; do
if [[ -z "${!var}" ]]; then
log_error "❌ Missing required environment variable: $var"
log_info ""
log_info "Required environment variables:"
log_info "- ELEMENT_CHAT_URL : URL of the Element chat (e.g. https://matrix.element.io)"
log_info "- ELEMENT_ROOM_ID : Matrix room ID (e.g. abcdefg:matrix.element.io)"
log_info "- NIGHTLY_CI_USER_TOKEN : Access token for sending messages (e.g. "sometoken")"
log_info "- GITHUB_REPOSITORY : GitHub repository (e.g. user/repo) set by GitHub Actions environment variable"
log_info "- GITHUB_RUN_ID : GitHub run ID (e.g. 123456789) set by GitHub Actions environment variable"
log_info "- BRANCH_NAME : Branch name (e.g. master)"
log_info "- NEEDS_JSON : JSON string containing job results"
log_info ""
exit 1
fi
done

log_info "Fetching jobs informations succeeded!
"
if [[ "$response" != *"jobs"* ]]; then
log_error "No jobs found in the below response!"
log_info "$response"
jobs=$(echo "$NEEDS_JSON" | jq -r 'keys[]' 2>/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 <<EOF
{
"msgtype": "m.text",
"body": "",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}\">NC-Nightly-${BRANCH_NAME}</a><br></br><b>Status: ${workflow_status}</b>"
}
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": "<a href=\"https://github.com/'$REPO_OWNER'/'$REPO_NAME'/actions/runs/'$RUN_ID'\">NC-Nightly-'$BRANCH_NAME'</a><br></br><b><i>Status: '$workflow_status'</i></b>"
}
'
-d "$payload"
)

if [[ "$send_message_to_room_response" != *"event_id"* ]]; then
Expand All @@ -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)"
Loading
Loading