Compare commits

...
Author SHA1 Message Date
OpenCloud Devops 1770793f26 🎉 Release 7.2.4 (#3328)
* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4

* 🎉 Release 7.2.4
2026-08-21 07:43:58 +02:00
Florian Schade 7bb32387cb Merge pull request #3361 from fschade/fix-search-opensearch-queries
fix(search): fix open-search kql field compilation

(cherry picked from commit b62af81bde)
2026-08-20 17:57:33 +02:00
Ralf Haferkamp 43166d138a chore: avoid introducing new config option in stable 2026-08-20 17:03:14 +02:00
Florian Schade 502628c4d6 Merge pull request #3337 from fschade/fix-search-event-acks
fix(search): ack every event the index service handles

(cherry picked from commit 06bc072925)
2026-08-20 16:46:47 +02:00
Florian Schade d6944d7b0b Merge pull request #3336 from fschade/fix-search-space-deleted-event
fix(search): take a deleted space out of the index when it is deleted

(cherry picked from commit 8a9889c12c)
2026-08-20 16:46:47 +02:00
Florian Schade 027a8cf9b6 Merge pull request #3338 from fschade/refactor-search-param-names
refactor(search): re-name the engine parameters

(cherry picked from commit 5e55b80e2c)
2026-08-20 16:46:47 +02:00
Viktor Scharf 0e6795492e [full-ci] chore: reva bump -2.46.8 (#3371) 2026-08-20 15:32:41 +02:00
Ralf Haferkampandjunkerderprovinz 0548d5db7e fix(postprocessing): retry publishing events instead of killing the server (#3370)
A single transient nats: timeout on event publish was fatal to the whole
single-binary server, with no retry despite the service already having
backoff machinery for step failures. Now retries with the same exponential
backoff, capped at half the ack wait so the source event isn't redelivered
to another worker mid-retry. Also stops acking the source event when
publish ultimately fails, so the upload resumes after restart instead of
staying orphaned, and no longer exits during graceful shutdown.

(cherry picked from commit 654f0e8e20)

Co-authored-by: junkerderprovinz <jdp@braethoria.com>
2026-08-20 14:00:31 +02:00
André Duffeck 64be6d614f Only log a debug message when an item is still in processing state
(cherry picked from commit 78796c4d95)
2026-08-20 12:34:06 +02:00
Ralf Haferkamp cbc6f8408a bump reva
for https://github.com/opencloud-eu/reva/pull/783
2026-08-20 12:34:06 +02:00
Viktor Scharf fa1e4e6104 [full-ci] chore: bump web to v7.1.5 (#3368) 2026-08-20 12:25:14 +02:00
Andre Duffeck cee105203c Merge pull request #3235 from pbleser-oc/feat/3182-posixfs-scan-add-subtree
feat(posixfs): #3182 add basepath option in the "posixfs scan" command

(cherry picked from commit 799f98339e)
2026-08-20 11:18:51 +02:00
Andre Duffeck 48f9874e48 Merge pull request #3233 from aduffeck/extend-posixfs-consistency-check
Do not check ignored paths

(cherry picked from commit c93f50dc37)
2026-08-20 11:18:51 +02:00
Andre Duffeck 8c40a33f6c Merge pull request #3220 from aduffeck/extend-posixfs-consistency-check
Extend posixfs consistency check

(cherry picked from commit 557adfc327)
2026-08-20 11:18:51 +02:00
Andre Duffeck aeff1f53b8 Merge pull request #3191 from pbleser-oc/feat/#3182-posixfs-scan-subtree
feat(posixfs): #3182 add basepath option in the "posixfs scan" command

(cherry picked from commit 942cca8eef)
2026-08-20 11:18:51 +02:00
André Duffeck 82294e3c71 Expose upload status and message
(cherry picked from commit 3d632fb90e)
2026-08-20 10:13:27 +02:00
Ralf Haferkamp be01619806 bump reva to latest stable-2.46 2026-08-20 10:13:27 +02:00
Viktor Scharf 38440bd8fc ci: run search acceptance tests against OpenSearch on opensearch PR title (#3323) 2026-08-20 07:51:11 +02:00
Sawjan Gurung 066eabd0c9 test(api): fix share role update test scenario (#3322)
Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2026-08-20 07:51:11 +02:00
Viktor Scharf 3516dc7f2f test: add api tests for cross-space search index mutation (#3320) 2026-08-20 07:51:11 +02:00
Viktor Scharf 954b8ccf41 ci: run search acceptance tests against OpenSearch in nightly (#3302)
* ci: run search acceptance tests against OpenSearch in nightly

* fix

* add issue tag to failed search tests
2026-08-20 07:51:11 +02:00
Sawjan Gurung 6da9b1326a ci: refactor nightly ci pipelines matrix for API tests (#2939)
* ci: refactor nightly ci pipelines matrix

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* ci: update nightly ci matrices

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

* ci: override matrix values from suite config

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>

---------

Signed-off-by: Saw-jan <saw.jan.grg3e@gmail.com>
2026-08-20 07:51:11 +02:00
Florian Schade 2711523aec Merge pull request #3351 from aduffeck/backport-3331
Fix opensearch space scope [stable-7.2]
2026-08-19 10:54:40 +02:00
André Duffeck 5d8d022bd7 Switch to Ginkgo tests for readability 2026-08-19 09:30:40 +02:00
André Duffeck 5218eeffdd Make sure to scope updates to the index by space 2026-08-19 09:29:26 +02:00
Ralf Haferkamp a75f592d37 fix(posixfs scan): Setup logger for scan command
Setup a logger for the FS. The nats kv clients for the caches need one
to be able to log NAT connection issues.

Fixes: #3184
(cherry picked from commit e358cb6e6e)
2026-08-18 09:37:38 +02:00
OpenCloud Devops a17a70c8b7 🎉 Release 7.2.3 (#3258)
* 🎉 Release 7.2.3

* 🎉 Release 7.2.3
2026-08-06 15:22:44 +02:00
Viktor Scharf df3a790a2f bump-opencloud-7.2.3 (#3261) 2026-08-06 15:11:55 +02:00
Andre Duffeck eb73d2af0e Fix missing favorite flag on opensearch hits (#3256) 2026-08-06 12:42:14 +02:00
OpenCloud Devops 31f01c6d3d 🎉 Release 7.2.2 (#3124)
* 🎉 Release 7.2.2

* 🎉 Release 7.2.2
2026-07-14 10:24:53 +02:00
Viktor Scharf 6597dad1f9 chore: bump opencloud v7.2.2 (#3125) 2026-07-14 09:44:49 +02:00
Benedikt Kulmann 8b01d08ce3 chore: bump web to v7.1.4 (#3122) 2026-07-13 14:27:38 +02:00
OpenCloud Devops c73ed01908 🎉 Release 7.2.1 (#3017)
* 🎉 Release 7.2.1

* 🎉 Release 7.2.1

* 🎉 Release 7.2.1
2026-07-06 16:56:15 +02:00
Viktor Scharf 8c4f9d96c0 Fix warming up the id cache for the user storage (#3072) 2026-07-06 16:32:23 +02:00
Jannik Stehle a83a3b7680 Merge pull request #3052 from opencloud-eu/bump-web-7.1.3
[full-ci] chore: bump web to v7.1.3
2026-07-02 09:23:54 +02:00
Jannik Stehle cee5657651 chore: bump web to v7.1.3 2026-07-01 09:50:17 +02:00
Michael Barz 0a500d25bf Merge pull request #3016 from opencloud-eu/do-not-push-to-rolling
skip rolling release for production tags
2026-06-25 10:01:54 +02:00
v.scharf be121d8a94 skip rolling release for production tags 2026-06-25 09:39:41 +02:00
OpenCloud Devopsandv.scharf c067b65f3f 🎉 Release 7.2.0 (#2934)
* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

* trigger ci

* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

* 🎉 Release 7.2.0

---------

Co-authored-by: v.scharf <v.scharf@opencloud.eu>
2026-06-25 08:19:55 +02:00
Ralf Haferkamp c7ed57263c Merge pull request #3012 from opencloud-eu/bump-web-7.1.2
[full-ci] chore: bump web to v7.1.2
2026-06-24 17:26:13 +02:00
Benedikt Kulmann 1b9cd062c7 chore: bump web to v7.1.2 2026-06-24 15:44:32 +02:00
Ralf Haferkamp ae53d5847e Merge pull request #3005 from rhafer/stable/7.2/reva-2.46.6
chore: bump reva to 2.46.6
2026-06-24 09:08:42 +02:00
Ralf Haferkamp 14b34903a7 chore: bump reva to 2.46.6 2026-06-23 16:30:01 +02:00
Michael Barz a44539d5f6 chore: enable rolling channel 2026-06-23 09:14:02 +02:00
Michael Barz 69a1350921 Merge pull request #3000 from opencloud-eu/replace-placeholder-for-release
🎉 release. replace placeholders
2026-06-22 19:44:23 +02:00
v.scharf d7375e9904 🎉 release. replace placeholders 2026-06-22 16:31:30 +02:00
Andre Duffeck b4ed8c4aeb Merge pull request #2999 from rhafer/stable/7.2/backports
[stable-7.2] Backport fixes from main
2026-06-22 15:56:49 +02:00
Benedikt Kulmann 8fd9a82acf Merge pull request #2998 from opencloud-eu/bump-web-7.1.1
[full-ci] chore: bump web to v7.1.1
2026-06-22 15:12:30 +02:00
André Duffeck cd99a6fd44 Extend the posixfs consistency command to fix name attr mismatches
(cherry picked from commit cadd02ce29)

Backports: https://github.com/opencloud-eu/opencloud/pull/2980
2026-06-22 14:42:08 +02:00
André Duffeck c24b6f1c33 Do not try to create personal spaces for lightweight or service users
This fixes error logs like

RR error when calling Createhome error="gateway: grpc failed with code CODE_INVALID_ARGUMENT" line=github.com/opencloud-eu/opencloud/services/proxy/pkg/middleware/create_home.go:87 service=proxy

e.g. during internal requests to the data provider.

(cherry picked from commit 1ea634e6e3)

Backports: https://github.com/opencloud-eu/opencloud/pull/2978
2026-06-22 14:41:10 +02:00
André Duffeck 569e630fe0 Handle events asynchronously
That should help to keep up with the stream of messages and prevent
services from being flagged as slow consumers.

(cherry picked from commit 06365d9739)

Backports: #2974
2026-06-22 14:40:11 +02:00
Ralf Haferkamp 7143dc9cba Revert "fix: disallow thumbnails for tiff and jpeg2000 images"
The alpine base images now has a fixed libvips. So we can enable
previews for these formats again.

This reverts commit c40629dd85.

(cherry picked from commit fab1986fb6)

Backports: https://github.com/opencloud-eu/opencloud/pull/2973
2026-06-22 14:39:20 +02:00
Christian Richter 3f83cbc524 change error level for trashing items interaction with search
Signed-off-by: Christian Richter <c.richter@opencloud.eu>
(cherry picked from commit faf3ff1959)

Backports: https://github.com/opencloud-eu/opencloud/pull/2951
2026-06-22 14:36:22 +02:00
André Duffeck 6ce5943e07 Do not choke on users that weren't cleaned up yet
(cherry picked from commit 92e6c8f3ff)

Backports: https://github.com/opencloud-eu/opencloud/pull/2944
2026-06-22 14:35:05 +02:00
Ralf Haferkamp 7eb08af8ae fix(graph): translate sharing roles consitently
GetRoleDefinition/s does now handle l10n correctly. Previsouly it just
returned the non-localized string. What made things worse was that
ListPermissions() mutated global list of available roles and replaced
some strings with translated values depending on the `accept-language`
header. Which resulted in GetRoleDefinition returning results in mixed
localization depending on who/what called ListPermissions before.

Fixes: #2800
(cherry picked from commit d163c8ed29)
Backports: https://github.com/opencloud-eu/opencloud/pull/2935
2026-06-22 14:34:16 +02:00
Jörn Friedrich Dreyer 7306abaaf9 add tls support for all nats connections
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
(cherry picked from commit 77fd4fca69)

Backports: https://github.com/opencloud-eu/opencloud/pull/2063
2026-06-22 14:33:10 +02:00
Benedikt Kulmann dd345bf48f chore: bump web to v7.1.1 2026-06-22 12:54:41 +02:00
Ralf Haferkamp f2e02e1f88 Merge pull request #2967 from rhafer/stable/7.2/reva-2.46.4
[stable-7.2] chore: bump reva to 2.46.5
2026-06-22 12:13:56 +02:00
Ralf Haferkamp 92a2980875 fix: Status codes for Space Disable/Delete
Allow a "permission denied error" from reva to bubble up to the client.
Reva was fixed to return "permission denied" only when the space to be
delete can actually be listed by the user. Other wise it will return
"not found". See reva commit 1bf72cb76394671f373e87f15f23f978cf41ab08.

So when a user with the 'can manage' role tries to purge an already
disabled space it will now get "Forbidden" status instead of a "Not
found".

Also fixes the expected status codes in the tests.

(cherry picked from commit 99d80f30a9)
2026-06-22 11:19:14 +02:00
Ralf Haferkamp b25367a845 chore: bump reva to 2.46.5 2026-06-22 11:17:21 +02:00
Ralf Haferkamp 4d82968bd0 Merge pull request #2964 from rhafer/stable/7.2/alpine-bump
[stable-7.2] Bump alpine base image
2026-06-18 11:31:45 +02:00
Ralf Haferkamp 90cc015a9c Bump alpine base image
Also adjusts the build image to use the opencloudeu/golang image. So
we're in sync with how the binaries are built in CI. It already contains
all the required dependencies.

(cherry picked from commit dbc8a21085)
2026-06-17 15:46:16 +02:00
Michael Barz 6dcb7a1ff2 fix: stable trigger 2026-06-11 13:38:44 +02:00
Michael Barz 63664db2f6 Use rc version in pkg/version/version.go 2026-06-11 13:36:13 +02:00
Michael Barz f8e26d3198 chore: bump version 2026-06-11 13:36:00 +02:00
v.scharf a6ca2c538e set stable in more places 2026-06-11 13:35:46 +02:00
Michael Barz 56e03f12ce chore: prepare stable-7.2 2026-06-11 13:35:22 +02:00
Andre Duffeck 8ad66f5c35 Merge pull request #2922 from rhafer/reva-bump
bump reva to latest main
2026-06-10 12:50:22 +02:00
Ralf Haferkamp a532da40dc bump reva to latest main 2026-06-10 11:44:11 +02:00
Heiko-Pohl 5d23251b96 Merge pull request #2912 from opencloud-eu/rename-guest-to-user-light
Rename role_name from "guest" to "user-light"
2026-06-10 11:13:28 +02:00
Ralf Haferkamp 11d022f681 Merge pull request #2918 from rhafer/docker-context
chore: reduce build context for docker
2026-06-10 09:42:38 +02:00
Ralf Haferkamp fb8c3d7683 Merge pull request #2916 from aduffeck/bump-reva-080f87
Bump reva
2026-06-10 09:34:08 +02:00
André Duffeck b921be3796 Bump reva
remove the now uneeded replace on inotifywaitgo
2026-06-09 17:36:19 +02:00
Ralf Haferkamp d0a0922e89 chore: reduce build context for docker
Always including the parent directory into the build context was a bad
idea. This reverts the change and only add the opencloud tree to the
build context by default.
To build a docker image with workspaces enabled as new make target
"dev-docker-with-workspace" is introduced that adjust the docker build
call accordingly (by setting the right build context and the new SRCDIR
arg to point to the correct directory for the opencloud tree).
2026-06-09 16:42:42 +02:00
Heiko Pohl 9502e00dc3 switch role_name from guest to user-light in proxy README.md 2026-06-09 11:41:52 +02:00
opencloudeu b19c17c879 [tx] updated from transifex 2026-06-09 09:31:40 +00:00
Michael Barz 3650555c69 Merge pull request #2911 from opencloud-eu/fix-translation-trigger
ci: fix translation sync trigger
2026-06-09 11:30:03 +02:00
Benedikt Kulmann db05fcb6ab ci: fix translation sync trigger 2026-06-09 10:57:26 +02:00
Ralf Haferkamp 09d4cbcf9c Merge pull request #2884 from opencloud-eu/dependabot/npm_and_yarn/services/idp/webpack-manifest-plugin-6.0.1
build(deps-dev): bump webpack-manifest-plugin from 5.0.0 to 6.0.1 in /services/idp
2026-06-08 17:30:25 +02:00
dependabot[bot] b39511e9f3 build(deps-dev): bump webpack-manifest-plugin in /services/idp
Bumps [webpack-manifest-plugin](https://github.com/shellscape/webpack-manifest-plugin) from 5.0.0 to 6.0.1.
- [Release notes](https://github.com/shellscape/webpack-manifest-plugin/releases)
- [Commits](https://github.com/shellscape/webpack-manifest-plugin/compare/v5.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: webpack-manifest-plugin
  dependency-version: 6.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 19:57:37 +00:00
399 changed files with 7398 additions and 11583 deletions

No files matched your search

+2
View File
@@ -3,3 +3,5 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.23.4
require github.com/gosexy/gettext v0.0.0-20160830220431-74466a0a0c4a // go-xgettext
require github.com/jessevdk/go-flags v1.6.1 // indirect
+2 -2
View File
@@ -1,4 +1,4 @@
# The test runner source for UI tests
WEB_COMMITID=2cee98bb27fab223fc4ce2455f5483ce7767ff1a
WEB_BRANCH=main
WEB_COMMITID=170677dc06b1dfab57bc4bea2dac8a5cd78b2784
WEB_BRANCH=stable-7.1
+263 -146
View File
@@ -75,11 +75,11 @@ OC_FED_DOMAIN = "%s:10200" % FED_OC_SERVER_NAME
event = {
"base": {
"event": ["push", "manual"],
"branch": "main",
"branch": "stable-*",
},
"cron": {
"event": "cron",
"branch": "main",
"branch": "stable-*",
},
"pull_request": {
"event": "pull_request",
@@ -89,6 +89,30 @@ event = {
},
}
OPENCLOUD_STORAGES = ["posix", "decomposed"]
API_TEST_NIGHTLY_CI_MATRIX = {
"posix": [
{
"withRemotePhp": False,
"enableWatchFs": True,
},
{
"withRemotePhp": True,
"enableWatchFs": False,
},
],
"decomposed": [
{
"withRemotePhp": False,
"enableWatchFs": False,
},
{
"withRemotePhp": True,
"enableWatchFs": False,
},
],
}
# configuration
config = {
"cs3ApiTests": {
@@ -121,7 +145,8 @@ config = {
"apiSettings",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
"enableWatchFs": False,
"emailNeeded": True,
"extraTestEnvironment": {
"EMAIL_HOST": "email",
@@ -144,14 +169,14 @@ config = {
#"collaborativePosix",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
},
"graphUserGroup": {
"suites": [
"apiGraphUserGroup",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
},
"spaces": {
"suites": [
@@ -176,12 +201,14 @@ config = {
"apiSearch1",
],
"skip": False,
"nightlyOpenSearch": True,
},
"search2": {
"suites": [
"apiSearch2",
],
"skip": False,
"nightlyOpenSearch": True,
},
"sharingNg": {
"suites": [
@@ -209,7 +236,7 @@ config = {
"apiNotification",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
"emailNeeded": True,
"extraTestEnvironment": {
"EMAIL_HOST": "email",
@@ -245,13 +272,14 @@ config = {
],
"skip": False,
"tikaNeeded": True,
"nightlyOpenSearch": True,
},
"ocm": {
"suites": [
"apiOcm",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
"federationServer": True,
"emailNeeded": True,
"extraTestEnvironment": {
@@ -287,14 +315,15 @@ config = {
"apiAuthApp",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
"enableWatchFs": False,
},
"cliCommands": {
"suites": [
"cliCommands",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
"antivirusNeeded": True,
"extraServerEnvironment": {
"ANTIVIRUS_SCANNER_TYPE": "clamav",
@@ -309,7 +338,7 @@ config = {
"apiTenancy",
],
"skip": False,
"withRemotePhp": [False],
"withRemotePhp": False,
"ldapNeeded": True,
"extraTestEnvironment": {
"USE_PREPARED_LDAP_USERS": True,
@@ -403,9 +432,10 @@ config = {
"architectures": ["arm64", "amd64"],
"production": {
# NOTE: need to be updated if new production releases are determined
"tags": ["2.0", "4.0"],
"tags": ["2.0", "4.0", "7.2"],
# NOTE: need to be set to true if patch releases are made from stable-X-branches
"skip_rolling": "false",
"skip_rolling": True,
"skip_daily": True,
"repo": docker_repo_slug,
"build_type": "production",
},
@@ -503,7 +533,7 @@ def main(ctx):
none
"""
if ctx.build.event == "cron" and ctx.build.sender == "translation-sync":
if ctx.build.event == "cron" and ctx.build.cron == "translation-sync":
return translation_sync(ctx)
is_release_pr = (ctx.build.event == "pull_request" and "🎉 release" in ctx.build.title.lower())
@@ -789,30 +819,7 @@ def testOpencloud(ctx):
],
"environment": CI_HTTP_PROXY_ENV,
},
{
"name": "open-search",
"image": OPEN_SEARCH,
"detach": True,
"environment": {
"discovery.type": "single-node",
"DISABLE_INSTALL_DEMO_CONFIG": True,
"DISABLE_SECURITY_PLUGIN": True,
},
"entrypoint": ["/usr/share/opensearch/opensearch-docker-entrypoint.sh", "opensearch"],
},
{
"name": "wait-for-open-search",
"image": OC_CI_ALPINE,
"commands": [
"bash -c '" +
"until curl -sS \"http://open-search:9200/_cat/health?h=status\" | grep \"green\\|yellow\"; do\n" +
" echo \"Waiting for http://open-search:9200 to be healthy...\"\n" +
" sleep 5\n" +
"done\n" +
"echo \"http://open-search:9200 healthy...\"\n" +
"'",
],
},
] + waitForOpenSearch() + [
{
"name": "test",
"image": OC_CI_GOLANG,
@@ -844,6 +851,7 @@ def testOpencloud(ctx):
pipeline = {
"name": "test-lint-unit",
"steps": steps,
"services": openSearchService(),
"when": [
event["base"],
event["cron"],
@@ -1184,6 +1192,77 @@ def wopiValidatorTests(ctx, storage, wopiServerType):
])
return [pipeline]
def build_api_test_workflow_matrix(ctx, storage, suite_cfg, default_cfg):
"""
Generates a matrix of feature combinations to run API tests with.
Args:
ctx: woodpecker context
storage: opencloud storage type
suite_cfg: suite config. E.g.: config["localApiTests"]["spaces"]
default_cfg: default suite config values
Returns:
A matrix to run API tests with. E.g.:
[
{
"withRemotePhp": False,
"enableWatchFs": False,
},
{
"withRemotePhp": True,
"enableWatchFs": False,
},
]
"""
# default for PRs and commit push events
matrices = [{
"withRemotePhp": default_cfg["withRemotePhp"],
"enableWatchFs": default_cfg["enableWatchFs"],
}]
if ctx.build.event == "cron":
matrices = API_TEST_NIGHTLY_CI_MATRIX[storage]
override_with_remote_php = None
override_enable_watch_fs = None
if "withRemotePhp" in suite_cfg:
override_with_remote_php = suite_cfg["withRemotePhp"]
if "enableWatchFs" in suite_cfg:
override_enable_watch_fs = suite_cfg["enableWatchFs"]
workflow_metrices = []
for m in matrices:
matrix = {
"withRemotePhp": m["withRemotePhp"],
"enableWatchFs": m["enableWatchFs"],
"openSearch": False,
}
if override_with_remote_php != None:
matrix["withRemotePhp"] = override_with_remote_php
if override_enable_watch_fs != None:
matrix["enableWatchFs"] = override_enable_watch_fs
base = {
"withRemotePhp": matrix["withRemotePhp"],
"enableWatchFs": matrix["enableWatchFs"],
}
if matrix not in workflow_metrices and base in matrices:
workflow_metrices.append(matrix)
# Add an OpenSearch search-engine variant for nightly running search tests,
# or on demand when "opensearch" is specified in the PR title
run_open_search = ctx.build.event == "cron" or "opensearch" in ctx.build.title.lower()
if run_open_search and storage == "posix" and suite_cfg.get("nightlyOpenSearch", False):
os_matrix = {
"withRemotePhp": False,
"enableWatchFs": False,
"openSearch": True,
}
if os_matrix not in workflow_metrices:
workflow_metrices.append(os_matrix)
return workflow_metrices
def localApiTestPipeline(ctx):
pipelines = []
@@ -1199,9 +1278,10 @@ def localApiTestPipeline(ctx):
"federationServer": False,
"collaborationServiceNeeded": False,
"extraCollaborationEnvironment": {},
"withRemotePhp": [False],
"enableWatchFs": [False],
"withRemotePhp": False,
"enableWatchFs": False,
"ldapNeeded": False,
"nightlyOpenSearch": False,
}
if "localApiTests" in config:
@@ -1211,68 +1291,80 @@ def localApiTestPipeline(ctx):
for item in defaults:
params[item] = matrix[item] if item in matrix else defaults[item]
if ctx.build.event == "cron":
params["storages"] = OPENCLOUD_STORAGES
# skip CLI tests in nightly pipeline
if name.startswith("cli"):
continue
# use decomposed storage if specified in the PR title
# run CLI tests only with decomposed storage
if "[decomposed]" in ctx.build.title.lower() or name.startswith("cli"):
params["storages"] = ["decomposed"]
if ctx.build.event == "cron":
params["withRemotePhp"] = [True, False]
params["enableWatchFs"] = [True, False]
# override withRemotePhp if specified in the suite config
if "withRemotePhp" in matrix:
params["withRemotePhp"] = matrix["withRemotePhp"]
for storage in params["storages"]:
for run_with_remote_php in params["withRemotePhp"]:
for run_with_watch_fs_enabled in params["enableWatchFs"]:
pipeline_name = "test-API"
if name.startswith("cli"):
pipeline_name = "test-CLI"
pipeline_name += "-%s" % name
if not run_with_remote_php:
pipeline_name += "-withoutRemotePhp"
pipeline_name += "-%s" % storage
if run_with_watch_fs_enabled:
pipeline_name += "-watchfs"
matrices = build_api_test_workflow_matrix(ctx, storage, matrix, defaults)
for m in matrices:
run_with_remote_php = m["withRemotePhp"]
run_with_watch_fs = m["enableWatchFs"]
run_with_open_search = m["openSearch"]
pipeline = {
"name": pipeline_name,
"steps": skipCheckStep(ctx, "acceptance-tests") + evaluateWorkflowStep() + restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBinPath"]) +
(tikaService() if params["tikaNeeded"] else []) +
(waitForWebOffices(["https://collabora:9980", "https://onlyoffice", "http://fakeoffice:8080"]) if params["collaborationServiceNeeded"] else []) +
(waitForClamavService() if params["antivirusNeeded"] else []) +
(waitForEmailService() if params["emailNeeded"] else []) +
(ldapService() if params["ldapNeeded"] else []) +
(waitForLdapService() if params["ldapNeeded"] else []) +
opencloudServer(
storage,
extra_server_environment = params["extraServerEnvironment"],
with_wrapper = True,
tika_enabled = params["tikaNeeded"],
watch_fs_enabled = run_with_watch_fs_enabled,
) +
(opencloudServer(storage, deploy_type = "federation", extra_server_environment = params["extraServerEnvironment"], watch_fs_enabled = run_with_watch_fs_enabled) if params["federationServer"] else []) +
((wopiCollaborationService("fakeoffice") + wopiCollaborationService("collabora") + wopiCollaborationService("onlyoffice")) if params["collaborationServiceNeeded"] else []) +
(openCloudHealthCheck("wopi", ["wopi-collabora:9304", "wopi-onlyoffice:9304", "wopi-fakeoffice:9304"]) if params["collaborationServiceNeeded"] else []) +
localApiTest(params["suites"], storage, params["extraTestEnvironment"], run_with_remote_php) +
logRequests(),
"services": (emailService() if params["emailNeeded"] else []) +
(clamavService() if params["antivirusNeeded"] else []) +
((fakeOffice() + collaboraService() + onlyofficeService()) if params["collaborationServiceNeeded"] else []),
"depends_on": getPipelineNames(buildOpencloudBinaryForTesting(ctx)),
"when": [
event["base"],
event["cron"],
event["pull_request"],
],
}
prefixStepCommands(pipeline, [
". ./.woodpecker.env",
'[ "$SKIP_WORKFLOW" = "true" ] && exit 0',
])
pipelines.append(pipeline)
pipeline_name = "test-API"
if name.startswith("cli"):
pipeline_name = "test-CLI"
pipeline_name += "-%s" % name
pipeline_name += "-%s" % storage
if run_with_remote_php:
pipeline_name += "-withRemotePhp"
if run_with_watch_fs:
pipeline_name += "-watchfs"
if run_with_open_search:
pipeline_name += "-opensearch"
server_environment = dict(params["extraServerEnvironment"])
if run_with_open_search:
server_environment["SEARCH_ENGINE_TYPE"] = "open-search"
server_environment["SEARCH_ENGINE_OPEN_SEARCH_CLIENT_ADDRESSES"] = "http://open-search:9200"
pipeline = {
"name": pipeline_name,
"steps": skipCheckStep(ctx, "acceptance-tests") + evaluateWorkflowStep() + restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBinPath"]) +
(tikaService() if params["tikaNeeded"] else []) +
(waitForOpenSearch() if run_with_open_search else []) +
(waitForWebOffices(["https://collabora:9980", "https://onlyoffice", "http://fakeoffice:8080"]) if params["collaborationServiceNeeded"] else []) +
(waitForClamavService() if params["antivirusNeeded"] else []) +
(waitForEmailService() if params["emailNeeded"] else []) +
(ldapService() if params["ldapNeeded"] else []) +
(waitForLdapService() if params["ldapNeeded"] else []) +
opencloudServer(
storage,
extra_server_environment = server_environment,
with_wrapper = True,
tika_enabled = params["tikaNeeded"],
watch_fs_enabled = run_with_watch_fs,
) +
(opencloudServer(storage, deploy_type = "federation", extra_server_environment = params["extraServerEnvironment"], watch_fs_enabled = run_with_watch_fs) if params["federationServer"] else []) +
((wopiCollaborationService("fakeoffice") + wopiCollaborationService("collabora") + wopiCollaborationService("onlyoffice")) if params["collaborationServiceNeeded"] else []) +
(openCloudHealthCheck("wopi", ["wopi-collabora:9304", "wopi-onlyoffice:9304", "wopi-fakeoffice:9304"]) if params["collaborationServiceNeeded"] else []) +
localApiTest(params["suites"], storage, params["extraTestEnvironment"], run_with_remote_php) +
logRequests(),
"services": (emailService() if params["emailNeeded"] else []) +
(clamavService() if params["antivirusNeeded"] else []) +
(openSearchService() if run_with_open_search else []) +
((fakeOffice() + collaboraService() + onlyofficeService()) if params["collaborationServiceNeeded"] else []),
"depends_on": getPipelineNames(buildOpencloudBinaryForTesting(ctx)),
"when": [
event["base"],
event["cron"],
event["pull_request"],
],
}
prefixStepCommands(pipeline, [
". ./.woodpecker.env",
'[ "$SKIP_WORKFLOW" = "true" ] && exit 0',
])
pipelines.append(pipeline)
return pipelines
def localApiTest(suites, storage = "decomposed", extra_environment = {}, with_remote_php = False):
@@ -1316,8 +1408,8 @@ def localApiTest(suites, storage = "decomposed", extra_environment = {}, with_re
def coreApiTestPipeline(ctx):
defaults = {
"withRemotePhp": [False],
"enableWatchFs": [False],
"withRemotePhp": False,
"enableWatchFs": False,
"storages": ["posix"],
"numberOfParts": 7,
"skipExceptParts": [],
@@ -1334,63 +1426,60 @@ def coreApiTestPipeline(ctx):
for item in defaults:
params[item] = matrix[item] if item in matrix else defaults[item]
if ctx.build.event == "cron":
params["storages"] = OPENCLOUD_STORAGES
# use decomposed storage if specified in the PR title
if "[decomposed]" in ctx.build.title.lower():
params["storages"] = ["decomposed"]
if ctx.build.event == "cron":
params["withRemotePhp"] = [True, False]
params["enableWatchFs"] = [True, False]
# override withRemotePhp if specified in the suite config
if "withRemotePhp" in matrix:
params["withRemotePhp"] = matrix["withRemotePhp"]
debugParts = params["skipExceptParts"]
debugPartsEnabled = (len(debugParts) != 0)
for storage in params["storages"]:
for runPart in range(1, params["numberOfParts"] + 1):
for run_with_remote_php in params["withRemotePhp"]:
for run_with_watch_fs_enabled in params["enableWatchFs"]:
if not debugPartsEnabled or (debugPartsEnabled and runPart in debugParts):
pipeline_name = "test-Core-API-%s" % runPart
if not run_with_remote_php:
pipeline_name += "-withoutRemotePhp"
pipeline_name += "-%s" % storage
if run_with_watch_fs_enabled:
pipeline_name += "-watchfs"
matrices = build_api_test_workflow_matrix(ctx, storage, matrix, defaults)
for m in matrices:
run_with_remote_php = m["withRemotePhp"]
run_with_watch_fs = m["enableWatchFs"]
if not debugPartsEnabled or (debugPartsEnabled and runPart in debugParts):
pipeline_name = "test-Core-API-%s" % runPart
pipeline_name += "-%s" % storage
if run_with_remote_php:
pipeline_name += "-withRemotePhp"
if run_with_watch_fs:
pipeline_name += "-watchfs"
pipeline = {
"name": pipeline_name,
"steps": skipCheckStep(ctx, "acceptance-tests") +
evaluateWorkflowStep() +
restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBinPath"]) +
opencloudServer(
storage,
with_wrapper = True,
watch_fs_enabled = run_with_watch_fs_enabled,
) +
coreApiTest(
runPart,
params["numberOfParts"],
run_with_remote_php,
storage,
) +
logRequests(),
"services": redisForOCStorage(storage),
"depends_on": getPipelineNames(buildOpencloudBinaryForTesting(ctx)),
"when": [
event["base"],
event["cron"],
event["pull_request"],
],
}
prefixStepCommands(pipeline, [
". ./.woodpecker.env",
'[ "$SKIP_WORKFLOW" = "true" ] && exit 0',
])
pipelines.append(pipeline)
pipeline = {
"name": pipeline_name,
"steps": skipCheckStep(ctx, "acceptance-tests") +
evaluateWorkflowStep() +
restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBinPath"]) +
opencloudServer(
storage,
with_wrapper = True,
watch_fs_enabled = run_with_watch_fs,
) +
coreApiTest(
runPart,
params["numberOfParts"],
run_with_remote_php,
storage,
) +
logRequests(),
"services": redisForOCStorage(storage),
"depends_on": getPipelineNames(buildOpencloudBinaryForTesting(ctx)),
"when": [
event["base"],
event["cron"],
event["pull_request"],
],
}
prefixStepCommands(pipeline, [
". ./.woodpecker.env",
'[ "$SKIP_WORKFLOW" = "true" ] && exit 0',
])
pipelines.append(pipeline)
return pipelines
def coreApiTest(part_number = 1, number_of_parts = 1, with_remote_php = False, storage = "posix"):
@@ -1722,7 +1811,7 @@ def dockerReleases(ctx):
docker_releases.append("rolling")
# on non tag events, do daily build
else:
elif not config["dockerReleases"]["production"]["skip_daily"]:
docker_releases.append("daily")
for releaseConfigName in docker_releases:
@@ -1813,7 +1902,7 @@ def dockerRelease(ctx, repo, build_type):
"name": "dryrun",
"image": PLUGINS_DOCKER_BUILDX,
"settings": {
"context": "..",
"context": ".",
"dry_run": True,
"platforms": "linux/amd64", # do dry run only on the native platform
"repo": "%s,quay.io/%s,registry.heinlein.group/%s" % (repo, repo, repo),
@@ -1836,7 +1925,7 @@ def dockerRelease(ctx, repo, build_type):
"name": "build-and-push",
"image": PLUGINS_DOCKER_BUILDX,
"settings": {
"context": "..",
"context": ".",
"repo": "%s,quay.io/%s,registry.heinlein.group/%s" % (repo, repo, repo),
"platforms": "linux/amd64,linux/arm64", # we can add remote builders
"auto_tag": False if build_type == "daily" else True,
@@ -2084,6 +2173,7 @@ def readyReleaseGo():
"image": READY_RELEASE_GO,
"settings": {
"git_email": "devops@opencloud.eu",
"release_branch": "stable-7.2",
"forge_type": "github",
"forge_token": {
"from_secret": "github_token",
@@ -3251,6 +3341,33 @@ def tikaService():
],
}]
def openSearchService():
return [{
"name": "open-search",
"image": OPEN_SEARCH,
"environment": {
"discovery.type": "single-node",
"DISABLE_INSTALL_DEMO_CONFIG": True,
"DISABLE_SECURITY_PLUGIN": True,
},
"entrypoint": ["/usr/share/opensearch/opensearch-docker-entrypoint.sh", "opensearch"],
}]
def waitForOpenSearch():
return [{
"name": "wait-for-open-search",
"image": OC_CI_ALPINE,
"commands": [
"bash -c '" +
"until curl -sS \"http://open-search:9200/_cat/health?h=status\" | grep \"green\\|yellow\"; do\n" +
" echo \"Waiting for http://open-search:9200 to be healthy...\"\n" +
" sleep 5\n" +
"done\n" +
"echo \"http://open-search:9200 healthy...\"\n" +
"'",
],
}]
def logRequests():
return [{
"name": "api-test-failure-logs",
+79
View File
@@ -1,5 +1,84 @@
# Changelog
## [7.2.4](https://github.com/opencloud-eu/opencloud/releases/tag/v7.2.4) - 2026-08-20
### ❤️ Thanks to all contributors! ❤️
@aduffeck, @rhafer, @v-scharf
### 🐛 Bug Fixes
- [stable-7.2] fix(search): fix open-search kql field compilation [[#3375](https://github.com/opencloud-eu/opencloud/pull/3375)]
- [stable-7.2] fix(search): ack every event the index service handles [[#3374](https://github.com/opencloud-eu/opencloud/pull/3374)]
- [stable-7.2] fix(postprocessing): retry publishing events [[#3370](https://github.com/opencloud-eu/opencloud/pull/3370)]
- [stable-7.2] Only log a debug message when an item is still in processing state [[#3369](https://github.com/opencloud-eu/opencloud/pull/3369)]
- [stable-7.2] backport fixes for the opencloud posixfs command [[#3367](https://github.com/opencloud-eu/opencloud/pull/3367)]
- [stable-7.2] Expose upload status and message [[#3365](https://github.com/opencloud-eu/opencloud/pull/3365)]
- Fix opensearch space scope [stable-7.2] [[#3351](https://github.com/opencloud-eu/opencloud/pull/3351)]
- [stable-7.2] fix(posixfs scan): Setup logger for scan command [[#3195](https://github.com/opencloud-eu/opencloud/pull/3195)]
### ✅ Tests
- [stable-7.2] api-tests against open-search in stable [[#3353](https://github.com/opencloud-eu/opencloud/pull/3353)]
## [7.2.3](https://github.com/opencloud-eu/opencloud/releases/tag/v7.2.3) - 2026-08-06
### ❤️ Thanks to all contributors! ❤️
@aduffeck, @v-scharf
### 🐛 Bug Fixes
- Fix missing favorite flag on opensearch hits [[#3256](https://github.com/opencloud-eu/opencloud/pull/3256)]
## [7.2.2](https://github.com/opencloud-eu/opencloud/releases/tag/v7.2.2) - 2026-07-14
### ❤️ Thanks to all contributors! ❤️
@kulmann, @v-scharf
### 📦️ Dependencies
- [full-ci] chore: bump web to v7.1.4 [[#3122](https://github.com/opencloud-eu/opencloud/pull/3122)]
## [7.2.1](https://github.com/opencloud-eu/opencloud/releases/tag/v7.2.1) - 2026-07-06
### ❤️ Thanks to all contributors! ❤️
@JammingBen, @v-scharf
### 🐛 Bug Fixes
- Fix warming up the id cache for the user storage [[#3072](https://github.com/opencloud-eu/opencloud/pull/3072)]
### 📦️ Dependencies
- [full-ci] chore: bump web to v7.1.3 [[#3052](https://github.com/opencloud-eu/opencloud/pull/3052)]
## [7.2.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.2.0) - 2026-06-24
### ❤️ Thanks to all contributors! ❤️
@Heiko-Pohl, @JammingBen, @ScharfViktor, @aduffeck, @butonic, @dragonchaser, @kulmann, @rhafer
### 🐛 Bug Fixes
- [stable-7.2] Backport fixes from main [[#2999](https://github.com/opencloud-eu/opencloud/pull/2999)]
- fix(idp): aarch64 build [[#2906](https://github.com/opencloud-eu/opencloud/pull/2906)]
- use ldap instead of ldaps internally [[#2880](https://github.com/opencloud-eu/opencloud/pull/2880)]
### 📚 Documentation
- Rename role_name from "guest" to "user-light" [[#2912](https://github.com/opencloud-eu/opencloud/pull/2912)]
### 📦️ Dependencies
- [full-ci] chore: bump web to v7.1.2 [[#3012](https://github.com/opencloud-eu/opencloud/pull/3012)]
- [full-ci] chore: bump web to v7.1.1 [[#2998](https://github.com/opencloud-eu/opencloud/pull/2998)]
- bump reva to latest main [[#2922](https://github.com/opencloud-eu/opencloud/pull/2922)]
- build(deps-dev): bump webpack-manifest-plugin from 5.0.0 to 6.0.1 in /services/idp [[#2884](https://github.com/opencloud-eu/opencloud/pull/2884)]
- build(deps): bump axios from 1.16.0 to 1.16.1 in /services/idp [[#2883](https://github.com/opencloud-eu/opencloud/pull/2883)]
## [7.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v7.1.0) - 2026-06-02
### ❤️ Thanks to all contributors! ❤️
+2 -3
View File
@@ -21,15 +21,14 @@ COPY ./ /opencloud/
WORKDIR /opencloud/opencloud
RUN make node-generate-prod
FROM golang:1.24-alpine AS build
RUN apk add bash make git curl gcc musl-dev libc-dev binutils-gold inotify-tools vips-dev
FROM quay.io/opencloudeu/golang-ci:1.25 AS build
COPY --from=generate /opencloud /opencloud
WORKDIR /opencloud/opencloud
RUN make go-generate build ENABLE_VIPS=true
FROM alpine:3.23
FROM alpine:3.24
RUN apk add --no-cache attr ca-certificates curl mailcap tree vips && \
echo 'hosts: files dns' >| /etc/nsswitch.conf
@@ -0,0 +1,21 @@
Bugfix: Retry publishing postprocessing events before giving up
A single transient failure while publishing an event to the event system took
the whole server down. The postprocessing service treated every publish error
as fatal and called log.Fatal, which exits the process and so also stopped all
the other services running in the same binary. A burst of uploads was enough to
run into one nats publish timeout and lose the server with it.
Publishing is now retried using the same exponential backoff that is already
used for failed postprocessing steps. Between the attempts the source event is
marked as in progress and the backoff is capped at half the ack wait, so the
event should not be redelivered to another worker while we are still retrying.
The number of retries is configurable via POSTPROCESSING_PUBLISH_MAX_RETRIES.
Should all attempts fail, the source event is no longer acknowledged. Before,
the event was acknowledged even though its successor was never published, so
the upload was left half processed in the store and did not recover on restart.
https://github.com/opencloud-eu/opencloud/issues/3271
https://github.com/opencloud-eu/opencloud/issues/2232
https://github.com/opencloud-eu/opencloud/issues/2422
+5 -10
View File
@@ -33,7 +33,7 @@ require (
github.com/go-micro/plugins/v4/store/nats-js-kv v0.0.0-20240726082623-6831adfdcdc4
github.com/go-micro/plugins/v4/wrapper/monitoring/prometheus v1.2.0
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
github.com/go-playground/validator/v10 v10.30.2
github.com/go-playground/validator/v10 v10.30.3
github.com/go-resty/resty/v2 v2.17.2
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/golang-jwt/jwt/v5 v5.3.1
@@ -64,7 +64,7 @@ require (
github.com/open-policy-agent/opa v1.15.2
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d
github.com/opencloud-eu/reva/v2 v2.46.2
github.com/opencloud-eu/reva/v2 v2.46.8
github.com/opensearch-project/opensearch-go/v4 v4.6.0
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
@@ -75,6 +75,7 @@ require (
github.com/rogpeppe/go-internal v1.14.1
github.com/rs/cors v1.11.1
github.com/rs/zerolog v1.35.0
github.com/shamaton/msgpack/v2 v2.4.1
github.com/sirupsen/logrus v1.9.4
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.2
@@ -84,7 +85,6 @@ require (
github.com/test-go/testify v1.1.4
github.com/testcontainers/testcontainers-go v0.42.0
github.com/testcontainers/testcontainers-go/modules/opensearch v0.42.0
github.com/theckman/yacspin v0.13.12
github.com/thejerf/suture/v4 v4.0.6
github.com/tidwall/gjson v1.19.0
github.com/tidwall/sjson v1.2.5
@@ -102,7 +102,7 @@ require (
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0
go.opentelemetry.io/otel/sdk v1.44.0
go.opentelemetry.io/otel/trace v1.44.0
golang.org/x/crypto v0.51.0
golang.org/x/crypto v0.52.0
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/image v0.40.0
golang.org/x/net v0.55.0
@@ -288,7 +288,6 @@ require (
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.1.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
@@ -315,7 +314,7 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pablodz/inotifywaitgo v0.0.9 // indirect
github.com/pablodz/inotifywaitgo v0.0.12 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
@@ -346,7 +345,6 @@ require (
github.com/sergi/go-diff v1.4.0 // indirect
github.com/sethvargo/go-diceware v0.5.0 // indirect
github.com/sethvargo/go-password v0.3.1 // indirect
github.com/shamaton/msgpack/v2 v2.4.1 // indirect
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
@@ -412,6 +410,3 @@ replace go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.202411151126
exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a
// to get the logger injection (https://github.com/pablodz/inotifywaitgo/pull/11)
replace github.com/pablodz/inotifywaitgo v0.0.9 => github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9
+8 -12
View File
@@ -449,8 +449,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
@@ -857,8 +857,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mna/pigeon v1.3.0 h1:/3fzVrl1C2RK3x04tyL+ribn+3S3VSEFFbCFLmRPAoc=
@@ -948,12 +946,10 @@ github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-202505121527
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY=
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9 h1:dIftlX03Bzfbujhp9B54FbgER0VBDWJi/w8RBxJlzxU=
github.com/opencloud-eu/inotifywaitgo v0.0.0-20251111171128-a390bae3c5e9/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d h1:JcqGDiyrcaQwVyV861TUyQgO7uEmsjkhfm7aQd84dOw=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20260310090739-853d972b282d/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.46.2 h1:1MNSZj8e2DEMGrzORvTlyCrwYQPnCgtRuPQT+bzD4DM=
github.com/opencloud-eu/reva/v2 v2.46.2/go.mod h1:V1bwCQXM1pGmswtjdSJVIweA9Vv5EmtUZr/7Cm5086A=
github.com/opencloud-eu/reva/v2 v2.46.8 h1:stqn5VfGiIkR7+b/BIsFAqbLbrzSARu43I/t/6G9X3Q=
github.com/opencloud-eu/reva/v2 v2.46.8/go.mod h1:RoFQt+u7edxwzHr1IZ2Y6VaDinMiRPQupAvMBy3WVmE=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -972,6 +968,8 @@ github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CF
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pablodz/inotifywaitgo v0.0.12 h1:DVJJTFMDlSAdO46yisPF2vzozs/r+4k6ih8MVghq78M=
github.com/pablodz/inotifywaitgo v0.0.12/go.mod h1:JWyDC6H+5oZRdUJUgKuaye+8Ph5hEs6HVzVoPKzWSGI=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@@ -1197,8 +1195,6 @@ github.com/testcontainers/testcontainers-go/modules/opensearch v0.42.0 h1:lXPr5N
github.com/testcontainers/testcontainers-go/modules/opensearch v0.42.0/go.mod h1:KJ+m9onB785g9VDRDuhvUvsbkRjnFp3Uz+CHoHpPieA=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4=
github.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg=
github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8=
github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -1362,8 +1358,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+5 -2
View File
@@ -17,7 +17,10 @@ include ../.make/docs.mk
.PHONY: dev-docker
dev-docker:
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev ../..
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev ..
dev-docker-with-workspace:
docker build -f docker/Dockerfile.multiarch -t opencloudeu/opencloud:dev --build-arg SRCDIR=opencloud ../..
.PHONY: dev-docker-multiarch
dev-docker-multiarch:
@@ -28,7 +31,7 @@ dev-docker-multiarch:
docker buildx rm opencloudbuilder || true
docker buildx create --platform linux/arm64,linux/amd64 --name opencloudbuilder
docker buildx use opencloudbuilder
docker buildx build --platform linux/arm64,linux/amd64 --output type=docker --file docker/Dockerfile.multiarch --tag opencloudeu/opencloud:dev-multiarch ../..
docker buildx build --platform linux/arm64,linux/amd64 --output type=docker --file docker/Dockerfile.multiarch --tag opencloudeu/opencloud:dev-multiarch ..
docker buildx rm opencloudbuilder
.PHONY: debug-docker
+4 -5
View File
@@ -1,20 +1,19 @@
FROM golang:alpine3.23 AS build
FROM quay.io/opencloudeu/golang-ci:1.25 AS build
ARG TARGETOS
ARG TARGETARCH
ARG VERSION
ARG STRING
ARG EDITION="dev"
RUN apk add bash make git curl gcc musl-dev libc-dev binutils-gold inotify-tools vips-dev
ARG SRCDIR
WORKDIR /build
RUN --mount=type=bind,target=/build,rw \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache \
GOOS="${TARGETOS:-linux}" GOARCH="${TARGETARCH:-amd64}" ; \
make -C opencloud/opencloud release-linux-docker-${TARGETARCH} ENABLE_VIPS=true DIST=/dist
make -C ${SRCDIR:-.}/opencloud release-linux-docker-${TARGETARCH} ENABLE_VIPS=true DIST=/dist
FROM alpine:3.23
FROM alpine:3.24
ARG VERSION
ARG REVISION
ARG TARGETOS
+273 -429
View File
@@ -1,17 +1,16 @@
package command
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
"github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/config/parser"
"github.com/opencloud-eu/opencloud/pkg/x/path/filepathx"
storageUsersParser "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config/parser"
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/event"
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/revaconfig"
@@ -19,26 +18,10 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/registry"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
"github.com/pkg/xattr"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/theckman/yacspin"
"github.com/vmihailenco/msgpack/v5"
)
// Define the names of the extended attributes we are working with.
const (
parentIDAttrName = "user.oc.parentid"
idAttrName = "user.oc.id"
spaceIDAttrName = "user.oc.space.id"
ownerIDAttrName = "user.oc.owner.id"
)
var (
spinner *yacspin.Spinner
restartRequired = false
ignorer *ignore.Ignorer
)
type IDCacher interface {
@@ -72,11 +55,23 @@ func init() {
// scanCmd performs a posixfs id cache warmup scan
func scanCmd(ocCfg *config.Config) *cobra.Command {
cmd := &cobra.Command{
Use: "scan",
scanCmd := &cobra.Command{
Use: "scan [path ...]",
Short: "Perform a filesystem scan and update the ID and filemetadata cache",
PreRunE: func(cmd *cobra.Command, args []string) error {
Long: `Perform a filesystem scan and update the ID and filemetadata cache.
You can specify one or more paths to limit the scope of the scan.
If no path is provided, the whole storage is checked, starting at the storage root directory.
The provided arguments determines the scope of the check:
- a storage root: the whole storage (all personal and project spaces) is scanned
- a space root: only that space is scanned
- a file or directory: only that single resource is scanned (and its children, if it is a directory)
Any specified file or directory must be underneath the storage root directory and if that is not the case,
the command is aborted with an error before performing any scanning.`,
Args: cobra.ArbitraryArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := parser.ParseConfig(ocCfg, true); err != nil {
return configlog.ReturnError(err)
}
@@ -93,432 +88,281 @@ func scanCmd(ocCfg *config.Config) *cobra.Command {
os.Exit(1)
}
// We want to initialize the driver but disable scanfs on boot, so we can trigger it manually afterwards
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
var fsStream events.Stream
var err error
fsStream, err = event.NewStream(cfg)
haltOnError, err := cmd.Flags().GetBool("halt-on-error")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create event stream for posix driver: %v\n", err)
os.Exit(1)
}
f, ok := registry.NewFuncs["posix"]
if !ok {
fmt.Fprintf(os.Stderr, "posix driver not found in registry\n")
os.Exit(1)
}
fs, err := f(drivers["posix"].(map[string]any), fsStream, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize filesystem driver '%s': %v\n", cfg.Driver, err)
return err
}
cacher, ok := fs.(IDCacher)
if !ok {
fmt.Fprintf(os.Stderr, "The posix driver does not expose WarmupIDCache.\n")
os.Exit(1)
storageRoot := cfg.Drivers.Posix.Root
paths := []string{storageRoot}
if len(args) > 0 {
paths = []string{}
for _, v := range args {
path := v
if !filepath.IsAbs(path) {
if v, err := filepath.Abs(path); err != nil {
fmt.Fprintf(os.Stderr, "Failed to make the specified path %q absolute: %v\n", v, err)
os.Exit(1)
} else {
path = v
}
}
// not ensuring whether the path is under the storage root here, will be done when iterating over them
path = filepath.Clean(path)
paths = append(paths, path)
}
}
fmt.Println("Starting posixfs scan...")
err = cacher.WarmupIDCache(cfg.Drivers.Posix.Root, true, false)
if err != nil {
fmt.Fprintf(os.Stderr, "Scan failed: %v\n", err)
return err
var scan func(path string) error = nil
{
// We want to initialize the driver but disable scanfs on boot, so we can trigger it manually afterwards
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
var fsStream events.Stream
var err error
fsStream, err = event.NewStream(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create event stream for posix driver: %v\n", err)
os.Exit(1)
}
log := logger("posixfs")
f, ok := registry.NewFuncs["posix"]
if !ok {
fmt.Fprintf(os.Stderr, "posix driver not found in registry\n")
os.Exit(1)
}
fs, err := f(drivers["posix"].(map[string]any), fsStream, &log)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to initialize filesystem driver '%s': %v\n", cfg.Driver, err)
return err
}
cacher, ok := fs.(IDCacher)
if !ok {
fmt.Fprintf(os.Stderr, "The posix driver does not expose WarmupIDCache.\n")
os.Exit(1)
}
scan = func(path string) error {
err := cacher.WarmupIDCache(path, true, false)
if err != nil {
logFailure("Error scanning path '%s': %v", path, err)
}
return err
}
}
fmt.Println("Scan completed successfully.")
return nil
errors := processPosixFsResources(paths, !haltOnError,
func(path string) error {
fmt.Println("Scanning personal spaces...")
return scan(path)
},
func(path string) error {
fmt.Println("Scanning project spaces...")
return scan(path)
},
func(path string) error {
fmt.Printf("Scanning space '%s'...\n", path)
return scan(path)
},
func(path string) error {
fmt.Printf("Scanning '%s'...\n", path)
return scan(path)
},
)
if len(errors) == 0 {
fmt.Println("Scan completed successfully.")
return nil
} else {
plural := "s"
if len(errors) == 1 {
plural = ""
}
verb := "completed"
if haltOnError {
verb = "aborted"
}
return fmt.Errorf("scan %s with %d error%s", verb, len(errors), plural)
}
},
}
return cmd
scanCmd.Flags().BoolP("halt-on-error", "E", false, "Halt at once when an error occurs when processing one of the paths (default behaviour is to keep going and attempt to process all paths).")
return scanCmd
}
// consistencyCmd returns a command to check the consistency of the posixfs storage.
func consistencyCmd(cfg *config.Config) *cobra.Command {
func consistencyCmd(ocCfg *config.Config) *cobra.Command {
consCmd := &cobra.Command{
Use: "consistency",
Short: "check the consistency of the posixfs storage",
Use: "consistency [path ...]",
Short: "Check the consistency of the posixfs storage",
Long: `Check the consistency of the posixfs storage.
You can specify one or more paths to limit the scope of the check.
If no path is provided, the whole storage is checked.
The provided arguments determines the scope of the check:
- a storage root: the whole storage (all personal and project spaces) is checked
- a space root: only that space is checked
- a file or directory: only that single entity is checked (and its children, if it is a directory)`,
Args: cobra.ArbitraryArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := parser.ParseConfig(ocCfg, true); err != nil {
return configlog.ReturnError(err)
}
// Parse storage users config
ocCfg.StorageUsers.Commons = ocCfg.Commons
return configlog.ReturnFatal(storageUsersParser.ParseConfig(ocCfg.StorageUsers))
},
RunE: func(cmd *cobra.Command, args []string) error {
return checkPosixfsConsistency(cmd, cfg)
cfg := ocCfg.StorageUsers
if len(args) == 0 {
args = []string{cfg.Drivers.Posix.Root}
}
log := logger("posixfs")
recalculateChecksums, err := cmd.Flags().GetBool("fix-checksums")
if err != nil {
return err
}
drivers := revaconfig.StorageProviderDrivers(cfg)
drivers["posix"] = revaconfig.Posix(cfg, false, false)
opts, err := options.New(drivers["posix"].(map[string]any))
if err != nil {
return err
}
ignorer := ignore.NewIgnorer(opts, &log)
checker := &consistencyChecker{
cfg: cfg,
ignorer: ignorer,
recalculateChecksums: recalculateChecksums,
}
return checker.Check(args)
},
}
consCmd.Flags().StringP("root", "r", "", "Path to the root directory of the posixfs storage")
_ = consCmd.MarkFlagRequired("root")
consCmd.Flags().Bool("fix-checksums", false, "Recalculate and fix the file checksums. This reads every file and can be slow on large storages.")
return consCmd
}
// checkPosixfsConsistency checks the consistency of the posixfs storage.
func checkPosixfsConsistency(cmd *cobra.Command, cfg *config.Config) error {
rootPath, _ := cmd.Flags().GetString("root")
indexesPath := filepath.Join(rootPath, "indexes")
opt, _ := options.New(map[string]interface{}{
"root": rootPath,
})
log := zerolog.Nop()
ignorer = ignore.NewIgnorer(opt, &log)
_, err := os.Stat(indexesPath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("consistency check failed: '%s' is not a posixfs root", rootPath)
}
return fmt.Errorf("error accessing '%s': %w", indexesPath, err)
}
spinnerCfg := yacspin.Config{
Frequency: 100 * time.Millisecond,
CharSet: yacspin.CharSets[11],
StopCharacter: "✓",
StopColors: []string{"fgGreen"},
StopFailCharacter: "✗",
StopFailColors: []string{"fgRed"},
}
spinner, err = yacspin.New(spinnerCfg)
err = spinner.Start()
if err != nil {
return fmt.Errorf("error creating spinner: %w", err)
}
checkSpaces(filepath.Join(rootPath, "users"))
spinner.Suffix(" Personal spaces check ")
spinner.StopMessage("completed\n")
spinner.Stop()
checkSpaces(filepath.Join(rootPath, "projects"))
spinner.Suffix(" Project spaces check ")
spinner.StopMessage("completed")
spinner.Stop()
if restartRequired {
fmt.Println("\n\n ⚠️ Please restart your openCloud instance to apply changes.")
}
return nil
}
func checkSpaces(basePath string) {
dirEntries, err := os.ReadDir(basePath)
if err != nil {
spinner.Message(fmt.Sprintf("Error reading spaces directory '%s'\n", basePath))
spinner.StopFail()
return
}
for _, entry := range dirEntries {
if entry.IsDir() {
fullPath := filepath.Join(basePath, entry.Name())
checkSpace(fullPath)
}
}
}
func checkSpace(spacePath string) {
spinner.Message("")
spinner.Suffix(fmt.Sprintf(" Checking space '%s'", spacePath))
info, err := os.Stat(spacePath)
if err != nil {
logFailure("Error accessing path '%s': %v", spacePath, err)
return
}
if !info.IsDir() {
logFailure("Error: The provided path '%s' is not a directory\n", spacePath)
return
}
spaceID, err := xattr.Get(spacePath, spaceIDAttrName)
if err != nil || len(spaceID) == 0 {
logFailure("Error: The directory '%s' does not seem to be a space root, it's missing the '%s' attribute\n", spacePath, spaceIDAttrName)
return
}
checkSpaceID(spacePath)
checkNodeIDs(spacePath)
}
func checkSpaceID(spacePath string) {
spinner.Message(" - checking space ID uniqueness")
entries, uniqueIDs, oldestEntry, err := gatherAttributes(spacePath)
if err != nil {
logFailure("Failed to gather attributes: %v", err)
return
}
if len(entries) == 0 {
return
}
if len(uniqueIDs) > 1 {
spinner.Pause()
fmt.Println("\n ⚠ Multiple space IDs found:")
for id := range uniqueIDs {
fmt.Printf(" - %s\n", id)
}
fmt.Printf("\n ⏳ Oldest entry is '%s' (modified on %s).\n",
filepath.Base(oldestEntry.Path), oldestEntry.ModTime.Format(time.RFC1123))
targetID := oldestEntry.ParentID
fmt.Printf(" ✅ Proposed target Parent ID: %s\n", targetID)
fmt.Printf("\n Do you want to unify all parent IDs to '%s'? This will modify %d entries, the directory, and the user index. (y/N): ", targetID, len(entries))
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(strings.ToLower(input))
if input != "y" {
spinner.Unpause()
logFailure("Operation cancelled by user.")
return
}
restartRequired = true
obsoleteIDs := []string{}
for id := range uniqueIDs {
if id != targetID {
obsoleteIDs = append(obsoleteIDs, id)
}
}
fixSpaceID(spacePath, obsoleteIDs, targetID, entries)
spinner.Unpause()
}
}
func walkParentIDs(dir string, parentID string) int {
fixes := 0
entries, err := os.ReadDir(dir)
if err != nil {
logFailure("Error reading directory '%s': %v", dir, err)
return 0
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
if ignorer.IsIgnored(fullPath) {
continue
}
actualParentID, err := xattr.Get(fullPath, parentIDAttrName)
if err != nil || string(actualParentID) != parentID {
err = xattr.Set(fullPath, parentIDAttrName, []byte(parentID))
if err != nil {
logFailure("Failed to fix parent ID for '%s': %v", fullPath, err)
} else {
spinner.Pause()
fmt.Printf("\n + Fixed parent ID for '%s'\n", fullPath)
spinner.Unpause()
fixes++
restartRequired = true
}
}
if entry.IsDir() {
nodeID, err := xattr.Get(fullPath, idAttrName)
if err != nil || len(nodeID) == 0 {
logFailure("Directory '%s' missing '%s', skipping its children", fullPath, idAttrName)
continue
}
walkParentIDs(fullPath, string(nodeID))
}
}
return fixes
}
func checkNodeIDs(spacePath string) {
spinner.Message(" - checking parent IDs")
rootID, err := xattr.Get(spacePath, idAttrName)
if err != nil || len(rootID) == 0 {
logFailure("Space root '%s' missing '%s' attribute", spacePath, idAttrName)
return
}
fixes := walkParentIDs(spacePath, string(rootID))
if fixes > 0 {
spinner.Pause()
fmt.Printf("\n ✓ Fixed %d incorrect parent IDs in %s\n", fixes, filepath.Base(spacePath))
spinner.Unpause()
}
}
func fixSpaceID(spacePath string, obsoleteIDs []string, targetID string, entries []EntryInfo) {
// Set all parentid attributes to the proper space ID
err := setAllParentIDAttributes(entries, targetID)
if err != nil {
logFailure("an error occurred during file attribute update: %v", err)
return
}
// Update space ID itself
fmt.Printf(" Updating directory '%s' with attribute '%s' -> %s\n", filepath.Base(spacePath), idAttrName, targetID)
err = xattr.Set(spacePath, idAttrName, []byte(targetID))
if err != nil {
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
return
}
err = xattr.Set(spacePath, spaceIDAttrName, []byte(targetID))
if err != nil {
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
return
}
// update the index
err = updateOwnerIndexFile(spacePath, obsoleteIDs)
if err != nil {
logFailure("Could not update the owner index file: %v", err)
}
}
func gatherAttributes(path string) ([]EntryInfo, map[string]struct{}, EntryInfo, error) {
dirEntries, err := os.ReadDir(path)
if err != nil {
return nil, nil, EntryInfo{}, fmt.Errorf("failed to read directory: %w", err)
}
var allEntries []EntryInfo
uniqueIDs := make(map[string]struct{})
var oldestEntry EntryInfo
oldestTime := time.Now().Add(100 * 365 * 24 * time.Hour) // Set to a future date to find the oldest entry
for _, entry := range dirEntries {
fullPath := filepath.Join(path, entry.Name())
if ignorer.IsIgnored(fullPath) {
continue
}
info, err := os.Stat(fullPath)
if err != nil {
fmt.Printf(" - Warning: could not stat %s: %v\n", entry.Name(), err)
continue
}
parentID, err := xattr.Get(fullPath, parentIDAttrName)
if err != nil {
continue // Skip if attribute doesn't exist or can't be read
}
entryInfo := EntryInfo{
Path: fullPath,
ModTime: info.ModTime(),
ParentID: string(parentID),
}
allEntries = append(allEntries, entryInfo)
uniqueIDs[string(parentID)] = struct{}{}
if entryInfo.ModTime.Before(oldestTime) {
oldestTime = entryInfo.ModTime
oldestEntry = entryInfo
}
}
return allEntries, uniqueIDs, oldestEntry, nil
}
func setAllParentIDAttributes(entries []EntryInfo, targetID string) error {
fmt.Printf(" Setting all parent IDs to '%s':\n", targetID)
for _, entry := range entries {
if entry.ParentID == targetID {
fmt.Printf(" - Skipping '%s' (already has target ID).\n", filepath.Base(entry.Path))
continue
}
fmt.Printf(" - Removing all attributes from '%s'. It will be re-assimilated\n", filepath.Base(entry.Path))
filepath.WalkDir(entry.Path, func(path string, d os.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("error walking path '%s': %w", path, err)
}
// Remove all attributes from the file.
if err := removeAttributes(path); err != nil {
fmt.Printf("failed to remove attributes from '%s': %v", path, err)
}
return nil
})
}
return nil
}
// updateOwnerIndexFile handles the logic of reading, modifying, and writing the MessagePack index file.
func updateOwnerIndexFile(basePath string, obsoleteIDs []string) error {
fmt.Printf(" Rewriting index file '%s'\n", basePath)
ownerID, err := xattr.Get(basePath, ownerIDAttrName)
if err != nil {
return fmt.Errorf("could not get owner ID from oldest entry '%s' to find index: %w", basePath, err)
}
indexPath := filepath.Join(basePath, "../../indexes/by-user-id", string(ownerID)+".mpk")
indexPath = filepath.Clean(indexPath)
// Read the MessagePack file
fileData, err := os.ReadFile(indexPath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("index file does not exist, skipping update")
}
return fmt.Errorf("could not read index file: %w", err)
}
var indexMap map[string]string
if err := msgpack.Unmarshal(fileData, &indexMap); err != nil {
return fmt.Errorf("failed to parse MessagePack index file (is it corrupt?): %w", err)
}
// Remove obsolete IDs from the map
itemsRemoved := 0
for _, id := range obsoleteIDs {
if _, exists := indexMap[id]; exists {
fmt.Printf(" - Removing obsolete ID '%s' from index.\n", id)
delete(indexMap, id)
itemsRemoved++
} else {
fmt.Printf(" - Obsolete ID '%s' not found in index\n", id)
}
}
if itemsRemoved == 0 {
return nil
}
// Write the data back to the file
updatedData, err := msgpack.Marshal(&indexMap)
if err != nil {
return fmt.Errorf("failed to marshal updated index map: %w", err)
}
if err := os.WriteFile(indexPath, updatedData, 0644); err != nil {
return fmt.Errorf("failed to write updated index file: %w", err)
}
fmt.Printf(" ✓ Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
return nil
}
func removeAttributes(path string) error {
attrNames, err := xattr.List(path)
if err != nil {
return fmt.Errorf("failed to list attributes for '%s': %w", path, err)
}
for _, attrName := range attrNames {
if err := xattr.Remove(path, attrName); err != nil {
return fmt.Errorf("failed to remove attribute '%s' from '%s': %w", attrName, path, err)
}
}
return nil
}
func logFailure(message string, args ...any) {
spinner.StopFailMessage(fmt.Sprintf("\n"+message, args...))
spinner.StopFail()
spinner.Start()
fmt.Fprintf(os.Stderr, message+"\n", args...)
}
// findStorageRoot walks up the directory tree starting at path until it finds a
// directory that contains an "indexes" subdirectory which marks the root of a
// posixfs storage. A user directory inside a space might also be named "indexes",
// so to disambiguate we require that the "indexes" directory is an internal
// directory: the storage's own indexes directory is skipped during assimilation
// and therefore never receives a node ID attribute, whereas a regular user
// directory would have one.
func findStorageRoot(path string) (string, error) {
current := path
for {
indexesPath := filepath.Join(current, "indexes")
if info, err := os.Stat(indexesPath); err == nil && info.IsDir() {
if id, err := xattr.Get(indexesPath, prefixes.IDAttr); err != nil || len(id) == 0 {
return current, nil
}
}
parent := filepath.Dir(current)
if parent == current {
return "", fmt.Errorf("'%s' does not appear to be inside a posixfs storage (no 'indexes' directory found)", path)
}
current = parent
}
}
// isSpaceRoot reports whether the given path is a space root, which is
// identified by the presence of the space ID attribute.
func isSpaceRoot(path string) bool {
spaceID, err := xattr.Get(path, prefixes.SpaceIDAttr)
return err == nil && len(spaceID) > 0
}
// iterates over a list of paths and processes them all, using the appropriate function
// depending on the type of resource
//
// note that whenever an error occurs, it collects that error and continues processing
// subsequent paths, and then returns a slice of errors at the end (or an empty slice
// if no errors occured)
func processPosixFsResources(paths []string,
keepGoing bool,
personalSpaceDir func(string) error,
projectSpaceDir func(string) error,
spaceRoot func(string) error,
entity func(string) error,
) []error {
// no need to guard this with a mutex for now, since the implementation is not parallelized
errors := []error{}
for _, path := range paths {
rootPath, err := findStorageRoot(path)
if err != nil {
errors = append(errors, err)
logFailure("error: %s", err)
if keepGoing {
continue
} else {
return errors
}
}
path = filepath.Clean(path)
if _, err := os.Stat(path); err != nil {
errors = append(errors, err)
logFailure("error accessing '%s': %w", path, err)
if keepGoing {
continue
} else {
return errors
}
}
contained, _ := filepathx.IsSameOrContainedBy(rootPath, path)
switch {
case path == rootPath:
if err := personalSpaceDir(filepath.Join(path, "users")); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
if err := projectSpaceDir(filepath.Join(path, "projects")); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
case isSpaceRoot(path):
if err := spaceRoot(path); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
case contained:
if err := entity(path); err != nil {
errors = append(errors, err)
if !keepGoing {
return errors
}
}
default:
err := fmt.Errorf("error: the provided path '%s' is neither a space root nor contained by the storage root '%s'", path, rootPath)
errors = append(errors, err)
logFailure(err.Error())
if !keepGoing {
return errors
}
}
}
return errors
}
@@ -0,0 +1,489 @@
// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
// SPDX-License-Identifier: Apache-2.0
package command
import (
"bufio"
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
storageUsersConfig "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/ignore"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/metadata/prefixes"
"github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/node"
"github.com/pkg/xattr"
"github.com/shamaton/msgpack/v2"
)
type consistencyChecker struct {
cfg *storageUsersConfig.Config
ignorer *ignore.Ignorer
recalculateChecksums bool
restartRequired bool
}
// checkPosixfsConsistency checks the consistency of the posixfs storage. The
// given path determines the scope of the check: the whole storage, a single
// space or a single entity within a space.
func (c *consistencyChecker) Check(paths []string) error {
_ = processPosixFsResources(paths, true,
func(path string) error {
fmt.Println("Checking personal spaces...")
c.checkSpaces(path)
return nil
},
func(path string) error {
fmt.Println("Checking project spaces...")
c.checkSpaces(path)
return nil
},
func(path string) error {
fmt.Printf("Checking space '%s'...\n", path)
c.checkSpace(path)
return nil
},
func(path string) error {
if c.ignorer.IsIgnored(path) {
return nil
}
fmt.Printf("Checking '%s'...\n", path)
c.checkEntity(path)
return nil
},
)
if c.restartRequired {
fmt.Println("\n\n ⚠️ Please restart your openCloud instance to apply changes.")
}
return nil
}
func (c *consistencyChecker) checkSpaces(basePath string) {
dirEntries, err := os.ReadDir(basePath)
if err != nil {
logFailure("Error reading spaces directory '%s': %v", basePath, err)
return
}
for _, entry := range dirEntries {
if entry.IsDir() {
fullPath := filepath.Join(basePath, entry.Name())
c.checkSpace(fullPath)
}
}
}
func (c *consistencyChecker) checkSpace(spacePath string) {
info, err := os.Stat(spacePath)
if err != nil {
logFailure("Error accessing path '%s': %v", spacePath, err)
return
}
if !info.IsDir() {
logFailure("Error: The provided path '%s' is not a directory\n", spacePath)
return
}
spaceID, err := xattr.Get(spacePath, prefixes.SpaceIDAttr)
if err != nil || len(spaceID) == 0 {
logFailure("Error: The directory '%s' does not seem to be a space root, it's missing the '%s' attribute\n", spacePath, prefixes.SpaceIDAttr)
return
}
c.checkSpaceID(spacePath)
c.checkNodes(spacePath)
}
func (c *consistencyChecker) checkSpaceID(spacePath string) {
entries, uniqueIDs, oldestEntry, err := c.gatherAttributes(spacePath)
if err != nil {
logFailure("Failed to gather attributes: %v", err)
return
}
if len(entries) == 0 {
return
}
if len(uniqueIDs) > 1 {
fmt.Println("\n ⚠ Multiple space IDs found:")
for id := range uniqueIDs {
fmt.Printf(" - %s\n", id)
}
fmt.Printf("\n ⏳ Oldest entry is '%s' (modified on %s).\n",
filepath.Base(oldestEntry.Path), oldestEntry.ModTime.Format(time.RFC1123))
targetID := oldestEntry.ParentID
fmt.Printf(" ✅ Proposed target Parent ID: %s\n", targetID)
fmt.Printf("\n Do you want to unify all parent IDs to '%s'? This will modify %d entries, the directory, and the user index. (y/N): ", targetID, len(entries))
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(strings.ToLower(input))
if input != "y" {
logFailure("Operation cancelled by user.")
return
}
c.restartRequired = true
obsoleteIDs := []string{}
for id := range uniqueIDs {
if id != targetID {
obsoleteIDs = append(obsoleteIDs, id)
}
}
c.fixSpaceID(spacePath, obsoleteIDs, targetID, entries)
}
}
func (c *consistencyChecker) walkNodes(dir string, parentID string) int {
fixes := 0
entries, err := os.ReadDir(dir)
if err != nil {
logFailure("Error reading directory '%s': %v", dir, err)
return 0
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
if c.ignorer.IsIgnored(fullPath) {
continue
}
fixes += c.checkNodeAttributes(fullPath, entry.Name(), parentID, entry.IsDir())
if entry.IsDir() {
nodeID, err := xattr.Get(fullPath, prefixes.IDAttr)
if err != nil || len(nodeID) == 0 {
logFailure("Directory '%s' missing '%s', skipping its children", fullPath, prefixes.IDAttr)
continue
}
fixes += c.walkNodes(fullPath, string(nodeID))
}
}
return fixes
}
// checkNodeAttributes checks and fixes the parent ID and name attributes of a
// single node. For files it additionally checks the blobsize and, when
// requested, the checksums. It returns the number of fixes applied.
func (c *consistencyChecker) checkNodeAttributes(path, name, parentID string, isDir bool) int {
fixes := 0
// Check if the parent ID attribute matches the expected parent ID, if not, fix it.
actualParentID, err := xattr.Get(path, prefixes.ParentidAttr)
if err != nil || string(actualParentID) != parentID {
if err := xattr.Set(path, prefixes.ParentidAttr, []byte(parentID)); err != nil {
logFailure("Failed to fix parent ID for '%s': %v", path, err)
} else {
fmt.Printf(" + Fixed parent ID for '%s'\n", path)
fixes++
c.restartRequired = true
}
}
// Check that the name attribute matches the actual name of the file/directory, if not, fix it.
nameAttr, err := xattr.Get(path, prefixes.NameAttr)
if err != nil || string(nameAttr) != name {
if err := xattr.Set(path, prefixes.NameAttr, []byte(name)); err != nil {
logFailure("Failed to fix name attribute for '%s': %v", path, err)
} else {
fmt.Printf(" + Fixed name attribute for '%s'\n", path)
fixes++
c.restartRequired = true
}
}
if !isDir {
fixes += c.checkBlobsize(path)
if c.recalculateChecksums {
fixes += c.fixChecksums(path)
}
}
return fixes
}
// checkEntity checks a single file or directory within a space, including its own
// parent ID, name and (for files) blobsize/checksums. If the entity is a directory
// its children are checked recursively.
func (c *consistencyChecker) checkEntity(path string) {
info, err := os.Stat(path)
if err != nil {
logFailure("Error accessing path '%s': %v", path, err)
return
}
// The expected parent ID is the ID attribute of the containing directory.
parentDir := filepath.Dir(path)
parentID, err := xattr.Get(parentDir, prefixes.IDAttr)
if err != nil || len(parentID) == 0 {
logFailure("Parent directory '%s' is missing the '%s' attribute", parentDir, prefixes.IDAttr)
return
}
fixes := c.checkNodeAttributes(path, info.Name(), string(parentID), info.IsDir())
if info.IsDir() {
nodeID, err := xattr.Get(path, prefixes.IDAttr)
if err != nil || len(nodeID) == 0 {
logFailure("Directory '%s' missing '%s' attribute", path, prefixes.IDAttr)
} else {
fixes += c.walkNodes(path, string(nodeID))
}
}
if fixes > 0 {
fmt.Printf(" ✓ Fixed %d incorrect node attributes for %s\n", fixes, filepath.Base(path))
}
}
// checkBlobsize verifies that the stored blobsize attribute matches the actual
// file size and fixes it if it doesn't. It returns the number of fixes applied.
func (c *consistencyChecker) checkBlobsize(path string) int {
info, err := os.Stat(path)
if err != nil {
logFailure("Error accessing file '%s': %v", path, err)
return 0
}
expectedSize := strconv.FormatInt(info.Size(), 10)
blobsize, err := xattr.Get(path, prefixes.BlobsizeAttr)
if err == nil && string(blobsize) == expectedSize {
return 0
}
if err := xattr.Set(path, prefixes.BlobsizeAttr, []byte(expectedSize)); err != nil {
logFailure("Failed to fix blobsize for '%s': %v", path, err)
return 0
}
fmt.Printf(" + Fixed blobsize for '%s'\n", path)
c.restartRequired = true
return 1
}
// fixChecksums recalculates the sha1, md5 and adler32 checksums of the file and
// updates the stored attributes if they differ. It returns the number of fixes applied.
func (c *consistencyChecker) fixChecksums(path string) int {
sha1h, md5h, adler32h, err := node.CalculateChecksums(context.Background(), path)
if err != nil {
logFailure("Failed to calculate checksums for '%s': %v", path, err)
return 0
}
checksums := map[string][]byte{
prefixes.ChecksumPrefix + "sha1": sha1h.Sum(nil),
prefixes.ChecksumPrefix + "md5": md5h.Sum(nil),
prefixes.ChecksumPrefix + "adler32": adler32h.Sum(nil),
}
fixes := 0
for attrName, sum := range checksums {
current, err := xattr.Get(path, attrName)
if err == nil && bytes.Equal(current, sum) {
continue
}
if err := xattr.Set(path, attrName, sum); err != nil {
logFailure("Failed to fix checksum '%s' for '%s': %v", attrName, path, err)
continue
}
fmt.Printf(" + Fixed checksum '%s' for '%s'\n", attrName, path)
c.restartRequired = true
fixes++
}
return fixes
}
func (c *consistencyChecker) checkNodes(spacePath string) {
rootID, err := xattr.Get(spacePath, prefixes.IDAttr)
if err != nil || len(rootID) == 0 {
logFailure("Space root '%s' missing '%s' attribute", spacePath, prefixes.IDAttr)
return
}
fixes := c.walkNodes(spacePath, string(rootID))
if fixes > 0 {
fmt.Printf(" ✓ Fixed %d incorrect node attributes in %s\n", fixes, filepath.Base(spacePath))
}
}
// fixSpaceID updates the parentid attributes of all entries in a space to a new target ID,
// updates the space's own ID attributes, and removes obsolete IDs from the user index file.
func (c *consistencyChecker) fixSpaceID(spacePath string, obsoleteIDs []string, targetID string, entries []EntryInfo) {
// Set all parentid attributes to the proper space ID
err := setAllParentIDAttributes(entries, targetID)
if err != nil {
logFailure("an error occurred during file attribute update: %v", err)
return
}
// Update space ID itself
fmt.Printf(" Updating directory '%s' with attribute '%s' -> %s\n", filepath.Base(spacePath), prefixes.IDAttr, targetID)
err = xattr.Set(spacePath, prefixes.IDAttr, []byte(targetID))
if err != nil {
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
return
}
err = xattr.Set(spacePath, prefixes.SpaceIDAttr, []byte(targetID))
if err != nil {
logFailure("Failed to set attribute on directory '%s': %v", spacePath, err)
return
}
// update the index
err = c.updateOwnerIndexFile(spacePath, obsoleteIDs)
if err != nil {
logFailure("Could not update the owner index file: %v", err)
}
}
func (c *consistencyChecker) gatherAttributes(path string) ([]EntryInfo, map[string]struct{}, EntryInfo, error) {
dirEntries, err := os.ReadDir(path)
if err != nil {
return nil, nil, EntryInfo{}, fmt.Errorf("failed to read directory: %w", err)
}
var allEntries []EntryInfo
uniqueIDs := make(map[string]struct{})
var oldestEntry EntryInfo
oldestTime := time.Now().Add(100 * 365 * 24 * time.Hour) // Set to a future date to find the oldest entry
for _, entry := range dirEntries {
fullPath := filepath.Join(path, entry.Name())
if c.ignorer.IsIgnored(fullPath) {
continue
}
info, err := os.Stat(fullPath)
if err != nil {
fmt.Printf(" - Warning: could not stat %s: %v\n", entry.Name(), err)
continue
}
parentID, err := xattr.Get(fullPath, prefixes.ParentidAttr)
if err != nil {
continue // Skip if attribute doesn't exist or can't be read
}
entryInfo := EntryInfo{
Path: fullPath,
ModTime: info.ModTime(),
ParentID: string(parentID),
}
allEntries = append(allEntries, entryInfo)
uniqueIDs[string(parentID)] = struct{}{}
if entryInfo.ModTime.Before(oldestTime) {
oldestTime = entryInfo.ModTime
oldestEntry = entryInfo
}
}
return allEntries, uniqueIDs, oldestEntry, nil
}
func setAllParentIDAttributes(entries []EntryInfo, targetID string) error {
fmt.Printf(" Setting all parent IDs to '%s':\n", targetID)
for _, entry := range entries {
if entry.ParentID == targetID {
fmt.Printf(" - Skipping '%s' (already has target ID).\n", filepath.Base(entry.Path))
continue
}
fmt.Printf(" - Removing all attributes from '%s'. It will be re-assimilated\n", filepath.Base(entry.Path))
filepath.WalkDir(entry.Path, func(path string, d os.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("error walking path '%s': %w", path, err)
}
// Remove all attributes from the file.
if err := removeAttributes(path); err != nil {
fmt.Printf("failed to remove attributes from '%s': %v", path, err)
}
return nil
})
}
return nil
}
// updateOwnerIndexFile handles the logic of reading, modifying, and writing the MessagePack index file.
func (c *consistencyChecker) updateOwnerIndexFile(basePath string, obsoleteIDs []string) error {
fmt.Printf(" Rewriting index file '%s'\n", basePath)
ownerID, err := xattr.Get(basePath, prefixes.OwnerIDAttr)
if err != nil {
return fmt.Errorf("could not get owner ID from oldest entry '%s' to find index: %w", basePath, err)
}
indexPath := filepath.Join(c.cfg.Drivers.Posix.Root, "indexes", "by-user-id", string(ownerID)+".mpk")
indexPath = filepath.Clean(indexPath)
// Read the MessagePack file
fileData, err := os.ReadFile(indexPath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("index file does not exist, skipping update")
}
return fmt.Errorf("could not read index file: %w", err)
}
var indexMap map[string]string
if err := msgpack.Unmarshal(fileData, &indexMap); err != nil {
return fmt.Errorf("failed to parse MessagePack index file (is it corrupt?): %w", err)
}
// Remove obsolete IDs from the map
itemsRemoved := 0
for _, id := range obsoleteIDs {
if _, exists := indexMap[id]; exists {
fmt.Printf(" - Removing obsolete ID '%s' from index.\n", id)
delete(indexMap, id)
itemsRemoved++
} else {
fmt.Printf(" - Obsolete ID '%s' not found in index\n", id)
}
}
if itemsRemoved == 0 {
return nil
}
// Write the data back to the file
updatedData, err := msgpack.Marshal(&indexMap)
if err != nil {
return fmt.Errorf("failed to marshal updated index map: %w", err)
}
if err := os.WriteFile(indexPath, updatedData, 0644); err != nil {
return fmt.Errorf("failed to write updated index file: %w", err)
}
fmt.Printf(" ✓ Successfully removed %d item(s) and saved index file.\n", itemsRemoved)
return nil
}
func removeAttributes(path string) error {
attrNames, err := xattr.List(path)
if err != nil {
return fmt.Errorf("failed to list attributes for '%s': %w", path, err)
}
for _, attrName := range attrNames {
if err := xattr.Remove(path, attrName); err != nil {
return fmt.Errorf("failed to remove attribute '%s' from '%s': %w", attrName, path, err)
}
}
return nil
}
+12 -2
View File
@@ -6,11 +6,13 @@ import (
"os/signal"
"syscall"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/opencloud-eu/opencloud/opencloud/pkg/register"
"github.com/opencloud-eu/opencloud/pkg/clihelper"
"github.com/opencloud-eu/opencloud/pkg/config"
"github.com/spf13/cobra"
oclog "github.com/opencloud-eu/opencloud/pkg/log"
)
// Execute is the entry point for the opencloud command.
@@ -38,3 +40,11 @@ func Execute() error {
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
return app.ExecuteContext(ctx)
}
func logger(name string) zerolog.Logger {
return oclog.NewLogger(
oclog.Name(name),
oclog.Level("info"),
oclog.Pretty(true),
oclog.Color(true)).Logger
}
+2 -12
View File
@@ -9,7 +9,6 @@ import (
"github.com/opencloud-eu/opencloud/pkg/config"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/config/parser"
oclog "github.com/opencloud-eu/opencloud/pkg/log"
mregistry "github.com/opencloud-eu/opencloud/pkg/registry"
sharing "github.com/opencloud-eu/opencloud/services/sharing/pkg/config"
sharingparser "github.com/opencloud-eu/opencloud/services/sharing/pkg/config/parser"
@@ -85,7 +84,7 @@ func cleanup(_ *cobra.Command, cfg *config.Config) error {
return configlog.ReturnError(errors.New("cleanup is only implemented for the jsoncs3 share manager"))
}
l := logger()
l := logger("migrate")
zerolog.SetGlobalLevel(zerolog.InfoLevel)
@@ -94,7 +93,7 @@ func cleanup(_ *cobra.Command, cfg *config.Config) error {
if !ok {
return configlog.ReturnError(errors.New("Unknown share manager type '" + driver + "'"))
}
mgr, err := f(rcfg[driver].(map[string]any), l)
mgr, err := f(rcfg[driver].(map[string]any), &l)
if err != nil {
return configlog.ReturnError(err)
}
@@ -167,12 +166,3 @@ func revaShareConfig(cfg *sharing.Config) map[string]any {
},
}
}
func logger() *zerolog.Logger {
log := oclog.NewLogger(
oclog.Name("migrate"),
oclog.Level("info"),
oclog.Pretty(true),
oclog.Color(true)).Logger
return &log
}
+20
View File
@@ -0,0 +1,20 @@
package nats
import (
"crypto/tls"
"github.com/nats-io/nats.go"
)
func Secure(enableTLS, insecure bool, rootCA string) nats.Option {
if enableTLS {
if rootCA != "" {
return nats.RootCAs(rootCA)
}
return nats.Secure(&tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: insecure,
})
}
return nil
}
+11 -8
View File
@@ -48,14 +48,17 @@ type HTTPServiceTLS struct {
}
type Cache struct {
Store string `yaml:"store" env:"OC_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_CACHE_STORE_NODES" desc:"A comma separated list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"OC_CACHE_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"OC_CACHE_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_CACHE_TTL" desc:"Time to live for events in the store. The duration can be set as number followed by a unit identifier like s, m or h." introductionVersion:"1.0.0"`
DisablePersistence bool `yaml:"disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"auth_username" env:"OC_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"auth_password" env:"OC_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Store string `yaml:"store" env:"OC_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_CACHE_STORE_NODES" desc:"A comma separated list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"OC_CACHE_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"OC_CACHE_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_CACHE_TTL" desc:"Time to live for events in the store. The duration can be set as number followed by a unit identifier like s, m or h." introductionVersion:"1.0.0"`
DisablePersistence bool `yaml:"disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"auth_username" env:"OC_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"auth_password" env:"OC_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_CACHE_ENABLE_TLS" desc:"Enable TLS for the connection to file metadata cache." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_CACHE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_CACHE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided OC_CACHE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
// Commons holds configuration that are common to all extensions. Each extension can then decide whether
+1 -1
View File
@@ -34,7 +34,7 @@ var (
// LatestTag is the latest released version plus the dev meta version.
// Will be overwritten by the release pipeline
// Needs a manual change for every tagged release
LatestTag = "7.1.0+dev"
LatestTag = "7.2.4+dev"
// Date indicates the build date.
// This has been removed, it looks like you can only replace static strings with recent go versions
+26
View File
@@ -1,7 +1,9 @@
package filepathx
import (
"fmt"
"path/filepath"
"strings"
)
// JailJoin joins any number of path elements into a single path,
@@ -10,3 +12,27 @@ import (
func JailJoin(jail string, elem ...string) string {
return filepath.Join(jail, filepath.Join(append([]string{"/"}, elem...)...))
}
// Determines whether the file or directory 'child' is same as or underneath the directory 'parent'.
//
// Note that 'parent' is expected to be a directory.
func IsSameOrContainedBy(parent string, child string) (bool, error) {
absParent, err := filepath.Abs(parent)
if err != nil {
return false, fmt.Errorf("failed to make parent directory absolute: %q: %w", parent, err)
}
absChild, err := filepath.Abs(child)
if err != nil {
return false, fmt.Errorf("failed to make child file/directory absolute: %q: %w", child, err)
}
rel, err := filepath.Rel(absParent, absChild)
if err != nil {
return false, fmt.Errorf("failed to determine the relative path between the parent directory %q and the child file/directory: %q: %w", absParent, absChild, err)
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return false, nil
}
return true, nil
}
+23
View File
@@ -1,9 +1,12 @@
package filepathx_test
import (
"fmt"
"strings"
"testing"
"github.com/opencloud-eu/opencloud/pkg/x/path/filepathx"
"github.com/stretchr/testify/require"
)
func TestJailJoin(t *testing.T) {
@@ -61,3 +64,23 @@ func TestJailJoin(t *testing.T) {
})
}
}
func TestIsSameOrContainedBy(t *testing.T) {
for _, tt := range []struct {
parent string
child string
expected bool
}{
{"foo", "foo", true},
{"/foo", "/foo", true},
{"foo", "foo/bar", true},
{"foo", "bar", false},
} {
t.Run(fmt.Sprintf("%s: %s vs %s", t.Name(), strings.ReplaceAll(tt.parent, "/", "."), strings.ReplaceAll(tt.child, "/", ".")), func(t *testing.T) {
require := require.New(t)
b, err := filepathx.IsSameOrContainedBy(tt.parent, tt.child)
require.NoError(err)
require.Equal(tt.expected, b)
})
}
}
@@ -9,9 +9,7 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/store"
"github.com/spf13/cobra"
microstore "go-micro.dev/v4/store"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/generators"
@@ -77,15 +75,6 @@ func Server(cfg *config.Config) *cobra.Command {
return err
}
evStore := store.Create(
store.Store(cfg.Store.Store),
store.TTL(cfg.Store.TTL),
microstore.Nodes(cfg.Store.Nodes...),
microstore.Database(cfg.Store.Database),
microstore.Table(cfg.Store.Table),
store.Authentication(cfg.Store.AuthUsername, cfg.Store.AuthPassword),
)
tm, err := pool.StringToTLSMode(cfg.GRPCClientTLS.Mode)
if err != nil {
logger.Error().Err(err).Msg("Failed to parse tls mode")
@@ -120,7 +109,6 @@ func Server(cfg *config.Config) *cobra.Command {
http.Context(ctx), // NOTE: not passing this "option" leads to a panic in go-micro
http.TraceProvider(tracerProvider),
http.Stream(evStream),
http.Store(evStore),
http.GatewaySelector(gatewaySelector),
http.HistoryClient(hClient),
http.ValueClient(vClient),
+9 -7
View File
@@ -50,13 +50,15 @@ type Events struct {
// Store configures the store to use
type Store struct {
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;ACTIVITYLOG_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;ACTIVITYLOG_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"ACTIVITYLOG_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"ACTIVITYLOG_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;ACTIVITYLOG_STORE_TTL" desc:"Time to live for events in the store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;ACTIVITYLOG_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;ACTIVITYLOG_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;ACTIVITYLOG_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;ACTIVITYLOG_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"ACTIVITYLOG_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;ACTIVITYLOG_STORE_TTL" desc:"Time to live for events in the store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;ACTIVITYLOG_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;ACTIVITYLOG_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;ACTIVITYLOG_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;ACTIVITYLOG_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;ACTIVITYLOG_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided ACTIVITYLOG_STORE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
// ServiceAccount is the configuration for the used service account
@@ -37,7 +37,6 @@ func DefaultConfig() *config.Config {
Store: "nats-js-kv",
Nodes: []string{"127.0.0.1:9233"},
Database: "activitylog",
Table: "",
},
RevaGateway: shared.DefaultRevaConfig().Address,
DefaultLanguage: "en",
@@ -5,6 +5,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -17,8 +18,13 @@ func Server(opts ...Option) (*http.Server, error) {
WithLogger(options.Logger).
WithCheck("http reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr))
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration := healthHandlerConfiguration.
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption))
return debug.NewService(
debug.Logger(options.Logger),
@@ -81,7 +81,6 @@ func Server(opts ...Option) (http.Service, error) {
svc.Logger(options.Logger),
svc.Stream(options.Stream),
svc.Mux(mux),
svc.Store(options.Store),
svc.Config(options.Config),
svc.GatewaySelector(options.GatewaySelector),
svc.TraceProvider(options.TraceProvider),
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2025\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jörn Friedrich Dreyer <jfd@butonic.de>, 2025\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-23 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Elías Martín, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-08 00:02+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-04 07:46+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: mitibor, 2026\n"
"Language-Team: Hungarian (https://app.transifex.com/opencloud-eu/teams/204053/hu/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Broglia, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-19 00:04+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"
@@ -5,16 +5,16 @@
#
# Translators:
# gapho shin, 2025
# Junghyuk Kwon <kwon@junghy.uk>, 2025
# 4580f07cef35810b72f8a1e8fd521c9a_6684a19 <07c3e3571dc5eebd81f2c3a10488f503_1171750>, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Junghyuk Kwon <kwon@junghy.uk>, 2025\n"
"Last-Translator: 4580f07cef35810b72f8a1e8fd521c9a_6684a19 <07c3e3571dc5eebd81f2c3a10488f503_1171750>, 2025\n"
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-30 00:02+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: BoneNI, 2026\n"
"Language-Team: Lao (https://app.transifex.com/opencloud-eu/teams/204053/lo/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-26 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
@@ -0,0 +1,104 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# skrzat <skrzacikus@gmail.com>, 2025
# Radoslaw Posim, 2025
# Matt, 2026
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Matt, 2026\n"
"Language-Team: Polish (https://app.transifex.com/opencloud-eu/teams/204053/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
#: pkg/service/response.go:44
msgid "description"
msgstr "opis"
#: pkg/service/response.go:43
msgid "display name"
msgstr "wyświetlana nazwa"
#: pkg/service/response.go:42
msgid "expiration date"
msgstr "data wygaśnięcia"
#: pkg/service/response.go:41
msgid "password"
msgstr "hasło"
#: pkg/service/response.go:40
msgid "permission"
msgstr "uprawnienie"
#: pkg/service/response.go:39
msgid "some field"
msgstr "jakieś pole"
#: pkg/service/response.go:26
msgid "{resource} was downloaded via public link {token}"
msgstr "{resource} zostało pobrane przez link publiczny {token}"
#: pkg/service/response.go:24
msgid "{user} added {resource} to {folder}"
msgstr "{user} dodał {resource} do {folder}"
#: pkg/service/response.go:36
msgid "{user} added {sharee} as member of {space}"
msgstr "{user} dodał {sharee} jako członka {space}"
#: pkg/service/response.go:27
msgid "{user} deleted {resource} from {folder}"
msgstr "{user} usunął {resource} z {folder}"
#: pkg/service/response.go:28
msgid "{user} moved {resource} to {folder}"
msgstr "{user} przeniósł {resource} do {folder}"
#: pkg/service/response.go:35
msgid "{user} removed link to {resource}"
msgstr "{user} usunął link do {resource}"
#: pkg/service/response.go:32
msgid "{user} removed {sharee} from {resource}"
msgstr "{user} usunął {sharee} z {resource}"
#: pkg/service/response.go:37
msgid "{user} removed {sharee} from {space}"
msgstr "{user} usunął {sharee} z {space}"
#: pkg/service/response.go:29
msgid "{user} renamed {oldResource} to {resource}"
msgstr "{user} zmienił nazwę z {oldResource} na {resource}"
#: pkg/service/response.go:33
msgid "{user} shared {resource} via link"
msgstr "Użytkownik {user} udostępnił {resource} przez link"
#: pkg/service/response.go:30
msgid "{user} shared {resource} with {sharee}"
msgstr "{user} udostępnił {resource} dla {sharee}"
#: pkg/service/response.go:34
msgid "{user} updated {field} for a link {token} on {resource}"
msgstr "{user} zaktualizował {field} dla linku {token} do {resource}"
#: pkg/service/response.go:31
msgid "{user} updated {field} for the {resource}"
msgstr "{user} zaktualizował {field} dla {resource}"
#: pkg/service/response.go:25
msgid "{user} updated {resource} in {folder}"
msgstr "{user} zaktualizował {resource} w {folder}"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-06 00:01+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Mário Machado, 2025\n"
"Language-Team: Portuguese (https://app.transifex.com/opencloud-eu/teams/204053/pt/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-26 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Lulufox, 2025\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-20 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-19 00:04+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Quan Tran, 2025\n"
"Language-Team: Vietnamese (https://app.transifex.com/opencloud-eu/teams/204053/vi/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: YQS Yang, 2025\n"
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"
@@ -11,7 +11,6 @@ import (
"github.com/opencloud-eu/opencloud/services/activitylog/pkg/config"
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
microstore "go-micro.dev/v4/store"
"go.opentelemetry.io/otel/trace"
)
@@ -25,7 +24,6 @@ type Options struct {
TraceProvider trace.TracerProvider
Stream events.Stream
RegisteredEvents []events.Unmarshaller
Store microstore.Store
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
Mux *chi.Mux
HistoryClient ehsvc.EventHistoryService
@@ -69,13 +67,6 @@ func RegisteredEvents(e []events.Unmarshaller) Option {
}
}
// Store configures the store to use
func Store(store microstore.Store) Option {
return func(o *Options) {
o.Store = store
}
}
// GatewaySelector adds a grpc client selector for the gateway service
func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option {
return func(o *Options) {
@@ -2,6 +2,7 @@ package service
import (
"context"
"crypto/tls"
"encoding/base32"
"encoding/json"
"fmt"
@@ -166,6 +167,18 @@ func New(opts ...Option) (*ActivitylogService, error) {
natsOptions := nats.Options{
Servers: o.Config.Store.Nodes,
}
if o.Config.Store.EnableTLS {
if o.Config.Store.TLSRootCACertificate != "" {
// when root ca is configured use it. an insecure flag is ignored.
nats.RootCAs(o.Config.Store.TLSRootCACertificate)(&natsOptions)
} else {
// enable tls and use insecure flag
nats.Secure(&tls.Config{MinVersion: tls.VersionTLS12, InsecureSkipVerify: o.Config.Store.TLSInsecure})(&natsOptions)
}
}
if o.Config.Store.AuthUsername != "" && o.Config.Store.AuthPassword != "" {
nats.UserInfo(o.Config.Store.AuthUsername, o.Config.Store.AuthPassword)(&natsOptions)
}
conn, err := natsOptions.Connect()
if err != nil {
return nil, err
@@ -10,6 +10,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -18,9 +19,14 @@ import (
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration := handlers.NewCheckHandlerConfiguration().
WithLogger(options.Logger).
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint)).
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption)).
WithCheck("antivirus reachability", func(ctx context.Context) error {
cfg := options.Config
switch cfg.Scanner.Type {
+7 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -13,9 +14,14 @@ import (
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration := handlers.NewCheckHandlerConfiguration().
WithLogger(options.Logger).
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption))
return debug.NewService(
debug.Logger(options.Logger),
@@ -5,6 +5,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -13,9 +14,14 @@ import (
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration := handlers.NewCheckHandlerConfiguration().
WithLogger(options.Logger).
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption))
return debug.NewService(
debug.Logger(options.Logger),
+2 -1
View File
@@ -83,7 +83,8 @@ EventLoop:
if !ok {
break EventLoop
}
cl.processEvent(event)
go cl.processEvent(event)
if cl.stopped.Load() {
break EventLoop
@@ -101,6 +101,9 @@ func Server(cfg *config.Config) *cobra.Command {
microstore.Database(cfg.Store.Database),
microstore.Table(cfg.Store.Table),
store.Authentication(cfg.Store.AuthUsername, cfg.Store.AuthPassword),
store.TLSEnabled(cfg.Store.EnableTLS),
store.TLSInsecure(cfg.Store.TLSInsecure),
store.TLSRootCA(cfg.Store.TLSRootCACertificate),
)
gr := runner.NewGroup()
+10 -7
View File
@@ -4,11 +4,14 @@ import "time"
// Store configures the store to use
type Store struct {
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;COLLABORATION_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;COLLABORATION_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"COLLABORATION_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"COLLABORATION_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;COLLABORATION_STORE_TTL" desc:"Time to live for events in the store. Defaults to '30m' (30 minutes). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;COLLABORATION_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;COLLABORATION_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;COLLABORATION_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;COLLABORATION_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"COLLABORATION_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"COLLABORATION_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;COLLABORATION_STORE_TTL" desc:"Time to live for events in the store. Defaults to '30m' (30 minutes). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;COLLABORATION_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;COLLABORATION_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;COLLABORATION_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;COLLABORATION_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;COLLABORATION_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided COLLABORATION_STORE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
@@ -71,6 +71,9 @@ func Server(cfg *config.Config) *cobra.Command {
microstore.Database(cfg.Store.Database),
microstore.Table(cfg.Store.Table),
store.Authentication(cfg.Store.AuthUsername, cfg.Store.AuthPassword),
store.TLSEnabled(cfg.Store.EnableTLS),
store.TLSInsecure(cfg.Store.TLSInsecure),
store.TLSRootCA(cfg.Store.TLSRootCACertificate),
)
service := grpc.NewService(
+10 -7
View File
@@ -36,13 +36,16 @@ type GRPCConfig struct {
// Store configures the store to use
type Store struct {
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;EVENTHISTORY_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;EVENTHISTORY_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"EVENTHISTORY_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"EVENTHISTORY_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;EVENTHISTORY_STORE_TTL" desc:"Time to live for events in the store. Defaults to '336h' (2 weeks). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;EVENTHISTORY_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;EVENTHISTORY_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;EVENTHISTORY_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;EVENTHISTORY_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"EVENTHISTORY_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"EVENTHISTORY_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;EVENTHISTORY_STORE_TTL" desc:"Time to live for events in the store. Defaults to '336h' (2 weeks). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;EVENTHISTORY_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;EVENTHISTORY_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;EVENTHISTORY_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;EVENTHISTORY_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;EVENTHISTORY_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided EVENTHISTORY_STORE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
// Events combines the configuration options for the event bus.
@@ -5,6 +5,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -17,8 +18,13 @@ func Server(opts ...Option) (*http.Server, error) {
WithLogger(options.Logger).
WithCheck("grpc reachability", checks.NewGRPCCheck(options.Config.GRPC.Addr))
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration := healthHandlerConfiguration.
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption))
return debug.NewService(
debug.Logger(options.Logger),
+20 -17
View File
@@ -121,26 +121,29 @@ type DataGateway struct {
}
type OCS struct {
Prefix string `yaml:"prefix" env:"FRONTEND_OCS_PREFIX" desc:"URL path prefix for the OCS service. Note that the string must not start with '/'." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
SharePrefix string `yaml:"share_prefix" env:"FRONTEND_OCS_SHARE_PREFIX" desc:"Path prefix for shares as part of a CS3 resource. Note that the path must start with '/'." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
HomeNamespace string `yaml:"home_namespace" env:"FRONTEND_OCS_PERSONAL_NAMESPACE" desc:"Home namespace identifier." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
AdditionalInfoAttribute string `yaml:"additional_info_attribute" env:"FRONTEND_OCS_ADDITIONAL_INFO_ATTRIBUTE" desc:"Additional information attribute for the user like {{.Mail}}." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
StatCacheType string `yaml:"stat_cache_type" env:"OC_CACHE_STORE;FRONTEND_OCS_STAT_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_STORE, the OCS API is deprecated" deprecationReplacement:""`
StatCacheNodes []string `yaml:"stat_cache_nodes" env:"OC_CACHE_STORE_NODES;FRONTEND_OCS_STAT_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_STORE_NODES, the OCS API is deprecated" deprecationReplacement:""`
StatCacheDatabase string `yaml:"stat_cache_database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
StatCacheTable string `yaml:"stat_cache_table" env:"FRONTEND_OCS_STAT_CACHE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
StatCacheTTL time.Duration `yaml:"stat_cache_ttl" env:"OC_CACHE_TTL;FRONTEND_OCS_STAT_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_TTL, the OCS API is deprecated" deprecationReplacement:""`
StatCacheDisablePersistence bool `yaml:"stat_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE" desc:"Disable persistence of the cache. Only applies when using the 'nats-js-kv' store type. Defaults to false." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE, the OCS API is deprecated" deprecationReplacement:""`
StatCacheAuthUsername string `yaml:"stat_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;FRONTEND_OCS_STAT_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when using the 'nats-js-kv' store type." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_AUTH_USERNAME, the OCS API is deprecated" deprecationReplacement:""`
StatCacheAuthPassword string `yaml:"stat_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;FRONTEND_OCS_STAT_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when using the 'nats-js-kv' store type." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_AUTH_PASSWORD, the OCS API is deprecated" deprecationReplacement:""`
Prefix string `yaml:"prefix" env:"FRONTEND_OCS_PREFIX" desc:"URL path prefix for the OCS service. Note that the string must not start with '/'." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
SharePrefix string `yaml:"share_prefix" env:"FRONTEND_OCS_SHARE_PREFIX" desc:"Path prefix for shares as part of a CS3 resource. Note that the path must start with '/'." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
HomeNamespace string `yaml:"home_namespace" env:"FRONTEND_OCS_PERSONAL_NAMESPACE" desc:"Home namespace identifier." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
AdditionalInfoAttribute string `yaml:"additional_info_attribute" env:"FRONTEND_OCS_ADDITIONAL_INFO_ATTRIBUTE" desc:"Additional information attribute for the user like {{.Mail}}." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
StatCacheType string `yaml:"stat_cache_type" env:"OC_CACHE_STORE;FRONTEND_OCS_STAT_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_STORE, the OCS API is deprecated" deprecationReplacement:""`
StatCacheNodes []string `yaml:"stat_cache_nodes" env:"OC_CACHE_STORE_NODES;FRONTEND_OCS_STAT_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_STORE_NODES, the OCS API is deprecated" deprecationReplacement:""`
StatCacheDatabase string `yaml:"stat_cache_database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
StatCacheTable string `yaml:"stat_cache_table" env:"FRONTEND_OCS_STAT_CACHE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
StatCacheTTL time.Duration `yaml:"stat_cache_ttl" env:"OC_CACHE_TTL;FRONTEND_OCS_STAT_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_TTL, the OCS API is deprecated" deprecationReplacement:""`
StatCacheDisablePersistence bool `yaml:"stat_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE" desc:"Disable persistence of the cache. Only applies when using the 'nats-js-kv' store type. Defaults to false." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_DISABLE_PERSISTENCE, the OCS API is deprecated" deprecationReplacement:""`
StatCacheAuthUsername string `yaml:"stat_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;FRONTEND_OCS_STAT_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when using the 'nats-js-kv' store type." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_AUTH_USERNAME, the OCS API is deprecated" deprecationReplacement:""`
StatCacheAuthPassword string `yaml:"stat_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;FRONTEND_OCS_STAT_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when using the 'nats-js-kv' store type." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_STAT_CACHE_AUTH_PASSWORD, the OCS API is deprecated" deprecationReplacement:""`
StatCacheEnableTLS bool `yaml:"stat_cache_enable_tls" env:"OC_CACHE_ENABLE_TLS;FRONTEND_OCS_STAT_CACHE_ENABLE_TLS" desc:"Enable TLS for the connection to file metadata cache." introductionVersion:"7.2.0"`
StatCacheTLSInsecure bool `yaml:"stat_cache_tls_insecure" env:"OC_INSECURE;OC_CACHE_TLS_INSECURE;FRONTEND_OCS_STAT_CACHE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
StatCacheTLSRootCACertificate string `yaml:"stat_cache_tls_root_ca_certificate" env:"OC_CACHE_TLS_ROOT_CA_CERTIFICATE;FRONTEND_OCS_STAT_CACHE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided FRONTEND_OCS_STAT_CACHE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
CacheWarmupDriver string `yaml:"cache_warmup_driver,omitempty"` // not supported by the OpenCloud product, therefore not part of docs
CacheWarmupDrivers CacheWarmupDrivers `yaml:"cache_warmup_drivers,omitempty"` // not supported by the OpenCloud product, therefore not part of docs
EnableDenials bool `yaml:"enable_denials" env:"FRONTEND_OCS_ENABLE_DENIALS" desc:"EXPERIMENTAL: enable the feature to deny access on folders." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
ListOCMShares bool `yaml:"list_ocm_shares" env:"OC_ENABLE_OCM;FRONTEND_OCS_LIST_OCM_SHARES" desc:"Include OCM shares when listing shares. See the OCM service documentation for more details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_LIST_OCM_SHARES, the OCS API is deprecated" deprecationReplacement:""`
IncludeOCMSharees bool `yaml:"include_ocm_sharees" env:"OC_ENABLE_OCM;FRONTEND_OCS_INCLUDE_OCM_SHAREES" desc:"Include OCM sharees when listing sharees." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_INCLUDE_OCM_SHAREES, the OCS API is deprecated" deprecationReplacement:""`
PublicShareMustHavePassword bool `yaml:"public_sharing_share_must_have_password" env:"OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on all public shares." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD, the OCS API is deprecated" deprecationReplacement:""`
WriteablePublicShareMustHavePassword bool `yaml:"public_sharing_writeableshare_must_have_password" env:"OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords for writable shares. Only effective if the setting for 'passwords on all public shares' is set to false." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"FRONTEND_OCS_PUBLIC_WRITABLE_SHARE_MUST_HAVE_PASSWORD, the OCS API is deprecated" deprecationReplacement:""`
EnableDenials bool `yaml:"enable_denials" env:"FRONTEND_OCS_ENABLE_DENIALS" desc:"EXPERIMENTAL: enable the feature to deny access on folders." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"The OCS API is deprecated" deprecationReplacement:""`
ListOCMShares bool `yaml:"list_ocm_shares" env:"OC_ENABLE_OCM;FRONTEND_OCS_LIST_OCM_SHARES" desc:"Include OCM shares when listing shares. See the OCM service documentation for more details." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_LIST_OCM_SHARES, the OCS API is deprecated" deprecationReplacement:""`
IncludeOCMSharees bool `yaml:"include_ocm_sharees" env:"OC_ENABLE_OCM;FRONTEND_OCS_INCLUDE_OCM_SHAREES" desc:"Include OCM sharees when listing sharees." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_INCLUDE_OCM_SHAREES, the OCS API is deprecated" deprecationReplacement:""`
PublicShareMustHavePassword bool `yaml:"public_sharing_share_must_have_password" env:"OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on all public shares." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD, the OCS API is deprecated" deprecationReplacement:""`
WriteablePublicShareMustHavePassword bool `yaml:"public_sharing_writeableshare_must_have_password" env:"OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords for writable shares. Only effective if the setting for 'passwords on all public shares' is set to false." introductionVersion:"1.0.0" deprecationVersion:"1.0.0" removalVersion:"7.2.0" deprecationInfo:"FRONTEND_OCS_PUBLIC_WRITABLE_SHARE_MUST_HAVE_PASSWORD, the OCS API is deprecated" deprecationReplacement:""`
ShowUserEmailInResults bool `yaml:"show_email_in_results" env:"OC_SHOW_USER_EMAIL_IN_RESULTS" desc:"Include user email addresses in responses. If absent or set to false emails will be omitted from results. Please note that admin users can always see all email addresses." introductionVersion:"1.0.0"`
}
+11 -8
View File
@@ -164,14 +164,17 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
"share_prefix": cfg.OCS.SharePrefix,
"home_namespace": cfg.OCS.HomeNamespace,
"stat_cache_config": map[string]any{
"cache_store": cfg.OCS.StatCacheType,
"cache_nodes": cfg.OCS.StatCacheNodes,
"cache_database": cfg.OCS.StatCacheDatabase,
"cache_table": cfg.OCS.StatCacheTable,
"cache_ttl": cfg.OCS.StatCacheTTL,
"cache_disable_persistence": cfg.OCS.StatCacheDisablePersistence,
"cache_auth_username": cfg.OCS.StatCacheAuthUsername,
"cache_auth_password": cfg.OCS.StatCacheAuthPassword,
"cache_store": cfg.OCS.StatCacheType,
"cache_nodes": cfg.OCS.StatCacheNodes,
"cache_database": cfg.OCS.StatCacheDatabase,
"cache_table": cfg.OCS.StatCacheTable,
"cache_ttl": cfg.OCS.StatCacheTTL,
"cache_disable_persistence": cfg.OCS.StatCacheDisablePersistence,
"cache_auth_username": cfg.OCS.StatCacheAuthUsername,
"cache_auth_password": cfg.OCS.StatCacheAuthPassword,
"cache_tls_enabled": cfg.OCS.StatCacheEnableTLS,
"cache_tls_insecure": cfg.OCS.StatCacheTLSInsecure,
"cache_tls_root_ca_certificate": cfg.OCS.StatCacheTLSRootCACertificate,
},
"prefix": cfg.OCS.Prefix,
"additional_info_attribute": cfg.OCS.AdditionalInfoAttribute,
+7 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -17,8 +18,13 @@ func Server(opts ...Option) (*http.Server, error) {
WithLogger(options.Logger).
WithCheck("web reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr))
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration := healthHandlerConfiguration.
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption))
return debug.NewService(
debug.Logger(options.Logger),
+14 -7
View File
@@ -84,11 +84,18 @@ type Cache struct {
ProviderCacheDisablePersistence bool `yaml:"provider_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GATEWAY_PROVIDER_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the provider cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
ProviderCacheAuthUsername string `yaml:"provider_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;GATEWAY_PROVIDER_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
ProviderCacheAuthPassword string `yaml:"provider_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;GATEWAY_PROVIDER_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
CreateHomeCacheStore string `yaml:"create_home_cache_store" env:"OC_CACHE_STORE;GATEWAY_CREATE_HOME_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
CreateHomeCacheNodes []string `yaml:"create_home_cache_nodes" env:"OC_CACHE_STORE_NODES;GATEWAY_CREATE_HOME_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
CreateHomeCacheDatabase string `yaml:"create_home_cache_database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
CreateHomeCacheTTL time.Duration `yaml:"create_home_cache_ttl" env:"OC_CACHE_TTL;GATEWAY_CREATE_HOME_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
CreateHomeCacheDisablePersistence bool `yaml:"create_home_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GATEWAY_CREATE_HOME_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the create home cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
CreateHomeCacheAuthUsername string `yaml:"create_home_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;GATEWAY_CREATE_HOME_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
CreateHomeCacheAuthPassword string `yaml:"create_home_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;GATEWAY_CREATE_HOME_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
ProviderCacheEnableTLS bool `yaml:"provider_cache_enable_tls" env:"OC_CACHE_ENABLE_TLS;GATEWAY_PROVIDER_CACHE_ENABLE_TLS" desc:"Enable TLS for the connection to file metadata cache." introductionVersion:"7.2.0"`
ProviderCacheTLSInsecure bool `yaml:"provider_cache_tls_insecure" env:"OC_INSECURE;OC_CACHE_TLS_INSECURE;GATEWAY_PROVIDER_CACHE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
ProviderCacheTLSRootCACertificate string `yaml:"provider_cache_tls_root_ca_certificate" env:"OC_CACHE_TLS_ROOT_CA_CERTIFICATE;GATEWAY_PROVIDER_CACHE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided GATEWAY_PROVIDER_CACHE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
CreateHomeCacheStore string `yaml:"create_home_cache_store" env:"OC_CACHE_STORE;GATEWAY_CREATE_HOME_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
CreateHomeCacheNodes []string `yaml:"create_home_cache_nodes" env:"OC_CACHE_STORE_NODES;GATEWAY_CREATE_HOME_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
CreateHomeCacheDatabase string `yaml:"create_home_cache_database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
CreateHomeCacheTTL time.Duration `yaml:"create_home_cache_ttl" env:"OC_CACHE_TTL;GATEWAY_CREATE_HOME_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
CreateHomeCacheDisablePersistence bool `yaml:"create_home_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GATEWAY_CREATE_HOME_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the create home cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
CreateHomeCacheAuthUsername string `yaml:"create_home_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;GATEWAY_CREATE_HOME_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
CreateHomeCacheAuthPassword string `yaml:"create_home_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;GATEWAY_CREATE_HOME_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
CreateHomeCacheEnableTLS bool `yaml:"create_home_cache_enable_tls" env:"OC_CACHE_ENABLE_TLS;GATEWAY_CREATE_HOME_CACHE_ENABLE_TLS" desc:"Enable TLS for the connection to file metadata cache." introductionVersion:"7.2.0"`
CreateHomeCacheTLSInsecure bool `yaml:"create_home_cache_tls_insecure" env:"OC_INSECURE;OC_CACHE_TLS_INSECURE;GATEWAY_CREATE_HOME_CACHE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
CreateHomeCacheTLSRootCACertificate string `yaml:"create_home_cache_tls_root_ca_certificate" env:"OC_CACHE_TLS_ROOT_CA_CERTIFICATE;GATEWAY_CREATE_HOME_CACHE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided GATEWAY_CREATE_HOME_CACHE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
+19 -16
View File
@@ -45,15 +45,15 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]a
"groupprovidersvc": cfg.GroupsEndpoint,
"permissionssvc": cfg.PermissionsEndpoint,
// sharing is located on the sharing service
"usershareprovidersvc": cfg.SharingEndpoint,
"publicshareprovidersvc": cfg.SharingEndpoint,
"ocmshareprovidersvc": cfg.OCMEndpoint,
"ocminvitemanagersvc": cfg.OCMEndpoint,
"ocmproviderauthorizersvc": cfg.OCMEndpoint,
"ocmcoresvc": cfg.OCMEndpoint,
"usershareprovidersvc": cfg.SharingEndpoint,
"publicshareprovidersvc": cfg.SharingEndpoint,
"ocmshareprovidersvc": cfg.OCMEndpoint,
"ocminvitemanagersvc": cfg.OCMEndpoint,
"ocmproviderauthorizersvc": cfg.OCMEndpoint,
"ocmcoresvc": cfg.OCMEndpoint,
"use_common_space_root_share_logic": true,
"commit_share_to_storage_grant": cfg.CommitShareToStorageGrant,
"share_folder": cfg.ShareFolder, // ShareFolder is the location where to create shares in the recipient's storage provider.
"commit_share_to_storage_grant": cfg.CommitShareToStorageGrant,
"share_folder": cfg.ShareFolder, // ShareFolder is the location where to create shares in the recipient's storage provider.
// other
"disable_home_creation_on_login": cfg.DisableHomeCreationOnLogin,
"datagateway": strings.TrimRight(cfg.FrontendPublicURL, "/") + "/data",
@@ -71,14 +71,17 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]a
"cache_auth_password": cfg.Cache.ProviderCacheAuthPassword,
},
"create_personal_space_cache_config": map[string]any{
"cache_store": cfg.Cache.CreateHomeCacheStore,
"cache_nodes": cfg.Cache.CreateHomeCacheNodes,
"cache_database": cfg.Cache.CreateHomeCacheDatabase,
"cache_table": "create_personal_space",
"cache_ttl": cfg.Cache.CreateHomeCacheTTL,
"cache_disable_persistence": cfg.Cache.CreateHomeCacheDisablePersistence,
"cache_auth_username": cfg.Cache.CreateHomeCacheAuthUsername,
"cache_auth_password": cfg.Cache.CreateHomeCacheAuthPassword,
"cache_store": cfg.Cache.CreateHomeCacheStore,
"cache_nodes": cfg.Cache.CreateHomeCacheNodes,
"cache_database": cfg.Cache.CreateHomeCacheDatabase,
"cache_table": "create_personal_space",
"cache_ttl": cfg.Cache.CreateHomeCacheTTL,
"cache_disable_persistence": cfg.Cache.CreateHomeCacheDisablePersistence,
"cache_auth_username": cfg.Cache.CreateHomeCacheAuthUsername,
"cache_auth_password": cfg.Cache.CreateHomeCacheAuthPassword,
"cache_tls_enabled": cfg.Cache.CreateHomeCacheEnableTLS,
"cache_tls_insecure": cfg.Cache.CreateHomeCacheTLSInsecure,
"cache_tls_root_ca_certificate": cfg.Cache.CreateHomeCacheTLSRootCACertificate,
},
},
"authregistry": map[string]any{
@@ -18,6 +18,7 @@ func Server(opts ...Option) (*http.Server, error) {
WithLogger(options.Logger).
WithCheck("nats reachability", func(ctx context.Context) error {
if options.Config.Cache.ProviderCacheStore == "nats-js-kv" && len(options.Config.Cache.ProviderCacheNodes) > 0 {
// no secureOption because we cannot yet configure tls for the cache store
return checks.NewNatsCheck(options.Config.Cache.ProviderCacheNodes[0])(ctx)
}
return nil
+5 -7
View File
@@ -4,9 +4,11 @@ import (
"context"
"fmt"
"os/signal"
"strings"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/log"
natspkg "github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/runner"
"github.com/opencloud-eu/opencloud/pkg/tracing"
"github.com/opencloud-eu/opencloud/pkg/version"
@@ -50,13 +52,9 @@ func Server(cfg *config.Config) *cobra.Command {
var kv jetstream.KeyValue
// Allow to run without a NATS store (e.g. for the standalone Education provisioning service)
if len(cfg.Store.Nodes) > 0 {
//Connect to NATS servers
natsOptions := nats.Options{
Servers: cfg.Store.Nodes,
User: cfg.Store.AuthUsername,
Password: cfg.Store.AuthPassword,
}
conn, err := natsOptions.Connect()
// Connect to NATS servers
secureOption := natspkg.Secure(cfg.Store.EnableTLS, cfg.Store.TLSInsecure, cfg.Store.TLSRootCACertificate)
conn, err := nats.Connect(strings.Join(cfg.Store.Nodes, ","), secureOption, nats.UserInfo(cfg.Store.AuthUsername, cfg.Store.AuthPassword))
if err != nil {
return err
}
+11 -8
View File
@@ -4,12 +4,15 @@ import "time"
// Cache defines the available configuration for a cache store
type Cache struct {
Store string `yaml:"store" env:"OC_CACHE_STORE;GRAPH_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_CACHE_STORE_NODES;GRAPH_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store are configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"GRAPH_CACHE_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"GRAPH_CACHE_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_CACHE_TTL;GRAPH_CACHE_TTL" desc:"Time to live for cache records in the graph. Defaults to '336h' (2 weeks). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
DisablePersistence bool `yaml:"disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GRAPH_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_CACHE_AUTH_USERNAME;GRAPH_CACHE_AUTH_USERNAME" desc:"The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_CACHE_AUTH_PASSWORD;GRAPH_CACHE_AUTH_PASSWORD" desc:"The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Store string `yaml:"store" env:"OC_CACHE_STORE;GRAPH_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_CACHE_STORE_NODES;GRAPH_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store are configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"GRAPH_CACHE_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"GRAPH_CACHE_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_CACHE_TTL;GRAPH_CACHE_TTL" desc:"Time to live for cache records in the graph. Defaults to '336h' (2 weeks). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
DisablePersistence bool `yaml:"disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GRAPH_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_CACHE_AUTH_USERNAME;GRAPH_CACHE_AUTH_USERNAME" desc:"The username to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_CACHE_AUTH_PASSWORD;GRAPH_CACHE_AUTH_PASSWORD" desc:"The password to authenticate with the cache. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_CACHE_ENABLE_TLS;GRAPH_CACHE_ENABLE_TLS" desc:"Enable TLS for the connection to file metadata cache." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_CACHE_TLS_INSECURE;GRAPH_CACHE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_CACHE_TLS_ROOT_CA_CERTIFICATE;GRAPH_CACHE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided GRAPH_CACHE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
+7 -4
View File
@@ -172,8 +172,11 @@ type Metadata struct {
// Store configures the store to use
type Store struct {
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;GRAPH_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"GRAPH_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;GRAPH_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;GRAPH_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;GRAPH_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"GRAPH_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;GRAPH_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;GRAPH_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;GRAPH_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;GRAPH_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;GRAPH_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided GRAPH_STORE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2025\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-02 00:02+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jannick Kuhr, 2026\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-23 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Elías Martín, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-08 00:02+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"
@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jérôme HERBINET, 2026\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Broglia, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-19 00:04+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: gapho shin, 2025\n"
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"
@@ -4,16 +4,16 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Stephan Paternotte <stephan@paternottes.net>, 2025
# Stephan Paternotte <stephan@paternottes.net>, 2026
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-06 00:01+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2026\n"
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -74,7 +74,7 @@ msgstr "Alle toegang weigeren."
#. default description for new spaces
#: pkg/service/v0/spacetemplates.go:32
msgid "Here you can add a description for this Space."
msgstr "Hier kun je een beschrijving voor deze ruimte toevoegen."
msgstr "Hier kunt u een beschrijving voor deze ruimte toevoegen."
#. UnifiedRole Viewer, Role Description (resolves directly)
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
@@ -8,15 +8,16 @@
# Maksymilian Styżej, 2025
# Xiaomi Box, 2025
# Radoslaw Posim, 2025
# Matt, 2026
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-06 00:01+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Radoslaw Posim, 2025\n"
"Last-Translator: Matt, 2026\n"
"Language-Team: Polish (https://app.transifex.com/opencloud-eu/teams/204053/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -39,7 +40,7 @@ msgstr "Może edytować"
#. directly)
#: pkg/unifiedrole/roles.go:134
msgid "Can edit without versions"
msgstr ""
msgstr "Może edytować bez wersji"
#. UnifiedRole Manager, Role DisplayName (resolves directly)
#: pkg/unifiedrole/roles.go:158
@@ -89,6 +90,7 @@ msgstr "Zobacz i pobierz"
#: pkg/unifiedrole/roles.go:161
msgid "View only documents, images and PDFs. Watermarks will be applied."
msgstr ""
"Wyświetlanie tylko dokumentów, obrazów i PDF. Zostaną nałożone znaki wodne."
#. UnifiedRole FileEditor, Role Description (resolves directly)
#: pkg/unifiedrole/roles.go:137
@@ -98,7 +100,7 @@ msgstr "Wyświetl , Pobierz i edytuj "
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
#: pkg/unifiedrole/roles.go:101
msgid "View, download and show all invited people."
msgstr ""
msgstr "Wyświetlanie, pobieranie i pokazywanie wszystkich zaproszonych."
#. UnifiedRole EditorLite, Role Description (resolves directly)
#: pkg/unifiedrole/roles.go:149
@@ -109,6 +111,7 @@ msgstr "Wyświetlanie, pobieranie i przesyłanie."
#: pkg/unifiedrole/roles.go:143
msgid "View, download, edit and show all invited people."
msgstr ""
"Wyświetlanie, pobieranie, edycja i pokazywanie wszystkich zaproszonych."
#. UnifiedRole Editor, Role Description (resolves directly)
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-06 00:01+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Mário Machado, 2025\n"
"Language-Team: Portuguese (https://app.transifex.com/opencloud-eu/teams/204053/pt/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-06 00:01+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Lulufox, 2025\n"
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-20 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Daniel Nylander <po@danielnylander.se>, 2025\n"
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-08 00:02+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: LinkinWires <darkinsonic13@gmail.com>, 2025\n"
"Language-Team: Ukrainian (https://app.transifex.com/opencloud-eu/teams/204053/uk/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-19 00:04+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Quan Tran, 2025\n"
"Language-Team: Vietnamese (https://app.transifex.com/opencloud-eu/teams/204053/vi/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: YQS Yang, 2025\n"
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"
+7 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -32,8 +33,13 @@ func Server(opts ...Option) (*http.Server, error) {
// only check nats if really needed
if options.Config.Events.Endpoint != "" {
secureOption := nats.Secure(
options.Config.Events.EnableTLS,
options.Config.Events.TLSInsecure,
options.Config.Events.TLSRootCACertificate,
)
readyHandlerConfiguration = readyHandlerConfiguration.
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint))
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint, secureOption))
}
return debug.NewService(
@@ -22,7 +22,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
@@ -30,16 +29,14 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/opencloud-eu/opencloud/pkg/l10n"
l10n_pkg "github.com/opencloud-eu/opencloud/services/graph/pkg/l10n"
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
"github.com/opencloud-eu/opencloud/pkg/conversions"
"github.com/opencloud-eu/opencloud/pkg/l10n"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity"
"github.com/opencloud-eu/opencloud/services/graph/pkg/identity/cache"
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
"github.com/opencloud-eu/opencloud/services/graph/pkg/unifiedrole"
"github.com/opencloud-eu/opencloud/services/graph/pkg/validate"
)
@@ -755,16 +752,8 @@ func (api DriveItemPermissionsApi) ListPermissions(w http.ResponseWriter, r *htt
loc := r.Header.Get(l10n.HeaderAcceptLanguage)
w.Header().Add("Content-Language", loc)
if loc != "" && loc != "en" {
err := l10n_pkg.TranslateEntity(loc, "en", permissions,
l10n.TranslateEach("LibreGraphPermissionsRolesAllowedValues",
l10n.TranslateField("Description"),
l10n.TranslateField("DisplayName"),
),
)
if err != nil {
api.logger.Error().Err(err).Msg("tranlation error")
}
for i := range permissions.LibreGraphPermissionsRolesAllowedValues {
permissions.LibreGraphPermissionsRolesAllowedValues[i] = unifiedrole.LocalizeRole(&permissions.LibreGraphPermissionsRolesAllowedValues[i], loc)
}
render.Status(r, http.StatusOK)
@@ -806,16 +795,8 @@ func (api DriveItemPermissionsApi) ListSpaceRootPermissions(w http.ResponseWrite
loc := r.Header.Get(l10n.HeaderAcceptLanguage)
w.Header().Add("Content-Language", loc)
if loc != "" && loc != "en" {
err := l10n_pkg.TranslateEntity(loc, "en", permissions,
l10n.TranslateEach("LibreGraphPermissionsRolesAllowedValues",
l10n.TranslateField("Description"),
l10n.TranslateField("DisplayName"),
),
)
if err != nil {
api.logger.Error().Err(err).Msg("tranlation error")
}
for i := range permissions.LibreGraphPermissionsRolesAllowedValues {
permissions.LibreGraphPermissionsRolesAllowedValues[i] = unifiedrole.LocalizeRole(&permissions.LibreGraphPermissionsRolesAllowedValues[i], loc)
}
render.Status(r, http.StatusOK)
+1 -1
View File
@@ -1119,7 +1119,7 @@ func (g Graph) DeleteDrive(w http.ResponseWriter, r *http.Request) {
return
case cs3rpc.Code_CODE_PERMISSION_DENIED:
logger.Debug().Interface("id", rid).Msg("could not delete drive: permission denied")
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "drive not found")
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "permission denied")
return
case cs3rpc.Code_CODE_NOT_FOUND:
logger.Debug().Interface("id", rid).Msg("could not delete drive: drive not found")
@@ -7,14 +7,17 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/opencloud-eu/opencloud/pkg/l10n"
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
"github.com/opencloud-eu/opencloud/services/graph/pkg/unifiedrole"
)
// GetRoleDefinitions a list of permission roles than can be used when sharing with users or groups
func (g Graph) GetRoleDefinitions(w http.ResponseWriter, r *http.Request) {
loc := r.Header.Get(l10n.HeaderAcceptLanguage)
w.Header().Add("Content-Language", loc)
render.Status(r, http.StatusOK)
render.JSON(w, r, g.availableRoles)
render.JSON(w, r, unifiedrole.LocalizeRoles(g.availableRoles, loc))
}
// GetRoleDefinition a permission role than can be used when sharing with users or groups
@@ -32,6 +35,8 @@ func (g Graph) GetRoleDefinition(w http.ResponseWriter, r *http.Request) {
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, err.Error())
return
}
loc := r.Header.Get(l10n.HeaderAcceptLanguage)
w.Header().Add("Content-Language", loc)
render.Status(r, http.StatusOK)
render.JSON(w, r, role)
render.JSON(w, r, unifiedrole.LocalizeRole(role, loc))
}
@@ -0,0 +1,170 @@
package svc_test
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks"
"google.golang.org/grpc"
"github.com/opencloud-eu/opencloud/pkg/shared"
"github.com/opencloud-eu/opencloud/services/graph/mocks"
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
"github.com/opencloud-eu/opencloud/services/graph/pkg/config/defaults"
service "github.com/opencloud-eu/opencloud/services/graph/pkg/service/v0"
"github.com/opencloud-eu/opencloud/services/graph/pkg/unifiedrole"
)
var _ = Describe("RoleManagement", func() {
var (
svc service.Service
gatewayClient *cs3mocks.GatewayAPIClient
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
eventsPublisher mocks.Publisher
permSvc mocks.Permissions
cfg *config.Config
rr *httptest.ResponseRecorder
ctx context.Context
)
BeforeEach(func() {
rr = httptest.NewRecorder()
ctx = context.Background()
cfg = defaults.FullDefaultConfig()
cfg.Identity.LDAP.CACert = ""
cfg.TokenManager.JWTSecret = "loremipsum"
cfg.Commons = &shared.Commons{}
cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
pool.RemoveSelector("GatewaySelector" + "eu.opencloud.api.gateway")
gatewayClient = &cs3mocks.GatewayAPIClient{}
gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient](
"GatewaySelector",
"eu.opencloud.api.gateway",
func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
return gatewayClient
},
)
eventsPublisher = mocks.Publisher{}
permSvc = mocks.Permissions{}
var err error
svc, err = service.NewService(
service.Config(cfg),
service.WithGatewaySelector(gatewaySelector),
service.EventsPublisher(&eventsPublisher),
service.PermissionService(&permSvc),
)
Expect(err).ToNot(HaveOccurred())
})
Describe("GetRoleDefinitions", func() {
It("returns all available roles in English when no Accept-Language is set", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions", nil)
r = r.WithContext(ctx)
svc.ServeHTTP(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
var roles []libregraph.UnifiedRoleDefinition
Expect(json.Unmarshal(rr.Body.Bytes(), &roles)).To(Succeed())
Expect(roles).NotTo(BeEmpty())
viewer := findRoleByID(roles, unifiedrole.UnifiedRoleViewerID)
Expect(viewer).NotTo(BeNil())
Expect(viewer.GetDisplayName()).To(Equal("Can view"))
})
It("returns translated roles when Accept-Language is German", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions", nil)
r.Header.Set("Accept-Language", "de")
r = r.WithContext(ctx)
svc.ServeHTTP(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
Expect(rr.Header().Get("Content-Language")).To(Equal("de"))
var roles []libregraph.UnifiedRoleDefinition
Expect(json.Unmarshal(rr.Body.Bytes(), &roles)).To(Succeed())
viewer := findRoleByID(roles, unifiedrole.UnifiedRoleViewerID)
Expect(viewer).NotTo(BeNil())
Expect(viewer.GetDisplayName()).To(Equal("Kann anzeigen"))
Expect(viewer.GetDescription()).To(Equal("Ansehen und herunterladen."))
})
It("does not mutate the global buildInRoles after a German request", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions", nil)
r.Header.Set("Accept-Language", "de")
r = r.WithContext(ctx)
svc.ServeHTTP(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
// A second request without a locale must still return English
rr2 := httptest.NewRecorder()
r2 := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions", nil)
r2 = r2.WithContext(ctx)
svc.ServeHTTP(rr2, r2)
Expect(rr2.Code).To(Equal(http.StatusOK))
var roles []libregraph.UnifiedRoleDefinition
Expect(json.Unmarshal(rr2.Body.Bytes(), &roles)).To(Succeed())
viewer := findRoleByID(roles, unifiedrole.UnifiedRoleViewerID)
Expect(viewer).NotTo(BeNil())
Expect(viewer.GetDisplayName()).To(Equal("Can view"))
})
})
Describe("GetRoleDefinition", func() {
It("returns a single role in English when no Accept-Language is set", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions/"+unifiedrole.UnifiedRoleViewerID, nil)
r = r.WithContext(ctx)
svc.ServeHTTP(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
var role libregraph.UnifiedRoleDefinition
Expect(json.Unmarshal(rr.Body.Bytes(), &role)).To(Succeed())
Expect(role.GetDisplayName()).To(Equal("Can view"))
})
It("returns a single role translated when Accept-Language is German", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions/"+unifiedrole.UnifiedRoleViewerID, nil)
r.Header.Set("Accept-Language", "de")
r = r.WithContext(ctx)
svc.ServeHTTP(rr, r)
Expect(rr.Code).To(Equal(http.StatusOK))
Expect(rr.Header().Get("Content-Language")).To(Equal("de"))
var role libregraph.UnifiedRoleDefinition
Expect(json.Unmarshal(rr.Body.Bytes(), &role)).To(Succeed())
Expect(role.GetDisplayName()).To(Equal("Kann anzeigen"))
Expect(role.GetDescription()).To(Equal("Ansehen und herunterladen."))
})
It("returns 404 for an unknown roleID", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1beta1/roleManagement/permissions/roleDefinitions/unknown-role-id", nil)
r = r.WithContext(ctx)
svc.ServeHTTP(rr, r)
Expect(rr.Code).To(Equal(http.StatusNotFound))
})
})
})
// findRoleByID returns the first role with the given ID from the slice, or nil.
func findRoleByID(roles []libregraph.UnifiedRoleDefinition, id string) *libregraph.UnifiedRoleDefinition {
for i := range roles {
if roles[i].GetId() == id {
return &roles[i]
}
}
return nil
}
+42 -2
View File
@@ -6,11 +6,11 @@ import (
"strings"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
"github.com/opencloud-eu/reva/v2/pkg/conversions"
"google.golang.org/protobuf/proto"
"github.com/opencloud-eu/reva/v2/pkg/conversions"
"github.com/opencloud-eu/opencloud/pkg/l10n"
graphl10n "github.com/opencloud-eu/opencloud/services/graph/pkg/l10n"
)
const (
@@ -546,6 +546,46 @@ func weightRoles(roleSet []*libregraph.UnifiedRoleDefinition, constraints string
return roleSet
}
// cloneRole returns a shallow struct copy of r with independent allocations for
// the Description and DisplayName pointer fields — the only fields mutated by
// TranslateEntity. All other fields (Id, LibreGraphWeight, RolePermissions) are
// either read-only or stripped before rendering, so sharing their values is safe.
func cloneRole(r *libregraph.UnifiedRoleDefinition) libregraph.UnifiedRoleDefinition {
c := *r
if r.Description != nil {
s := *r.Description
c.Description = &s
}
if r.DisplayName != nil {
s := *r.DisplayName
c.DisplayName = &s
}
return c
}
// LocalizeRole returns a translated, independent copy of a single role definition.
// The global buildInRoles singleton is never modified.
func LocalizeRole(r *libregraph.UnifiedRoleDefinition, locale string) libregraph.UnifiedRoleDefinition {
c := cloneRole(r)
if locale != "" && locale != "en" {
_ = graphl10n.TranslateEntity(locale, "en", &c,
l10n.TranslateField("Description"),
l10n.TranslateField("DisplayName"),
)
}
return c
}
// LocalizeRoles returns a translated, independent copy of each role definition.
// The global buildInRoles singleton is never modified.
func LocalizeRoles(roles []*libregraph.UnifiedRoleDefinition, locale string) []libregraph.UnifiedRoleDefinition {
out := make([]libregraph.UnifiedRoleDefinition, len(roles))
for i, r := range roles {
out[i] = LocalizeRole(r, locale)
}
return out
}
// GetAllowedResourceActions returns the allowed resource actions for the provided role by condition
func GetAllowedResourceActions(role *libregraph.UnifiedRoleDefinition, condition string) []string {
if role == nil {
@@ -2,6 +2,7 @@ package unifiedrole_test
import (
"slices"
"sync"
"testing"
. "github.com/onsi/gomega"
@@ -267,3 +268,84 @@ func TestGetAllowedResourceActions(t *testing.T) {
})
}
}
func TestLocalizeRole_English(t *testing.T) {
g := NewWithT(t)
original := unifiedrole.RoleViewer
result := unifiedrole.LocalizeRole(original, "en")
// Strings are unchanged for English
g.Expect(result.GetDisplayName()).To(Equal(original.GetDisplayName()))
g.Expect(result.GetDescription()).To(Equal(original.GetDescription()))
// Result is an independent copy — mutating it must not touch the global
translated := "mutated"
result.DisplayName = &translated
g.Expect(original.GetDisplayName()).NotTo(Equal("mutated"))
}
func TestLocalizeRole_German(t *testing.T) {
g := NewWithT(t)
original := unifiedrole.RoleViewer
result := unifiedrole.LocalizeRole(original, "de")
g.Expect(result.GetDisplayName()).To(Equal("Kann anzeigen"))
g.Expect(result.GetDescription()).To(Equal("Ansehen und herunterladen."))
// Global singleton must be untouched
g.Expect(original.GetDisplayName()).NotTo(Equal("Kann anzeigen"))
g.Expect(original.GetDescription()).NotTo(Equal("Ansehen und herunterladen."))
}
func TestLocalizeRole_EmptyLocale(t *testing.T) {
g := NewWithT(t)
original := unifiedrole.RoleViewer
result := unifiedrole.LocalizeRole(original, "")
// Empty locale falls back to source strings
g.Expect(result.GetDisplayName()).To(Equal(original.GetDisplayName()))
g.Expect(result.GetDescription()).To(Equal(original.GetDescription()))
}
func TestLocalizeRoles_German(t *testing.T) {
g := NewWithT(t)
roles := unifiedrole.BuildInRoles
results := unifiedrole.LocalizeRoles(roles, "de")
g.Expect(results).To(HaveLen(len(roles)))
// Every result is a value (not a pointer)
for i, r := range results {
// Id is preserved
g.Expect(r.GetId()).To(Equal(roles[i].GetId()))
// Global singleton is not mutated
g.Expect(roles[i].GetDisplayName()).NotTo(Equal(r.GetDisplayName()),
"global displayName for role %s was mutated", r.GetId())
}
}
func TestLocalizeRole_ConcurrentCallsDoNotRace(t *testing.T) {
// Run with -race to detect data races on the global buildInRoles strings.
const goroutines = 20
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
locale := "de"
if i%2 == 0 {
locale = "fr"
}
go func(loc string) {
defer wg.Done()
_ = unifiedrole.LocalizeRoles(unifiedrole.BuildInRoles, loc)
}(locale)
}
wg.Wait()
// After all concurrent translations the globals must still hold English strings
g := NewWithT(t)
g.Expect(unifiedrole.RoleViewer.GetDisplayName()).To(Equal("Can view"))
}
+1 -1
View File
@@ -79,7 +79,7 @@
"typescript": "^6.0.3",
"url-loader": "4.1.1",
"webpack": "5.105.2",
"webpack-manifest-plugin": "5.0.0",
"webpack-manifest-plugin": "6.0.1",
"workbox-webpack-plugin": "7.4.1"
},
"packageManager": "pnpm@11.1.3"
+14 -17
View File
@@ -161,8 +161,8 @@ importers:
specifier: 5.105.2
version: 5.105.2
webpack-manifest-plugin:
specifier: 5.0.0
version: 5.0.0(webpack@5.105.2)
specifier: 6.0.1
version: 6.0.1(webpack@5.105.2)
workbox-webpack-plugin:
specifier: 7.4.1
version: 7.4.1(webpack@5.105.2)
@@ -5074,23 +5074,23 @@ packages:
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
webpack-manifest-plugin@5.0.0:
resolution: {integrity: sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==}
engines: {node: '>=12.22.0'}
webpack-manifest-plugin@6.0.1:
resolution: {integrity: sha512-R0p/8/IJVY5hIhQtkeWUQugalVpIwojc09eb14zGq+oiZOCmN5paAz2NBJfd+6v9eBbxAS3YMjc2ov8UMlCDLQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
webpack: ^5.47.0
webpack: ^5.75.0
webpack-sources@1.4.3:
resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==}
webpack-sources@2.3.1:
resolution: {integrity: sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==}
engines: {node: '>=10.13.0'}
webpack-sources@3.3.4:
resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==}
engines: {node: '>=10.13.0'}
webpack-sources@3.5.0:
resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==}
engines: {node: '>=10.13.0'}
webpack@5.105.2:
resolution: {integrity: sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==}
engines: {node: '>=10.13.0'}
@@ -10770,24 +10770,21 @@ snapshots:
webidl-conversions@4.0.2: {}
webpack-manifest-plugin@5.0.0(webpack@5.105.2):
webpack-manifest-plugin@6.0.1(webpack@5.105.2):
dependencies:
tapable: 2.3.3
webpack: 5.105.2
webpack-sources: 2.3.1
webpack-sources: 3.5.0
webpack-sources@1.4.3:
dependencies:
source-list-map: 2.0.1
source-map: 0.6.1
webpack-sources@2.3.1:
dependencies:
source-list-map: 2.0.1
source-map: 0.6.1
webpack-sources@3.3.4: {}
webpack-sources@3.5.0: {}
webpack@5.105.2:
dependencies:
'@types/eslint-scope': 3.7.7
+7 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/opencloud-eu/opencloud/pkg/checks"
"github.com/opencloud-eu/opencloud/pkg/handlers"
"github.com/opencloud-eu/opencloud/pkg/nats"
"github.com/opencloud-eu/opencloud/pkg/service/debug"
"github.com/opencloud-eu/opencloud/pkg/version"
)
@@ -14,12 +15,17 @@ import (
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
secureOption := nats.Secure(
options.Config.Nats.EnableTLS,
options.Config.Nats.TLSSkipVerifyClientCert,
options.Config.Nats.TLSCert,
)
// For nats readiness and liveness checks are identical
// the nats server will neither be healthy nor ready when it can not reach the nats server/cluster
checkHandler := handlers.NewCheckHandler(
handlers.NewCheckHandlerConfiguration().
WithLogger(options.Logger).
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Nats.Host+":"+strconv.Itoa(options.Config.Nats.Port))),
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Nats.Host+":"+strconv.Itoa(options.Config.Nats.Port), secureOption)),
)
return debug.NewService(
@@ -130,6 +130,9 @@ func Server(cfg *config.Config) *cobra.Command {
microstore.Database(cfg.Store.Database),
microstore.Table(cfg.Store.Table),
store.Authentication(cfg.Store.AuthUsername, cfg.Store.AuthPassword),
store.TLSEnabled(cfg.Store.EnableTLS),
store.TLSInsecure(cfg.Store.TLSInsecure),
store.TLSRootCA(cfg.Store.TLSRootCACertificate),
)
svc := service.NewEventsNotifier(evts, channel, logger, gatewaySelector, valueService,
+10 -7
View File
@@ -70,11 +70,14 @@ type ServiceAccount struct {
// Store configures the store to use
type Store struct {
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;NOTIFICATIONS_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;NOTIFICATIONS_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"NOTIFICATIONS_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"NOTIFICATIONS_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;NOTIFICATIONS_STORE_TTL" desc:"Time to live for notifications in the store. Defaults to '336h' (2 weeks). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;NOTIFICATIONS_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;NOTIFICATIONS_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
Store string `yaml:"store" env:"OC_PERSISTENT_STORE;NOTIFICATIONS_STORE" desc:"The type of the store. Supported values are: 'memory', 'nats-js-kv', 'redis-sentinel', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
Nodes []string `yaml:"nodes" env:"OC_PERSISTENT_STORE_NODES;NOTIFICATIONS_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
Database string `yaml:"database" env:"NOTIFICATIONS_STORE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
Table string `yaml:"table" env:"NOTIFICATIONS_STORE_TABLE" desc:"The database table the store should use." introductionVersion:"1.0.0"`
TTL time.Duration `yaml:"ttl" env:"OC_PERSISTENT_STORE_TTL;NOTIFICATIONS_STORE_TTL" desc:"Time to live for notifications in the store. Defaults to '336h' (2 weeks). See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
AuthUsername string `yaml:"username" env:"OC_PERSISTENT_STORE_AUTH_USERNAME;NOTIFICATIONS_STORE_AUTH_USERNAME" desc:"The username to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
AuthPassword string `yaml:"password" env:"OC_PERSISTENT_STORE_AUTH_PASSWORD;NOTIFICATIONS_STORE_AUTH_PASSWORD" desc:"The password to authenticate with the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
EnableTLS bool `yaml:"enable_tls" env:"OC_PERSISTENT_STORE_ENABLE_TLS;NOTIFICATIONS_STORE_ENABLE_TLS" desc:"Enable TLS for the connection to the store. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"7.2.0"`
TLSInsecure bool `yaml:"tls_insecure" env:"OC_INSECURE;OC_PERSISTENT_STORE_TLS_INSECURE;NOTIFICATIONS_STORE_TLS_INSECURE" desc:"Whether to verify the server TLS certificates." introductionVersion:"7.2.0"`
TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OC_PERSISTENT_STORE_TLS_ROOT_CA_CERTIFICATE;NOTIFICATIONS_STORE_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_STORE_TLS_INSECURE will be seen as false." introductionVersion:"7.2.0"`
}
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Ivan Fustero, 2025\n"
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jonas, 2025\n"
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-23 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
"Language-Team: Greek (https://app.transifex.com/opencloud-eu/teams/204053/el/)\n"
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-06 00:01+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: miguel tapias, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/opencloud-eu/teams/204053/fi/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-05-04 07:46+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: mitibor, 2026\n"
"Language-Team: Hungarian (https://app.transifex.com/opencloud-eu/teams/204053/hu/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-22 00:03+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: Simone Broglia, 2025\n"
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2026-04-19 00:04+0000\n"
"POT-Creation-Date: 2026-06-09 09:30+0000\n"
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
"Last-Translator: iikaka88, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/opencloud-eu/teams/204053/ja/)\n"
Loaded 100 of 399 files, more files were not shown because too many files have changed in this diff. Show more