diff --git a/.drone.jsonnet b/.drone.jsonnet index 40c089d..913eaaa 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -3,6 +3,8 @@ local browser = "firefox"; local nextcloud = "32.0.5"; local redis = "7.0.15"; local nginx = "1.24.0"; +local nats = "2.10"; +local postgresql = "16-bullseye"; local platform = '25.09'; local python = '3.12-slim-bookworm'; local debian = 'bookworm-slim'; @@ -29,10 +31,10 @@ local build(arch, test_ui) = [{ ] }, { - name: "download", - image: "debian:" + debian, + name: "nextcloud", + image: "nextcloud:" + nextcloud + "-fpm", commands: [ - "./download.sh " + nextcloud + "./nextcloud/build.sh" ] }, { @@ -62,18 +64,47 @@ local build(arch, test_ui) = [{ commands: [ "./redis/test.sh" ] + }, + { + name: "nats", + image: "debian:" + debian, + commands: [ + "./nats/build.sh" + ] + }, + { + name: "nats test", + image: "syncloud/platform-" + distro_default + "-" + arch + ":" + platform, + commands: [ + "./nats/test.sh" + ] + }, + { + name: "signaling", + image: "debian:" + debian, + commands: [ + "./signaling/build.sh" + ] + }, + { + name: "signaling test", + image: "syncloud/platform-" + distro_default + "-" + arch + ":" + platform, + commands: [ + "./signaling/test.sh" + ] }, { name: "postgresql", - image: "docker:" + dind, + image: "postgres:" + postgresql, commands: [ "./postgresql/build.sh" - ], - volumes: [ - { - name: "dockersock", - path: "/var/run" - } + ] + }, + { + name: "postgresql test", + image: "syncloud/platform-" + distro_default + "-" + arch + ":" + platform, + commands: [ + "./postgresql/test.sh" ] }, { diff --git a/bin/service.nats.sh b/bin/service.nats.sh new file mode 100755 index 0000000..04265f3 --- /dev/null +++ b/bin/service.nats.sh @@ -0,0 +1,3 @@ +#!/bin/bash +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) +exec $DIR/nats/bin/nats.sh -c ${SNAP_DATA}/config/nats.conf diff --git a/bin/service.php-fpm.sh b/bin/service.php-fpm.sh index 19e4279..2b4dd42 100755 --- a/bin/service.php-fpm.sh +++ b/bin/service.php-fpm.sh @@ -1,21 +1,5 @@ -#!/bin/bash - +#!/bin/bash -e DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) - -if [[ -z "$1" ]]; then - echo "usage $0 [start]" - exit 1 -fi - -case $1 in -start) - exec $DIR/php/bin/php-fpm.sh -y ${SNAP_DATA}/config/php-fpm.conf -c ${SNAP_DATA}/config/php.ini - ;; -post-start) - timeout 5 /bin/bash -c 'until [ -S '${SNAP_COMMON}'/log/php5-fpm.sock ]; do echo "waiting for ${SNAP_COMMON}/log/php5-fpm.sock"; sleep 1; done' - ;; -*) - echo "not valid command" - exit 1 - ;; -esac +exec $DIR/php/bin/php-fpm.sh \ + -y ${SNAP_DATA}/config/php-fpm.conf \ + -c ${SNAP_DATA}/config/php.ini diff --git a/bin/service.postgresql.sh b/bin/service.postgresql.sh index e12f597..ae68b9c 100755 --- a/bin/service.postgresql.sh +++ b/bin/service.postgresql.sh @@ -2,25 +2,5 @@ DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) -if [[ -z "$1" ]]; then - echo "usage $0 [start]" - exit 1 -fi -# shellcheck source=config/env . "${SNAP_DATA}/config/env" - -case $1 in -start) - exec ${DIR}/postgresql/bin/pg_ctl.sh -w -s -D ${PSQL_DATABASE} start - ;; -reload) - exec ${DIR}/postgresql/bin/pg_ctl.sh -s -D ${PSQL_DATABASE} reload - ;; -stop) - exec ${DIR}/postgresql/bin/pg_ctl.sh -s -D ${PSQL_DATABASE} stop -m fast - ;; -*) - echo "not valid command" - exit 1 - ;; -esac +exec ${DIR}/postgresql/bin/pg_ctl.sh -w -s -D ${PSQL_DATABASE} start diff --git a/bin/service.signaling.sh b/bin/service.signaling.sh new file mode 100755 index 0000000..7d3a22d --- /dev/null +++ b/bin/service.signaling.sh @@ -0,0 +1,3 @@ +#!/bin/bash +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) +exec $DIR/signaling/bin/signaling.sh -config ${SNAP_DATA}/config/signaling.conf diff --git a/config/nats.conf b/config/nats.conf new file mode 100644 index 0000000..d6a64ec --- /dev/null +++ b/config/nats.conf @@ -0,0 +1,28 @@ +# NATS Server Configuration for Nextcloud Signaling +# Unix socket for client connections +host: /var/snap/nextcloud/current/nats.sock + +# Logging +debug: false +trace: false +logtime: true + +# Authorization (optional, can be enabled for production) +# authorization { +# user: nats +# password: changeme +# } + +# Cluster (optional, for high availability) +# cluster { +# port: 6222 +# } + +# Max connections +max_connections: 1000 + +# Max payload size (1MB) +max_payload: 1048576 + +# Write deadline for connections +write_deadline: "2s" diff --git a/config/nginx.conf b/config/nginx.conf index 9b7f156..6b2c23f 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -17,6 +17,15 @@ http { server unix:/var/snap/nextcloud/common/log/php5-fpm.sock; } + upstream signaling { + server unix:/var/snap/nextcloud/current/signaling.sock; + } + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + # Set the `immutable` cache control options only for assets with a cache busting `v` argument map $arg_v $asset_immutable { "" ""; @@ -108,6 +117,28 @@ http { # always provides the desired behaviour. index index.php index.html /index.php$request_uri; + # Nextcloud Talk Signaling Server + location /standalone-signaling/ { + proxy_pass http://signaling/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /standalone-signaling/spreed { + proxy_pass http://signaling/spreed; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 86400; + } + # Rule borrowed from `.htaccess` to handle Microsoft DAV clients location = / { if ( $http_user_agent ~ ^DavClnt ) { diff --git a/config/signaling.conf b/config/signaling.conf new file mode 100644 index 0000000..8a3d86e --- /dev/null +++ b/config/signaling.conf @@ -0,0 +1,82 @@ +[http] +# Path to Unix socket to listen on for HTTP requests +# Comment the "listen" line to disable the listener +listen = /var/snap/nextcloud/current/signaling.sock + +[app] +# Set to "true" to enable debug logging +debug = false + +# The NATS server URL (Unix socket) +natsurl = unix:///var/snap/nextcloud/current/nats.sock + +[sessions] +# The hash key to use for session ids (32 bytes hex encoded) +# Can be generated with: openssl rand -hex 16 +hashkey = {{ signaling_session_hashkey }} + +# The block key to use for session data encryption (32 bytes hex encoded) +# Can be generated with: openssl rand -hex 16 +blockkey = {{ signaling_session_blockkey }} + +[clients] +# Shared secret for connections from internal clients +# Can be generated with: openssl rand -hex 16 +internalsecret = {{ signaling_internal_secret }} + +[backend] +# Comma-separated list of backend endpoints (e.g., Nextcloud instances) +# Leave empty to accept connections from all backends with valid secret +backends = backend1 + +# Shared secret for authenticating backend requests (same as in Nextcloud Talk settings) +# This secret needs to be configured in Nextcloud Talk admin settings +secret = {{ signaling_backend_secret }} + +# Allow all backends (for testing only, not recommended for production) +allowall = false + +# Maximum number of concurrent backend connections per host +connectionsperhost = 8 + +# Timeout for backend requests +timeout = 10s + +[backend1] +url = https://localhost +secret = {{ signaling_backend_secret }} + +[nats] +# URL of the NATS server (Unix socket) +url = unix:///var/snap/nextcloud/current/nats.sock + +[mcu] +# Type of MCU to use (leave empty for none, set to "janus" for Janus gateway) +# type = janus + +# URL of the Janus gateway WebSocket +# url = ws://localhost:8188 + +# Maximum bitrate for streams (in bits per second) +# maxstreambitrate = 1048576 + +# Maximum bitrate for screen sharing (in bits per second) +# maxscreenbitrate = 2097152 + +[turn] +# API key for TURN REST API (if using coturn with REST API) +# apikey = + +# Shared secret for TURN REST API (if using coturn with REST API) +# secret = + +# TURN server URLs (comma-separated) +# servers = turn:turn.example.com:3478?transport=udp,turn:turn.example.com:3478?transport=tcp + +[geoip] +# Path to GeoIP2 database file (optional, for location-based server selection) +# license = + +[stats] +# Set to "true" to enable statistics collection +enabled = false diff --git a/hooks/installer.py b/hooks/installer.py index 24669a5..5248979 100644 --- a/hooks/installer.py +++ b/hooks/installer.py @@ -3,7 +3,9 @@ from os.path import realpath import logging +import os import re +import secrets import shutil import uuid from crontab import CronTab @@ -30,6 +32,14 @@ SYSTEMD_PHP_FPM = '{0}.php-fpm'.format(APP_NAME) SYSTEMD_POSTGRESQL = '{0}.postgresql'.format(APP_NAME) +SIGNALING_SECRETS_FILE = 'signaling.secrets' + + +def generate_hex_secret(): + """Generate a 16-byte hex-encoded secret (32 characters).""" + return secrets.token_hex(16) + + class Installer: def __init__(self): if not logger.factory_instance: @@ -47,6 +57,33 @@ def __init__(self): self.cron = Cron(CRON_USER) self.db = Database(self.app_dir, self.data_dir, self.config_dir, PSQL_PORT) self.oc_config = OCConfig(join(self.app_dir, 'bin/nextcloud-config')) + self.signaling_secrets_path = join(self.data_dir, SIGNALING_SECRETS_FILE) + + def get_signaling_secrets(self): + """Get or create signaling server secrets.""" + if isfile(self.signaling_secrets_path): + # Load existing secrets + secrets_dict = {} + with open(self.signaling_secrets_path, 'r') as f: + for line in f: + if '=' in line: + key, value = line.strip().split('=', 1) + secrets_dict[key] = value + return secrets_dict + else: + # Generate new secrets + secrets_dict = { + 'signaling_session_hashkey': generate_hex_secret(), + 'signaling_session_blockkey': generate_hex_secret(), + 'signaling_internal_secret': generate_hex_secret(), + 'signaling_backend_secret': generate_hex_secret() + } + # Save secrets to file + with open(self.signaling_secrets_path, 'w') as f: + for key, value in secrets_dict.items(): + f.write('{0}={1}\n'.format(key, value)) + os.chmod(self.signaling_secrets_path, 0o600) + return secrets_dict def install_config(self): @@ -55,6 +92,7 @@ def install_config(self): storage.init_storage(APP_NAME, USER_NAME) templates_path = join(self.app_dir, 'config') + signaling_secrets = self.get_signaling_secrets() variables = { 'app_dir': self.app_dir, 'common_dir': self.common_dir, @@ -64,6 +102,7 @@ def install_config(self): 'config_dir': self.config_dir, 'domain': urls.get_app_domain_name(APP_NAME) } + variables.update(signaling_secrets) gen.generate_files(templates_path, self.config_dir, variables) fs.makepath(self.nextcloud_config_path) diff --git a/meta/snap.yaml b/meta/snap.yaml index 137099e..0981115 100644 --- a/meta/snap.yaml +++ b/meta/snap.yaml @@ -1,7 +1,7 @@ apps: postgresql: user: nextcloud - command: bin/service.postgresql.sh start + command: bin/service.postgresql.sh daemon: forking plugs: - network @@ -16,15 +16,35 @@ apps: restart-condition: always before: [php-fpm] + nats: + user: nextcloud + daemon: simple + command: bin/service.nats.sh + plugs: + - network + - network-bind + restart-condition: always + before: [signaling] + + signaling: + user: nextcloud + daemon: simple + command: bin/service.signaling.sh + plugs: + - network + - network-bind + restart-condition: always + after: [nats] + php-fpm: user: nextcloud - command: bin/service.php-fpm.sh start + command: bin/service.php-fpm.sh daemon: forking plugs: - network - network-bind restart-condition: always - post-start-command: bin/service.php-fpm.sh post-start + start-timeout: 600s after: [postgresql] before: [nginx] diff --git a/nats/bin/nats.sh b/nats/bin/nats.sh new file mode 100755 index 0000000..8d4036e --- /dev/null +++ b/nats/bin/nats.sh @@ -0,0 +1,4 @@ +#!/bin/bash -e +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) +LIBS=$(echo ${DIR}/usr/lib/*-linux-gnu*) +exec ${DIR}/usr/lib/*-linux*/ld-*.so.* --library-path $LIBS ${DIR}/nats-server "$@" diff --git a/nats/build.sh b/nats/build.sh new file mode 100755 index 0000000..8990221 --- /dev/null +++ b/nats/build.sh @@ -0,0 +1,22 @@ +#!/bin/sh -ex + +DIR=$( cd "$( dirname "$0" )" && pwd ) +cd ${DIR} +BUILD_DIR=${DIR}/../build/snap/nats +mkdir -p ${BUILD_DIR} + +cp -r /usr ${BUILD_DIR} + +apt update +apt install -y wget + +# Download forked nats-server with Unix socket support +ARCH=$(uname -m) +case $ARCH in + x86_64) ARCH=amd64 ;; + aarch64) ARCH=arm64 ;; +esac +wget -q https://github.com/cyberb/nats-server/releases/download/v2.14.1-unix/nats-server-linux-${ARCH} -O ${BUILD_DIR}/nats-server +chmod +x ${BUILD_DIR}/nats-server + +cp -r ${DIR}/bin ${BUILD_DIR}/ diff --git a/nats/test.sh b/nats/test.sh new file mode 100755 index 0000000..0a877d8 --- /dev/null +++ b/nats/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh -ex + +DIR=$( cd "$( dirname "$0" )" && pwd ) +cd ${DIR} +BUILD_DIR=${DIR}/../build/snap/nats +$BUILD_DIR/bin/nats.sh --version diff --git a/nextcloud/build.sh b/nextcloud/build.sh new file mode 100755 index 0000000..e62355e --- /dev/null +++ b/nextcloud/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh -ex + +DIR=$( cd "$( dirname "$0" )" && pwd ) +cd ${DIR} +BUILD_DIR=${DIR}/../build/snap/nextcloud +mkdir -p $BUILD_DIR +cp -r /usr/src/nextcloud/. ${BUILD_DIR}/ diff --git a/postgresql/Dockerfile b/postgresql/Dockerfile deleted file mode 100644 index 9b21923..0000000 --- a/postgresql/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -ARG MAJOR_VERSION -FROM postgres:$MAJOR_VERSION-bullseye \ No newline at end of file diff --git a/postgresql/build.sh b/postgresql/build.sh index 136d8ff..bba8093 100755 --- a/postgresql/build.sh +++ b/postgresql/build.sh @@ -1,30 +1,30 @@ -#!/bin/sh -ex +#!/bin/sh -xe DIR=$( cd "$( dirname "$0" )" && pwd ) cd ${DIR} -MAJOR_VERSION=16 - BUILD_DIR=${DIR}/../build/snap/postgresql -docker ps -a -q --filter ancestor=postgres:syncloud --format="{{.ID}}" | xargs docker stop | xargs docker rm || true -docker rmi postgres:syncloud || true -docker build --build-arg MAJOR_VERSION=$MAJOR_VERSION -t postgres:syncloud . -docker run postgres:syncloud postgres --help -docker create --name=postgres postgres:syncloud mkdir -p ${BUILD_DIR} -cd ${BUILD_DIR} + +rm -rf usr/lib/*/perl +rm -rf usr/lib/*/perl-base +rm -rf usr/lib/*/dri +rm -rf usr/lib/*/mfx +rm -rf usr/lib/*/vdpau +rm -rf usr/lib/*/gconv +rm -rf usr/lib/*/lapack +rm -rf usr/lib/gcc +rm -rf usr/lib/git-core + +cp -r /usr ${BUILD_DIR} +cp -r /lib ${BUILD_DIR} + +PGBIN=$(echo ${BUILD_DIR}/usr/lib/postgresql/*/bin) +MAJOR_VERSION=$(basename $(dirname $PGBIN)) echo "${MAJOR_VERSION}" > ${BUILD_DIR}/../db.major.version -docker export postgres -o postgres.tar -tar xf postgres.tar -rm -rf postgres.tar -ls -la -ls -la bin -ls -la usr/bin -ls -ls usr/share/postgresql-common/pg_wrapper -PGBIN=$(echo usr/lib/postgresql/*/bin) -ldd $PGBIN/initdb || true mv $PGBIN/postgres $PGBIN/postgres.bin mv $PGBIN/pg_dump $PGBIN/pg_dump.bin -cp $DIR/bin/* bin +mkdir ${BUILD_DIR}/bin +cp $DIR/bin/* ${BUILD_DIR}/bin cp $DIR/pgbin/* $PGBIN diff --git a/postgresql/test.sh b/postgresql/test.sh new file mode 100755 index 0000000..776f63f --- /dev/null +++ b/postgresql/test.sh @@ -0,0 +1,10 @@ +#!/bin/bash -xe + +DIR=$( cd "$( dirname "$0" )" && pwd ) +cd ${DIR} + +BUILD_DIR=${DIR}/../build/snap/postgresql +cd ${BUILD_DIR} +PGBIN=$(echo usr/lib/postgresql/*/bin) +ldd $PGBIN/initdb || true +./bin/initdb.sh --help diff --git a/signaling/Dockerfile b/signaling/Dockerfile new file mode 100644 index 0000000..6894358 --- /dev/null +++ b/signaling/Dockerfile @@ -0,0 +1 @@ +FROM strukturag/nextcloud-spreed-signaling:latest diff --git a/signaling/bin/signaling.sh b/signaling/bin/signaling.sh new file mode 100755 index 0000000..d69ae9e --- /dev/null +++ b/signaling/bin/signaling.sh @@ -0,0 +1,4 @@ +#!/bin/bash -e +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) +LIBS=$(echo ${DIR}/usr/lib/*-linux-gnu*) +exec ${DIR}/usr/lib/*-linux*/ld-*.so.* --library-path $LIBS ${DIR}/usr/bin/nextcloud-spreed-signaling "$@" diff --git a/signaling/build.sh b/signaling/build.sh new file mode 100755 index 0000000..286ce14 --- /dev/null +++ b/signaling/build.sh @@ -0,0 +1,23 @@ +#!/bin/sh -ex + +DIR=$( cd "$( dirname "$0" )" && pwd ) +cd ${DIR} +BUILD_DIR=${DIR}/../build/snap/signaling + +mkdir -p ${BUILD_DIR}/bin + +cp -r /usr ${BUILD_DIR} + +apt update +apt install -y wget + +# Download forked signaling binary with Unix socket support +ARCH=$(uname -m) +case $ARCH in + x86_64) ARCH=amd64 ;; + aarch64) ARCH=arm64 ;; +esac +wget -q https://github.com/cyberb/nextcloud-spreed-signaling/releases/download/v2.0.0-unix2/signaling-linux-${ARCH} -O ${BUILD_DIR}/usr/bin/nextcloud-spreed-signaling +chmod +x ${BUILD_DIR}/usr/bin/nextcloud-spreed-signaling + +cp ${DIR}/bin/* ${BUILD_DIR}/bin/ diff --git a/signaling/test.sh b/signaling/test.sh new file mode 100755 index 0000000..a97d954 --- /dev/null +++ b/signaling/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh -ex + +DIR=$( cd "$( dirname "$0" )" && pwd ) +cd ${DIR} +BUILD_DIR=${DIR}/../build/snap/signaling +$BUILD_DIR/bin/signaling.sh --version diff --git a/test/test.py b/test/test.py index a3ac4bb..e144228 100644 --- a/test/test.py +++ b/test/test.py @@ -269,13 +269,6 @@ def test_upgrade(app_archive_path, device_host, device_password): local_install(device_host, device_password, app_archive_path) -def test_upgrade_from_store(device, app, app_archive_path, device_host, device_password): - response = device.app_remove(app) - assert response.status_code == 200, response.text - response = device.app_install(app) - assert response.status_code == 200, response.text - local_install(device_host, device_password, app_archive_path) - def test_install_calendar(device): device.run_ssh('snap run nextcloud.occ app:install calendar', retries=10, sleep=10)