Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f3a31b3
feat: build docker image via github actions
rizlas Apr 10, 2026
e125ffa
Merge pull request #1 from ConsortiumGARR/gh
rizlas Apr 10, 2026
063e3cd
ci: add upstream tag support, GHA layer cache and conditional version…
rizlas Apr 10, 2026
8d9a98d
ci: use metadata-action for tag management, strip v prefix from upstr…
rizlas Apr 10, 2026
4c88a6c
ci: use native runners matrix to eliminate QEMU overhead
rizlas Apr 10, 2026
26343ff
ci: fix lower casing
rizlas Apr 10, 2026
c834c2c
chore: indentation
rizlas Apr 10, 2026
79db332
ci: actions update
rizlas Apr 10, 2026
2550278
ci: fix tag naming pattern
rizlas Apr 10, 2026
3768c8e
Merge pull request #702 from nextcloud/release-1.3.2
icewind1991 May 6, 2026
e863e8a
fix: fix noticition deprecation warning
icewind1991 May 18, 2026
fac2251
chore: psalm php version
icewind1991 May 18, 2026
9b36fc0
silence more innocent websocket errors
icewind1991 Mar 4, 2026
51b8e03
fix: bind to ipv6 dualstack by default
icewind1991 Mar 4, 2026
c44b877
fix: trim incomming credentials
icewind1991 Mar 4, 2026
036b515
chore: fix flake deprecation
icewind1991 Mar 4, 2026
9484005
config parser 0.15.2
icewind1991 Mar 6, 2026
d98ee9e
Merge pull request #705 from nextcloud/unknown-notification
icewind1991 May 18, 2026
1c9f096
Merge pull request #687 from nextcloud/ipv6-bind
icewind1991 May 18, 2026
aaf9270
Merge pull request #699 from nextcloud/bugfix/noid/reduce-db-access-w…
icewind1991 May 18, 2026
9957d58
Merge branch 'nextcloud:main' into main
rizlas May 19, 2026
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
173 changes: 173 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Inspired by https://github.com/sredevopsorg/multi-arch-docker-github-workflow
name: Docker Image CI

on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
inputs:
upstream_tag:
description: "Upstream tag of nextcloud/notify_push to build"
required: false

jobs:
build:
runs-on: ${{ matrix.runner }}

permissions:
contents: read
packages: write

strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
repository: ${{ inputs.upstream_tag && 'nextcloud/notify_push' || github.repository }}
ref: ${{ inputs.upstream_tag || github.ref_name }}

- name: Log in to GitHub Packages
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Lower case docker image name
id: image
uses: ASzc/change-string-case-action@v8
with:
string: ${{ github.repository }}

- name: Sanitize upstream tag
if: inputs.upstream_tag != ''
id: tag
run: echo "value=${INPUT_TAG#v}" >> $GITHUB_OUTPUT
env:
INPUT_TAG: ${{ inputs.upstream_tag }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=tag
type=raw,value=latest
type=raw,value=${{ steps.tag.outputs.value }},enable=${{ inputs.upstream_tag != '' }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=ghcr.io/${{ steps.image.outputs.lowercase }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}

- name: Export digest
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}

- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge:
runs-on: ubuntu-latest
needs: build

permissions:
contents: read
packages: write

steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true

- name: Log in to GitHub Packages
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Lower case docker image name
id: image
uses: ASzc/change-string-case-action@v8
with:
string: ${{ github.repository }}

- name: Sanitize upstream tag
if: inputs.upstream_tag != ''
id: tag
run: echo "value=${INPUT_TAG#v}" >> $GITHUB_OUTPUT
env:
INPUT_TAG: ${{ inputs.upstream_tag }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=tag
type=raw,value=latest
type=raw,value=${{ steps.tag.outputs.value }},enable=${{ inputs.upstream_tag != '' }}

- name: Get timestamp
id: timestamp
run: echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT

- name: Create and push manifest
id: manifest
working-directory: /tmp/digests
continue-on-error: true
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
--annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \
--annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
--annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \
--annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
$(printf 'ghcr.io/${{ steps.image.outputs.lowercase }}@sha256:%s ' *)

- name: Create and push manifest (without annotations)
if: steps.manifest.outcome == 'failure'
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/${{ steps.image.outputs.lowercase }}@sha256:%s ' *)

- name: Inspect manifest
run: |
docker buildx imagetools inspect 'ghcr.io/${{ steps.image.outputs.lowercase }}:${{ steps.meta.outputs.version }}'
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ rand = { version = "0.8.5", features = ["small_rng"] }
ahash = "0.8.12"
flexi_logger = { version = "0.31.8", features = ["colors"] }
tokio-stream = { version = "0.1.17", features = ["net"] }
nextcloud-config-parser = "0.15.1"
nextcloud-config-parser = "0.15.2"
url = "2.5.4"
clap = { version = "4.5.43", features = ["derive"] }
sd-notify = { version = "0.5.0", optional = true }
Expand Down
4 changes: 2 additions & 2 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<description><![CDATA[Push update support for desktop app.

Once the app is installed, the push binary needs to be setup. You can either use the setup wizard with `occ notify_push:setup` or see the [README](http://github.com/nextcloud/notify_push) for detailed setup instructions]]></description>
<version>1.3.2</version>
<version>1.3.3</version>
<licence>agpl</licence>
<author>Robin Appelman</author>
<namespace>NotifyPush</namespace>
Expand All @@ -27,7 +27,7 @@ Once the app is installed, the push binary needs to be setup. You can either use
<bugs>https://github.com/nextcloud/notify_push/issues</bugs>

<dependencies>
<nextcloud min-version="29" max-version="34"/>
<nextcloud min-version="30" max-version="34"/>
</dependencies>

<repair-steps>
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
};
lib = pkgs.lib;

hostTarget = pkgs.hostPlatform.config;
hostTarget = pkgs.stdenv.hostPlatform.config;
targets = [
"x86_64-unknown-linux-musl"
"i686-unknown-linux-musl"
Expand Down
3 changes: 2 additions & 1 deletion lib/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use OCP\Notification\IDismissableNotifier;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\Notification\UnknownNotificationException;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\IShare;

Expand Down Expand Up @@ -102,7 +103,7 @@ public function getName(): string {
}

public function prepare(INotification $notification, string $languageCode): INotification {
throw new \InvalidArgumentException();
throw new UnknownNotificationException();
}

public function dismissNotification(INotification $notification): void {
Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config https://getpsalm.org/schema/config"
phpVersion="8.0"
phpVersion="8.1"
>
<projectFiles>
<directory name="lib"/>
Expand Down
10 changes: 3 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use sqlx::any::AnyConnectOptions;
use std::convert::{TryFrom, TryInto};
use std::env::var;
use std::fmt::{Debug, Display, Formatter};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::str::FromStr;

Expand Down Expand Up @@ -183,9 +183,7 @@ impl TryFrom<PartialConfig> for Config {
let bind = match config.socket {
Some(socket) => Bind::Unix(socket, socket_permissions),
None => {
let ip = config
.bind
.unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
let ip = config.bind.unwrap_or(IpAddr::V6(Ipv6Addr::UNSPECIFIED));
let port = config.port.unwrap_or(7867);
Bind::Tcp((ip, port).into())
}
Expand All @@ -194,9 +192,7 @@ impl TryFrom<PartialConfig> for Config {
let metrics_bind = match (config.metrics_socket, config.metrics_port) {
(Some(socket), _) => Some(Bind::Unix(socket, socket_permissions)),
(None, Some(port)) => {
let ip = config
.bind
.unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
let ip = config.bind.unwrap_or(IpAddr::V6(Ipv6Addr::UNSPECIFIED));
Some(Bind::Tcp((ip, port).into()))
}
_ => None,
Expand Down
7 changes: 5 additions & 2 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ pub async fn handle_user_socket(
// hack while warp only has opaque error types
match formatted.as_str() {
"WebSocket protocol error: Connection reset without closing handshake"
| "Broken pipe (os error 32)"
| "IO error: Connection reset by peer (os error 104)" => {
log::debug!("websocket error: {e:#}")
}
Expand Down Expand Up @@ -262,11 +263,13 @@ async fn socket_auth(
let username_msg = read_socket_auth_message(rx).await?;
let username = username_msg
.to_str()
.map_err(|_| AuthenticationError::InvalidMessage)?;
.map_err(|_| AuthenticationError::InvalidMessage)?
.trim();
let password_msg = read_socket_auth_message(rx).await?;
let password = password_msg
.to_str()
.map_err(|_| AuthenticationError::InvalidMessage)?;
.map_err(|_| AuthenticationError::InvalidMessage)?
.trim();

// cleanup all pre_auth tokens older than 15s
let cutoff = Instant::now() - Duration::from_secs(15);
Expand Down
34 changes: 27 additions & 7 deletions src/storage_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use log::debug;
use rand::{thread_rng, Rng};
use sqlx::any::AnyConnectOptions;
use sqlx::{query_as, Any, AnyPool, FromRow};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Instant;
use tokio::time::Duration;

Expand All @@ -27,6 +28,7 @@ pub struct UserStorageAccess {
struct CachedAccess {
access: Vec<UserStorageAccess>,
valid_till: Instant,
updating: AtomicBool,
}

impl CachedAccess {
Expand All @@ -36,11 +38,16 @@ impl CachedAccess {
access,
valid_till: Instant::now()
+ Duration::from_millis(rng.gen_range((4 * 60 * 1000)..(5 * 60 * 1000))),
updating: AtomicBool::new(false),
}
}

pub fn is_valid(&self) -> bool {
self.valid_till > Instant::now()
self.valid_till > Instant::now() || self.updating.load(Ordering::SeqCst)
}

pub fn prepare_update(&self, value: bool) {
self.updating.store(value, Ordering::SeqCst);
}
}

Expand Down Expand Up @@ -71,14 +78,27 @@ impl StorageMapping {
&self,
storage: u32,
) -> Result<Ref<'_, u32, CachedAccess>, DatabaseError> {
if let Some(cached) = self.cache.get(&storage).filter(|cached| cached.is_valid()) {
Ok(cached)
} else {
let users = self.load_storage_mapping(storage).await?;
if let Some(cached) = self.cache.get(&storage) {
if cached.is_valid() {
return Ok(cached);
}

cached.prepare_update(true);
let users = self
.load_storage_mapping(storage)
.await
.inspect_err(|_| cached.prepare_update(false))?;

self.cache.insert(storage, CachedAccess::new(users));
Ok(self.cache.get(&storage).unwrap())
drop(cached);
let cached = CachedAccess::new(users);
self.cache.insert(storage, cached);
return Ok(self.cache.get(&storage).unwrap());
}

let users = self.load_storage_mapping(storage).await?;

self.cache.insert(storage, CachedAccess::new(users));
Ok(self.cache.get(&storage).unwrap())
}

pub async fn get_users_for_storage_path(
Expand Down