Compare commits

...
268 Commits
Author SHA1 Message Date
github-actions[bot] 149944726e Bump versions for v10.78 2026-08-09 15:27:00 +00:00
Lauri Ojansivu bf0b757862 Prepare v10.78 release 2026-08-09 18:25:59 +03:00
Lauri Ojansivu 1c7de782f0 CHANGELOG: the snap and release-notes fixes from the v10.77 build logs.
Thanks to xet7 !
2026-08-09 18:21:43 +03:00
Lauri Ojansivu a820b056b2 Snap: libcurl4 does not exist on armhf, so name the package noble ships.
The armhf snap failed all three attempts in v10.77 while s390x, ppc64el
and riscv64 each built on attempt 1. The Launchpad build log says why,
one line into the mongodb part:

    Stage package not found in part 'mongodb': libcurl4.
    Build failed

Ubuntu 24.04's 64-bit time_t transition renamed the library, and
`libcurl4` is not a real package on ANY architecture in noble - the
binary is `libcurl4t64` everywhere. On the 64-bit architectures the
renamed package keeps the old name alive:

    Package: libcurl4t64
    Architecture: amd64
    Provides: libcurl4 (= 8.5.0-2ubuntu10)

and the armhf libcurl4t64 has no Provides line at all, because there the
ABI really changed. That is what makes this class of bug reach exactly
one architecture, which is also what makes it look like flakiness on
that architecture.

It had already happened, to libssl3 and libgoogle-perftools4 in v10.71;
the comment above this list describes it exactly. libcurl4 was the same
transition and was left behind. Every other stage-package was checked
against the noble armhf main+universe indices at the same time - all 14
of them - and libcurl4 was the only one that did not resolve.

The workflow's own guidance made it worse. snapcraft ends a missing
stage-package as a "Stopped" build with no artifact, which from the
outside is indistinguishable from Launchpad cancelling a build, so the
message said "often an OOM in the Meteor npm install, or a transient
build-farm reset ... re-run, and if it persists on this arch open a
Launchpad question" - the wrong advice for a failure that will never
succeed on a re-run. The step now looks for the line first, names the
package that is missing, says it is NOT transient, explains the t64
Provides asymmetry, and gives the one-line archive query that checks a
name.

New test tests/snapStagePackages.test.cjs, 6 checks: no stage-package is
a known pre-t64 name, libcurl is asked for by the name noble publishes,
the two fixed in v10.71 have not regressed, and - negatively - a name
that was never renamed keeps its plain name, because the rule is not
"add t64 to everything". It also pins that the reason stays written down
beside the list. Verified it fails when libcurl4 is put back.

Thanks to xet7 !
2026-08-09 18:20:42 +03:00
Lauri Ojansivu 6dce9dda79 Release notes and snap logs: report what really happened.
Four things the v10.77 run got wrong, all of them silent - nothing
errored, the reports were simply not what they claimed to be.

THREE GOOD SNAPS WERE REPORTED AS FAILED. s390x, ppc64el and riscv64
each printed "Remote build <arch> succeeded on attempt 1" and downloaded
their .snap, and each job then ended FAILED - because the step that
SAVES the build log could not upload it:

    The path for one of the files in artifact is not valid:
    /snapcraft-wekan-f82a93c2…_s390x_2026-08-09T11:39:05.txt.
    Contains the following character:  Colon :

snapcraft names a remote-build log after the recipe and an ISO
timestamp, and upload-artifact refuses a colon because NTFS cannot hold
one. The colons become dashes now, so the timestamp is kept rather than
the name thrown away - and the upload is continue-on-error, because a
DIAGNOSTIC upload must never be able to fail the build it is diagnosing.
The snap is the deliverable; the renaming is not the last thing that
could ever make a log file unuploadable.

EVERY ROW OF THE PROVENANCE TABLE APPEARED TWICE. provenance-table.sh
globbed `provenance/**/*.tsv provenance/*.tsv`, and with globstar `**/`
matches ZERO or more directories, so the first pattern already covered
the second and every file was read twice. It is one pattern now, and
rows are also deduplicated on the WHOLE line - a retried step or a job
that ran again records an identical line, and nothing tells the copies
apart. Deliberately not `sort -u -k1,1 -k2,2`: two rows sharing a bundle
and a binary but differing elsewhere are not a duplicate, they are a
real disagreement about which Node.js went into a bundle, and hiding one
at random is worse than showing both.

AMD64 WAS MISSING FROM THAT TABLE - the platform most people download.
Its step runs `cd .build` before `mkdir -p provenance`, so the rows went
to .build/provenance while upload-artifact looked at provenance/ from
the workspace root and found nothing: "No files were found with the
provided path: provenance/". Every other build job records from the
workspace root, which is why only this one was affected. It uses
$GITHUB_WORKSPACE now.

SIX PLATFORMS SAID VERSION "LATEST". amd64, arm64, win64, win32,
mac-arm64 and mac-x64 passed the literal string; only the
extra-architecture job asked which version `latest` actually was, and it
did so with its own inline curl+python. "Which FerretDB did v10.77 ship"
is the one question that column exists to answer. All seven sites now
call the new releases/ferretdb-latest-tag.sh, which asks once per job
and caches, authenticates when there is a token so a shared 60/hour
limit is not what makes it fail, checks that the answer is shaped like a
tag before printing it into a markdown cell, and prints NOTHING and
exits 0 when it cannot find out - the caller's ${FERRET_TAG:-latest} is
then exactly what it had before. A release note must never fail a build
that produced a good bundle.

The table's own prose linked [wekan/node](https://github.com/wekan/node);
the binaries come from wekan/node-patches, which is what the rows
themselves already linked.

New test tests/provenanceTable.test.cjs, 20 checks, most of them running
the scripts rather than reading them. Verified in both directions: with
the doubled glob restored the glob guard fails, with amd64's relative
path restored its guard fails, and with the literal 'latest' restored
its guard fails.

Thanks to xet7 !
2026-08-09 18:15:22 +03:00
Lauri Ojansivu 47c81cd478 CHANGELOG: rewrap the Upcoming summary.
Thanks to xet7 !
2026-08-09 16:01:11 +03:00
Lauri Ojansivu bc426ba7c2 CHANGELOG: the Upcoming binaries table is FerretDB v1.48.0, not v10.77's v1.45.0.
Thanks to xet7 !
2026-08-09 15:58:58 +03:00
Lauri Ojansivu a0f445c723 CHANGELOG: the Docker provenance and bundled-binary freshness work.
Thanks to xet7 !
2026-08-09 15:51:44 +03:00
Lauri Ojansivu ce69db8c5f Pin that every bundled binary is fetched as the newest one.
WeKan ships binaries other projects publish - FerretDB, the MongoDB
Database Tools, Node.js - and fetches each from a release URL. There are
many such URLs, across workflows, release scripts and compose files, and
they must all agree: one that names a fixed version stops receiving
security fixes silently, and nothing about the build fails when it does.

That is not hypothetical. A Quay scan of the v10.77 image reported Go
advisories in the FerretDB binary baked into the bundle - stdlib 1.25.9
wanting 1.25.11, golang.org/x/sys v0.38.0 wanting 0.44.0. The source was
already fixed and v1.48.0 was already published carrying go1.25.11 and
x/sys v0.46.0; the image had simply captured an older latest at build
time. Had any of these URLs been pinned instead, the rebuild would not
have fixed it either.

The test finds the URLs by searching the repository rather than from a
list, so a build site added later is checked too, and it accepts three
shapes: latest/download/<asset>, the bare latest API endpoint that
release-all.yml asks which version latest resolved to for the provenance
table, and ${VAR} whose default is checked separately to be latest.
Node.js is a different mechanism with the same effect - NODE_VERSION is
the bare major 24 and resolve-node-source.sh answers with that CPU's
newest 24.x from whichever of nodejs.org, unofficial-builds or
wekan/node-patches has one - so it is asserted to stay that way.

Thanks to xet7 !
2026-08-09 15:50:11 +03:00
Lauri Ojansivu 5b31695343 Docker images: full build provenance, at every site that pushes one.
BuildKit attaches MINIMAL provenance by default - a build id and a timestamp -
which is what those `unknown on unknown` rows on quay.io are: the OCI marker for
"not a runnable image", one per platform, so `docker pull` never selects them.
mode=max records what actually answers a supply-chain question instead: which
commit, which base image, which build arguments.

WeKan pushes images from FOUR places - docker-publish.yml's build-push-action,
two `docker buildx build --push` in release-all.yml, and one in
releases/docker-build.sh - and none of them said anything about provenance, so
all four took the default. All four ask for mode=max now. The local `--load`
check build in docker-build.sh deliberately does not: the docker exporter cannot
carry an attestation at all.

SBOM stays off, and the test says why so it is a decision rather than a drift:
it enumerates every OS package and npm dependency, taking the attestation from
~10 KiB per platform to megabytes.

tests/dockerProvenance.test.cjs finds the build sites by SEARCHING for them
rather than from a list, so a fifth one cannot quietly fall back to the default.

It also pins a trap I fell into twice while writing this, once in the shell
script and once in a YAML `run:` block:

    docker buildx build \
      # explain the next flag
      --provenance=mode=max \
      --platform ... \

A `#` after a line continuation comments out the REST OF THE JOINED LINE, so
that is `docker buildx build` with every argument swallowed - no platforms, no
tags, no push. `bash -n` accepts it, because it is valid syntax; it is simply a
different command. Both comments live above their command now, and the test
fails on any comment sitting among a continued command's arguments - while
ignoring a comment block that merely quotes a multi-line example, which is what
it flagged first.

Thanks to xet7 !
2026-08-09 15:38:53 +03:00
Lauri Ojansivu 4a8fbd954e CHANGELOG: the Flatpak fix, beside the AppImage one it repeats.
Same group, because it is the same area and the same one-line cause - a gh call
without --repo in a job that does not check out. The subsection is 'bugs' now
rather than 'bug'.

Thanks to xet7 !
2026-08-09 15:25:28 +03:00
Lauri Ojansivu 04fb6e3381 Flatpak: attach the bundles to the release, and only the bundles.
The same one-line bug AppImage.yml had, and a second one beside it.

    gh release upload 'v10.77' assets/* --clobber
    failed to run git: fatal: not a git repository

Both flatpaks built and were uploaded as artifacts; the job that attaches them
does not check the repository out - it only downloads artifacts - so `gh` had no
git remote to infer the repository from. Every gh call in this workflow passes
--repo now, including the ones in jobs that DO check out.

The second: `assets/*` is not the bundles. The artifacts also carry the ostree
repository the flatpak was exported through - config, objects/, refs/,
summaries/, summary, summary.idx - which is build scaffolding, and attaching it
to a release would have put a few hundred directories there beside the two
files anyone wants. The upload names the bundles and their checksums instead.
TSC hit this exact thing and fixed it the same way.

No architectures to add, and the header now says why rather than leaving it to
be rediscovered. A flatpak runs against a RUNTIME, so an architecture exists
here only if freedesktop.org publishes org.freedesktop.Platform for it: x86_64
and aarch64, the i386 and arm runtimes having been discontinued. That is the
difference from AppImage.yml, which could grow to four - an AppImage carries its
own runtime binary, and those exist for i686 and armhf too.

Thanks to xet7 !
2026-08-09 15:13:39 +03:00
Lauri Ojansivu 5957fa0ad8 CHANGELOG: the two AppImage commits, under a new Upcoming section.
v10.77 was cut while they were being written, so they get their own Upcoming
above the release, and the links point at the hashes the rebase onto the version
bump produced rather than the ones that will be collected.

Thanks to xet7 !
2026-08-09 15:09:54 +03:00
Lauri Ojansivu 97e57e531b AppImage: add the other two architectures a runtime exists for.
x86_64 and aarch64 become four, with i686 and armhf. That is the whole set: an
AppImage BEGINS with a runtime binary the kernel executes, and runtimes exist
for exactly x86_64, aarch64, i686 and armhf. ppc64le, s390x, riscv64 and loong64
have none, so they cannot have an AppImage at all however the job is written -
they are served by the bundle zip and the .deb, and the matrix now says so where
somebody would otherwise try.

The two are not equally safe, and they are not treated as though they were.

i686 builds on the x86_64 runner, which runs 32-bit x86 natively: no emulation,
no multiarch, and appimagetool-i686 runs as-is. It is an ordinary job.

armhf builds on the aarch64 runner, which runs 32-bit ARM only if the kernel has
CONFIG_COMPAT and the 32-bit loader is installed. GitHub promises neither, so it
is continue-on-error - the same treatment TSC gives its emulated armhf job. If
it proves reliable, drop the flag; if it never works, drop the entry. What it
must not do is decide whether the other three reach the release.

The smoke test needed the distinction too. It starts the AppImage and waits for
WeKan to answer on port 8080, which a 64-bit runner cannot do for a 32-bit build
it cannot execute. A CHECK THAT CANNOT RUN IS NOT A FAILED CHECK: when the
binary will not execute at all AND it is not this machine's architecture, the
AppImage is as good as anything here knows, so it warns, says it is uploaded
unchecked, and moves on. A build that DOES start and then does not answer still
fails - that is the bug the step exists for.

The publish job's two hardcoded architecture lists learned all four, so a
missing one warns instead of going unnoticed, and the base-bundle check looks
for wekan-<v>-{i386,armhf}.zip as well.

Untested here: this is a workflow, so the first real run is the test. The two
new jobs are the only ones that can fail, and armhf cannot take the release with
it.

Thanks to xet7 !
2026-08-09 15:08:23 +03:00
Lauri Ojansivu fa296d0c80 AppImage: attach the built AppImages to the release again.
Both AppImages built, both passed the smoke test that starts them and waits for
WeKan to answer on port 8080, both uploaded as artifacts, both downloaded by the
attach job with matching digests - and then nothing reached the release:

    gh release upload 'v10.77' assets/* --clobber
    failed to run git: fatal: not a git repository

The attach job does not check the repository out. It has no reason to - it only
downloads artifacts - so `gh` has no git remote to infer the repository from.
Every other gh call in this workflow already passes --repo; these two did not.

Every gh call passes it now, including the two in jobs that DO check out. A call
that relies on an inferred remote breaks the day somebody removes the checkout,
and the error it gives names neither gh nor the workflow.

Worth recording for whoever reads the failed run: the log looks far worse than
the bug. `::error::` lines appear in the build jobs' output for the bundle-file
check and the port-8080 check, and in the attach job for "no architecture
produced an AppImage" - none of which fired. They are the SCRIPT being echoed by
the ##[group] header, not output. The real failure is one line, in the last step
of the last job.

Thanks to xet7 !
2026-08-09 15:08:23 +03:00
github-actions[bot] 040c18bc23 Bump versions for v10.77 2026-08-09 11:16:31 +00:00
Lauri Ojansivu 7241570491 Prepare v10.77 release 2026-08-09 14:15:35 +03:00
Lauri Ojansivu 979ad3a5cd CHANGELOG: the two snap commits, under a new Upcoming section.
v10.76 was cut while they were being written, so they get their own Upcoming
above the release.

Thanks to xet7 !
2026-08-09 14:09:41 +03:00
Lauri Ojansivu 3c9291338c Snap CPU platforms: say which six, why not the other two, and all four channels.
The page listed five architectures and omitted armhf, which snapcraft.yaml has
built all along. It said the release publishes candidate, beta and edge and that
"the stable channel is published manually later" - which is no longer true and
was the reason a build reached three channels of four. And it said nothing about
the two architectures that are bundles but not snaps, so "missing" and "cannot
be there" looked identical.

The matrix is the six `build-for:` entries with the bundle name beside each,
because the two names are not always the same, and a new section explains the
three ways they differ:

  ppc64le / ppc64el   the same 64-bit POWER under two names - the bundles use
                      the kernel's, the store uses Debian's. The only rename.

  armhf / armv7       NOT a rename, and getting this wrong ships a snap that
                      crashes. node-patches builds armhf to the Debian baseline
                      (hard-float, VFPv3-D16, no NEON assumed) so it runs on any
                      ARMv7-A, and armv7 with NEON for boards that have it. The
                      store has ONE 32-bit ARM architecture serving every such
                      device, so it must carry the BASELINE build; the NEON one
                      would be an illegal instruction on a board without NEON.
                      armv7 ships as a bundle only.

  i386                cannot have a new snap at all, and this is categorically
                      different from a missing Node.js build. node-patches
                      patches SOURCE so a binary can be built; here the BASE SNAP
                      does not exist, because Ubuntu 24.04 has no i386 port. No
                      patch set can produce a base Canonical does not publish.
                      The last base with one was core18, end-of-life.

Then what to do about it: the three snaps, all four channels, and why stable is
possible - core24 is a released base, so the snap carries grade: stable, which a
grade: devel core26 snap cannot. A table of what each snap has in the store
today and what is still to upload, including the two fossils that channel
promotion cannot fix: wekan-ondra's armhf at 0.22 and its i386 at 0.X-ci, which
have no newer revision to promote.

And the caveat that matters when running it: without a pinned version the newest
revision of each architecture is promoted, and edge is often ahead of stable, so
a bare run publishes edge builds to stable users.

The armv7 note in models/lib/snapArchitectures.js said "different CPUs", which
was the right conclusion from the wrong reason. It now says what node-patches
actually builds, and a test cross-checks it against that repo's own workflow so
the explanation cannot drift from the binaries.

Thanks to xet7 !
2026-08-09 14:05:17 +03:00
Lauri Ojansivu a5d8a1fd97 Snap: release every snap, every architecture, to all four channels.
The helper that did this released ONE snap, ONE revision, to THREE channels:

    snapcraft release wekan $1 edge,beta,candidate

wrong three ways at once. It names only `wekan`, leaving wekan-ondra and
wekan-gantt-gpl to be done by hand. It takes ONE revision number, and revisions
are PER ARCHITECTURE - the store's own listing shows wekan at 3601 on amd64 and
3600 on arm64 for the same 10.76 - so one number can only ever be right for one
of them. And it leaves out `stable`, so a build reached three channels of four
and somebody had to remember the fourth.

releases/snap-release-all-channels.sh resolves the revision per (snap,
architecture) from the store itself, so no revision number is ever typed, and
releases it to all four channels in ONE call - a revision reaches all of them or
none. A (snap, architecture) with no revision is reported and skipped rather
than failing the run: the three snaps genuinely have different architecture
sets, and one architecture failing must not stop the rest.

THE MAPPING IS THE HAZARD, and it is now in one place with both directions
tested (models/lib/snapArchitectures.js):

  ppc64le and ppc64el ARE the same hardware. The bundles at
  https://github.com/wekan/wekan name it the way Node.js and the kernel do; the
  Snap Store names it the way Debian does. Nothing warns when the wrong one is
  used - an unrecognised --arch is simply an architecture the store has never
  heard of, so it looks like it worked.

  armhf and armv7 are NOT. armhf is 32-bit Raspberry Pi OS; armv7 is the
  ODroid-U3. Different CPUs, different bundles. The store has ONE 32-bit ARM
  architecture, armhf, so that is what is published; mapping armv7 onto it would
  hand Raspberry Pi users the ODroid build. The negative test for that is the
  most important one in the suite.

The six architectures are exactly snapcraft.yaml's `build-for` list, and a test
compares the two so the table cannot drift from what is actually built. i386 and
armv7 are recorded as not-built, with their reasons, and the script prints them
on every run - "it is missing" and "it cannot be there" look identical from the
outside.

tests/snapArchitectures.test.cjs: 23 checks including that the newest revision
for THAT architecture wins, that a version filter publishes what was built
rather than whatever was uploaded last, that `snapcraft list-revisions` parses
when a revision has no channels, and that junk never reaches snapcraft.

Thanks to xet7 !
2026-08-09 14:05:17 +03:00
github-actions[bot] ab29f74f6a Bump versions for v10.76 2026-08-09 02:36:55 +00:00
Lauri Ojansivu 48d8dc9de8 CHANGELOG: repoint commit links after history moved.
A rebase rewrote the commits these entries link to, so the links named commits
that no longer exist and would have 404ed on GitHub. Repointed by
releases/fix-changelog-hashes.sh, which only touches a link no ref in this clone
can reach.

Thanks to xet7 !
2026-08-09 05:35:11 +03:00
Lauri Ojansivu 983aba8488 Prepare v10.76 release 2026-08-09 05:35:10 +03:00
Lauri Ojansivu 988e2f0794 CHANGELOG: the three round-trip fixes, under a new Upcoming section.
v10.75 was cut while they were being written, so they get their own Upcoming
above the release. The FerretDB one is summarised here and told in full in that
repo's CHANGELOG, since it is the database that changed.

Thanks to xet7 !
2026-08-09 05:35:10 +03:00
Lauri Ojansivu 90a41dbb22 Two queries per request instead of two per card.
Neither of these gave a wrong answer. They asked the right question one document
at a time, and each await is a full round-trip that starts only once the last
one finished - invisible on a board with four cards, and the whole cost of the
request on a real one.

The bulk label endpoint (PUT .../cards/labels) awaited a getCard per id and then
an update per id. At BULK_CARDS_MAX = 500 that is ONE THOUSAND sequential
round-trips for a single request. The reads are all the same question, so they
are one `$in` query indexed into a Map; the writes genuinely differ - each card
merges its own labelIds - so they stay individual updates, but they are issued
together and awaited once instead of each waiting for the last.

Two details that had to survive: the loop still iterates cardIds rather than the
query result, because a batch read comes back in the database's order and
without the ids that matched nothing - iterating it would silently reorder
`updated` and lose `notFound`. And every write is awaited before the 200 is
sent, so the response still means what it says.

The bulk DELETE beside it is deliberately left alone: its per-card work is real
(cardRemover runs the sub-item hooks, each card gets its own activity), so
batching the reads would save one query inside a loop that does far more than
query.

Global search resolved each `user:`/`member:`/`assignee:`/`creator:` name with
its own awaited findOne, so `member:ann member:bob member:carol` was three
serial round-trips before the search itself could start. They are all the same
question - which of these names is an account - so it is asked once, with `$in`,
and answered from a map. A name typed under two operators is one lookup now, not
two. Unknown names are still reported against the operator they were typed
under, because "ann is not a user" is not useful without saying where ann was
typed.

tests/queryBatching.test.cjs pins both, including the negatives: no per-card
read remains in the label handler, no per-username query remains in search, the
response order and notFound list are preserved, and the delete handler is still
per-card on purpose.

Thanks to xet7 !
2026-08-09 05:35:10 +03:00
github-actions[bot] 8da91adaa7 Bump versions for v10.75 2026-08-09 01:48:39 +00:00
Lauri Ojansivu 80830cd43e Prepare v10.75 release 2026-08-09 04:46:10 +03:00
Lauri Ojansivu f2edde0651 CHANGELOG: a new Upcoming section for the two commits after v10.74.
The release script renamed the old Upcoming to v10.74, and two commits landed
after it: the linked-card cache that could not see a card added later, and the
build-script parity fix. They get a fresh Upcoming section above the release,
which is where development entries go.

Both were rebased onto the version bump, so their hashes changed; the links
point at the ones that will be pushed, not the ones that will be collected.

Thanks to xet7 !
2026-08-09 04:44:59 +03:00
Lauri Ojansivu ada480c93a Linked-card ids must not be cached for the life of a subscription.
The helper the five linked-card cursors share memoized its result per board, to
spare them the duplicate queries the five copies of that preamble used to make.
That is wrong: publishComposite re-runs a child's find() when the parent
document changes, and a cache living for the whole subscription then serves the
ids computed the FIRST time forever - a linked card added later would never be
published, and one removed would go on being published.

The five cursors each ran these same queries before the helper existed, so
computing per call is exactly the cost they always had, and it is correct. The
guard that pinned the memoization now pins its absence, with the reason.

Thanks to xet7 !
2026-08-09 04:43:30 +03:00
Lauri Ojansivu 114d6c0599 Build script parity: git pull and git push, on Windows too.
The one red suite in the test run, and it was right twice over.

`Setup: "git pull" and "git push" that finish the job` added two entries to
build.sh's menu and left build.bat behind, so `build.bat git-pull` answered
nothing. It answers to both names now, with its own :gitpull/:gitpush labels -
the ones its interactive menu already used.

The guard also had to learn what a `!` entry is. It marks something build.sh
runs ITSELF rather than by executing a file in releases/. Most are raw commands
and the .bat runs those the same way, so those still compare as before; the
exception is an entry naming a build.sh FUNCTION, which is shell the .bat has no
way to call. Comparing those as scripts is what made this suite fail the moment
"Update git" was replaced. A function entry is now exempt from the script
comparison and checked separately: build.bat must implement a label of the same
name, so one dropping out of a menu still fails.

The node suites are 339 run, 0 failed for the first time in this branch.

Thanks to xet7 !
2026-08-09 04:43:30 +03:00
github-actions[bot] fc0ce5137b Bump versions for v10.74 2026-08-09 01:01:29 +00:00
Lauri Ojansivu c3c95cefb9 Prepare v10.74 release 2026-08-09 04:00:21 +03:00
Lauri Ojansivu aea9811a76 CHANGELOG: link the follow-up commit the ParentBleed entry describes.
The five linked-card cursors are its own commit, and its story is told inside
the ParentBleed entry - so a reader could read about the fix and had no way to
reach it.

Thanks to xet7 !
2026-08-09 03:54:58 +03:00
Lauri Ojansivu 5a1227da6c CHANGELOG: the dependency bumps and the two tooling commits, and the subsections in the order CLAUDE.md gives.
Six dependabot bumps and two developer-facing commits since v10.73 had no entry
at all. Adding them showed the subsections were also out of order - fixes came
before features - so they are now CRITICAL SECURITY, features, dependencies,
bug fixes, developer-facing, which is what CLAUDE.md specifies.

Thanks to dependabot and xet7 !
2026-08-09 03:50:24 +03:00
Lauri Ojansivu 8a5ca2c2de CHANGELOG: the canary tokens, the filesystem integrity check and the two Database problems fixes, under Upcoming.
Thanks to xet7 !
2026-08-09 03:44:21 +03:00
Lauri Ojansivu 30e8e38f6c Fix the two bugs Admin Panel / Problems / Database problems was reporting.
The page was doing its job and nobody had acted on it. Two real WeKan bugs were
sitting there as rows marked "unknown / unclassified", whose advice was to add a
rule to models/lib/databaseErrors.js. Both are fixed, and both now classify.

1. moveSwimlane, four times over a week:

     Error: update is not available on the server. Please use updateAsync()

   Board.ensureDefaultSwimlaneId() called the SYNCHRONOUS Swimlanes.upsert().
   Meteor 3 removed it on the server, so every move that reached the default
   swimlane self-heal threw. The async twin already existed one function below.
   This getter is synchronous and cannot await, so the upsert is now STARTED and
   not waited for: the self-heal still happens, and the call returns what is in
   the cache - which every caller already had to handle, because it was
   undefined before the self-heal existed at all. A caller that needs the
   swimlane back in the same tick uses ensureDefaultSwimlaneIdAsync().

2. moveList:

     ValidationError: Failed validation
     Cannot read properties of undefined (reading 'title')

   List.move() inserted a new list with `title: this.title`. When a list has no
   title, `title` is required by the schema, so the insert fails validation - and
   collection2's own error formatter then reads a property of the undefined field
   and throws, which is why the admin saw a crash naming neither the list nor the
   real problem. It now says what is wrong before the insert: "This list has no
   title, so it cannot be moved to another board. Give it a title first." The two
   console.log lines that used to sit there printed the title to a log nobody
   reads and did not stop the crash; they are gone.

And two classifier rules so neither reads as "unknown" again. Both say plainly
that this is WeKan's bug rather than the database's or the admin's - there is
nothing to configure - and send the reporter to the right place: upgrade, then
open an issue with the method name. Severity `warning`, the vocabulary this
stream uses.

Thanks to xet7 !
2026-08-09 03:43:24 +03:00
Lauri Ojansivu 2cc0d42ddf Filesystem integrity: is that still the file WeKan stored, and did this server stop cleanly?
Attachments and avatars are files under WRITABLE_PATH, and the database holds one
document per file. NOTHING checked that the two still agree. A file can be
replaced, truncated, back-dated or deleted by anything that reaches the
filesystem - a bad restore, a sync tool, a container rebuild, a shell on the
volume - and WeKan would keep serving whatever is there now, silently.

A baseline is recorded per file in the existing WeKan database (no new files
under WRITABLE_PATH): path, size, modification time, and md5, sha256 AND sha512.
Three digests, because they answer different questions. md5 is what other tools
print, so an admin can compare against a backup with what they already have - it
is never alone, its collision resistance is gone. sha256 is the working digest.
sha512 earns its place on the day the two DISAGREE: two digests over the same
bytes cannot, so when they do, the bytes were not read the same way twice - a
failing disk, a partial write, a truncated copy - which is a different problem
from a substitution and is its own critical finding.

ed25519 is the fourth check and it is NOT a hash - it is a signature, and it
answers the one thing three digests cannot: who says these are the right hashes?
Anybody who can rewrite a file can rewrite a row of hashes. Each entry is signed
over a canonical line (fixed field order, no JSON, whose escaping is not
guaranteed stable) and verified on every scan. A record that does not match its
signature is critical and is NEVER explained away by an ordinary edit. The key
comes from WEKAN_INTEGRITY_PRIVATE_KEY, which WeKan never stores - that is the
configuration that means something against an attacker who reaches the database
too - or is generated and kept in the database, which detects filesystem-only
tampering and is honest about being no more than that. A MALFORMED supplied key
is reported rather than silently replaced by a generated one, which would look
like it was working while checking nothing the operator meant.

The scan is paced by policy, not by hope: once a day, checked hourly; nothing
starts or continues at or above 60% CPU; 50 ms between files plus 20 ms per
megabyte; a fifteen-minute budget, after which it stops and continues tomorrow;
and ONE read per file for all three digests. A run that stopped early does not
report the files it never reached as missing.

The finding is a change WITH NO RECORD SAYING WHY. Files change when people use
WeKan, and those changes are accounted for, reported once at info and
re-baselined so they do not repeat daily. A change with nothing to explain it is
at least medium and is deliberately NOT re-baselined, so it keeps showing until
somebody looks. A modification time that moved BACKWARDS is called out
separately: nothing does that by accident.

Crashes and downtime, in the same stream, because a crash leaves nothing behind:
the process is gone, so it cannot write a message about being gone. A heartbeat
every minute is the only thing that works, and the NEXT start reads it. A first
run and a clean stop record nothing - reporting every deliberate restart is how
a Problems page becomes one nobody opens. A short gap with no clean-shutdown
mark is a note; a long one is a high-severity "it stopped without shutting down
cleanly, and here is how long it was down". Uncaught exceptions and unhandled
rejections land here too, with their cause, before the process goes.

Admin Panel / Problems / Filesystem integrity is the new stream, with the
Username / IP address / Attempts columns. The baseline itself is never published
and never client-writable: it is a map of every file on the server, and the key
document is a private key.

tests/fileIntegrity.test.cjs, 34 checks: every digest case including same-size
substitution and disagreeing digests, the ed25519 forgery attempt, the canonical
line, explained vs unexplained, and the pacing - including that an unreadable
clock does not start a scan, which it did until the test said so.

Thanks to xet7 !
2026-08-09 03:43:07 +03:00
Lauri Ojansivu d577f07700 Canary tokens: who tried to override permissions, and from where.
The security event log said what a guard DID - a request blocked, a filename
sanitized - which answers "is WeKan defending itself" and not the question an
admin asks next: who did that, and from where. It also could not tell a browser
that got confused from somebody working through the Hall of Fame one entry at a
time.

A CANARY is a tripwire at a point that only a permission-override attempt
reaches. Ordinary use never gets there - the code path runs when a request asks
for something the rules forbid - so a trip is not noise, and it is worth an
admin's attention with the actor attached. Design:
docs/Security/Remediation/WeKan.md §12.

Three properties, each enforced by a test:

SILENT. Tripping one changes NOTHING the caller can observe. tripCanary()
always returns false and tripCanaryDeny() always returns true, so a call site
reads as the refusal it replaces:

    if (forbidden) return false;                          // before
    if (forbidden) return tripCanary('card.vote-field', { userId });

The return value is decided before any reporting is attempted and the reporting
is wrapped, so a canary can never throw into, delay or disturb the request it
watches. The REST handler re-throws the ORIGINAL error object. This is not
politeness: a canary that announces itself is a map of which paths are watched,
and a probe would simply avoid them.

BOUNDED. A canary sits where an attacker can loop, so one row per attempt would
be a denial of service they get for free. models/lib/canaryTokens.js - pure, no
Meteor, no clock of its own - records the first trip of a (canary, actor) pair
at once, COUNTS the rest of the window, flushes one summary carrying the total
when the window closes, and stops writing after 60 summaries for a pair that
never gives up. The tracked-pair map is capped at 5000 and evicts the LEAST
RECENTLY seen, so a long-running attacker is not pushed out by a passing one and
a botnet varying its address cannot grow it. Idle pairs are swept on a timer. A
thousand attempts in a minute cost ONE row, and a suppressed trip costs one map
lookup. The username lookup is cached and capped, and neither it nor the insert
is awaited on the caller's path.

ATTRIBUTED. Every event carries userId, username, ip and count. The username is
denormalised at write time on purpose: it is what the account was CALLED when it
tried, so a rename does not rewrite history and a deletion does not erase it.
The address uses the same spoofing-safe resolver as the login throttle -
X-Forwarded-For only as far as HTTP_FORWARDED_COUNT says to trust it - or an
attacker could write a colleague's address into the security log by sending a
header. `detail` says what was attempted in the words of the feature, never the
payload.

Seventeen canaries are wired at the permission checks that refuse the attempts
behind BoardBleed, ParentBleed, ChecklistBleed, PathBleed and CommentBleed, plus
the direct vote/poker field writes and a stored path that points outside the
storage root. All server-side: no Playwright, no browser, nothing to install.

Admin Panel / Problems / Security gains Username, IP address and Attempts
columns, and both new columns are searchable - the thing an admin does with one
security event is pivot on it.

tests/canaryTokens.test.cjs (25 checks) drives the limiter through bursts, an
endless probe, eviction order and junk input; tests/canaryCoverage.test.cjs (18)
pins every canary to its call site and asserts none of them changes what the
caller gets back.

Thanks to xet7 !
2026-08-09 03:29:20 +03:00
Lauri Ojansivu 839d3a820c CHANGELOG: the audit findings and the security-test coverage, under Upcoming.
The five linked-card cursors and the streaming attachment read join the
ParentBleed and PathBleed entries they extend; the comment-reaction ownership
fix and the security-test coverage guard get subsections of their own.

Thanks to Alpastx and xet7 !
2026-08-09 02:58:35 +03:00
Lauri Ojansivu a4a9c03b1f Security tests: say which vulnerability you guard, and check the list.
"Are the security tests enough to prevent what is in the Hall of Fame" was
unanswerable. WeKan publishes 58 named vulnerabilities at
https://wekan.fi/hall-of-fame/ ; some suites name the one they belong to, most
did not, and the Hall of Fame lives in a different repository that CI never
checks out. Answering it meant reading 58 pages against 300 suites by hand,
which means it was never answered - and a regression test that quietly stops
existing is worth nothing, the failure mode tests/testsAreRegistered.test.cjs
already caught once when two *bleed suites drifted out of the mocha index.

So the list lives in the repository that has the tests, and
tests/securityRegressionCoverage.test.cjs keeps the two in step. Every published
vulnerability is either GUARDED - named by a suite that still exists - or
RECORDED, a gap with a written reason, which is the CHANGELOG's "TODO Later"
pattern applied to tests. The gap count is pinned, so a vulnerability cannot be
published with neither a test nor a note; a gap that turns out to be guarded
after all fails too, so coverage is never understated; and a file that merely
REGISTERS suites is not accepted as coverage, or the guard would pass itself.

Getting there meant naming vulnerabilities in the suites that already guarded
them, which is most of what this commit is:

- tests/securityMeifukun.test.cjs guards eight reports and named one. Its
  sections are RedirectBleed, SourceBleed, LiveBleed, CasBleed, OidcBleed,
  MetricsBleed, ImpersonateBleed and InviteBleed - seven vulnerabilities that
  looked untested and were not.
- tests/noIdentityReplacement.test.cjs guards IdentityBleed and PatternBleed,
  the same CodeQL finding twice.
- ExportBleed, CrashBleed, MimeBleed and the four LockoutBleed suites now say so.
- tests/crossBoardParentCardLeak.test.cjs also guards BoardBleed's cross-board
  move deny, and now checks it on Lists and Swimlanes too, not only Cards.

The count that comes out of it: 29 of 58 published vulnerabilities have a named
regression test, and 29 are recorded gaps - mostly older fixes from before WeKan
tested its security fixes at all. They are not known to be unprotected; they are
known to be unchecked, which is a different and more honest statement, and each
one now says what it would take to close it.

Thanks to xet7 !
2026-08-09 02:55:13 +03:00
Lauri Ojansivu 156121c4bc Comment reactions: react as yourself, not as somebody else.
Found while auditing for more of GHSA-pqr4-rxgp-hv2m (CommentBleed - the REST
comment delete that checked board membership and nothing about WHOSE comment it
was). A CardCommentReactions document holds

    { cardCommentId, reactions: [ { reactionCodepoint, userIds: [...] } ] }

and the whole array is ONE field, whose allow rule was board membership for
insert, update and remove alike. So any member could $set `reactions` to
anything: add a colleague's userId to a reaction they never made, or remove one
they did. `toggleReaction()` only ever touches Meteor.userId(), so no legitimate
client sends anything else - the rule simply never said so.

Integrity rather than confidentiality: reactions are visible to the whole board
already. But it puts words in another person's mouth, and it is the same shape
as the advisory - a per-user object gated only by "are you on this board".

A deny rule now refuses an update that changes any OTHER user's presence in any
reaction. The decision is pure and lives in models/lib/reactionOwnership.js:
it compares MEMBERSHIP rather than array order, because the client rebuilds the
array on every toggle and a reordered array with the same membership is the same
set of reactions. The modifier forms that cannot be checked that way -
$push/$pull/$addToSet/$unset, or a dotted `reactions.0.userIds` - are refused
outright, since toggleReaction() always $sets the whole array and letting them
through would be a hole the size of the one being closed.

The membership rule it sits on top of is unchanged: read-only and no-comment
members still may not react at all.

tests/reactionOwnership.test.cjs covers both directions - adding and removing my
own reaction, the first reaction and the last one going away, a reordered array,
several of my own toggled at once - and the attacks: adding somebody else,
deleting somebody else's, wiping an array that was not only mine, moving
somebody else between reactions, an anonymous caller, and junk in either array.

Thanks to xet7 !
2026-08-09 02:54:56 +03:00
Lauri Ojansivu 2c4e81ae34 ParentBleed, five cursors further: linked cards leaked the same way.
GHSA-jvv9-498p-hxrg was reported against the ANCESTOR cursor of the `board`
publication - a cross-board `parentId` made it publish a private board's cards
to another board's subscribers. Auditing for more of the same found the
linked-card cursors beside it with the identical hole and a wider blast radius:
five of them, publishing the linked card, its comments, its attachments, its
checklists and its checklist items.

A `cardType-linkedCard` names a card by id exactly as `parentId` does, and that
card may live on any board. Creating one requires read access to that board -
but the person who created it is not the person the publication is sending to.
Every subscriber of the board carrying the linked card was getting the source
card's full document and all of its children, whether or not they may see the
board it lives on.

The same answer as the ancestor cursor: a linked card whose source board the
subscriber cannot see is not sent. Cards on THIS board are their own answer,
since they are already going to this subscriber. The five cursors repeated the
same fifteen-line preamble verbatim; they share one helper now, memoized per
board, which is also what stops the sixth from being written without the check.

Two more findings from the same audit, in the same file and its neighbour:

- Three further hand-written copies of the board-visibility `$or` (the boards
  publication, getAllBoardsPage, getAllBoardsTileData). All three were correct,
  which is exactly the state RevokeBleed's copy was in before it drifted. They
  call boardVisibilitySelectors() now, so there is no hand-written copy left in
  the file.

- The STREAMING attachment export read `versions.original.path` with nothing but
  an existsSync. The attachment allow rule refuses a client-supplied path, so
  this is not the PathBleed hole - but it was the last place a stored path was
  read without the containment check, and a path is only as trustworthy as every
  way it could have been written.

tests/crossBoardParentCardLeak.test.cjs grows six checks: all five cursors go
through the helper, the helper asks the same visibility question, no cursor
builds its own linkedId list any more, the assigned-only member narrowing
survived the de-duplication, the helper is memoized, and no hand-written
visibility array remains.

Thanks to Alpastx and xet7 !
2026-08-09 02:54:41 +03:00
Lauri Ojansivu b37adda8a4 CHANGELOG: the four Alpastx security reports, under Upcoming.
PathBleed, RevokeBleed, ParentBleed and CommentBleed, each linking its own fix
commit and its Hall of Fame page, under a CRITICAL SECURITY ISSUES header.

Thanks to Alpastx and xet7 !
2026-08-09 02:27:23 +03:00
Lauri Ojansivu a8fa6cfa3f CommentBleed: the REST API let any board member delete anyone's comment.
Over DDP, deleting a comment is author-or-board-admin, and a board that sets
`restrictCommentEditing` takes even the admin's ability away (#5906). That rule
was enforced in a CardComments collection hook keyed off the Meteor userId, with
an early `if (!userId) return` for the genuine server-internal callers - board
copy, cleanup, migrations - that run with no authenticated user.

An HTTP request carries no Meteor userId into the invocation context. So

    DELETE /api/boards/:boardId/cards/:cardId/comments/:commentId

which checked only `Authentication.checkBoardAccess` - board MEMBERSHIP - reached
the collection with no user, the hook took its "server-internal, trust it" path,
and any normal member could delete any comment on the board. HTTP 200, the
comment gone, restrictCommentEditing or not, while the same deletion over DDP
was correctly refused (CWE-639, CWE-863). Broken object-level authorization:
harassment and evidence destruction on a shared board.

The fix does not rely on the hook seeing something it cannot see. The handler
loads the comment - 404 when there is none - and applies the same rule itself,
with req.userId, before removing anything. That rule is now an exported function
(`assertCanMutateComment` in models/cardComments.js) that the hooks and the REST
handler both call, so DDP and HTTP cannot enforce different things; it carries a
403 statusCode so a refusal answers Forbidden rather than 500. The no-userId
path stays, documented for the internal callers it was written for.

tests/restCommentDeleteAcl.test.cjs pins that the handler loads the comment,
that the ACL runs BEFORE the removal, that membership alone is no longer
sufficient, that the DDP hooks still call the same function, and - so a future
edit route cannot repeat this - that no REST PUT for comments exists without the
same check.

Thanks to Alpastx and xet7 !

Fixes GHSA-pqr4-rxgp-hv2m.
2026-08-09 02:20:52 +03:00
Lauri Ojansivu 35daaaa279 ParentBleed: one card id bridged a private board into another board's feed.
A card's `parentId` may name a card on ANOTHER board, and setting it was
authorized only against the CHILD board's write ACL - nothing asked whether the
person setting it, or the people subscribed to the child board, were allowed to
see the other board at all.

The board publication walks the whole ancestor chain, because the
'prefix-with-full-path' subtask setting renders a subtask's complete path
(#3453), and it published the complete ancestor card DOCUMENTS to every
subscriber of the child board. So a member who could write on shared board B and
knew a card id on private board A could point a card at it and have A's card -
title, description, custom fields - delivered over DDP to people who are not
members of A (CWE-200, CWE-862).

Closed at both ends, because either alone leaves half the hole open.

The WRITE refuses a parent whose board the actor cannot see. That is one
question, asked in one place (server/lib/visibleBoardIds.js) with the same
selectors the publication and All Boards use - so an ACTIVE org, team or domain
share counts and a revoked one does not. It is enforced on the REST card create,
on the REST card update (the PUT the report used), and on DDP by a deny rule
covering both insert and update, beside the cross-board MOVE deny that
GHSA-gm7v-pc38-53jr added. A parent that does not exist is refused too. Clearing
or omitting a parent is untouched.

The PUBLICATION sends only the ancestors whose board the subscriber may see. The
board being published is its own answer, so a normal same-board subtask path is
unchanged; an ancestor on a board the subscriber cannot see is simply not sent,
and a chain that yields nothing publishes nothing.

This is the shape of the check the linked-card path already made - creating a
linked card requires read access to the source card's board - applied to the
field that did not have it.

tests/crossBoardParentCardLeak.test.cjs pins the filtered cursor, that the
unfiltered publish of every ancestor is gone, that both REST paths validate
before writing, that the deny rule covers insert as well as update, and that
clearing a parent and the pre-existing cross-board move deny still work.

Thanks to Alpastx and xet7 !

Fixes GHSA-jvv9-498p-hxrg.
2026-08-09 02:20:37 +03:00
Lauri Ojansivu 08baf7fd1b RevokeBleed: revoking an org, team or domain share did not revoke it.
`isActive: false` is how a board admin REVOKES a share with an organization, a
team or an email domain. The All Boards list honoured it - it matches shares
with `$elemMatch: { ..., isActive: true }` - so a revoked user watched the board
disappear from their list and everything looked correct.

The `board` publication did not. That is the publication that sends the board
document and its lists, swimlanes, cards, comments and attachments, and it
matched shares with a dotted selector instead:

    $or.push({ 'orgs.orgId': { $in: orgsIds } });
    $or.push({ 'teams.teamId': { $in: teamsIds } });
    $or.push({ 'domains.domain': { $in: emailDomains } });

A dotted path matches an element of the array without saying anything about
that element's other fields, so isActive was never consulted. Anyone who still
knew the boardId - a bookmark, a browser tab, a note - could subscribe and
receive the whole private board after their access had been taken away
(CWE-639, CWE-863). A revoke the primary data publication does not honour is
not a revoke.

The rule was written out twice and the two copies disagreed, so it is written
once now: models/lib/boardVisibilitySelectors.js builds the `$or`, both
Boards.userBoards and the `board` publication call it, and every share kind is
matched with `$elemMatch` requiring `isActive: true`. `includePublic: false`
still drops the public clause and only that clause, which is what the search
over all boards needs.

tests/boardShareRevokeBypass.test.cjs pins each share kind's selector shape,
that no clause anywhere matches a share without requiring isActive, that the
three dotted selectors are not produced at all, and that junk in the id lists is
dropped rather than handed to Mongo. tests/searchBoardScope.test.cjs asked its
question of the old array literal; it now asks it of the builder - the
behaviour it protects is unchanged.

Thanks to Alpastx and xet7 !

Fixes GHSA-gwc4-fw7p-gw58.
2026-08-09 02:20:13 +03:00
Lauri Ojansivu 0a9eb0ef4f PathBleed: an avatar could name any file on disk, and board export read it.
Attachments blocked client-supplied `versions.*.path`; avatars did not. The
avatar allow rule was the single line `update: isOwner` - being the owner let
you write ANY field of your own avatar document, `versions.original.path`
included. That field is server-managed metadata: it says where on disk the
bytes are.

Board export then read it. For every member whose avatar is a local WeKan file,
the exporter opens `versions.original.path` and embeds the bytes as base64 in
`profile.avatarFile`, so an authenticated user could point their own avatar at
/etc/passwd - or at anything else under WRITABLE_PATH - export a board they are
a member of, and decode the file out of the JSON. Arbitrary file read as the
WeKan OS user (CWE-22, CWE-73), from any account that can own an avatar.

Both halves are closed.

The WRITE: the guards that attachments had are now in one module,
models/lib/fileVersionFields.js, and BOTH permission files import it - avatars
had gone without them precisely because each collection wrote its own copy.
Avatars refuse an insert carrying versions.*.path or .storage, refuse any update
touching the versions subtree, and restrict updates to the same field whitelist
attachments use. Being the owner is still required; it is no longer sufficient.

The READ: nothing is read from a stored path unless the path RESOLVES to
somewhere inside WeKan's own storage (models/lib/storagePathContainment.js).
That is the half that also holds when a path is poisoned some other way - a
document written before this fix, a restored backup, a bad migration. The check
sits at getBase64Data, the one place the export turns a stored path into bytes
for attachments and avatars alike, and at both avatar call sites. The download
path in fileStoreStrategy.js has always checked containment this way; its
private copy of the function is gone and it uses the shared one, so the download
and the export ask the same question.

tests/avatarVersionPathTraversal.test.cjs pins the payload being refused on
insert and on update, that renaming an avatar still works, that ..-escapes are
resolved rather than string-matched, that a sibling directory with the root as a
prefix is not inside it, and that no unguarded read of a stored version path
remains in the exporter.

Thanks to Alpastx and xet7 !

Fixes GHSA-4mxf-m8pq-xc9p.
2026-08-09 02:19:15 +03:00
Lauri Ojansivu b2ef10d9d1 Merge pull request #6579 from wekan/dependabot/github_actions/actions/upload-artifact-7
Bump actions/upload-artifact from 4 to 7
2026-08-08 23:41:43 +03:00
Lauri Ojansivu ad62101e10 Merge pull request #6578 from wekan/dependabot/npm_and_yarn/aws-sdk/lib-storage-3.1104.0
Bump @aws-sdk/lib-storage from 3.1085.0 to 3.1104.0
2026-08-08 23:41:27 +03:00
Lauri Ojansivu a1c500e33b Merge pull request #6577 from wekan/dependabot/npm_and_yarn/markdown-it-15.0.0
Bump markdown-it from 14.3.0 to 15.0.0
2026-08-08 23:41:09 +03:00
Lauri Ojansivu 454ec04b97 Merge pull request #6576 from wekan/dependabot/npm_and_yarn/tests/playwright/playwright/test-1.62.1
Bump @playwright/test from 1.62.0 to 1.62.1 in /tests/playwright
2026-08-08 23:40:53 +03:00
Lauri Ojansivu f4e5843016 Merge pull request #6575 from wekan/dependabot/github_actions/actions/checkout-7
Bump actions/checkout from 4 to 7
2026-08-08 23:40:40 +03:00
Lauri Ojansivu 0dbcfc44e0 Merge pull request #6574 from wekan/dependabot/github_actions/actions/download-artifact-8
Bump actions/download-artifact from 4 to 8
2026-08-08 23:40:26 +03:00
dependabot[bot] 0612630836 Bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 20:33:36 +00:00
dependabot[bot] eab75eab70 Bump @aws-sdk/lib-storage from 3.1085.0 to 3.1104.0
Bumps [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) from 3.1085.0 to 3.1104.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1104.0/lib/lib-storage)

---
updated-dependencies:
- dependency-name: "@aws-sdk/lib-storage"
  dependency-version: 3.1104.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 20:33:15 +00:00
dependabot[bot] aa8f591839 Bump markdown-it from 14.3.0 to 15.0.0
Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 14.3.0 to 15.0.0.
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/markdown-it/markdown-it/compare/14.3.0...15.0.0)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-version: 15.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 20:32:49 +00:00
dependabot[bot] e16ffbc12b Bump @playwright/test from 1.62.0 to 1.62.1 in /tests/playwright
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.62.0 to 1.62.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.62.0...v1.62.1)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-version: 1.62.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 20:32:48 +00:00
dependabot[bot] d911d71e53 Bump actions/checkout from 4 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 20:32:46 +00:00
dependabot[bot] 79b10a1b43 Bump actions/download-artifact from 4 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 20:32:35 +00:00
Lauri Ojansivu 8c2565e996 releases/: delete two superseded scripts, and two exemptions that outlived them.
An audit of releases/ against who actually calls each script. It holds 89 of
them, and they divide cleanly:

  20  invoked by .github/workflows - CI checks out the repo and runs the FILE, so
      these cannot move anywhere;
  65  reachable from the build.sh / build.bat Releases menu, which is what the
      menu is for;
   1  sourced by another script (ensure-tools.sh);
   3  referenced by nothing that runs.

Of those last three, wekan-entrypoint.sh is the Docker image's entrypoint - named
by the Dockerfile, not by a caller in this repo - so it stays.
old-build-bundle-arm64.sh and translations/old-pull-translations.sh are the other
two, and both were already recorded as superseded, by build-bundle-arm64.sh and
pull-translations.sh. Nothing referenced them but the exemption list that excused
them from being menu entries. They are deleted, and their exemptions with them.

Two more exemption notes were wrong rather than stale-adjacent:
fix-changelog-hashes.sh said "Setup -> Update git runs it", an option that no
longer exists - it is Setup -> git pull and git push now, both of which run it -
and resolve-fork-node-tag.sh excused a file that is not in the tree at all. An
exemption naming a missing script is an exemption nobody can check.

Checked after: every remaining releases/ script is either a menu entry or exempt
with a reason, and no exemption names a file that does not exist.

Thanks to xet7 !
2026-08-08 14:22:12 +03:00
Lauri Ojansivu 6def3a14dd Setup: "git pull" and "git push" that finish the job, replacing "Update git".
"Update git" fetched, rebased, ran the CHANGELOG hash repair, printed git status
and left the rest to the reader - and the rest is where the mistakes were. A
rebase rewrites local commits, so CHANGELOG links written before it name commits
that no longer exist; the repair fixes the file but commits nothing, so the next
push carries a dirty tree; and a push rejected as non-fast-forward leaves a repo
that needs a pull the caller has to know to run. Three manual steps after an
option whose name did not say what it did.

Two options now, one per direction, each ending in a state that is either correct
or unchanged:

  git pull  - fetch; fast-forward when there is nothing local to replay (no
              commit is rewritten, so no link can go stale); otherwise rebase,
              then repoint the CHANGELOG links the rebase moved AND commit that,
              so the tree is clean. A conflict is the one thing a script must not
              paper over - a half-applied rebase IS the wrong state - so the
              rebase is aborted, the repo is exactly as it was, and the message
              says what to run by hand.
  git push  - repair the links BEFORE publishing them, since a stale link that
              reaches GitHub 404s for every reader; say so when there is nothing
              to push; and on the one rejection with an obvious answer, origin
              moved, pull and retry ONCE. Never force: force-pushing a branch
              somebody has pulled rewrites their history too, and a second
              rejection is a different problem that retrying does not fix.

build.bat had a 41-line batch REIMPLEMENTATION of the old flow. It is gone: both
options there call `bash ./build.sh git-pull` / `git-push`, so there is one
implementation of this logic and Windows cannot drift from it. The same two are
CLI commands (./build.sh git-pull, git-push), so the menu, the .bat and the
command line are the same code.

Verified against throwaway repositories - an origin plus two clones - rather than
by reading: already-up-to-date does nothing; origin-only-moved fast-forwards and
rewrites no commit; both-sides-moved rebases, and a CHANGELOG link to a rewritten
commit is repointed to the replayed commit with the same subject and committed,
leaving the tree clean; a conflicting rebase aborts with HEAD, the working tree
and the index exactly as before and no rebase in progress; a rejected push pulls
and lands on the second try; and pushing with nothing to push says so.

Thanks to xet7 !
2026-08-08 14:22:12 +03:00
github-actions[bot] 0409b348af Bump versions for v10.73 2026-08-08 06:39:43 +00:00
Lauri Ojansivu 466bf21a57 Prepare v10.73 release 2026-08-08 09:38:41 +03:00
Lauri Ojansivu c2ded4a599 CHANGELOG: the CodeQL fix belongs in Upcoming, not inside the released v10.72.
v10.72 was tagged and published at 08:32; the PatternBleed fix was committed at
09:05. Its entry went into the section directly above the previous release, and
by then that section had been renamed from "Upcoming" to "v10.72" by the release
commit - so the changelog claimed a released version contains a fix that is not
in its tag. Checked rather than assumed: the v10.72 tag points at "Bump versions
for v10.72", and the fix is not an ancestor of it.

There is a "# Upcoming WeKan ® release" section above v10.72 again, holding the
security subsection, and v10.72's first subsection reads "This release updates
the following dependencies:" once more instead of the "and updates" it was left
with. The Upcoming section carries the shape CLAUDE.md prescribes: the
**In short:** paragraph, then the binaries table - v10.72's rows, since nothing
here rebuilds them, and the paragraph says so - then the subsection, then the
closing thanks line.

The entry's commit link is repointed. `git pull` rebased the three commits after
the release onto it, which gave them new hashes, so the link written before the
rebase named a commit that no longer exists.

And a real bug in the guard that was supposed to notice all this:

    /^(This release|and ) .*:$/

The space is INSIDE the alternative AND after it, so matching an "and" header
needs "and  updates" with two spaces. Every `and ...:` subsection header
therefore failed to match, and the grouping check never reset its current group
at a subsection boundary: entries under a later subsection were attributed to the
last group line above them, which is exactly the mistake that loop exists to
catch. With it fixed, the section reports 22 grouped entries and one loose - the
one-entry security subsection, which CLAUDE.md keeps flat.

Thanks to GitHub CodeQL code scanning and xet7 !
2026-08-08 09:31:19 +03:00
Lauri Ojansivu 9dbdcbefc0 CHANGELOG: name the CodeQL finding PatternBleed and link its Hall of Fame page.
CLAUDE.md's security process is the Hall of Fame as well as the CHANGELOG: the
name, the row in hall-of-fame/index.html and the page under it. I had written the
entry without them.

Thanks to GitHub CodeQL code scanning and xet7 !
2026-08-08 09:27:28 +03:00
Lauri Ojansivu 74223ce69f CHANGELOG: the CodeQL identity-replacement fix, under Upcoming.
Its own SECURITY ISSUES subsection, first in the release as CLAUDE.md prescribes,
so the dependency subsection that used to open it becomes an "and updates".

Thanks to GitHub CodeQL code scanning and xet7 !
2026-08-08 09:27:28 +03:00
Lauri Ojansivu f3a12533d7 CodeQL js/identity-replacement: a replacement that replaced a hyphen with itself.
Code scanning alert #431, tests/releaseNodeSources.test.cjs:

    new RegExp(`^\s*${p.replace('-', '-')}\)\s+nodename=`, 'm')

It reads as "escape the hyphen before interpolating this into a regex" and does
nothing whatsoever, so the value went in raw. Nothing failed, because a hyphen
outside a character class needs no escaping - but the guard it looked like was
not there, and a platform name carrying a `.` or a `+` would have matched the
wrong row or thrown. CWE-116, and CodeQL is right to call it out: the usual cause
of the shape is a mistyped backslash escape, where '\"' is silently just '"'.

The platform name is escaped for real now, with the same escapeRegExp the other
guards in tests/ use.

tests/noIdentityReplacement.test.cjs catches the class rather than the instance,
because code scanning reports these days later in a web UI and the node suites
report in fifteen seconds. Three things it took to make it honest:

  * it compares the two sides as VALUES, not as source text. '\"' IS '"' - that
    is the entire point of CodeQL's example - so a text comparison would call
    them different and miss the mistake the guard exists for.
  * the two quote styles are separate alternatives, not a backreference with a
    character class excluding both quotes: `replace('"', '\"')` contains a double
    quote inside a single-quoted literal, so the first shape of the pattern could
    not match the very example above. Caught by asserting the pattern against
    that line rather than trusting it.
  * comments are stripped and the guard skips its own file: this one and the file
    it was written for both QUOTE the bad line to explain it, and a guard that
    reports the sentence describing a bug as the bug gets switched off.

Verified in both directions: the repository is clean now, the same scan against
the previous commit reports the offending line, all three bad shapes are detected
and four working replacements - including a real escapeRegExp - are not.

Thanks to xet7 !
2026-08-08 09:27:28 +03:00
github-actions[bot] 0369b5042c Bump versions for v10.72 2026-08-08 05:24:23 +00:00
Lauri Ojansivu 70a3ac84c3 Prepare v10.72 release 2026-08-08 08:23:19 +03:00
Lauri Ojansivu 6234956437 CHANGELOG: the suite catching up, under Upcoming.
The two commits that fixed what the companion-repo move and the cross-board card
fix left failing, and the Playwright retry loop that could not reach its own
error. The run after them was green: 328 node suites, 518 mocha tests,
249/243/243 in the three browsers, 98 conformance cases with none differing, and
FerretDB's own suites.

Thanks to xet7 !
2026-08-08 08:14:22 +03:00
Lauri Ojansivu 96daaac3db The last two from the run: an ignore comment, and a retry loop that outlived its test.
Down from five failures to none. Both of these are the same kind of thing - a
check that described the world before a change, and a loop that could not reach
its own error.

swcLegacyHelpers still required the .gitignore comment block that named each
clone: `- node/ : clone of the Node.js ...`. That comment went with the entries
it described. What has to be explained now is the one directory that replaced
them, so .gitignore says what .tools/ holds - companion repos, cloned inside the
checkout so they work in a Flatpak sandbox that shares only this directory, with
the entries it replaced named so the change is legible - and the guard requires
THAT. An ignore of a whole directory with no explanation is the kind nobody dares
remove later. The rule about the shape of a per-repo entry stays: if one is ever
re-added it must be rooted and a directory, since a bare `node` matches anything
a package happens to call that.

The webkit failure was one test of 243, in the shared boardPage fixture rather
than in an assertion:

    Test timeout of 60000ms exceeded while setting up "boardPage".
    Error: page.waitForTimeout: Target page, context or browser has been closed

openBoard retries five times, waiting up to 20s for a list each time with a
second between - about 105 seconds, against a 60 second test timeout. So the loop
could never reach its own error: Playwright killed the test first, and the report
said the page had closed instead of saying the board never rendered. Retrying
past the point where the result can still be used is not resilience, it is a
worse error message.

The loop is bounded by a deadline now, 45s, leaving room for the rest of the
fixture inside the 60s timeout. The first attempt keeps the full 20s look, later
attempts get whatever is left, and it stops rather than starting a wait it cannot
finish - so a slow board still gets one long look, and a board that will not
render fails with "did not render any lists within 45s". Simulated across three
cases: never renders (gives up at 45s, 3 attempts), renders at 4s and at 19s
(both on the first attempt) - all inside the test timeout, where the old shape
overran it by 45 seconds.

Thanks to xet7 !
2026-08-08 06:52:35 +03:00
Lauri Ojansivu 5548029088 The five failures the .tools move and the card-move fix left behind.
An EVERYTHING run found four node suites and one mocha test failing. All five
are consequences of the two changes before them, and each one is a guard doing
its job.

Three broke on the companion-repo move:

  * meteorignoreScanScope listed the foreign checkouts by name - FerretDB, node,
    mongo-tools, TSC, gitea, the two WeKan variants - one .meteorignore entry
    each. They are in .tools/ now, one entry that covers all of them, so a list
    of names is a list of history: it fails for the ones that moved and says
    nothing about the next repo somebody clones. It asks the property instead:
    .tools is excluded, and nothing at the top of this checkout that is its own
    git repository is left for Meteor to walk. The variant check became the
    stronger version of itself - no directory with client/, server/ and models/
    is reachable from the top - since a second WeKan is loaded EAGERLY, not
    merely scanned.
  * swcLegacyHelpers checked .gitignore for the same list; one entry now.
  * cpuMonitorWiring reads FerretDB's Go source and still opened FerretDB/. It
    reads .tools/FerretDB, and skips with a note when the clone is not there -
    it is a separate repository, and not every checkout has cloned it.

dockerComposeBackends caught a real omission: the LDAP_GROUP_BASEDN block from
#5539 went into docker-compose.yml only, and every compose file's wekan service
must be identical - what a user reads when they edit their settings must not
depend on which backend they picked. The block is in all of them now.

The mocha failure is the one worth reading twice.
cards.dependencies.move.tests.js pinned move() issuing the inbound-dependency
cleanup itself: a multi-document update with a compound selector. That is
exactly what #6572 removed, because this helper runs in the client bundle where
Meteor allows updates only by id - it threw "Untrusted code may only updateAsync
documents by ID" on every cross-board move, before the move ran. So the test was
pinning the bug. It now asserts the contract that replaced it: move() clears the
moved card's own dependencies, and makes NO update that is not by id - not a
selector, not multi. `{ _id: x }` still counts as by id, since Meteor's rule is
"by id", not "not an object". The inbound half is the server hook, pinned by
tests/cardMoveUntrustedUpdate.test.cjs.

Verified against the tree: all four node suites' conditions hold, the four
compose files the guard compares are identical again, and the FerretDB source
the CPU guard reads is present at the new path with every pattern it asserts.

Thanks to xet7 !
2026-08-08 03:46:39 +03:00
Lauri Ojansivu e7e7a700c6 CHANGELOG: the webhook username, under Upcoming.
Thanks to xet7 !
2026-08-08 02:50:33 +03:00
Lauri Ojansivu 5b83e8e256 Webhooks send the username too, and TODO Later loses three settled entries.
An audit of the TODO Later backlog against the open issues and the current
source. Three no longer belong there.

#3113 - an outgoing webhook said who did something by DISPLAY name and had no
field for the login name. params.user comes from getActivityUserName(), which
prefers getName() because the same params feed the e-mail notification text,
where "Lauri Ojansivu commented on ..." is what a person wants to read; a webhook
consumer needs the identifier, received "Lauri Ojansivu" where it needed "xet7",
and matching users by display name is wrong the moment two people share one.
Changing what `user` means would break every consumer already reading it, which
is what left this recorded as needing a maintainer decision. It needs none: the
username travels as its OWN field. `user` is untouched, `username` is beside it
in the default WEBHOOKS_ATTRIBUTES list, and a deployment that pinned its own
list still gets exactly the fields it asked for.

#6542 - "Import finished. Users imported: 60, but only 25 are listed". The Admin
Panel People pane paginates and passes its total to the shared table page
(totalLabelKey: 'people-number'), so the 60 the importer reported is on screen
next to the 25 rows of the current page. That is what the report was about, and
it is already true in this source.

#6500 - Docker `latest` not loading in a browser: already closed upstream, so it
was only sitting in the backlog.

The rest of the backlog stays, each with its reason: they need an SMTP server, an
LDAP directory, a container, a browser to drag in, or a maintainer's decision on
an intended contract - none of which a source reading can settle.

tests/webhookUsername.test.cjs pins both halves of the webhook change: that the
username is there and set from the same user document as the display name, and
that `user` still means what it always did, including the notification text that
depends on it. Three of its seven assertions fail on the previous commit.

Thanks to xet7 !

Fixes #3113,
Fixes #6542.
2026-08-08 02:50:15 +03:00
Lauri Ojansivu ebd8a2d1d9 CHANGELOG: companion repos in .tools, under Upcoming.
Thanks to xet7 !
2026-08-08 02:41:43 +03:00
Lauri Ojansivu 40fff29d08 Companion repos live in .tools/, and the build scripts clone them there.
wekan/FerretDB is a separate git repository that WeKan's test runs need: the
conformance run builds it from source, and "Run all FerretDB tests" runs its own
suites. It was cloned as a subdirectory of the repo root, which is why
.gitignore and .meteorignore carried an entry per companion repo - nine of them,
each one a chance for a clone to reach a commit or a Meteor rebuild. They live
in .tools/ now, one directory both files already exclude.

build.sh gains ensure_tool_repo: it returns the path to .tools/<name>, cloning
wekan/<name> if it is not there - SSH first, since a maintainer pushes, HTTPS
after, so everyone else still gets a working clone. Its messages go to stderr,
because the path is what it prints. The directory comes from the script's own
location rather than the caller's cwd, so a clone cannot land in whatever
directory somebody happened to be in.

EVERYTHING's FerretDB stage calls it rather than assuming stage 3 already
cloned - a whole-run must not depend on the order of its own stages -
db-conformance.sh builds from .tools/FerretDB and makes .tools before cloning,
and build.bat does the same on Windows: it clones into .tools\FerretDB with the
same SSH-then-HTTPS fallback instead of printing instructions and stopping, and
says so plainly when git itself is missing.

CLAUDE.md's directory list describes the new layout, including that a companion
repo is cloned on demand, so a fresh checkout needs no manual setup.

tests/toolsDirectoryRepos.test.cjs pins both halves: that nothing still reaches
for the old path and both scripts clone, and that a repo INSIDE .tools still
finds its way back out - FerretDB's own build.sh used to reach WeKan's log
directory by counting ../, which lands one level short from .tools; it searches
for the checkout now (fixed in the FerretDB repo, same commit message).

Thanks to xet7 !
2026-08-08 02:41:24 +03:00
Lauri Ojansivu 909a5d9d5b From .gitignore and .meteorignore remove other repos that will be kept at .tools directory that is already ignored.
Thanks to xet7 !
2026-08-08 02:30:40 +03:00
Lauri Ojansivu e5d3935bc2 CHANGELOG: the LDAP group base and the list dates API, under Upcoming.
Thanks to leandro-cyberproject, mimZD and xet7 !
2026-08-08 02:20:03 +03:00
Lauri Ojansivu 4319fc72db CHANGELOG: TODO Later gains the card-drag flicker and the invite email.
#6430's remaining flicker and #5707's invitation email both need something this
environment does not have - a browser to drag in, and a real SMTP server - so
they are recorded with the reason and what was already established, rather than
guessed at.

Thanks to xet7 !
2026-08-08 02:19:08 +03:00
Lauri Ojansivu 50083104c7 API: a list says when it changed, and when its cards last did.
#5251 asked for the last change date of a list, to build an offline Android
client that syncs only what moved. Neither half existed: GET
/api/boards/:boardId/lists returned {_id, title} and no dates at all, and while
the single-list endpoint returns the whole document - modifiedAt included - that
date answers the wrong question. It moves when the LIST document changes (title,
sort, archived); a card being added, edited or archived does not touch it, and
those are the changes the client is asking about.

The list endpoint now reports both:

  modifiedAt      - the list document itself
  cardsModifiedAt - the newest change among the cards in it, null when none

One query for the board's cards and a reduction in memory, not a query per list -
a query per list is what made this expensive enough to ask about. Archived cards
are included on purpose: archiving is one of the changes named in the issue.

The reduction is models/lib/listActivityDates.js, a pure helper, so it is
testable without Meteor and so the handler stays readable. It reads
dateLastActivity as well as modifiedAt and takes whichever is newer, because
boards written by older WeKan carry only the former; a card with no usable date
is skipped rather than counted as now or as zero; and a list whose cards carry no
date is ABSENT from the result, so the endpoint reports null rather than an
invented time - a client polling on a wrong date either never syncs or syncs
forever.

tests/listActivityDates.test.cjs exercises the helper directly: newest wins, per
list, legacy field, string and numeric dates, an undated card cannot drag the
answer backwards, and a negative case feeding it nulls, strings, numbers, a card
with no listId and an Invalid Date - none of which may throw, since the handler
passes whatever the database holds. It also pins the endpoint's shape: both
fields, one cards query, only the three fields it reduces over, and no
`archived: false` on that query.

Thanks to mimZD and xet7 !

Fixes #5251,
2026-08-08 02:19:08 +03:00
Lauri Ojansivu 8b2e0038df LDAP: groups may live in their own subtree, not only under the user base.
#5539: WeKan could not authenticate LDAP users whose groups sit in a different
ou than the users themselves.

Both group searches - getUserGroups, which feeds the login restriction, admin
status sync, group->role sync and org/team sync, and isUserInGroup - searched
this.options.BaseDN. That is the USER base. A directory that keeps ou=groups
beside ou=people, which is a common layout and the reporter's, has no groups
under it, so every group search came back empty; with LDAP_GROUP_FILTER_ENABLE
on, isUserInGroup concluded "not a member" and refused the login. Nothing in the
package could say where the groups were - there was no setting for it.

LDAP_GROUP_BASEDN says where. Unset, or set to a blank string, it falls back to
BaseDN, so a directory that keeps users and groups in one subtree behaves exactly
as it did; a present-but-empty env var is one somebody meant to fill in, not a
search base, and searching "" would silently search the directory root.

The three USER searches - searchUsers, getUserById, getUserByUsername - keep
BaseDN. Pointing those at a group subtree would break login for everyone, so the
test pins which searches moved and which did not, in both directions.

Documented beside the settings it belongs with, in docs/Login/LDAP.md and
docker-compose.yml: a setting nobody can find is one that does not exist.

tests/ldapGroupBaseDn.test.cjs pins all of it, including the fallback and the
documentation. Six of its nine assertions fail on the previous commit; the three
that pass there are the user searches, which is the point of asserting them.

Not verified against a directory - this sandbox has none. The defect is read from
the source: two group searches, one user base, no way to say otherwise.

Thanks to leandro-cyberproject and xet7 !

Fixes #5539,
2026-08-08 02:16:04 +03:00
Lauri Ojansivu 244375ce13 CHANGELOG: the five misplaced controls, under Upcoming.
Thanks to Alishara and xet7 !
2026-08-08 02:05:34 +03:00
Lauri Ojansivu c687daadf4 Five controls that had drifted away from what they belong to.
The screenshots in #6465 (comment 5187985815, images only, each with the fault
circled):

"Checkbox is misplaced" - Admin Panel people table. The select-all box and the
"all users" it labels were two loose inline nodes in a centred table heading: the
box is replaced content with its own baseline, and at the width that column
happens to have, it landed on the word. They are one <label for> now, an
inline-flex row with a gap that cannot collapse - which also makes the word click
the box.

"This checkbox is crazy" - Member settings. The rescue-card-description row
carried a `b &nbsp;` spacer and `.left`, which is float: inline-start. A float
inside a flex container is ignored and the spacer became a stray flex item, so
the box drifted up beside the "Card settings" heading instead of sitting with its
own text. It is now the same shape every other checkbox row in that file uses:
.flex anchor, .materialCheckBox, span.

"(2/5) <-- move this up here" and "Make this same height as the lanes left and
right" - one cause, not two. The WIP counter is already inside the h2 right after
the title; the title is a `+viewer`, and .viewer is display: block with a 22px
min-height, so it pushed everything after it onto a second line AND reserved a
band under it, which is what made that list's header taller than its neighbours'.
Inline in the heading, the counter stays where the markup already puts it. Same
treatment the quick-access header and the table view already give their viewers.

"Please move the button down. This is too close ... panic ;-)" - the backup
schedule's Save sat directly under the 1..28 day buttons, close enough to hit
while aiming for a date. The gap goes on the form-group that FOLLOWS the day
grid, so it applies where that grid is and nowhere else.

"The alignment is out of place" - the attachment move button. This one is
normalisation rather than a measured fix: the row is align-items: flex-end, so a
margin of the button's own offsets it from the controls it acts on; that margin
is zeroed and the button aligns with the controls explicitly. Whether that is the
whole of what was circled needs a browser, and the test says so.

tests/uiSpacingReports.test.cjs pins each cause, including the premise behind the
WIP fix (.viewer really is a 22px block). Six of its nine assertions fail on the
previous commit; the three that pass there are the premises, which is the point
of stating them separately.

Not verified in a browser: this sandbox has none. Each fix is read off the markup
and CSS against what the screenshots show.

Thanks to Alishara and xet7 !
2026-08-08 02:05:14 +03:00
Lauri Ojansivu 0b7aed89bd CHANGELOG: the OIDC null userinfo, under Upcoming.
Thanks to Esther125 and xet7 !
2026-08-08 02:00:00 +03:00
Lauri Ojansivu a50b483ff9 OIDC: a provider that refuses the request says so, instead of "'ocs' of null".
#5174: an OIDC login against a provider that rejected the scopes failed with
"Error in OAuth Server: Cannot read property 'ocs' of null", which says nothing
about the refusal that caused it.

getTokenContent() returns NULL for a token it cannot parse, and the ADFS/B2C
branch assigns that straight into userinfo. The next line was the Nextcloud hack,
`if (userinfo.ocs)`, so the first thing to touch the failed response was a
property read on null. Every claim read after it had the same problem - that line
was only first by accident of ordering, which is why the fix is not one null
check but an order: the provider's answer is validated once, as a whole, before
anything reads a field off it.

Checked, in this order, each naming what failed:

  * the token response is an object at all;
  * it carries an access_token or an id_token - a 200 with neither is the
    provider answering with a complaint, so the error lists the fields that DID
    come back (keys only: the values are secrets, and JSON.stringify of a token
    response in a log is how refresh tokens leak);
  * userinfo is a non-null object, saying WHICH path produced nothing (claims
    expected in the access token, or the userinfo endpoint) and pointing at
    OAUTH2_REQUEST_PERMISSIONS, which is what was actually wrong in #5174;
  * the ocs and metadata hacks unwrap to something - `ocs` present with no
    `ocs.data` used to set userinfo to undefined and fail on the next line;
  * B2C's userinfo["emails"][0] is an array before it is indexed: a tenant that
    does not release the claim made that undefined[0];
  * expires_in parses to a finite number - parseInt(undefined) is NaN, and NaN
    propagated into the account's expiry silently. Falls back to the OAuth2
    default hour.

tests/oidcNullUserinfo.test.cjs pins the order rather than the individual checks:
it asserts each guard sits BEFORE the read it protects, since an edit that reads
a claim earlier would restore the original bug without touching a check. It also
pins that the no-token error names keys and never stringifies the response. All
eight assertions fail on the previous commit.

Not reproduced against a real provider - this sandbox has none. The null path is
read from the source: getTokenContent returns null for a falsy token, and the
ADFS/B2C branch passes its result to the line that threw.

Thanks to Esther125 and xet7 !

Fixes #5174,
2026-08-08 01:59:42 +03:00
Lauri Ojansivu de8814a7cd CHANGELOG: the board type scale, under Upcoming.
Thanks to xet7 !
2026-08-08 01:52:15 +03:00
Lauri Ojansivu a95451c67b Board type scale: a list heading outranks its cards again.
Reported by email with a screenshot: the "Add card" link and the "Add" button
are too big, and things should be sized in relation to each other the way an h1
is bigger than an h2.

The measurements agreed. A list heading was 14px, while a minicard title and the
"Add card" link both set no size at all and inherited the document's 16px, and
the composer's Add button was a 50px-tall slab. So the column was labelled in
fine print, the affordance for making a card was as loud as the cards
themselves, and the biggest thing on screen while typing was a button.

The scale is now heading 16 > card title 14 >= add-card link 13, each stating
its own size instead of inheriting, and the Add button is a 34px control rather
than a slab. Mobile keeps its own larger sizes - the .mobile-view rules with
!important, and the @media blocks - because a phone is read at arm's length.

tests/listTypeScale.test.cjs pins the ORDER rather than the numbers, since that
is what "in relation to each other" means, plus that each of the three states a
size of its own: an inherited size is exactly how the link and the card title
both landed on 16px, level with each other and above their heading. It reads the
base scale only, skipping @media blocks, and takes the LAST rule for a selector -
.list-header .list-header-name has two rules, and reading the first one finds the
one that sets no size at all. Verified against the previous commit: heading 14,
card title and add-card link unset, button 50px - it fails on all four counts.

Not verified in a browser: this sandbox has none. The sizes are read from the
source, and the relative order is what the guard holds.

Thanks to xet7 !
2026-08-08 01:52:00 +03:00
Lauri Ojansivu f049503ee5 CHANGELOG: the cross-board card move, under Upcoming.
Thanks to jullbo and xet7 !
2026-08-08 01:24:11 +03:00
Lauri Ojansivu 24927c3a7a Moving a card to another board works again, and the REST route cannot corrupt one.
#6572: every cross-board move failed immediately with "Not permitted. Untrusted
code may only updateAsync documents by ID. [403]", whether or not the card had
any dependencies, and before the move itself ran - so the card stayed put.

models/cards.js is isomorphic, so its helpers run in the client bundle, and
client code calls card.move() directly (list.js, swimlanes.js, minicard.js,
globalSearch.js, sidebarFilters.js). Meteor lets untrusted code update only BY ID
- a bare id or an { _id: ... } selector. The cross-board branch of move() cleaned
up inbound "Red Strings" (#3392) with a compound selector and multi: true, which
is rejected on the client every time, including when it would have matched
nothing. addDependency, setDependencyProps and removeDependency in the same file
each carry a comment saying updates must be by _id only; this was the one place
that did not follow it.

The card's own dependencies are still cleared by the move (that rides along in
the by-id update). The inbound half - the links pointing AT the card from the
board it left - is now a Cards.after.update hook in server/models/cards.js, where
a selector is allowed, and being server-side it also covers the REST API and
import paths that never called the helper at all. It pulls both stored shapes:
{ cardId, ... } objects, and the bare id strings older data still holds, which
normalizeDependencies hides on read.

The issue also reports that the REST workaround corrupts the card, and it does.
A PUT of boardId/listId/swimlaneId naming the DESTINATION board is not a board
move - isBoardMove needs newBoardId, newSwimlaneId and newListId - so that branch
never ran, while the same-board swimlane and list branches did: the card kept its
old boardId and got the other board's listId and swimlaneId written onto it,
belonging to a list and a swimlane on a board it was not on, showing on neither,
undone only by a hand-written database update. Those two branches now verify the
target belongs to the board in the URL and otherwise refuse with 400 naming the
parameters to use, and they are skipped during a board move - they would rewrite
listId before the isBoardMove update, whose selector pins the card's original
listId, so that update would match nothing and silently do nothing: the same
broken card by another route.

tests/cardMoveUntrustedUpdate.test.cjs pins both halves: every Cards update in
the isomorphic model is by _id and none is multi-document (it reads the whole
call, because multi is the third argument and a check that stopped at the first
one could never fail), the inbound cleanup is server-side and keyed on a real
board change, and the REST branches are board-checked and skipped during a move.
Verified against the previous commit: the model guard reports the compound
selector and the multi update, and 6 of the 8 REST assertions fail.

Thanks to jullbo and xet7 !

Fixes #6572,
2026-08-08 01:23:22 +03:00
Lauri Ojansivu 75d1e74715 CHANGELOG: the Windows Playwright ALL logs, under Upcoming.
Thanks to xet7 !
2026-08-08 00:39:07 +03:00
Lauri Ojansivu f138b3124f build.bat: the "Playwright ALL browsers" option writes logs again.
The EVERYTHING run's WeKan stage failed on one node suite:
dbConformanceWiring, "build.bat: playwright-all must be logged". It is a real
gap, and splitting the whole-suite browser job into three uncovered it rather
than caused it.

`call :onelog playwright-all` existed in exactly one place: inside the combined
three-project browser line of the sequential all-tests flow, which is gone now
that each browser is its own job with its own log. Where it did exist it never
worked - the call sat inside the `cmd /c` string of a STARTED child process,
which has no build.bat labels to call, so %ONELOG% was empty and Tee-Object was
handed an empty path.

Meanwhile the option the name was supposed to cover, Tests -> 11 "Playwright ALL
browsers", wrote no log at all: one `playwright test --project=chromium
--project=firefox --project=webkit` call straight to the terminal, nothing left
to read afterwards. That is the exact thing the guard is about, and build.sh's
same option has always written one log per browser.

So the option now runs the three browsers one at a time - unchanged, and still
sequential because three at once against one dev server exhausts RAM on smaller
machines - each through the same :onelog helper as every other Tests option, and
each with its own --output so Playwright does not clear another browser's traces
at startup. The guard drops `playwright-all`, which named an implementation that
is gone and was broken, and gains what it was reaching for: that the ALL-browsers
option logs, and that it covers all three.

Verified: every build.bat label reference resolves, all seven menus still print
exactly what they dispatch, the build.sh menu entries all still match a case arm,
and the whole-suite per-browser jobs are untouched. Mocha ran in this run for the
first time since it was fixed - 518 passing - and the other stages were green:
320 node suites with this one failure, 249/243/243 in the three browsers, the
conformance run and all of FerretDB's own tests.

Thanks to xet7 !
2026-08-08 00:38:50 +03:00
Lauri Ojansivu fdc85961a2 CHANGELOG: the menu option that quit the script, under Upcoming.
Thanks to xet7 !
2026-08-07 23:37:10 +03:00
Lauri Ojansivu b55cbda20d build.sh: Tests -> EVERYTHING runs again instead of quitting the script.
Renaming the menu entry broke it. choose() shows the SHORT labels and sets $opt
to the FULL DESCRIPTION of the one picked, and the dispatcher hundreds of lines
below matches that description as a case arm - so the description is an
identifier written twice, and the rename in the previous commit changed only one
of them. With no arm matching and no catch-all, the case fell through, the
`for _once` loop ended and the script EXITED: choosing Tests -> 1 printed nothing
and ran nothing.

The arm now carries the same string as the menu entry, so EVERYTHING runs its
four stages again. Two guards so it cannot come back:

  * tests/buildScriptParity.test.cjs checks every one of the 25 choose() entries
    against the case arms, and fails on a description that matches none. It fails
    on the previous commit's build.sh.
  * the dispatcher has a *) arm that says which option has no handler and that
    nothing was run, so if one ever does get past the test, the person in front
    of the menu is told rather than dropped back to the shell.

build.bat was checked for the same thing and cannot fail this way: its menus
dispatch on the NUMBER typed, not on a sentence, and its EVERYTHING hands the run
to releases/run-everything.sh rather than reimplementing it. Verified anyway,
both directions: every printed menu number has an if-dispatch in all seven menus,
and every goto/call target resolves to a label. That is now pinned too.

Thanks to xet7 !
2026-08-07 23:36:26 +03:00
Lauri Ojansivu 94940ada13 CHANGELOG: the card-drag list resize and the build-script run, under Upcoming.
Also repoints six entries at the commits that exist on this branch. The board
export and mocha entries were written against a commit that was amended after
they were filled in, and the four snap entries against one that was amended the
same way, so each of them linked a commit the amend had orphaned. A CHANGELOG
entry cannot carry the hash of the commit it is in; it goes in its own commit
afterwards, linking the code commit.

Thanks to xet7 !
2026-08-07 19:21:46 +03:00
Lauri Ojansivu c6b0af16eb Card drag no longer resizes every list, and EVERYTHING runs every check.
#6573: picking up a card made every list on the board expand horizontally to fill
the window, and dropping it snapped them back, which makes landing a card in the
intended list a guess. Two correct changes met. A list's custom width is an
inline --list-width custom property, and what turns it into a width is a rule in
list.css whose selector needs an ancestor: .js-swimlane, .dragscroll, or a
swimlane-<id> id. In lists view the container is
.swimlane.list-group.js-lists.dragscroll - no js-swimlane class, no such id - so
.dragscroll was the only one of the three that matched, and every list's width
there hung on that one class. #6558 then taught a card drag to stop the board
panning under the same pointer, and it does that by REMOVING the dragscroll class
for the duration of the drag. So the drag deleted the class the width rule
matched on: flex: none and the width declarations switched off and the lists
re-flowed, for exactly as long as the drag lasted. It also explains the
workaround in the report - dragging one list's edge first sets that list's width
through the resize path, so that column stopped jumping while the others did not.

A layout rule may not hang on a class that an interaction removes. .js-lists is
on the container in both views and nothing takes it off, so the width rules,
their mobile-mode counterpart and the resize rules name it now.
tests/listWidthDuringDrag.test.cjs pins that every width rule still matches in
lists view with dragscroll gone - it fails on the previous CSS - and pins the
premise that suspending the pan really does remove that class.

build.sh -> Tests -> EVERYTHING is what a release is tested with, so a check that
is in the Tests menu and in no whole-run is a check that runs when somebody
remembers it. The floating-promises guard was that: menu entry 12, in no run. It
is the first of four stages now - seconds, so an unawaited permission check is
reported before an hour of browsers - and its return code is in the verdict like
the others. What runs there is checks only: the menu entry may install ripgrep
and the @typescript-eslint packages and write the rule into .eslintrc.json, which
is a person setting the guard up, while EVERYTHING runs unattended and must
modify nothing - a run that rewrites .eslintrc.json is no longer testing the
commit it started from. The two checks are one shared function using grep rather
than ripgrep, so they also work where nothing may be installed.

build.bat ran the three browsers as one Playwright invocation into one
wekan-alltests-browsers.log, where neither "which browser failed" nor "what did
WebKit print" was answerable afterwards, and CLAUDE.md's "check the newest test
logs" names the per-browser files. It starts chromium, firefox and webkit as
three jobs with a log, a status and a summary row each now, with a per-browser
--output because Playwright clears its output directory at startup. EVERYTHING is
not reimplemented there: Windows hands the run to releases/run-everything.sh,
which calls build.sh --run-everything, so the new stage arrives on both platforms
at once. The parity guard pins all of it, including that the shared checks
install nothing.

Verified without node or meteor, which this sandbox has neither of: the extracted
checks were run for real against the tree (clean) and against fixtures with an
unawaited auth call, a missing rule and no .eslintrc.json at all (all three fail
as they should); the new and changed guards were ported assertion-for-assertion
to Python and run against the same files, including that the #6573 guard fails on
the pre-fix CSS; build.sh passes bash -n and every build.bat call now resolves to
a label.

Thanks to yulqen and xet7 !

Fixes #6573,
2026-08-07 19:21:24 +03:00
Lauri Ojansivu ee16a41a99 Board export: pin every section, and fix the mocha stage that stopped running.
The newest test run had two failures.

tests/changelogFormat.test.cjs failed on "the binaries table follows the
summary": the Upcoming section written after v10.71 had lost the binaries table
that every release section carries. It is restored from what v10.71 shipped, and
two group lines that had wrapped onto a second line are one line again, which is
what the grouping guard requires.

The mocha stage did not run at all. client/lib/tests/boardTriggersClass.tests.js
reads boardTriggers.jade and boardTriggers.js off disk with fs and path, which
worked only while node-polyfill-webpack-plugin's browser shims were in the client
bundle; the @meteorjs/rspack 2.1.0 update dropped that plugin and the client
build stopped with "Cannot find module 'path' for matched aliased key 'path'".
That is not one suite failing - the bundle does not build, so every mocha suite
is skipped and the stage reports a crash where its results should be. The suite
was on the wrong side rather than wrong: it now lives in server/lib/tests, and
tests/clientTestsNoNodeBuiltins.test.cjs is the mirror of the existing guard that
keeps browser-only packages out of the server bundle.

Issue #6274, "export includes only comments from current year", and its fix in
PR #6275 were then checked against the current source. The fix is intact and
survived the streaming-export rewrite that came after it: both exporters select
comments by cardId and activities by boardId OR cardId. The follow-up its
reporter left on that PR - "the export is now missing the lists part" - is not
reproducible: result.lists has been written by the JSON exporter continuously
since the CSV/TSV export was added, both JSON writers emit the same fourteen
sections, and the flat formats carry each list's name on the card's own row. So
no export code changed; what was missing was the guard, and
tests/exportBoardSections.test.cjs pins the section list, that the two JSON
writers agree on it, that no exporter selects comments by board again, and the
two smaller fixes from that PR.

Not run here: this sandbox has no node, meteor or any other JS runtime, so the
new and changed guards were verified by porting them assertion-for-assertion to
Python and running that against the same files - changelogFormat 16/16, the
export and client-bundle guards clean. The mocha suites behind the fixed build
have not executed yet; the next run is what shows them.

Thanks to Tuphal, KhaoulaMaleh and xet7 !
2026-08-07 18:20:23 +03:00
Lauri Ojansivu 3c7926662c Snap: fix the three failures that left v10.71 without armhf, s390x, ppc64el, riscv64 and gantt-gpl snaps. 2026-08-07 15:04:39 +03:00
Lauri Ojansivu 9773312176 Updated ChangeLog. 2026-08-07 13:58:47 +03:00
Lauri Ojansivu 6cada892d9 Upgrade to jquery 4.
Thanks to jquery developers !
2026-08-07 13:44:12 +03:00
Lauri Ojansivu f053619d80 Merge remote-tracking branch 'remotes/origin/dependabot/npm_and_yarn/jquery-4.0.0' 2026-08-07 13:34:14 +03:00
Lauri Ojansivu 4063cd20ee Merge remote-tracking branch 'remotes/origin/dependabot/npm_and_yarn/babel/parser-8.0.4' 2026-08-07 13:30:46 +03:00
Lauri Ojansivu a74aee2e45 Updated translations. 2026-08-07 13:27:14 +03:00
Lauri Ojansivu e30eb57e1a Updated dependencies.
Thanks to developers of dependencies !
2026-08-07 13:22:20 +03:00
Lauri Ojansivu 0878de010e Updated rspack and other dependencies.
Thanks to developers of dependencies !
2026-08-07 13:15:19 +03:00
Lauri Ojansivu 65984b912e Upgraded to Meteor 3.5.1.
Thanks to Meteor developers !
2026-08-07 13:10:42 +03:00
Lauri Ojansivu a7f840be48 CLAUDE.md: drop the do-not-push standing rule.
Remove the 'commit only, do not push' guidance from both the maintainer-mode
intro and General practices; the maintainer decides when to push, directly on
the branch (no PR, no branches).
2026-08-06 08:02:35 +03:00
Lauri Ojansivu 2f29b56f36 CHANGELOG: record the snap-launchpad LP_CREDENTIALS false-positive fix under Upcoming. 2026-08-06 08:01:35 +03:00
Lauri Ojansivu 8ebeb85832 snap-launchpad: stop misreporting working LP_CREDENTIALS on every Stopped Launchpad build.
When a Launchpad remote build ends as 'Stopped' with no .snap, the job scans
the build log to tell a real credential failure from a build cancelled mid-run
(an OOM in the Meteor npm install, a build-farm reset). The scan pattern was
'unauthoriz|401|403', and a bare 401/403 matches any three digits in a large
build log - a package size, a hash, an 'attempt 4/6' line - so it fired on
almost every Stopped build and told the maintainer to re-create credentials
that were working: the build had reached 'Building:', which needs valid
credentials. The pattern now matches those codes only in an HTTP-error context
(HTTP Error 401, 403 Forbidden) or an explicit phrase (invalid credentials,
not logged in, bad credentials), so the credential hint appears only for a real
authorization failure.
2026-08-06 08:00:37 +03:00
github-actions[bot] 5337b17ec6 Bump versions for v10.71 2026-08-06 02:56:05 +00:00
Lauri Ojansivu 2f79371fc3 Prepare v10.71 release 2026-08-06 05:54:53 +03:00
Lauri Ojansivu 210233d8bf Updated ChangeLog. 2026-08-06 05:51:39 +03:00
Lauri Ojansivu c09e538fea Make the release scripts run on macOS bash 3.2.
releases/release-all.sh and releases/version.sh read a command's output
into an array with `mapfile`. `mapfile` (a.k.a. `readarray`) is a bash 4+
builtin, and macOS still ships bash 3.2, so `./releases/release-all.sh`
failed on a Mac with `mapfile: command not found` and could not trigger a
release from there. `brew install mapfile` finds nothing because mapfile
is a shell builtin, not a program.

Each `mapfile -t VAR < <(cmd)` is replaced with the portable
`VAR=(); while IFS= read -r line; do VAR+=("$line"); done < <(cmd)`, which
builds the same array on bash 3.2 and on the bash 5 the Ubuntu release
runner uses. Converted: release-all.sh's RELEASED version list, and
version.sh's three reads - the Node.js files, the MongoDB files, and the
two newest CHANGELOG release lines.

Thanks to xet7 !
2026-08-06 05:49:36 +03:00
Lauri Ojansivu 97ce77350c Updated ChangeLog. 2026-08-06 04:47:24 +03:00
Lauri Ojansivu 354d356a0f Take the MongoDB Database Tools from wekan/mongo-tools-patches.
Every place that downloaded bsondump, mongodump, mongoexport, mongofiles,
mongoimport, mongorestore, mongostat and mongotop from wekan/mongo-tools now
downloads them from wekan/mongo-tools-patches: the amd64 base bundle, the
per-arch overwrite in the Linux bundles and in repack-bundle-for-arch.sh, the
Windows and macOS bundle jobs, check-arch-binaries.sh, require-binaries.sh,
test-download-urls.sh, the Dockerfile, both snapcraft files, the Flatpak and
AppImage workflows, the Sandstorm build-deps note and the CPU-platforms docs.

wekan/mongo-tools was a fork of a large Go project that changed none of its
source - its six commits were all the build workflow. wekan/mongo-tools-patches
keeps that build and drops the fork: it clones the newest upstream
mongodb/mongo-tools release, applies the patches in dist/ (there are none yet,
which is the honest state of it), and publishes the same <tool>-<arch>[.exe]
assets with a .sha256sum beside each - the same model wekan/node-patches
replaced the wekan/node fork with.

Nothing else changes: the asset names, the per-tool tolerance for an
architecture that has no binary, and the checksum verification are what they
were, because the build script came across unchanged.

The new checkout is excluded from git and from Meteor's scan the same way
node-patches and the old mongo-tools clone are.

Thanks to xet7 !
2026-08-06 04:47:24 +03:00
github-actions[bot] 22c4a710f7 Bump versions for v10.70 2026-08-05 18:28:57 +00:00
Lauri Ojansivu 74fd91eba4 Prepare v10.70 release 2026-08-05 21:28:01 +03:00
Lauri Ojansivu 8df11cf8f9 Updated ChangeLog. 2026-08-05 21:22:07 +03:00
Lauri Ojansivu b9eff391b9 Take Node.js from official, then unofficial, then wekan/node-patches.
WeKan's bundle .zip, Docker image and snap now resolve their Node.js from
three sources in order - https://nodejs.org/dist/ , then
https://unofficial-builds.nodejs.org/download/release/ , then the
wekan/node-patches releases - instead of only from the retired wekan/node
fork. A platform none of the three publishes a Node.js for is NOT BUILT that
release rather than failing it, and returns by itself on the first run after
one appears. Sandstorm is amd64 only and untouched.

releases/resolve-node-source.sh is the one place that order and the
platform-name mapping live; the bundle jobs, the extra-architecture preflight
and the Dockerfile all ask it, so the image and the .zip of one CPU cannot be
built on Node.js from different places. The snap takes its node out of the
bundle, so it follows without a change of its own.

Thanks to xet7 !
2026-08-05 21:21:51 +03:00
Lauri Ojansivu 1fb7674f84 Updated ChangeLog. 2026-08-05 18:02:31 +03:00
Lauri Ojansivu 59e54e384a win32/mac-x64: resolve the fork version, and cross-build ia32 native modules against the fork's node.lib.
The win32 and mac-x64 preflights checked .../releases/download/${NODE_VERSION}/…
with NODE_VERSION=24 (the bare MAJOR), which 404s - the fork tags by full version
(v24.19.0) - so both jobs ALWAYS skipped. They now resolve the newest v24.x fork
tag carrying the asset via a shared helper, releases/resolve-fork-node-tag.sh
(authenticated with the workflow GITHUB_TOKEN, so the GitHub API is not
rate-limited); the win32 preflight hands the resolved node_full to the rebuild.

And the win32 native-module rebuild is completed. npm_config_arch=ia32 alone was
not enough: an ia32 addon must LINK against an ia32 node.lib, and nodejs.org
dropped 32-bit Windows in Node 23, so node-gyp cannot fetch win-x86/node.lib. The
rebuild now assembles a nodedir node-gyp CAN use - node headers (arch-independent,
from nodejs.org) plus the fork's own node-win32.lib (now published beside
node-win32.exe) at the two paths node-gyp looks in - and points node-gyp at it
with npm_config_nodedir, so bcrypt cross-builds to ia32 against the fork's Node.

(The inline python that resolved the tag broke YAML - a multi-line heredoc cannot
be indented into a `run: |` block - which is why it is a helper script, reused by
both preflights and mirroring embed-verified-node.sh / check-arch-binaries.sh.)

Thanks to xet7 !
2026-08-05 17:59:39 +03:00
Lauri Ojansivu 1c4e05158d Updated ChangeLog. 2026-08-05 17:38:53 +03:00
Lauri Ojansivu b46acc47c5 Extra-arch bundles: make ppc64le and riscv64 best-effort too, so an emulated CPU that crashes cannot fail the release.
Every extra arch is built by running the fork's target-CPU node UNDER QEMU to
rebuild the native modules, and qemu-user does not run every Node.js binary
perfectly. ppc64le crashed at node startup deserializing its V8 snapshot:

    # Check failed: IsFreeSpaceOrFiller(filler).
    ... v8::Isolate::Initialize ...
    qemu: uncaught target signal 5 (Trace/breakpoint trap) - core dumped
    install-node-for-arch.sh: line 144: npm install ... Error 133

so the ppc64le leg failed. With ppc64le (and riscv64) still marked REQUIRED, one
such emulated-CPU failure failed the whole build-extra-arches matrix job.

They join s390x/i386/armhf/armv7/loong64 as best-effort (optional: true): a leg
that cannot run this release SKIPS with a warning instead of failing, so the
matrix never fails on one exotic CPU, the docker job (which now builds only the
bundles that landed) is never dragged down, and the release ships whatever built.
amd64 and arm64 (native, not in this matrix) remain the required core. ppc64le
returns the release it runs cleanly again - its qemu/snapshot crash is a
node-fork/emulation issue to chase separately, not something this bundle build can
fix inline.

Thanks to xet7 !
2026-08-05 17:38:53 +03:00
Lauri Ojansivu d093486766 Native bundles: authenticate the fork-version lookup, tolerate a missing checksum, and cross-build win32 ia32.
Three separate native-bundle failures in one release run:

* mac-arm64 failed the major->tag resolution. embed-verified-node.sh and
  check-arch-binaries.sh call api.github.com to find the fork's newest v24.x tag,
  but no step passed a token, so the call was UNauthenticated - 60 requests/hour
  per IP, shared across runners - and 403-rate-limited intermittently. That is why
  amd64 (which ran first) resolved fine while mac-arm64 (later) did not. A
  workflow-level GITHUB_TOKEN: ${{ github.token }} authenticates every such call
  (5000/hour), fixing the native jobs and the extra-arch preflight alike.

* win64 failed because the fork published node-win64.exe WITHOUT its
  node-win64.exe.sha256sum, and the embed helper 404'd on the sidecar. The
  checksum is a belt-and-suspenders check on top of an already-authenticated HTTPS
  download from the fork, so a MISSING one is now a warning and the binary ships
  UNVERIFIED (a checksum that is present and does NOT match is still fatal). This
  matches how check-arch-binaries.sh already treats a missing fork checksum.

* win32 failed at setup-node: "Unable to find Node version '24' for platform win32
  and architecture x86" - there is NO 32-bit Windows Node 24 (the reason the fork
  exists). It now runs the runner's x64 Node to DRIVE node-gyp and cross-builds
  the native modules (bcrypt) to ia32 via npm_config_arch=ia32; the shipped
  node.exe is still the fork's 32-bit node-win32.exe. tests/releaseBundleCoverage
  is updated for the ia32 cross-build.

Thanks to xet7 !
2026-08-05 17:38:53 +03:00
Lauri Ojansivu 9d9d8535b3 Updated ChangeLog. 2026-08-05 17:38:53 +03:00
Lauri Ojansivu 0aa74076a2 docker: build the image for whatever bundles landed, so one failed exotic arch never skips it.
The docker job `needs: build-extra-arches`, a matrix. When any leg failed - a
single exotic CPU like ppc64le - the whole matrix job was "failed", and a job
whose `needs` dependency failed is SKIPPED. So one failed extra-arch skipped the
ENTIRE multi-arch image, even though amd64/arm64 and the other arches were fine.

Two changes make the image resilient:

  * The job now runs on `always() && needs.release.result == 'success'`, so a
    partially-failed build-extra-arches no longer skips it. It still lists
    build-extra-arches in `needs`, so it waits for the successful legs to upload
    their bundles first.

  * A new "Decide which platforms" step probes which wekan-<version>-<arch>.zip
    bundles actually landed on the release. amd64 and arm64 are required (no image
    without them, and the release would have failed already); the exotic arches
    (ppc64le, s390x, riscv64, 386, arm/v7) are included only if their bundle is
    present, and a missing one is a ::warning that drops just that platform. The
    decided set drives the wait loop, buildx --platform "${PLATFORMS}" and the
    push-verify want="${WANT}" from one place, so they always agree.

The result: a failed or best-effort-skipped exotic arch drops only itself from
the image and returns the next release that builds it; the image is never skipped
and never fails because of one CPU. tests/releaseDockerPlatforms.test.cjs and the
docker cross-check in tests/releaseSnapArches.test.cjs are updated for the
decided-set design (required amd64/arm64, optional exotics, loong64 excluded).

Thanks to xet7 !
2026-08-05 17:38:53 +03:00
github-actions[bot] b0990fa063 Bump versions for v10.69 2026-08-05 11:44:23 +00:00
Lauri Ojansivu e8134c4276 Prepare v10.69 release 2026-08-05 14:43:10 +03:00
Lauri Ojansivu 1345747020 Updated ChangeLog. 2026-08-04 23:23:22 +03:00
Lauri Ojansivu 96e3c94864 Updated ChangeLog. 2026-08-04 23:17:23 +03:00
Lauri Ojansivu 414594ce99 Native bundles: resolve the fork's full Node version from the pinned major, so the download URL is right.
build-amd64 (and the other native jobs) call
  embed-verified-node.sh .build/bundle/node node-x64 "$NODE_VERSION"
but $NODE_VERSION is the pinned MAJOR, '24', while the wekan/node fork tags its
releases by full version, v24.19.0. So the helper built the URL
  https://github.com/wekan/node/releases/download/24/node-x64
which 404s, and the amd64 bundle failed at the Node embed step.

embed-verified-node.sh now resolves a bare major to the newest fork tag v<major>.x
that actually carries the asset (the same GitHub-API walk check-arch-binaries.sh
uses), and takes a full tag (with or without the leading v) as given. If no
v<major>.x release has the asset it stops with a message naming the fork asset to
build, rather than 404-ing on a bad URL.

Note: this fixes the URL; a build still needs the fork to HAVE published the
asset. As of now the fork's v24.19.0 carries only node-mac-x64/node-win32.exe/
node-win64.exe - its node-x64/node-arm64/node-mac-arm64 and the Linux arches were
dropped when the "release all missing" run replaced the assets, so those bundles
cannot build until the fork republishes them (a wekan/node-side fix).

Thanks to xet7 !
2026-08-04 23:17:23 +03:00
github-actions[bot] d6d8170726 Bump versions for v10.68 2026-08-04 19:15:38 +00:00
Lauri Ojansivu 2ece549e21 Prepare v10.68 release 2026-08-04 22:14:00 +03:00
Lauri Ojansivu e632add20d Updated ChangeLog. 2026-08-04 22:10:46 +03:00
Lauri Ojansivu a461e0df39 Updated ChangeLog. 2026-08-04 21:37:17 +03:00
Lauri Ojansivu bff53e93ad Add the win32 and mac-x64 .zip bundles, so every wekan/node fork platform has a WeKan bundle.
The wekan/node fork builds Node.js for 13 platforms and WeKan takes its Node.js
only from the fork, so every one of those platforms should have a WeKan .zip
bundle. Twelve did; 32-bit Windows (win32) and Intel macOS (mac-x64) did not -
the fork builds node-win32.exe and node-mac-x64 (nodejs.org has neither a 32-bit
Windows nor, going forward, a place WeKan will take Intel macOS from), but there
was no job to turn them into bundles. This adds both.

build-win32 mirrors build-win64 on a windows runner, with two differences: it
installs a 32-bit (x86) Node via setup-node so the native modules it rebuilds
(bcrypt) are ia32, matching the fork's 32-bit node.exe the bundle ships, and it
takes ferretdb-win32.exe. build-mac-x64 mirrors build-mac-arm64 on a macos-13
Intel runner, taking node-mac-x64 and ferretdb-mac-amd64.

Both are BEST-EFFORT: a preflight step checks whether the fork has published the
platform's node binary for this version and, if not, SKIPS the job with a warning
instead of a red failure - the fork builds these on its own schedule, and the
bundle appears the run after the fork publishes the binary. Every build step is
gated on that skip. release-notes waits for both so their provenance rows land in
the table. tests/releaseBundleCoverage.test.cjs pins that all six native fork
assets and all seven extra-arch legs map to a bundle; the cpu-exec and snap-arch
guards learn about the two new non-Linux bundles and the armv7 leg.

Thanks to xet7 !
2026-08-04 21:37:17 +03:00
Lauri Ojansivu 41d4eaf7e8 Add the armv7 .zip bundle: the wekan/node fork builds node-armv7, so it gets its own bundle.
The wekan/node fork builds a generic ARMv7 Node.js (node-armv7) as well as
Debian's hard-float one (node-armhf); each is a real fork platform, so each gets
its own WeKan .zip bundle. This adds an armv7 leg to build-extra-arches: it runs
in the same linux/arm/v7 emulated container as armhf, takes its Node.js from the
fork's node-armv7, and shares FerretDB's armhf binary (FerretDB has no distinct
armv7). It is best-effort (optional), like the other 32-bit ARM/x86 bundles - if
the fork's node-armv7 is ever absent for this major it skips rather than failing
the matrix job. The result is wekan-<version>-armv7.zip alongside the armhf one.
tests/releaseArchSkipAndBaseAttach.test.cjs pins armv7 as a best-effort matrix
entry.

Thanks to xet7 !
2026-08-04 21:37:17 +03:00
Lauri Ojansivu e9171ff7d1 Extra-arch bundles and the Docker image take Node.js only from the wekan/node fork.
WeKan now takes its Node.js from the wekan/node fork for EVERY platform and from
nowhere else - the .zip bundles, the emulated cross-builds and the multi-arch
Docker image alike. The reason is control: the fork is Node built from source, so
a Node bug can be PATCHED and rebuilt, which cannot be done with nodejs.org's or
unofficial-builds' opaque binaries, and one source means a set of artifacts is
never half built on one Node.js and half on another depending on the CPU. The
fork already covers the CPUs the other two do not build at all (32-bit x86/ARM,
loong64, ...), so it is the only source that fits every platform.

check-arch-binaries.sh no longer walks nodejs.org -> unofficial-builds -> fork;
it lists the fork's OWN releases for this major, newest first, and takes the
newest that carries node-<arch>, verifying it against that release's
node-<arch>.sha256sum. install-node-for-arch.sh drops the official/unofficial
tarball branch and always installs the fork's bare node binary (grafting npm -
arch-independent JavaScript - from the official amd64 tarball, a build-time tool,
not the shipped node). The Dockerfile maps every TARGETARCH to a fork asset
(node-x64/arm64/ppc64le/s390x/riscv64/i386/armhf) and installs it the same way.

s390x becomes best-effort (optional) like i386/armhf/loong64: the fork has not
published node-s390x for this major yet, so the preflight SKIPS it - a warning,
exit 0 - instead of failing the whole matrix job and skipping docker with it, and
it returns on its own the release after the fork publishes node-s390x. ppc64le
and riscv64 stay required - the fork builds them reliably.
tests/releaseArchSkipAndBaseAttach.test.cjs pins the fork-only sourcing (no
official/unofficial fallback for the node binary) and the s390x skip.

Thanks to xet7 !
2026-08-04 21:37:17 +03:00
Lauri Ojansivu d71f030527 Fix the docker release verify: read the CPU variant so linux/arm/v7 is not misread as linux/arm.
The docker job built and pushed the multi-arch image for all seven platforms
correctly; the step AFTER it, which inspects each registry's manifest to prove
every platform actually arrived, then failed the run over a manifest that was
right.

buildx builds linux/arm/v7, but a registry records that manifest as architecture
"arm" with variant "v7" in a separate field. The verify step's imagetools
--format printed only `{{.Platform.OS}}/{{.Platform.Architecture}}`, so the entry
read back as bare "linux/arm", and the check for "linux/arm/v7" in want= never
matched it - "wekanteam/wekan:v10.67 has no linux/arm/v7 in its manifest (it has:
... linux/arm ...)". The image was complete; the check was blind to the variant.

The --format now appends `/{{.Platform.Variant}}` when a variant is present, so
32-bit ARM reads back as linux/arm/v7, and a trailing /v8 is normalised away so
arm64 (which a registry may record as arm64/v8) still matches want=, which names
it the way everyone writes it, linux/arm64. tests/releaseDockerPlatforms.test.cjs
pins the variant-aware format and the /v8 normalisation so an OS/arch-only format
cannot come back.

Thanks to xet7 !
2026-08-04 21:37:17 +03:00
Lauri Ojansivu ac899ece2d Native bundles take Node.js from the wekan/node fork, not nodejs.org.
WeKan takes its Node.js from the wekan/node fork for EVERY platform now, not
only the arches nobody else builds. The reason is control: the fork is Node
built from source, so a Node bug can be patched and rebuilt - which cannot be
done with nodejs.org's opaque binaries - and one source means a set of bundles
is never half built on one Node.js and half on another. The fork also already
covers the arches nodejs.org and unofficial-builds do not build at all (32-bit
x86/ARM, loong64, win32), so it is the only source that fits every platform.

The four native bundles (amd64, arm64, win64, mac-arm64) embedded a verified
nodejs.org tarball; embed-verified-node.sh now downloads the fork's bare
node-<asset> binary (node-x64 / node-arm64 / node-win64.exe / node-mac-arm64)
and verifies it against the node-<asset>.sha256sum the fork publishes beside it.
That is all a bundle needs - it ships the node binary and runs `node main.js`;
the BUILD still uses the runner's own node+npm, so no npm is grafted here (the
Docker image and the emulated cross-builds, which run npm, graft it separately).
Provenance records source wekan/node and the fork's SHA256.
tests/releaseNodeVerified.test.cjs pins the fork source for all four.

The extra-arch bundles, the Docker image and a new win32 bundle move to the fork
too, in following commits.
2026-08-04 21:37:17 +03:00
github-actions[bot] f1eb4ce64e Bump versions for v10.67 2026-08-04 17:22:36 +00:00
Lauri Ojansivu 67a23e4a15 Prepare v10.67 release 2026-08-04 20:20:28 +03:00
Lauri Ojansivu 78f4955cad Updated ChangeLog.
Repoint the docker 386/arm/v7 link after the v10.66 version-bump rebase:
a0f573bce -> 703527a322 .
2026-08-04 20:18:18 +03:00
Lauri Ojansivu 77efd38dac Updated ChangeLog.
For commit a0f573bce4bbd06cb5790a0888438ef611152144 .
2026-08-04 20:16:39 +03:00
Lauri Ojansivu 703527a322 docker: add linux/386 and linux/arm/v7 on a debian:trixie base, Node from the fork.
Adds two Docker platforms that were shipping as .zip bundles only.

Base image: ubuntu:26.04 -> debian:trixie. Ubuntu publishes no linux/386
image (which is why 386 could never build); Debian does, and debian:trixie
carries every arch this image targets - amd64, arm64, 386, arm/v7, ppc64le,
riscv64, s390x - so one base covers them all, and it is the same base the
per-arch .zip bundles already build in. (The Dockerfile installs no MongoDB
server - every arch defaults to the bundle's FerretDB - so nothing here is
Ubuntu-specific; sha checks switched from `shasum` to `sha256sum`, which is
coreutils and always present.)

Node.js: nodejs.org and unofficial-builds ship no Node 24 for 32-bit x86 or
ARM, but the wekan/node fork does - as a BARE node binary (node-i386 /
node-armhf) plus a .sha256sum, not a tarball. A new `fork` branch in the arch
case downloads that binary, verifies it against its .sha256sum, and grafts npm
+ npx from the official amd64 tarball (npm is arch-independent JavaScript that
runs on the fork's node). Debian's 32-bit ARM port is armhf (ARMv7 VFPv3-D16),
which is what linux/arm/v7 runs, so node-armhf is the match.

linux/386 and linux/arm/v7 go back into the buildx --platform list and the
want= verify list. linux/loong64 stays out: no Docker base publishes it and the
registries do not agree on its manifest yet. tests/releaseDockerPlatforms.test.cjs
now checks the debian base, that 386/arm/v7 are in and loong64 out, and that
every built platform has a matching branch in the Dockerfile arch case.

NOT built here (no buildx/emulation) - confirm on CI. Depends on the fork having
published node-i386 / node-armhf and the bundle having wekan-<v>-{i386,armhf}.zip
for the release.
2026-08-04 20:16:39 +03:00
github-actions[bot] 57f2105bdd Bump versions for v10.66 2026-08-04 16:42:02 +00:00
Lauri Ojansivu c2e7dc2212 Prepare v10.66 release 2026-08-04 19:40:40 +03:00
Lauri Ojansivu fdfe131344 Updated ChangeLog.
For commit 370c091cf9 .
2026-08-04 19:38:40 +03:00
Lauri Ojansivu 370c091cf9 docker: drop linux/arm/v7 too - the Dockerfile has no Node 24 for armv7l.
With linux/386 gone, the multi-arch build got past the base image and then
failed inside the Dockerfile RUN on linux/arm/v7:
  + echo Unsupported architecture: arm
  + exit 1
Docker's TARGETARCH for linux/arm/v7 is `arm`, and the Dockerfile's arch case
handles only amd64/arm64/ppc64le/s390x/riscv64 - it deliberately has no `arm`
branch, because it installs Node.js from nodejs.org / unofficial-builds and
NEITHER ships a Node 24 for armv7l (the Dockerfile comment says so). But
linux/arm/v7 was still in the buildx --platform list, so the RUN hit the
"Unsupported architecture" guard and the whole build failed.

linux/arm/v7 is removed from --platform and from the want= verify list, joining
linux/386 and linux/loong64: all three ship as .zip bundles (armhf's Node comes
from the wekan/node fork there) but not as Docker images, because the image
sources its Node from nodejs.org/unofficial only. tests/releaseDockerPlatforms.test.cjs
now pins arm/v7 out of both lists too.
2026-08-04 19:37:49 +03:00
Lauri Ojansivu 45ba822041 Updated ChangeLog.
Repoint the docker linux/386 link after the v10.65 release rebase:
160c3eb6c -> a2d47df8c2 .
2026-08-04 18:58:36 +03:00
Lauri Ojansivu 15778b7a20 Updated ChangeLog.
For commit 160c3eb6c0a9726657af1e5413d7cb961f5df03b .
2026-08-04 18:57:57 +03:00
Lauri Ojansivu a2d47df8c2 docker: drop linux/386 from the image platforms - ubuntu:26.04 has no i386.
With loong64 no longer skipping build-extra-arches, the docker job ran again
for the first time in a while and failed at the base image:
  #2 ERROR: no match for platform in manifest: not found
  ubuntu:26.04: failed to resolve source metadata ... no match for platform
The image is FROM ubuntu:26.04, and Ubuntu publishes no i386 image, so
`docker buildx build --platform ...,linux/386,...` cannot resolve the base for
that one platform and the whole multi-arch build stops - the workflow's own
bundle-build comment already notes `docker run --platform linux/386
ubuntu:26.04` answers "no matching manifest".

linux/386 is removed from the build's --platform list and from the want= list
that verifies the pushed manifest, exactly as linux/loong64 already was: i386
ships as a .zip bundle (built on debian:trixie, which has 386) but not as a
Docker image. tests/releaseDockerPlatforms.test.cjs pins that 386 and loong64
are out of both lists, that the two lists match, and that the base is
ubuntu:26.04.
2026-08-04 18:57:57 +03:00
github-actions[bot] 5c7dfa7a8d Bump versions for v10.65 2026-08-04 15:11:02 +00:00
Lauri Ojansivu e729efa068 Prepare v10.65 release 2026-08-04 18:08:28 +03:00
Lauri Ojansivu efab4624b1 Updated ChangeLog.
Group the Upcoming entries by area, per CLAUDE.md - a subsection with more
than one entry is grouped, not loose: "Extra-architecture bundles" (loong64)
and "The Launchpad snap builds" (the flatten and the licence-hint fix). The
In short now accounts for the third entry in a closing clause.
2026-08-04 18:07:37 +03:00
Lauri Ojansivu d4ca61b95c Updated ChangeLog.
For commit 646c5db79d .
2026-08-04 18:01:02 +03:00
Lauri Ojansivu 646c5db79d snap-launchpad: stop the failure hint blaming the licence when it is already set.
The Launchpad project xet7-craft-remote-build is set to MIT / X/Expat, but the
snap-launchpad failure hint printed "check its Licence, 'None specified' ... set
the licence to MIT" on EVERY failure - so an s390x build that Launchpad stopped
for another reason read as a licence problem that was already fixed, and the
time went to re-checking a correct setting.

The hint now says the licence should already be MIT (re-set it only if the page
shows "None specified") and, for the real remaining case, explains that a
"Stopped" build with a BUILDING (not FAILED) log that ends mid-step is Launchpad
cancelling the build as it runs - typically an OOM in the memory-heavy Meteor
npm install, or a transient build-farm reset, both of which the three retries
already cover. The large-history push that also broke these builds is fixed
separately by the repo flatten (previous commit).
2026-08-04 18:00:06 +03:00
Lauri Ojansivu 0d1b61b8b1 Updated ChangeLog.
For commit 50e2e14d99 .
2026-08-04 17:52:21 +03:00
Lauri Ojansivu 50e2e14d99 snap-launchpad: flatten history before remote-build so the Launchpad push fits.
The ppc64el (and before it v10.55 riscv64) snap-launchpad build failed with
"Git operation failed with: Could not push 'HEAD' to git.launchpad.net/...
snapcraft-wekan-<hash>", ~4-5 minutes into the push, on every one of the three
attempts. snapcraft remote-build pushes the project's git repository to
Launchpad and builds it there; it rejects a SHALLOW clone (which is why the
checkout is fetch-depth: 0), but it does not need the history, and WeKan's full
history is large enough that the push times out or is refused mid-upload.

After the full checkout, the repo is now re-initialised as ONE commit of the
tagged tree: git rev-parse --is-shallow-repository is still false, so
remote-build accepts it, but the push carries the source tree (tens of MB)
instead of the whole history (hundreds). The snap version comes from
snapcraft.yaml (verified in the next step), not git-describe, so dropping the
history changes nothing about what is built. tests/releaseSnapLaunchpadFlatten.test.cjs
pins the flatten, its order, and that the checkout stays full-depth.
2026-08-04 17:51:10 +03:00
Lauri Ojansivu 62ee7ef17a Updated ChangeLog.
For commit 27da39f958 .
2026-08-04 17:31:28 +03:00
Lauri Ojansivu 27da39f958 Best-effort loong64: skip it when its base image is missing, so docker is not skipped.
The docker job needs build-extra-arches, and GitHub skips a job whose needed
job failed. build-extra-arches is a matrix, and its loong64 leg hard-failed:
no debian/ubuntu image publishes linux/loong64 (node-loong64 and
ferretdb-loong64 exist, the container does not), so the preflight stopped it.
One failed matrix leg makes the whole build-extra-arches job "failure", which
SKIPPED docker - and through docker the charts, ucs and nextcloud jobs that
need it. That is what left docker skipped in run #209.

loong64 is now optional: true, like i386 and armhf, and check-arch-binaries.sh
skips a best-effort arch whose BASE IMAGE is missing - not only one whose
Node.js is missing - with a warning and exit 0 emitting skip=true, gated the
same way every other best-effort skip is. So the matrix job succeeds and docker
runs; loong64 stays visible as a skip and returns to a real build the day a
linux/loong64 base image is published.
2026-08-04 17:29:17 +03:00
Lauri Ojansivu 44b05c3b75 Updated ChangeLog.
Repoint the release-job checkout-order link after the version-bump rebase:
b6f2e13e7 -> ea4a7a5eb8 .
2026-08-04 16:44:24 +03:00
Lauri Ojansivu 6e2ffa42e9 Updated ChangeLog.
For commit b6f2e13e71f14c81a54b2e74a3161d7e24f71a9d .
2026-08-04 16:43:08 +03:00
Lauri Ojansivu ea4a7a5eb8 Release job: check out BEFORE downloading bundles, so it stops deleting them.
The release job in v10.63 and v10.64 shipped with no amd64/arm64 bundle, and
every downstream snap/docker/AppImage job 404'd on wekan-<version>-amd64.zip.
The cause was not softprops (v10.63's guess) but the checkout: the job
downloaded the bundles into the workspace and THEN ran actions/checkout for the
provenance script. The workspace is not a git repo at that point, so checkout's
first act is "Deleting the contents of '<workspace>'" to make room for a fresh
clone - and it does this even with `clean: false`, which only skips the
`git clean` in an already-checked-out repo. The just-downloaded
wekan-<version>-{amd64,arm64}.zip were deleted before the attach step, which
then failed with "wekan-<version>-amd64.zip is missing or empty".

The checkout now runs FIRST, into the empty workspace, and the bundles are
downloaded on top of the checked-out tree, where nothing removes them.
tests/releaseBundlesSurviveCheckout.test.cjs now pins the ORDER (checkout
before the bundle download) rather than clean: false, which was never enough.

Thanks to xet7 !
2026-08-04 16:43:08 +03:00
github-actions[bot] f60982169c Bump versions for v10.64 2026-08-04 13:24:19 +00:00
Lauri Ojansivu 9300afaa03 Prepare v10.64 release 2026-08-04 16:22:46 +03:00
Lauri Ojansivu d725d9d5b4 Updated ChangeLog.
For commit c5a4092418 .
2026-08-04 16:14:03 +03:00
Lauri Ojansivu c5a4092418 Admin reports by URL: keep the subscription the autorun's count.set killed.
Opening an admin report by its address - /admin/problems/files typed,
bookmarked or refreshed - drew the column headers, "No results" and a
"1 / 1" pager over data that was plainly there, while the count method
reported five. 78b2f9ebc fixed one half of this (re-subscribe once the
login lands) but the report still came up empty, because the deeper cause
was untouched.

The onCreated autorun opens the pane and subscribes when problemsOpenPane
or the logged-in user changes. It called openReportPane()/loadReport()
directly in its reactive body, so it became reactive on cfg.count -
loadReport reads it through pageInfo - and loadReport's own count method
then did cfg.count.set(...). That re-ran the autorun, and a Meteor.subscribe
made inside an autorun is AUTO-CANCELLED when the autorun re-runs; the
re-run took the "same user, same pane" branch, did not re-subscribe, and
left the report with no subscription at all - "attachments in minimongo: 0".
Reached from the menu it worked, because switchMenu opens the pane from an
EVENT, not a computation, so that subscribe was never auto-managed; only
the URL path, which runs inside the autorun, hit it.

The autorun now reads only paneId and userId reactively and runs its body
inside Tracker.nonreactive, so cfg.count.set no longer re-runs it and the
subscribe is not auto-cancelled. The subscription's lifetime is managed by
loadReport (stops the previous) and a new onDestroyed (stops the last)
instead of the autorun. tests/adminReportsSubscriptionLifetime.test.cjs
pins the nonreactive body and the onDestroyed cleanup.

Thanks to xet7 !
2026-08-04 16:12:49 +03:00
Lauri Ojansivu 003eab9c29 Updated ChangeLog.
Repoint the Upcoming section's commit links after the version-bump rebase
moved the two code commits to new hashes:
081d50b45 -> e802d52eed and
17d7493d6 -> 55463f685a .
2026-08-04 15:58:24 +03:00
Lauri Ojansivu e6ae24e6e3 Updated ChangeLog.
For commit 17d7493d6f66b8363271cc5ae89ddee0d692fd9e .
2026-08-04 15:56:15 +03:00
Lauri Ojansivu 55463f685a Attach the base bundles robustly, and skip Node-less best-effort arches.
Two release-build failures from the v10.63 run (logs in ../log/wekan3):

1. The release shipped NO amd64/arm64 bundle, so every snap job (native,
   wekan-ondra, wekan-gantt-gpl) 404'd on wekan-10.63-amd64.zip. The base
   bundles were the only ones attached by the central `release` job with
   softprops `files:`, which does not fail on an unmatched file - so a run
   that produced no zip created a release with none and reported success,
   and softprops never listed the .sha256sum either. Every other bundle
   self-attaches with `gh release upload --clobber`, loud on a missing file
   and verified from the release side. The release job now attaches amd64
   and arm64 the same way - failing the release if a base bundle is missing
   instead of 404'ing ten downstream jobs - and checksums the exact bytes it
   attaches so the base bundles get a .sha256sum like the rest.

2. build-extra-arches failed on i386 and armhf: Node has no linux-x86 build
   at all, and no source builds Node 24 for armv7l, so there is nothing to
   build a bundle against. They are now marked best-effort (optional: true);
   when its Node.js is absent everywhere, check-arch-binaries.sh emits
   skip=true (a warning, exit 0) and the build steps are gated on it, so the
   arch is skipped rather than failing the run. It returns on its own the
   first release after wekan/node publishes node-i386 / node-armhf. A
   required arch whose Node.js is missing is still fatal.

tests/releaseArchSkipAndBaseAttach.test.cjs pins both; the base-attach guard
in tests/releaseBundlesSurviveCheckout.test.cjs is updated to the new path.

Thanks to xet7 !
2026-08-04 15:56:14 +03:00
Lauri Ojansivu 863b54d471 Updated ChangeLog.
For commit 081d50b45d3650d909915b92f84f118e7f65c25f .
2026-08-04 15:56:14 +03:00
Lauri Ojansivu e802d52eed Ship a named, checksummed Node.js and FerretDB in every native bundle.
The four native bundles (amd64, arm64, win64, mac-arm64) shipped Node.js
with `cp $(command -v node)` - the runner's node. It has no published
checksum, so the release provenance table could only say "no checksum
published" and nobody could tell WHICH Node.js build a platform carried -
the exact thing needed when a Node.js CVE lands. Worse, build-arm64 has
no setup-node, so ubuntu-24.04-arm's DEFAULT Node 22 went into the arm64
bundle instead of the pinned Node 24, silently.

Each native bundle now downloads the pinned Node.js for its OS+CPU from
nodejs.org and verifies it against SHASUMS256.txt
(releases/embed-verified-node.sh), and verifies FerretDB against the
.sha256sum wekan/FerretDB now publishes beside every binary. Provenance
records nodejs.org + the exact version + the verified SHA256; win64 and
mac-arm64 now upload provenance too, so every platform is traceable. The
emulated arches already did this via install-node-for-arch.sh.
tests/releaseNodeVerified.test.cjs pins that no native bundle goes back
to the runner's node or an unverified download.

Thanks to xet7 !
2026-08-04 15:56:14 +03:00
github-actions[bot] 1888836994 Bump versions for v10.63 2026-08-04 12:00:55 +00:00
Lauri Ojansivu d01ce5f41f Prepare v10.63 release 2026-08-04 14:59:00 +03:00
Lauri Ojansivu b31a57609e Updated ChangeLog. 2026-08-04 14:58:24 +03:00
Lauri Ojansivu 85d2ec6dad AppImage/Flatpak: keep the extension in the checksum name, so they are not rebuilt.
releases/expected-assets.sh looks for WeKan-<v>-<arch>.AppImage.sha256sum and
WeKan-<v>-<arch>.flatpak.sha256sum (asset name + .sha256sum, the same convention
the wekan-<v>-<arch>.zip.sha256sum bundles use). But the builds wrote
WeKan-<v>-<arch>.sha256sum - extension DROPPED - so "what the release is missing"
never found the checksum and rebuilt the AppImage and Flatpak on every run even
when they were already published. The checksum (and md5sum) now keep the
.AppImage / .flatpak extension, matching what the check expects.
2026-08-04 14:56:20 +03:00
github-actions[bot] 8175ba8549 Bump versions for v10.62 2026-08-04 11:40:48 +00:00
Lauri Ojansivu 4b8f2e0218 Prepare v10.62 release 2026-08-04 14:39:26 +03:00
Lauri Ojansivu 27e35c1236 Updated ChangeLog. 2026-08-04 14:37:04 +03:00
Lauri Ojansivu 809dc794f2 Release: stop checkout wiping the bundles, so the release actually ships them.
This is the root cause of the snap / docker / AppImage 404s on
wekan-<version>-amd64.zip. The `release` job downloads the per-arch bundles to
the workspace root, then checks the repo out (for the provenance script) BEFORE
creating the GitHub Release. actions/checkout defaults to clean: true, whose
`git clean -ffdx` deleted those untracked zips - so the "Create GitHub Release"
step's `files: wekan-<version>-{amd64,arm64}.zip` matched nothing. softprops does
not fail on unmatched files, so the release was created (job "success") WITH NO
BUNDLES, and every downstream job that downloads a bundle 404'd. The checkout now
sets clean: false, so the bundles survive and are attached.

Also: the snapcraft `wekan` part downloaded the bundle with a single wget that
treated a 404 as fatal, so it also broke on the brief CDN lag right after an
upload. It now retries (--tries/--retry-on-http-error), like the curl downloads
elsewhere in the release.

tests/releaseBundlesSurviveCheckout.test.cjs pins that a checkout after the
bundle download keeps clean: false; it fails on the pre-fix workflow.
2026-08-04 14:33:20 +03:00
github-actions[bot] f8dd16aaa5 Bump versions for v10.61 2026-08-04 10:58:01 +00:00
Lauri Ojansivu 928ec27b0e Prepare v10.61 release 2026-08-04 13:54:15 +03:00
Lauri Ojansivu a889ae1070 Test: releaseSnapArches expects no i386 snap (core24 has no i386 port).
The snap i386 platform was removed because core24 cannot build it; this guard
still pinned i386 as a snap platform and a Launchpad arch. Updated to expect the
six buildable snap platforms (amd64, arm64, armhf, ppc64el, riscv64, s390x) and
to treat i386 like loong64 - an arch that has a wekan-<arch>.zip BUNDLE but is
deliberately not a snap - with the reason recorded in the test.
2026-08-04 13:52:35 +03:00
Lauri Ojansivu c5be008a00 Updated ChangeLog. 2026-08-04 13:48:45 +03:00
Lauri Ojansivu 8790b66806 Scaffold a Debian type:base snap so i386 (etc.) can ship as a snap.
core24 has no i386 port, so `base: core24` cannot build an i386 snap (removed in
the previous commit). Debian still ships i386 and is glibc, so - unlike an
Alpine/musl base - WeKan's existing binaries run unchanged. Since the WeKan snap
only assembles the prebuilt wekan-<arch>.zip (and wekan-i386.zip already exists),
the only missing piece for an i386 snap is an i386-capable base.

snap-base-debian/ adds `wekan-base-debian13` (type: base), a trimmed Debian
trixie rootfs, starting with i386 - which builds NATIVELY on an amd64 runner (no
qemu, no Launchpad). It is isolated from the working core24 snapcraft.yaml so it
can never break the mainstream builds.

This is a documented SCAFFOLD, not a tested artefact: a custom base snap is
advanced snapcraft and needs a real `snapcraft pack` + local install + a WeKan
snap running on top before it is wired into CI or sent for the (manual) store
review. README.md carries the full plan, the local test loop, the publish/ACL
steps, and the known pitfalls.
2026-08-04 13:42:35 +03:00
Lauri Ojansivu bbc3578747 Release build: fix the win64, mac-arm64, extra-arch and snap jobs.
The v10.60 release job succeeded (the release-notes shell-injection fix landed),
but four of the per-platform build jobs failed, each for its own reason:

- build-win64: the "Check the binaries" step ran `set -euo pipefail` under the
  Windows default shell (PowerShell), which answered "A parameter cannot be found
  that matches parameter name 'euo'". It is a bash script; it now says
  `shell: bash`.

- build-mac-arm64: the zip step ran `sha256sum`, which does not exist on macOS
  (that is the GNU coreutils name), so the job died with exit 127. Use
  `shasum -a 256`, the BSD/macOS spelling, which writes the same format.

- build-extra-arches: the job downloaded wekan-<version>-amd64.zip but never
  unpacked it, so the container mounted an EMPTY /bundle and the native-module
  rebuild died with "ENOENT ... /bundle/programs/server/package.json" (exit 254).
  Added the unzip the arm64/win64/mac-arm64 jobs already do.

- snap-native / snap-launchpad: snapcraft.yaml declared an i386 platform, but
  core24 (Ubuntu 24.04) has no i386 port, so snapcraft rejected it with "trying
  to build snap in one of ['i386'], but none of these build architectures are
  supported" - a PARSE error that failed EVERY snap build, not only i386's.
  Removed the i386 platform and its snap-launchpad matrix entry; i386 users are
  served by the .deb and AppImage, and an i386 snap is not buildable on a modern
  base.

loong64 still reports its documented "debian:trixie has no linux/loong64 image"
gap (there is no loong64 userland to rebuild in), which is intentional and left
visible.
2026-08-04 13:31:01 +03:00
Lauri Ojansivu 43ff0e7a5c Docker + AppImage: skip gracefully when the release bundle is not published yet.
These jobs do not build WeKan from source — they download the prebuilt release
zip (wekan-<version>-<arch>.zip) that release-all.yml publishes, and fail hard
if it is missing. So they failed whenever they ran before the release was
complete: dockerimage.yml / docker-publish.yml on a push or a tag pushed early
in the release, and AppImage.yml when release-all-missing asked it to wrap a
version whose base bundle was not published (`gh release download` → "no assets
to download"). The v10.59 release itself had failed (the release-notes shell
injection, fixed separately), so every one of these downstream jobs 404'd.

Each now checks whether the release asset it needs is published and SKIPS with a
notice (a green run) when it is not, instead of failing:
- dockerimage.yml / docker-publish.yml gate the image build on the pinned
  version's amd64 zip being on the release;
- AppImage.yml flips its per-arch BUILD_THIS off when that arch's bundle is
  absent, and its publish job treats "no AppImage, and no base bundle either" as
  nothing-to-do rather than an error.

The real images/AppImages are still built by release-all.yml's own jobs once the
release is up; these standalone/gap-filling runs simply stop failing in the
meantime.
2026-08-04 13:12:30 +03:00
github-actions[bot] bd32ae644a Bump versions for v10.60 2026-08-04 09:46:56 +00:00
Lauri Ojansivu da5496260e Prepare v10.60 release 2026-08-04 12:45:40 +03:00
Lauri Ojansivu d6ff3909a0 Updated ChangeLog. 2026-08-04 12:40:13 +03:00
Lauri Ojansivu 541dc8b7b6 Release notes: pass the CHANGELOG through env, not inline into the shell.
The release and release-notes jobs built the GitHub Release body with
`printf '%s\n' "${{ needs.prepare.outputs.changelog }}"`. Interpolated inline,
the CHANGELOG becomes part of the shell source before bash parses it, so every
backtick in it runs as a command. The v10.59 notes are full of `code` spans, so
the release job failed with "Incorrect: command not found",
"loginFailureDecision.js: Permission denied", "POST: command not found" and
published no release. The changelog now reaches both steps through the
environment (env: CHANGELOG), and the scripts use "$CHANGELOG", where the shell
treats it as data and never parses its backticks, $( ) or quotes.

tests/releaseNotesNoShellInjection.test.cjs pins that outputs.changelog is only
ever consumed as an env: assignment, never inline in a run: script; it fails on
both pre-fix printf lines.

Thanks to xet7.
2026-08-04 12:40:13 +03:00
github-actions[bot] ecba538c35 Bump versions for v10.59 2026-08-04 09:18:56 +00:00
Lauri Ojansivu 2662382aa3 Prepare v10.59 release 2026-08-04 12:17:59 +03:00
Lauri Ojansivu 23eaea752a Updated translations 2026-08-04 12:17:26 +03:00
Lauri Ojansivu c1ea40b64e Updated ChangeLog. 2026-08-04 12:09:37 +03:00
Lauri Ojansivu 3e8c5a89e2 Release build: check the repo out in the arm64, mac-arm64 and win64 jobs.
The binary pre-check `bash releases/require-binaries.sh`, wired into the build
jobs, needs the repo on disk, but three jobs did not have it there:

- build-arm64 and build-mac-arm64 download a prebuilt bundle artifact and never
  checked the repo out at all, so the "Check the binaries ..." step died with
  exit 127 ("releases/require-binaries.sh: No such file or directory") before a
  bundle was ever assembled. Each now checks the tag out first, before
  download-artifact drops the bundle into the same workspace.

- build-win64 checks the repo out into src/ (for start-wekan.bat and
  snapcraft.yaml) but called the script at the workspace root. It now calls
  src/releases/require-binaries.sh, the same src/ path its start-wekan.bat copy
  already uses.

tests/releaseBuildJobsCheckout.test.cjs pins, for every build job that runs
require-binaries.sh, that the job checks the repo out where the call looks for it
(root vs src/) and that a root checkout precedes download-artifact so it cannot
wipe the downloaded bundle. It fails on all three pre-fix breakages.

Thanks to xet7.
2026-08-04 12:09:37 +03:00
Lauri Ojansivu 642e5b300b Updated ChangeLog. 2026-08-04 12:09:37 +03:00
Lauri Ojansivu c3742f2ed7 Fix LockoutBleed: user enumeration and unthrottled password brute-force.
GHSA-2g94-9x3m-hv37. Two verified weaknesses on the login path chained into
account takeover, and both are fixed here.

1. The bundled wekan-accounts-lockout brute-force protection was completely
   inert. Its two Accounts.validateLoginAttempt hooks decided whether an attempt
   failed by comparing the error's reason string ('Incorrect password' /
   'User not found'), but Meteor's accounts-base ships ambiguousErrorMessages
   defaulting to true, so _handleError rewrites every credential failure to
   "Something went wrong. Please check your credentials." before those hooks run.
   The literals never matched, both hooks returned early, the failure counter
   was never incremented, and no account ever locked. The hooks now decide from
   the attempt's structural fields (new loginFailureDecision.js): any genuine
   password failure counts, while the benign 'no-2fa-code' step (password already
   correct, second factor still to come) never counts, so 2FA users are not
   locked out of their own sign-in.

2. The login path leaked which accounts exist by timing: a real user runs bcrypt
   (~50 ms) while a missing one returns in ~2 ms. A dummy bcrypt comparison
   against a fixed cost-10 hash (loginTimingDefense.js) equalises the missing-user
   path on the DDP login method (loginTimingNormalization.js, a handler that runs
   ahead of the built-in one and never authenticates) and on the REST endpoint.

The REST POST /users/login twin also enumerated by message (a distinct
"...not found." string) and never ran the lockout hooks at all. It now fails
missing-user and wrong-password identically, runs the timing equaliser, and
throttles failed attempts per client address (loginAttemptThrottle.js), keyed
safely behind HTTP_FORWARDED_COUNT, resetting on success.

Tests: loginFailureDecision, loginTimingDefense, loginAttemptThrottle and
loginBruteForceEnumerationWiring (four new plain-Node suites).

Thanks to NinjaGPT and xet7 !

Fixes GHSA-2g94-9x3m-hv37.
2026-08-04 12:09:37 +03:00
github-actions[bot] ffb2636024 Bump versions for v10.58 2026-08-03 23:45:37 +00:00
Lauri Ojansivu 852ecd7b16 Prepare v10.58 release 2026-08-04 02:44:39 +03:00
Lauri Ojansivu 9bd8c49502 Updated ChangeLog. 2026-08-04 02:31:05 +03:00
Lauri Ojansivu 18c6dcc68f Guard the other way a workflow fails before it starts.
`uses: ./.github/workflows/X.yml` fails at STARTUP - taking the calling
workflow down with it, so nothing runs and there is no job log to read - when
X.yml does not exist, has no `workflow_call` trigger, or is handed a `with:`
key it never declared. That is the same class of failure as the job-level
`if:` reading matrix, and just as invisible: the YAML is valid, and only
GitHub's loader objects.

It is worth pinning now because release-all-missing.yml calls AppImage.yml
and Flatpak.yml here, TSC's calls five workflows, and the `only` input they
all take was added by hand to each of them. One typo in a `with:` key would
stop the whole run with "error parsing called workflow".

Parsed by indentation rather than with js-yaml, which is not a dependency of
this repository. These files are written in a fixed shape, and the two things
this needs - which workflows a job calls with which `with:` keys, and which
inputs a workflow declares under workflow_call - are read from it directly.

Verified against all three shapes by breaking each in turn:

  passes 'onlyy' which AppImage.yml does not declare
  calls missing Nope.yml
  AppImage.yml has no workflow_call

and checked across every repository first - TSC, wekan, both variants, node,
FerretDB, mongo-tools and gitea - where all reusable-workflow calls already
match.

Thanks to xet7 !
2026-08-04 02:16:27 +03:00
Lauri Ojansivu 603a31cc9b Updated ChangeLog. 2026-08-04 02:13:55 +03:00
Lauri Ojansivu 76a4e9cf5b Updated ChangeLog. 2026-08-04 02:07:50 +03:00
Lauri Ojansivu dcdda5f0f8 A job-level if: cannot see matrix. Decide it in the job env instead.
The `only` filter I added to the build workflows was written as a job-level
condition:

    if: ${{ inputs.only == '' || contains(fromJSON(inputs.only), matrix.arch) }}

GitHub refuses to load a workflow that does this:

    Invalid workflow file
    (Line: 109, Col: 9): Unrecognized named-value: 'matrix'

`matrix` is available to a job's runs-on, env, name, container, services,
continue-on-error, timeout-minutes, strategy and steps - but NOT to
`jobs.<id>.if`, which is evaluated before the matrix is expanded. It looks
entirely reasonable, which is why it was written in five workflows in one
sitting.

It is worse than a job that does not run: a workflow that will not load takes
every workflow that CALLS it with it, so TSC's release-all-missing.yml failed
at startup with "error parsing called workflow" and built nothing at all.

The decision moves to the job's `env:`, which CAN see matrix, and every step
asks `if: ${{ env.BUILD_THIS == 'true' }}`. Steps that already had a
condition keep it, ANDed inside parentheses - matrix.emulated,
matrix.mode == 'windows' and the rest are unchanged.

Fixed in AppImage.yml and Flatpak.yml here, and in the same commit's worth of
changes in TSC (Deb, AppImage, Flatpak, Windows, Mac), wekan/node,
wekan-ondra and wekan-gantt-gpl. Twelve files.

tests/workflowExpressions.test.cjs is the guard, and it exists because a YAML
parser is perfectly happy with every one of these: the file is valid YAML and
only GitHub's expression evaluator rejects it, when the workflow is
dispatched. It pins that no job-level `if:` reads matrix or steps, that every
${{ }} is closed, and that a workflow declaring an `only` input actually
consults it - a filter that is declared and ignored would quietly build
everything, which is the failure that looks like success.

Verified by putting the bad line back: the guard fails with
"job-level if: cannot see matrix" and names AppImage.yml:107.

The closed-brace check counts differently than it first did, because three
real lines broke it: these workflows run docker with Go templates
(--format '{{range .Manifest.Manifests}}...'), which are full of `}}` that
close nothing of GitHub's. It strips complete expressions and looks for a
leftover opener instead of counting braces.

Thanks to xet7 !
2026-08-04 02:07:50 +03:00
Lauri Ojansivu c831a423ed Merge pull request #6569 from wekan/dependabot/npm_and_yarn/fast-uri-3.1.5
Bump fast-uri from 3.1.4 to 3.1.5
2026-08-04 02:07:30 +03:00
Lauri Ojansivu 72237e5db6 Merge pull request #6568 from wekan/dependabot/npm_and_yarn/postcss-8.5.25
Bump postcss from 8.5.22 to 8.5.25
2026-08-04 02:07:16 +03:00
Lauri Ojansivu 38b299b17f Merge pull request #6567 from wekan/dependabot/npm_and_yarn/socket.io-parser-4.2.7
Bump socket.io-parser from 4.2.6 to 4.2.7
2026-08-04 02:06:47 +03:00
Lauri Ojansivu d61e53d8a3 Merge pull request #6566 from wekan/dependabot/npm_and_yarn/brace-expansion-5.0.9
Bump brace-expansion from 5.0.8 to 5.0.9
2026-08-04 02:06:33 +03:00
Lauri Ojansivu 06236f1d97 Updated ChangeLog. 2026-08-04 01:55:14 +03:00
dependabot[bot] c560cb405e Bump fast-uri from 3.1.4 to 3.1.5
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.5)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 22:52:33 +00:00
dependabot[bot] 2dac7f4813 Bump postcss from 8.5.22 to 8.5.25
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.22 to 8.5.25.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.22...8.5.25)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 22:52:30 +00:00
dependabot[bot] 76f4c78573 Bump socket.io-parser from 4.2.6 to 4.2.7
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.6 to 4.2.7.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.6...socket.io-parser@4.2.7)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-version: 4.2.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 22:52:29 +00:00
dependabot[bot] a0209bdf96 Bump brace-expansion from 5.0.8 to 5.0.9
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.8 to 5.0.9.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.8...v5.0.9)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 22:52:27 +00:00
Lauri Ojansivu d62c4f3766 Add release-all-missing.yml: finish a release without remaking it.
release-all.yml is one run that bumps the version, tags, builds every
platform, and publishes Docker images, snaps, the website and the charts. It
is the right thing for MAKING a release and the wrong thing for FINISHING
one. When the v10.57 run ended with all six extra architectures missing -
four on a shell-quoting bug, two on a base image that is not built for their
CPU - there was no way to get those six except to run the whole thing again,
version bump and all.

This finishes a release that already exists. It never bumps a version, never
tags, and never touches Docker, snap, the website or the charts.

What makes it possible: only build-amd64 runs Meteor. Every other bundle
WeKan ships is that same bundle with its native modules rebuilt, its Node.js
swapped and its database binaries replaced. So this downloads the PUBLISHED
wekan-<v>-amd64.zip - verifying its checksum, since it is the bundle every
other architecture is cut from - and repacks it with
releases/repack-bundle-for-arch.sh, the same script release-all.yml's
build-extra-arches runs. A bundle added to a release months later is
therefore built exactly like the ones already on it.

It builds the six extra architectures and calls AppImage.yml and
Flatpak.yml, which are reusable workflows now and take the same `only`
filter.

It does NOT claim to build everything, and says which: amd64 is the Meteor
build, arm64/win64/mac-arm64 each need their own kind of runner and their own
steps, and the Sandstorm .spk is signed. When one of those is missing the run
names it and points at release-all.yml, as a warning rather than a failure.

What a complete release looks like is releases/expected-assets.sh, in one
place, with the kind of each asset saying which build makes it. Present means
the .sha256sum is there too - a bundle whose checksum upload failed is
half-published - except the .spk, which is signed rather than checksummed.

Verified against the real v10.57 release: every one of its five assets
matches a name this predicts, with nothing left unaccounted for. Three
states, through the plan's own logic:

  v10.57 as published   6 extra + 4 AppImage/Flatpak to build; amd64, arm64,
                        win64 and mac-arm64 reported as needing
                        release-all.yml (they predate the checksums, which is
                        noted in the script)
  only the 6 extra      extra=[all six], appimage=[], flatpak=[], nothing
  arches missing        manual - the case this exists for
  complete release      everything empty, nothing built

Thanks to xet7 !
2026-08-04 01:47:51 +03:00
Lauri Ojansivu 78b2f9ebcd Admin Panel reports: subscribe again once the login lands.
Opening a report BY ITS ADDRESS - /admin/problems/files typed, bookmarked or
refreshed - drew the column headers, "No results" and a "1 / 1" pager over
data that was plainly there. Reached by clicking the menu entry it worked,
so this only ever happened to the URL.

A full page load resumes the login from localStorage ASYNCHRONOUSLY, and the
route sets `problemsOpenPane` before that lands. The subscription was
therefore made with no user; the publication's isAdmin check answered
`this.ready()` with no rows; and nothing re-subscribed, because the autorun
that opens the pane did not depend on the user. The count METHOD, called
later from the same page, happily reported five - which is what made this
look like a publication bug rather than a timing one.

The autorun depends on Meteor.userId() now. openReportPane() returns early
when the pane is already open, so re-running it after the login would do
nothing at all - hence the second branch, which re-subscribes the report
that is already open now that there is a user to subscribe as.

Found by the e2e test's own diagnosis, added in the previous run for exactly
this purpose: "attachments in minimongo: 0; report_pages index: no
report-files index doc" - neither half arrived, which of the three
possibilities it lists is the one meaning the publication returned early or
never ran.

ALSO, a release-build bug of my own from earlier today:
build-extra-arches had no actions/checkout. It calls
releases/check-arch-binaries.sh and releases/record-provenance.sh and mounts
$(pwd)/releases into the container - none of which exist without one, so
every one of the six extra architectures would have stopped at "No such file
or directory". The checkout goes before the artifact download, because
actions/checkout cleans the workspace and would remove the bundle.

And releases/repack-bundle-for-arch.sh: the amd64 bundle turned into one
architecture's zip, lifted out of that job so it is one script rather than
one job's inline steps. Only build-amd64 runs Meteor; arm64, win64,
mac-arm64 and the six extra architectures are all this same bundle with its
native modules rebuilt, its Node.js swapped and its database binaries
replaced. That is what lets a release be completed one architecture at a
time without rebuilding the rest, and it had better be the same script or a
bundle added later would differ from the ones beside it.

Thanks to xet7 !
2026-08-04 01:27:49 +03:00
Lauri Ojansivu d80d8de1a0 Mobile All Boards: the left menu must be the width of its column.
On a phone the left menu was drawn 260px wide inside a 157.5px grid track
and lay OVER the board icons. The grid itself was right: 42% of a 375px
phone is 157.5, plus the 8px gap puts the board column at x=165.5, which is
exactly where the boards start. The menu simply did not fit in its track.

260px is --wekan-left-menu-width, the DESKTOP width the drag-grip sets -
leftMenu.js does not even offer the grip below 800px. The menu carried it on
a phone anyway, because the phone rules never said otherwise and the
`max-width: 100%` on the base rule does not do what its comment claimed. A
percentage width on a grid item does not resolve against the track, so it
capped nothing. That is not a browser quirk: Chromium, Firefox and WebKit
all drew the same 260px.

`width: auto` in the phone rules instead. A grid item with an auto width
stretches to its grid area (justify-self defaults to stretch), so it fills
the track exactly with no percentage to resolve. The base rule keeps its
max-width, which is still right where this menu is not a grid item, and its
comment now says what it does rather than what it was hoped to do.

Measured, not guessed: decoding the failing test's screenshot pixel by pixel
shows the menu background #f7f7f7 running from x=3 to x=255 with the blue
board tiles painted on top of it from x=170, and the page background #dedede
only from x=260.

Also: when the Files report draws its headers and then "No results", the
test now says which half is missing. A row needs the page rows AND the
report_pages index that names them; the server count it already checks
proves only that the query matches. Rows but no index, index but no rows,
and neither are three different bugs that all show up as "element(s) not
found". That report is still failing - the server counts 5 and the pane
renders none, with every static link between them verified correct - so the
next run should name the layer instead of another read-through.

Thanks to xet7 !
2026-08-04 00:38:34 +03:00
Lauri Ojansivu aab81e8ffd Make the DnsBleed response stub a real stream, like IncomingMessage is.
Seven of the DnsBleed mocha tests failed with "Timeout of 2000ms exceeded" -
every one of the ALLOW cases, while every block case passed, which is what
hid it.

The stub was at fault, not fetchSafe. It faked http.IncomingMessage with a
bare EventEmitter that emitted 'data' and 'end' from a process.nextTick,
into the void if nothing was listening yet. No real response does that: an
IncomingMessage is a PAUSED Readable that buffers its body until a listener
attaches or it is resumed, so reading late cannot lose data.

That only started to matter when fetchSafe was split into resolving the
response and then reading it, which the redirect handling needs - it has to
see the status before deciding whether to read a body at all. The nextTick
queue drains BEFORE promise microtasks, so the fake had already fired 'end'
by the time the awaited continuation attached its listeners.

Verified rather than assumed, both halves:

  - against a real server: a request whose 'data' listener is attached two
    nextTicks, a setImmediate and 20ms after the response callback still
    receives the whole body.
  - against the real fetchSafe, driven by each stub in turn: the old
    EventEmitter stub times out after 2s; the Readable-backed one returns
    status=200, the body, the pinned IP 93.184.216.34 and Host
    example.com.

The security assertions still hold through the new stub, checked the same
way: a 302 with the default maxRedirects:0 is refused, a host resolving to
127.0.0.1 is refused with ZERO requests sent, a followed redirect pins each
hop to its own resolved IP, the abandoned hop reports destroyed=true, and
Authorization and Cookie are dropped on the cross-origin hop.

destroy()/destroyed are now the stream's own rather than a hand-rolled flag,
so "fetchSafe abandoned this body" is asserted against real stream
behaviour.

Thanks to xet7 !
2026-08-04 00:24:05 +03:00
Lauri Ojansivu f47018c455 Ignore the gitea fork checkout, in both .gitignore and .meteorignore.
wekan/gitea, forked from go-gitea/gitea, is cloned to gitea/ here: a whole
Go + web application, 1,401 directories, 6,209 files, 384 MB. Nothing in
WeKan imports it.

Left out of .meteorignore it would have gone straight back into the build
scan - about a fifth again of everything Meteor legitimately walks in this
repository, and that scan is what ran the build out of memory. Left out of
.gitignore it would fill git status.

The guard added for exactly this caught it: with the entry missing from
.meteorignore, `every git repository cloned in here is excluded from BOTH
files` fails with

  cloned repositories Meteor would still walk - add them to .meteorignore
  actual: [ 'gitea' ]

It is named in the explicit list as well as found by the walk, because the
walk can only check a clone that is present - a checkout without gitea/ must
still not lose the entry.

Thanks to xet7 !
2026-08-03 23:28:02 +03:00
Lauri Ojansivu 361e74cc5b Updated ChangeLog. 2026-08-03 23:06:15 +03:00
Lauri Ojansivu c6e3e2ef15 _build is the handoff to Meteor, not leftovers - do not ignore it.
Excluding /_build/ and /_build-local-test/ in .meteorignore was wrong and
broke the build, with an error that does not mention .meteorignore at all:

  error: Could not find mainModule for 'os' architecture:
  _build/main-prod/server-meteor.js
  Check the "meteor" section of your package.json file?

They look exactly like build output that should be ignored - they are
gitignored, and they are the first two entries of the 296 KB pattern list
that the heap snapshot turned up. They are not leftovers. rspack compiles
the app INTO _build/main-prod/ (and _build/test/ for a test run), and Meteor
then reads server-meteor.js and client-meteor.js from there as the
application's main modules: package.json names client/main.js and
server/main.js, and the rspack integration repoints mainModule at its own
output. Ignoring them hides the files Meteor is about to be handed.

They are 3 directories each, so there was nothing to win by excluding them
and a build to lose. The rest of the excludes - the Node.js fork checkout,
mongo-tools, TSC, the TSC website and the two variant WeKan checkouts - are
where the 5,860 directories were, and they stay.

The guard now asserts the OPPOSITE for these two, so the mistake cannot be
made again by anyone reading .gitignore and concluding the obvious thing.
The general "gitignored at the top level must be in .meteorignore" rule
gains them as documented exceptions: .gitignore happens to write them
unanchored today, so that rule skipped them by luck rather than by design,
and normalising .gitignore would have turned the guard itself into the thing
that breaks the build.

Thanks to xet7 !
2026-08-03 23:05:50 +03:00
Lauri Ojansivu 2be8defd28 Updated ChangeLog. 2026-08-03 22:58:53 +03:00
Lauri Ojansivu 062f9127a8 A cloned repository must be in both .gitignore and .meteorignore.
The guard added with the .meteorignore fix derived its list FROM .gitignore,
so it only caught a clone that had got half way: one added to NEITHER file
was invisible to it. That is the state every one of these arrived in, so it
is the state worth failing on.

The check walks the tree looking for a directory with a .git of its own -
existsSync, because a submodule's .git is a file, not a directory - rather
than comparing against a list, so a clone nobody thought to name is caught
too. Anything it finds must be ignored by git (asked of `git check-ignore`,
not of a hand-parsed .gitignore) and, when it is a top-level directory,
listed in .meteorignore. It does not descend into a repository it has
already found: that repository's own submodules ride along with it.

A directory with a .git of its own is another project. It is not WeKan's
source and it is not WeKan's history, so it belongs in .gitignore to keep
`git status` readable and in .meteorignore to keep the build from walking
it.

Verified by planting a directory with a .git in it: the guard fails and
names it.

Thanks to xet7 !
2026-08-03 22:58:39 +03:00
Lauri Ojansivu 4726a4c3e3 Updated ChangeLog. 2026-08-03 22:54:18 +03:00
Lauri Ojansivu cf178e5948 Stop Meteor scanning the other repositories cloned beside the app.
`meteor build` ran out of JavaScript heap, and raising --max-old-space-size
never helped because the ceiling was not the variable.

A heap snapshot taken as the build died says what it was: of a 4.1 GB heap,
980 MB was 14,267,543 IgnoreRule objects, 667 MB sliced strings, 308 MB
concatenated strings and 274 MB in 945 copies of one 296 KB pattern list -
about 2.5 GB of ignore machinery, and only 945 directories in.

Meteor builds one ignore matcher per directory it descends into, each
carrying the whole accumulated pattern list. There were 6,867 directories
under the app; roughly 1,000 of them were WeKan. The rest were repositories
cloned in beside it - the Node.js fork checkout (4,132 directories, 45,527
files, 2.3 GB), mongo-tools, TSC, the TSC website, two more WeKan checkouts
- plus Meteor's own _build/ and _build-local-test/ output, which it was
scanning as source to produce. All of them were in .gitignore, which Meteor
does not read; it reads .meteorignore, which listed only .tools/, FerretDB/
and tests/. So the cost was very nearly seven times what the snapshot
caught.

The two variant checkouts were the worst of them: wekan-ondra and
wekan-gantt-gpl contain client/, server/ and models/, and Meteor loads
server/ and client/ eagerly - a second and third copy of the whole app
pulled into the build.

Excluding all of it takes the scan back to WeKan itself.

Also: build.sh exited 0 while printing "ERROR: the WeKan build failed".
build_wekan returns 1, the menu called it bare and threw that away, so
anything driving it non-interactively saw a green run and a missing bundle.

tests/meteorignoreScanScope.test.cjs pins the excludes, that they are
anchored to the repo root rather than matching a directory of that name at
any depth, and - for the next clone dropped in here - fails on any new
top-level directory that .gitignore excludes while .meteorignore does not,
which is exactly how each of these arrived.

Thanks to xet7 !
2026-08-03 22:52:45 +03:00
Lauri Ojansivu 317b50b8a7 Read the heap snapshot: the build is recompiling an ignore list hundreds of times
The capped snapshot worked - 6 GB written at a 4096 MB heap - and
releases/analyse-heapsnapshot.py (added here) says what is in it. Of 4,120 MB:

    980 MB   14,267,543 x  object                IgnoreRule
    667 MB   21,869,662 x  sliced string
    308 MB   10,105,340 x  concatenated string
    274 MB          945 x  string   "_build/test/** _build/main-dev/** node_modules/** .babelrc ..."
    237 MB    millions  x  string   "t(?=$|\/$)", "s(?=$|\/$)", "e(?=$|\/$)" ...

Around 2.5 GB of the 4.1 GB heap is one thing: ignore-rule machinery. The
regexes are the `ignore` npm package's own rule compilation, IgnoreRule is its
rule class, and the 274 MB string is the pattern list it is compiling.

The arithmetic says what is happening. 274 MB across 945 copies is 296 KB per
copy - a pattern list of about 15,000 entries - and 14,267,543 rules across 945
constructions is 15,097 rules each. So a ~15,000-pattern ignore list is being
compiled from scratch 945 times, and the sliced and concatenated strings are the
paths it is built from. The repo has 3,030 tracked files and 104 top-level
entries, so 15,000 patterns is not "one per file": the list is accumulating
across those rebuilds.

That is the memory. It is not the minifier, not the heap ceiling, and not
anything in WeKan's own code - it is build tooling recompiling a growing ignore
list once per unit of work.

WHAT I COULD NOT DETERMINE HERE: which component does it. `ignore` is not a
WeKan dependency except through eslint, which does not run during a build, and
~/.meteor is not visible from this sandbox - so the caller is inside meteor-tool
or @meteorjs/rspack and needs a look from a shell that can read it:

    grep -rl IgnoreRule ~/.meteor/packages/meteor-tool/*/mt-os.linux.*/dev_bundle/lib/node_modules/

releases/analyse-heapsnapshot.py is kept rather than left in /tmp, because this
will be wanted again. It streams, so it answers on a machine that cannot open
the file in DevTools, and it explains in its header why it reports self size
rather than retained size.

Two bugs in my own reporting, found by reading the output it produced:

  * the failure said "allowed 15542 MB and reached 4280.0 MB" - it quoted the
    COMPUTED limit while the snapshot mode had capped the build at 4096. It
    quotes what the build was actually given now.
  * it told you to run WEKAN_BUILD_HEAP_SNAPSHOT=1 even when that is what you
    had just run. When a snapshot was requested it now points at the file and
    the command that reads it.

Heap.*.heapsnapshot is gitignored: Node writes it into the repo root because
that is the build's working directory, and it is as large as the heap.

300 node suites pass, 0 failed.
2026-08-03 22:29:56 +03:00
Lauri Ojansivu 38c5764f75 Cap the heap when taking a snapshot, so the snapshot can actually be written
The first heap-snapshot run was killed by the kernel - "Päätetty" - and left a
0-byte Heap.20260803.220733.2327163.0.001.heapsnapshot behind. The build log
ends mid-rspack with no GC dump at all, because the process never got to fail
its own way.

Writing a snapshot costs memory ON TOP of the heap being dumped. Asking a
15.5 GB heap to write a 15.5 GB snapshot, on a 30 GB machine with 12 GB already
in use, does not fit - the OOM killer arrives before the file does. So the flag
as written could not do the one thing it existed for.

WEKAN_BUILD_HEAP_SNAPSHOT=1 now caps the heap at 4096 MB rather than using the
computed limit. The build fails sooner, which is the point: a snapshot is not
more useful for being bigger. What is wanted is WHAT is retaining memory, and
whatever grows to 15 GB is already the largest thing on the heap at 4 GB - it
just gets there sooner and writes a file that fits in RAM and on disk.
WEKAN_BUILD_HEAP_SNAPSHOT_MB overrides it if 4096 turns out to be too early to
be representative.

The message says all of this, including that the build WILL fail and that a
full-size snapshot was already tried and killed, so nobody repeats it.

300 node suites pass, 0 failed.
2026-08-03 22:13:01 +03:00
Lauri Ojansivu bc3029aac7 Point the heap failure at the command that diagnoses it, and log the diagnosis
The fourth run died the same way - 15526.3 MB of 15542 - and produced no more
information than the third, because the heap snapshot is opt-in and nothing told
anyone it existed. The flag was added in the last commit and the failure path
was not updated to mention it, which makes it a flag nobody will use.

The error now names the exact command:

    WEKAN_BUILD_HEAP_SNAPSHOT=1 ./build.sh

and says what it produces, where, and how to read it.

It also says plainly what NOT to try, because the obvious move is wrong and has
now been wrong twice: the limit has already been raised once, 8192 to 15542, and
the build used the extra; removing the second JS minifier moved the peak by 6 MB.
Both of those are cheap to try again and neither will work, so the message says
so rather than leaving the next person to repeat them.

And the diagnosis is appended to the build log, not only printed. The point of
that log is that "check the newest test logs" answers the question - a diagnosis
that lives only in terminal scrollback is one the next reader does not have, and
the last four runs are exactly the case: the log ends on a V8 stack trace and
the explanation was somewhere above it on a terminal since closed.

Verified by rendering the message against the real log of the run that prompted
it: it reads back 15526.3 MB of 15542.

300 node suites pass, 0 failed.
2026-08-03 21:57:45 +03:00
Lauri Ojansivu 6ba8d647fb Changelog Upcoming: record the heap-snapshot option and the reverted minifier.
Thanks to xet7 !
2026-08-03 21:23:35 +03:00
Lauri Ojansivu 03b677f285 Put standard-minifier-js back, and let a build leave a heap snapshot
The theory was that rspack minifies for production itself and standard-minifier-js
was running Meteor's minifier over that same output a second time, in exactly
the phase where `meteor build` runs out of memory. The run settles it:

  with the minifier      peak 15526.6 MB   died after both rspack compiles
  without it             peak 15520.4 MB   died after both rspack compiles

Six megabytes apart, which is noise, and it died in the same place. The minifier
was not the consumer, so it goes back. An unverified change to the release
artifact that demonstrably fixes nothing does not belong in the tree - and
because the build never completes, there was no way to confirm the resulting
bundle was still correctly minified either. Both .meteor/packages and
.meteor/versions are restored exactly as they were, byte for byte.

Three guesses is enough. build.sh takes WEKAN_BUILD_HEAP_SNAPSHOT=1 now, which
adds Node's --heapsnapshot-near-heap-limit=1 to the build so it writes a heap
snapshot just before it would die, rather than only dying. Off by default,
because the snapshot is about as large as the heap limit; when it is on the
build prints where the file lands and how to read it - Chrome DevTools, Memory,
Load, sorted by retained size.

That turns the next run into evidence instead of a fourth theory. Whatever is
retaining 15 GB in Meteor's bundle-writing phase will be at the top of that
list.

300 node suites pass, 0 failed.
2026-08-03 21:23:20 +03:00
Lauri Ojansivu cea2ff6f7d Drop standard-minifier-js: rspack already minifies, and Meteor was doing it again
Two builds died in the same place - after both rspack compiles report done, in
the phase where Meteor writes the production bundle - the second with twice the
heap of the first:

  => Compiled Rspack client app:      done
  => Compiled Rspack server app:      done
  <--- Last few GCs --->              8146 MB of 8192, then 15526 MB of 15542

So it was not a ceiling set too low. rspack is the bundler here and it minifies
for production itself - the asset-size warnings a build prints are about
rspack's own minified output, 4.797 MiB of client-rspack.js and its chunks.
standard-minifier-js then ran Meteor's minifier plugin over that same output a
second time, in exactly the phase that runs out of memory.

.meteor/packages says so in a comment rather than leaving the removal to look
like an accident, including what to check first if this turns out to be wrong: a
bundle that is broken rather than merely unminified twice means put it back.
The stale pin is dropped from .meteor/versions so the two files agree;
minifier-js stays, because it is a library other packages depend on rather than
the plugin that was running.

Four guards failed on this and on the last few commits, and all four were
pinning something that deliberately changed. Each keeps its assertion and gains
the reason:

  changelogFormat      - three Upcoming summaries of mine had no commit link,
                         which every summary in a release must have, and the
                         section was not grouped by area. That one was a real
                         defect in the CHANGELOG, not in the guard: the entries
                         are linked and grouped now, under "The extra-
                         architecture bundles", "Which Node.js a bundle carries"
                         and "Running the tests locally".
  buildScriptParity    - looked for `rm -rf .build` in the first 400 characters
                         of build_wekan. That window was an accident of how long
                         the comment above it happened to be, and it broke when
                         build_wekan gained an explanation of where its log
                         goes. It searches the whole function. It also needed
                         the five release-workflow helpers exempted from "every
                         script is a menu entry": they are called by a build job
                         with its matrix and environment, and would have nothing
                         to do on a developer's machine.
  cpuExecWiring        - looked for the qemu handling inline in release-all.yml.
                         It moved into releases/install-node-for-arch.sh, which
                         is the fix for the apostrophe-in-a-single-quoted-string
                         bug, so the guard looks there and also pins that the
                         workflow runs the file.
  dbConformanceWiring  - pinned ../log literally in three places. The root is
                         WEKAN_LOG_ROOT now, because ../log is not writable in a
                         Flatpak sandbox; what is pinned is that the same
                         question is asked and that WEKAN_LOGDIR still wins.

300 node suites pass, 0 failed.

NOT verified: the build itself - Meteor is not installable in this sandbox, so
whether dropping the minifier gets past the OOM is the next run's answer, and
whether the resulting bundle is correct is the thing to look at if it does.
2026-08-03 21:08:32 +03:00
Lauri Ojansivu c0d9581df4 Say when a build died of heap exhaustion, and how much it had
The build failed again, and the log now says why on its own - but its last word
was a V8 stack trace, which reads as a crash rather than as the resource limit
it is. Running out of heap and failing to compile look identical at the level
build_wekan reports at, and they have nothing in common.

When the build log contains "JavaScript heap out of memory", the error now says
so, with the limit the build was given and the peak it actually reached, pulled
out of the last Mark-Compact line:

  The build ran out of JavaScript heap. It was allowed 15542 MB and reached
  15526.6 MB.

and points at the last line before the GC dump, which is the stage it got to.

It also says what NOT to do. Raising the number again is the obvious move and is
wrong once the figure is already large: this build used 15.5 GB, and the run
before it died the same way with half as much, so the ceiling is not what is
short. That belongs in the message rather than in somebody's memory.

Verified against the real log of the run that prompted it: the extraction finds
15526.6 MB from the final Mark-Compact line, and the sentence renders.
2026-08-03 20:58:46 +03:00
Lauri Ojansivu 581475a0d5 Work the Node heap limit out from the machine instead of fixing it at 8 GB
The build log added last commit earned its keep on the first run: the WeKan
stage failed again, and this time ../log/<datetime>/wekan-build.log says why,
on its last lines.

  FATAL ERROR: Ineffective mark-compacts near heap limit
  Allocation failed - JavaScript heap out of memory

at 8146 MB of an 8192 MB limit, on a machine with 30 GiB of RAM. The build had
not run out of memory. It had run out of the ceiling this script gave it -
--max-old-space-size=8192, hard-coded at the top with a comment saying to lower
it if your machine has less RAM, which is only half the problem: a constant is
simultaneously too small on a large machine and too large on a small one, and
nobody edits it until a build dies.

It is half of total RAM now, clamped to [4096, 16384]:

  8 GiB  ->  4096   (the floor - a small machine is not handed something unusable)
  16 GiB ->  8192   (exactly the old constant, so nothing changes there)
  30 GiB -> 15542   (this machine, nearly double what just failed)
  64 GiB -> 16384   (the ceiling - past this, a heap that big means something is
                     wrong rather than something is big)

Half is the share that leaves the rest of the machine usable while a build runs.
Total RAM comes from /proc/meminfo, or hw.memsize on macOS, and falls back to
assuming 16 GiB if neither answers. The chosen size is printed at startup so the
log says what the build was given, and exporting TOOL_NODE_FLAGS or NODE_OPTIONS
yourself still wins.

Computed unconditionally rather than only when neither variable is set, which
was the first version of this and was wrong: with only ONE of them exported, the
other would have been handed a bare "--max-old-space-size=".

The dev server on :3000 had the same 8192 written into its command line and now
uses the same figure, so the two cannot drift.

Verified by running the block with /proc/meminfo stubbed at 8, 16, 30 and 64
GiB, giving the four values above; and with TOOL_NODE_FLAGS exported alone, which
keeps the exported value and still gives NODE_OPTIONS a real one.
2026-08-03 20:24:54 +03:00
Lauri Ojansivu 76f278871c Put the logs somewhere writable, so a run in a sandbox keeps them
Every log build.sh writes goes into a log/<datetime>/ directory, and the root of
those was hard-coded to ../log - one level up from the repo. That is the right
default and stays the default: a test run then does not appear in git status,
and the docs and the release process read it there.

But the parent of the repo is not always there to write into. A Flatpak sandbox
shares only the repository directory, so `..` is missing or read-only and
`mkdir -p ../log` fails - after which every redirection in the script either
failed outright or quietly dropped its log into the repo root. That is the worst
of both: the run looks like it logged something, and git status fills up with
stray files that are not part of the tree.

WEKAN_LOG_ROOT is resolved once at startup and used everywhere the path was
built by hand: ../log when the parent is writable, ./log inside the repo when it
is not, and whatever you set if you set it. The shape is log/<datetime>/ either
way, so nothing that reads these logs has to know which happened, and the chosen
path is printed when a run starts - including a line saying why, when it is the
fallback.

Everywhere means everywhere: the whole-run directory, one_log for the individual
test options, the Playwright per-browser runs, the EVERYTHING orchestrator, and
the wekan-log.log the whole session is teed into. releases/db-conformance.sh
makes the same choice when it is run on its own rather than under build.sh, and
the thirteen menu entries that told the reader "logs in ../log/<datetime>/" now
say "log/<datetime>/" - those strings are also the case labels the menu
dispatches on, so both copies had to move together.

/log/ is gitignored, for the case it lands inside the repo.

Verified both branches by running the resolver in a temporary tree: with a
writable parent it chooses ../log and creates it; with the parent chmod'ed
read-only it prints the note, chooses ./log inside the repo, creates that, and
does NOT create ../log. build.sh, db-conformance.sh and run-everything.sh all
pass bash -n.
2026-08-03 20:03:16 +03:00
Lauri Ojansivu 4b77eb655f Log the WeKan build, so a run that fails in it leaves something to read
The newest test run ended

  FAIL   WeKan tests (sequential)
  PASS   Database conformance (all databases for this CPU)
  PASS   FerretDB tests (unit, vet, integration)

and ../log/2026-08-03_19-32-26/ held twelve conformance and FerretDB logs and
NOTHING about WeKan. Not one line about why WeKan never got as far as running a
test. The build writes to the terminal only, so the one question that run raised
was the one question its logs could not answer - and "check the newest test
logs" is how these runs are meant to be read.

What can be said from the tree it left: node_modules is 654 MB, so
`meteor npm install` finished; .meteor/local is 43 MB where a completed build is
gigabytes, and .build does not exist at all, so `meteor build .build --directory`
started and died partway. Why, nothing recorded. Meteor is not installable in
the sandbox this is being fixed from, so it cannot be reproduced here either.

build_wekan tees into wekan-build.log in the run's own log directory now, and
prints the path before it starts. An interactive build still scrolls past
exactly as it did.

Two more things found while in there.

build_wekan did not check whether the build worked. `meteor build` failing left
the function returning success, and the caller learned about it only afterwards
by noticing .build/bundle was missing - which is how this failure was reported,
one level away from the thing that failed. It checks the exit status and the
bundle, and says which log to read.

run_all_tests minted its own ../log/<datetime>/ even when a larger run had
already set WEKAN_LOGDIR. That is exactly the split-across-two-directories the
per-run directory exists to prevent; it landed in the same second as the outer
one this time, so nothing showed, and on a slower machine half a run's logs
would have gone somewhere else. It uses the outer directory when there is one
and exports its own when there is not, so every job of a run agrees on it
instead of each calling date() again.

Verified: build.sh passes bash -n, and the tee construct was exercised on its
own - a failing command inside the group produces exit 1 from the function, the
ERROR path, and a complete log file, rather than tee's own zero.
2026-08-03 19:48:33 +03:00
Lauri Ojansivu c429ea71d9 Checksum every bundle, and put the binaries' provenance atop the release notes
Two things a WeKan release did not say, and now does.

WHAT IT SHIPS. Each bundle zip now has a wekan-<version>-<arch>.zip.sha256sum
beside it on the release, so a download can be checked. The bundles were the one
thing in this chain with no checksum at all - the binaries inside them are
verified where their sources publish one, and the zip carrying them was not.

WHERE ITS BINARIES CAME FROM. A bundle is assembled out of a Node.js, a
FerretDB and the MongoDB tools, and WHICH source serves each varies per release
and per CPU: nodejs.org builds some architectures, unofficial-builds others, the
wekan/node fork the ones neither of them does, and not all of them publish a
checksum. None of that was visible anywhere except a build log that expires, so
"where did the node in the arm64 bundle come from, and was it checked" had no
answer.

Each build job records a row per binary - bundle, binary, source, version,
whether it was checked, the SHA256 - and the release notes open with the table.
A row saying "no checksum published" is not a failed check but a source that
publishes nothing to check against, and the table says so, because those are the
ones worth fixing at the source rather than mistaking for failures.

The table is written twice, and that is deliberate. The release is CREATED
before win64, mac-arm64 and the extra architectures are built - those jobs need
a release to upload their bundles to - so the table it is created with can only
cover amd64 and arm64. A new release-notes job runs after all of them and
rewrites the notes with every bundle in. It is always(), so a platform that
failed does not cost the release the table for the platforms that succeeded.

FerretDB is now verified too, where it can be: wekan/FerretDB publishes a
.sha256sum beside each binary as of the commit in that repository alongside
this one, and the extra-arches job checks the download against it. An older
release without one is recorded as unverified rather than failing the build -
the binary IS available, which is the test that matters.

Nothing new fails a build. A missing checksum is a warning, a missing MongoDB
tool is a warning; only a binary that does not exist at all stops the run, which
is what it did before.

Verified: every script parses under bash -n; release-all.yml is valid YAML with
its nineteen jobs and the new one wired to all five build jobs; and the table
renders correctly from real rows - three of them, two sources, one with a
checksum and one without - producing the markdown checked by eye.
2026-08-03 19:26:24 +03:00
Lauri Ojansivu 22183ef0d4 Changelog Upcoming: record the verifiability order and the checksum check.
Replaces the fork-first entry, which this supersedes: the order is official,
then unofficial, then the fork, by how verifiable each build is - and the
checksum is now actually checked, with a retry before it is fatal.

Thanks to xet7 !
2026-08-03 19:13:28 +03:00
Lauri Ojansivu 1a263e9e21 Prefer the verifiable Node.js builds, and actually verify them
The three sources do not offer the same assurances, and this was checked rather
than assumed:

  nodejs.org         SHASUMS256.txt, and SHASUMS256.txt.sig / .asc signed with
                     the Node.js release keys
  unofficial-builds  SHASUMS256.txt, no signature
  wekan/node fork    neither, until the commit in that repo alongside this one

So the order goes back to official, then unofficial, then the fork - descending
verifiability, with the fork as the backstop for what the other two do not
build. A binary somebody else signed is worth more than one this project built,
and the fork's own fixes are build-configuration fixes upstream's builds do not
need: they are what makes the CPUs upstream does NOT build compile at all.

Preferring a source because it publishes a checksum and then not checking it
would be preferring it for nothing, so the checksum is now looked up in the
preflight - from the source's own SHASUMS256.txt, or from the fork's per-binary
.sha256sum - and the download step refuses a file that does not match it.

A mismatch is retried before it is fatal. The overwhelmingly likely cause is a
truncated or corrupted transfer, a CDN edge that served a partial file or a
connection cut mid-stream, and that is fixed by asking again. What retrying must
not do is give up quietly: after three attempts the build stops, because at that
point the file being served is not the file that was published.

Where no checksum exists the log says so in as many words, rather than leaving
the reader to assume a check was made. FerretDB and the MongoDB tools are in
that position today - neither wekan/FerretDB nor wekan/mongo-tools publishes
checksums for its release binaries, which is the next gap to close.

Verified by exercising download_and_verify directly: a matching checksum
returns 0, a missing one returns 0 with the file named as unverified, and a
mismatching one retries three times and exits 1. And by resolving the real
checksums for every architecture against the live releases - s390x and ppc64le
from nodejs.org, riscv64 and loong64 from unofficial-builds - then downloading
the riscv64 tarball and confirming its hash matches the published one.
2026-08-03 19:13:03 +03:00
Lauri Ojansivu 05f99fe4e4 Changelog Upcoming: record the fork-first Node.js source.
Thanks to xet7 !
2026-08-03 19:04:20 +03:00
Lauri Ojansivu 77d38e099f Take Node.js from the wekan/node fork first, on every architecture
The fork is not "the CPUs nobody else builds" any more - it builds all thirteen
platforms WeKan ships a bundle for, and it carries fixes upstream does not: the
ICU genccode architecture name, the x86 /SAFESEH opt-out, the zlib SSE2 and NEON
flags, the V8 template disambiguator. Those are build-configuration fixes that
any platform benefits from, and a set of bundles should not be half built on one
Node.js and half on another depending on which CPU it is for.

So the probe order is fork, then nodejs.org, then unofficial-builds - the last
two now being fallbacks for the case the fork has not published a version yet,
rather than the other way round.

The rest of the rule is unchanged and still matters: the newest Node.js that
exists FOR THIS CPU, walking versions from newest down and asking all three
sources at each one. Preferring the fork does not mean pinning to whatever the
fork last built.

Checked against the live releases, which show the transition in progress - the
fork has v24.18.1 for the architectures it already built and has not yet run for
v24.19.0:

  i386     v24.18.1  fork
  loong64  v24.18.1  fork        was coming from unofficial-builds before this
  s390x    v24.19.0  official    fork has not built v24.19.0 yet
  ppc64le  v24.19.0  official    likewise
  riscv64  v24.18.1  unofficial  likewise
  armhf    -         none        still nothing anywhere

Once node.yml has run for v24.19.0 with its new matrix, every one of these
resolves to the fork; until then the fallback chain keeps the bundles building
instead of blocking on it.
2026-08-03 19:04:05 +03:00
Lauri Ojansivu deff51f20c Changelog Upcoming: record the newest-per-CPU Node.js rule.
Splits the version-fallback out of the binary-check entry, because it is its own
change and its own reason: the fallback now applies to all three sources rather
than only the fork, which is what makes riscv64 build again.

Thanks to xet7 !
2026-08-03 18:54:41 +03:00
Lauri Ojansivu 60c9e55d2f Use the newest Node.js that exists FOR THIS CPU, not the newest that exists
The version fallback added with the preflight check was only wired to the
wekan/node fork. nodejs.org and unofficial-builds were still pinned to the exact
newest release, so a CPU that HAD a perfectly good build, one patch version
back, was reported as having none at all.

riscv64 was exactly that. unofficial-builds publishes riscv64 up to v24.18.1 and
has not got to v24.19.0; the check looked only at v24.19.0, found 404s, and
stopped the job saying "no Node.js for riscv64 exists anywhere" - while
node-v24.18.1-linux-riscv64.tar.xz was sitting there.

The rule is now "the newest Node.js that exists for this CPU". It walks the 24.x
releases from newest down and asks all three sources at each one - official,
then unofficial, then the fork - and the first hit wins, which is by
construction the newest build that exists anywhere for that architecture. Those
are the same thing on amd64 and arm64 and regularly are not anywhere else:
nodejs.org builds a handful of architectures, unofficial-builds adds a few, the
fork builds the rest, each on its own schedule, so the further a CPU is off the
beaten path the further behind its newest build tends to be.

When the answer is behind the newest, the log says which version it got, from
where, which version it wanted, and what to build to bring it in line. It is a
warning, not an error: the alternative is no bundle at all for that CPU.

The walk stops after 12 releases. A CPU whose newest build is a dozen releases
old is not slightly behind, it is unmaintained, and saying so is more use than
quietly shipping something from last year.

Re-ran the check against the live releases for all six architectures:

  s390x    v24.19.0  official     newest
  ppc64le  v24.19.0  official     newest
  riscv64  v24.18.1  unofficial   was failing before this change
  i386     v24.18.1  fork
  loong64  v24.18.1  unofficial   the fork has it too; unofficial is tried first
  armhf    -         none         exit 1, naming node-armhf

and checked armhf by hand across six releases back to v24.10.0 at all three
sources, to be sure "nothing anywhere" is the truth about armhf and not a bug in
the walk. It is: every one is a 404.

Four of the six now come out newer or working where they did not before, and the
one that still fails fails for a real reason.
2026-08-03 18:54:21 +03:00
Lauri Ojansivu cbf5228cd2 Changelog Upcoming: record the release-build fixes.
The six extra-architecture jobs that all failed in the v10.57 run, grouped under
the area they belong to: the shell quoting bug that emptied the CPU name out of
the Node.js URL, the base image that is not built for i386 or loong64, and the
checks that now run before any of it and name the missing file.

Thanks to xet7 !
2026-08-03 18:41:49 +03:00
Lauri Ojansivu 92198b0671 Release build: fix all six extra-architecture jobs, and check binaries first
Every one of the six extra-architecture jobs failed in the v10.57 run, and none
of the six failures was about WeKan.

FOUR OF THEM - ppc64le, riscv64, s390x, armhf - died on the same 404, at a URL
with no architecture in it at all:

  curl: (22) The requested URL returned error: 404
  No Node.js for  at official (https://nodejs.org/dist/v24.18.1/node-v24.18.1-linux-.tar.xz)

The container script was passed as `bash -c '...'` and contained apostrophes, in
comments like "# Where this CPU\'s Node.js comes from". A single-quoted shell
string cannot hold an apostrophe - \' does not escape it, it ENDS the string -
so the script was chopped into words at the first one, and ${NODE_ARCH} landed
in a region the RUNNER's shell expanded rather than the container's. The runner
has no NODE_ARCH, so it expanded to nothing. Reproduced here before changing
anything: that construct really does deliver only the fragment up to the
apostrophe and leave the rest as separate arguments.

The script is a file now, releases/install-node-for-arch.sh, mounted into the
container. A file has no quoting layer to get wrong, and `bash -n` can check it.

THE OTHER TWO - i386 and loong64 - died earlier still, on

  docker: no matching manifest for linux/386 in the manifest list entries

ubuntu:26.04 publishes amd64, arm/v7, arm64, ppc64le, riscv64 and s390x. Not
386. Not loong64. The base image is named per architecture in the matrix now and
is debian:trixie, which publishes 386 as well - checked against the registry
rather than assumed.

loong64 still cannot be built, and that is now said rather than discovered: no
image on Docker Hub publishes linux/loong64 at any tag, so there is no loong64
userland to rebuild the native modules in. node-loong64 and ferretdb-loong64
both exist; the container does not. It stays in the matrix so it stays visible.

AND UNDERNEATH BOTH: nothing checked that the binaries these bundles are
assembled from had been published at all. A bundle takes FerretDB from
wekan/FerretDB, the MongoDB tools from wekan/mongo-tools, and on the CPUs nobody
else builds for, Node.js from wekan/node - and any of those can be absent
because a build has not finished. releases/check-arch-binaries.sh now runs
before anything is built and checks the base image, the Node.js, FerretDB and
the tools; releases/require-binaries.sh does the same for the amd64, arm64,
win64 and mac-arm64 bundles. Each missing file gets a line naming it and the
repository that should publish it, and the job stops there. The MongoDB tools
stay a warning: FerretDB is the database and the launcher does not need them.

It also handles wekan/node running behind. The fork builds the architectures
nobody else does, one release at a time, so the newest Node.js is regularly a
version it has not reached - today nodejs.org is at v24.19.0 and the fork at
v24.18.1. Asking for the exact newest version would fail for exactly the CPUs
the fork exists to serve, so the check falls back to the fork's newest release
that HAS that CPU and says out loud that the bundle is a patch version behind.

Verified by running the check against the real releases, for all six:

  s390x    Node.js v24.19.0, official        exit 0
  ppc64le  Node.js v24.19.0, official        exit 0
  i386     Node.js v24.18.1, wekan/node      exit 0
  loong64  Node.js v24.18.1, wekan/node      exit 0
  armhf    nothing anywhere                  exit 1, naming node-armhf
  riscv64  nothing anywhere                  exit 1, naming node-riscv64

armhf and riscv64 are the two the fork has not finished; the log now says which
file to publish instead of a bare 404. i386 and loong64 resolve because
node-i386 and node-loong64 were added to the fork's v24.18.1 release.

NOT verified: the builds themselves, which need the runners. What was checked:
that release-all.yml is still valid YAML and every job has its check step; that
the three new scripts pass `bash -n`; that the quoting bug reproduces and the
file form does not; that debian:trixie really publishes linux/386 and
ubuntu:26.04 really does not, from the Docker registry; and the six preflight
results above, run against the live releases.
2026-08-03 18:41:10 +03:00
Lauri Ojansivu 73e06940c3 AppImage and Flatpak workflows, built from the bundle a release already carries.
Two more ways to install WeKan, and neither of them rebuilds it.

release-all.yml already produces a bundle that runs offline with nothing
installed: the Meteor server, the bundled Node.js the release was built
against, the wekan/FerretDB v1 fork with its embedded pure-Go SQLite backend,
the wekan/mongo-tools binaries, the cpu-exec/qemu-user shim from #6458 and
start-wekan.sh to bring them up. That bundle is already relocatable and already
self-contained - it is the offline zip people download today. So both workflows
take wekan-<version>-<arch>.zip off the release and wrap it, which means the
AppImage and the Flatpak are byte-for-byte the same server, Node and FerretDB
as the zip beside them, rather than second and third builds that could drift
from it and from each other.

For the person installing, both mean the same thing: one file, no MongoDB to
install, no Node to install, no database server to run. FerretDB v1 keeps
everything in SQLite files.

AppImage.yml wraps the bundle in an AppDir with WeKan's own icon and an AppRun
that hands over to start-wekan.sh, so every variable that launcher documents -
WRITABLE_PATH, PORT, ROOT_URL, MONGO_URL, WEKAN_DB - still works. It defaults
WRITABLE_PATH to ~/.local/share/wekan, because an AppImage is read-only and may
be sitting on a USB stick, so the data cannot live beside it. After packing it
STARTS the AppImage and waits for WeKan to answer on its port before uploading
anything: an AppImage that packs is not an AppImage that runs, and a broken
bundle should be caught here rather than by whoever downloads it.

Flatpak.yml uses `flatpak build` rather than flatpak-builder, because the
bundle is the build and there is nothing to compile. Its sandbox gets
--share=network and --persist=.wekan and nothing else: WeKan is a server
reached in a browser, so it needs the network and a place for its data, not
X11, audio or the user's files. It produces both a single-file bundle per
architecture and a multi-architecture repository - the bundle is what you
install by hand, the repository is what makes `flatpak update` work - and the
repository is static files, so a website can serve it with no server to run.

The Flatpak repository is unsigned. Signing needs a GPG key, which is a secret
rather than something a workflow can invent, so the README written into the
repository sets out the four steps to sign it when a key exists: make the key,
put the private half in GPG_PRIVATE_KEY, add --gpg-sign to the export and the
update-repo, and publish the public half beside the .flatpakrepo.

Both cover x86_64 and aarch64, which is where the two constraints meet: AppImage
and the freedesktop runtime have those architectures, and WeKan publishes
amd64 and arm64 Linux bundles. Each builds on a runner of its own CPU, because
appimagetool packs for the architecture it runs on. If a bundle for another
architecture appears on a release, adding a matrix line wraps it the same way.

NOT verified: neither workflow has been run - that needs the runners. What was
checked: that both are valid YAML and their matrices resolve; that the icon
they use exists in public/; that the four files they assert on
(bundle/main.js, bundle/node, bundle/ferretdb, bundle/start-wekan.sh) are what
release-all.yml actually puts in the bundle; and that the newest release really
does carry wekan-10.57-amd64.zip and wekan-10.57-arm64.zip under those names.
2026-08-03 18:19:04 +03:00
Lauri Ojansivu 33e193f14b Changelog: the three places its own shape was not followed.
CLAUDE.md sets the shape of a `<details>` block precisely - `<details>` and
`</details>` each on their own line, the whole `<summary>` on ONE line, a blank
line under it, the body, a blank line, the close - and three blocks were missing
the blank line before `</details>`: the Version block in `# Platforms`, the
`Carried to a future release.` block at the top of `# TODO Later`, and one entry
in v10.55.

They render either way, which is why they survived; the rule is there so every
block looks the same in the file, and a block that does not is the one somebody
copies next.

Also rewrapped the one line in the `# TODO Later` intro that had grown to 152
characters, against the 80 the file is wrapped at. It carries no link, so
nothing stopped it being wrapped.

Thanks to xet7 !
2026-08-03 17:58:34 +03:00
Lauri Ojansivu 2e51e7ceec gitignore: secretchronicles.github.io, cloned under this working copy.
The TSC website (Secretchronicles/secretchronicles.github.io), cloned inside
this repo to be worked on beside the game itself. Like the others it is a whole
git repository of its own, so git would not commit it into this one, but it
showed up as untracked noise in every status.

Thanks to xet7 !
2026-08-03 17:53:50 +03:00
Lauri Ojansivu b742621225 gitignore: TSC, cloned under this working copy.
git@github.com:Secretchronicles/TSC, cloned inside this repo to be worked on.
It is a whole git repository of its own, so git would not commit it into this
one, but it showed up as untracked noise in every status.

Listed beside the other local-only working copies - node, FerretDB, sandstorm,
mongo-tools, mongosh and the two wekan variants - with the same one-line note
saying what it is a clone of.

Thanks to xet7 !
2026-08-03 17:53:50 +03:00
github-actions[bot] 147f056e97 Bump versions for v10.57 2026-08-03 12:41:32 +00:00
Lauri Ojansivu 037a9d8fd3 Prepare v10.57 release 2026-08-03 15:40:26 +03:00
Lauri Ojansivu 965b506a71 Changelog Upcoming: record FollowBleed and TransitBleed.
Two reported bypasses of the SSRF guard, so the section now leads with the
CRITICAL SECURITY ISSUES header and the documentation reorganisation becomes
"and reorganises the documentation:". Both entries are grouped under "The SSRF
guard - what it checks, and what it was deciding from", because that is the one
thing both reports found from two directions.

The "In short" paragraph is rewritten around them: what each bypass was, in one
clause each, then the documentation work accounted for in the closing sentence.

Also records the Sandstorm bridge guard reading the page at its new path, which
the docs reorganisation left pointing at a directory.

Thanks to RandomGenerator, tonghuaroot and xet7 !
2026-08-03 15:34:18 +03:00
Lauri Ojansivu 4c183b4d59 GHSA-j9p2-jm73-p549 and GHSA-c5xr-mg26-vq5w: two ways past the SSRF guard.
Two separately reported bypasses of the same defence, fixed together because
they are the same mistake in the two halves of it: the guard decided from
something it did not control.

FollowBleed (GHSA-j9p2-jm73-p549, reported by RandomGenerator against v10.53) -
the live Trello import validated the attachment URL with validateAttachmentUrl(),
which is the fix that closed LiveBleed / CVE-2026-30844, and then downloaded it
with the platform fetch(), which FOLLOWS REDIRECTS. So the guard only ever saw
the request, and the target gets to answer:

  1. the attacker puts http://<public-host>/attachment.txt on a Trello card
  2. validateAttachmentUrl() resolves it, sees a public IP, allows it
  3. that host answers  302 Location: http://127.0.0.1:18080/secret
  4. fetch() follows, and the loopback body is stored as the imported
     attachment, readable back through WeKan

Non-blind SSRF against loopback services, internal admin panels, cloud metadata
and anything else reachable from the container - the exact thing the validation
was added to stop, reached through the response instead of the request.

A guard on the URL alone cannot hold, so fetchSafe() now guards EVERY HOP.
`maxRedirects` (default 0) is how many redirects a caller is willing to follow:
0 keeps the old behaviour of refusing any 3xx outright, which is right for
outgoing webhooks and avatar downloads because a legitimate one never redirects.
A caller that must follow one passes a small number, and each hop goes through
the same protocol allowlist, blocked-range check and DNS pinning as the original
URL before a packet is sent to it. Credentials are dropped on a cross-origin
redirect, so following Trello's 302 to S3 cannot hand the API key and token to
whoever the redirect names.

All three live-import sinks - downloadAttachmentBase64, inlineBoardBackground,
inlineMemberAvatars - download through it. Refusing every redirect was not an
option: Trello's own attachment endpoint answers with a 302 to a signed S3 URL,
so that would have meant importing no attachments at all.

The offline importers had the same hole and were not in the report. They handed
the validated URL to Attachments.loadAsync(), and Meteor-Files downloads with
the platform fetch() too, so a pasted Trello or WeKan board export reached
127.0.0.1 by exactly the same 302. models/lib/importAttachmentDownload.js
validates, downloads through fetchSafe with every hop checked, and hands back
the bytes, which are stored with the writeAsync() both importers already use for
an attachment that arrived inline.

TransitBleed (GHSA-c5xr-mg26-vq5w, reported by tonghuaroot) - isIpBlocked() in
models/lib/attachmentUrlValidation.js is the one block-list behind BOTH the
input-time validator and the delivery-time guard, and its IPv6 half classified
an address by its spelling: startsWith('::ffff:'), startsWith('2001:db8'), and
the first hextet parsed out of the string. IPv6 has several standard ways to
write "this packet goes to an IPv4 address" and none of them looks like
`::ffff:`:

  2002:a9fe:a9fe::       6to4 (RFC 3056)     -> 169.254.169.254
  64:ff9b::c0a8:101      NAT64 (RFC 6052)    -> 192.168.1.1
  2001:0:...             Teredo (RFC 4380)   -> complement of the low 32 bits
  0:0:0:0:0:ffff:7f00:1  IPv4-mapped, spelled out -> 127.0.0.1

On a host with a 6to4 relay or a NAT64 gateway, which is ordinary in cloud and
Kubernetes networks, the packet arrives at that IPv4 address. So
http://[2002:a9fe:a9fe::]/latest/meta-data/ read cloud metadata straight through
the guard whose whole job was to stop it, and even a plain IPv4-mapped address
written out in full missed the check.

The address is EXPANDED to its 16 bytes once and every check reads those bytes,
so notation cannot change the answer, and every transition form has its embedded
IPv4 extracted and re-checked with the IPv4 rules - 6to4, NAT64 (well-known and
RFC 8215 local-use prefixes), Teredo through both its server and its obfuscated
client address, IPv4-mapped, IPv4-translated, IPv4-compatible and ISATAP under
any routing prefix, not only the link-local one. The deprecated fec0::/10
site-local range is blocked too. A transition address wrapping a PUBLIC IPv4 is
still allowed, because a guard that blocks everything gets switched off.

Tests. tests/followbleed.test.cjs replays the reported attack against a stubbed
transport - a public host that 302s to 127.0.0.1 - and asserts the second hop is
never sent, then covers redirects to a hostname resolving to a private IP, to
metadata, to a non-http scheme, relative Location, the chain limit, credential
stripping across origins, 303/307 method handling, and that a legitimate
public-to-public redirect is still followed with each hop pinned. It also pins
the sinks: the only bare fetch() left in the live import is the hardcoded
api.trello.com call, and neither offline importer uses loadAsync any more.
tests/transitbleed.test.cjs checks every transition form in both directions.
Both are in the plain-node suite; the guard's own dnsbleed suite gained the same
cases. 300 node suites pass.

Thanks to RandomGenerator and tonghuaroot for the coordinated disclosures, and
xet7 !
2026-08-03 15:30:15 +03:00
Lauri Ojansivu 6cd64284dc Sandstorm bridge guard: read the page at its new path.
The docs reorganisation (f628a698) turned
`docs/Platforms/FOSS/Container/Sandstorm` from a page into a directory, and
this test reads that page at run time to pin what it documents. So it did not
merely go stale - `fs.readFileSync` on a directory throws EISDIR and the suite
died before its assertions ran.

It reads the directory's landing page, `README.md`, which is where the
migration-bridge documentation ended up. Both assertions are unchanged and both
pass.

Thanks to xet7 !
2026-08-03 15:29:43 +03:00
Lauri Ojansivu a6104aab28 Updates 2026-08-03 13:47:50 +03:00
Lauri Ojansivu 2aa777df2c Updated ChangeLog. 2026-08-03 13:24:41 +03:00
Lauri Ojansivu aaab92b9f4 Fixed formatting 2026-08-03 13:09:07 +03:00
Lauri Ojansivu f628a69889 Reorganized docs Platforms etc.
Thanks to xet7 !
2026-08-03 13:07:12 +03:00
github-actions[bot] 3cb4cab095 Bump versions for v10.56 2026-08-02 00:22:30 +00:00
Lauri Ojansivu 52cee0c4f4 Prepare v10.56 release 2026-08-02 03:21:14 +03:00
Lauri Ojansivu 92fc66e929 Changelog: the aligned board counts, and the snap build that says why it failed
Two entries for the two commits since the last changelog update, and the bug
subsection is grouped by area now that it holds three: the snap, and All Boards.
Grouping meant rewording two summaries - an entry under a group must not repeat
the group's own name, and both began with it.

The In short paragraph follows: it named two of the three fixes and now names
all of them.

The counts entry goes under All Boards with the left-menu one; the remote-build
diagnostics are release tooling rather than a bug in WeKan itself, so they get
their own subsection after the fixes, as CLAUDE.md orders them.

Thanks to xet7 !
2026-08-02 02:54:28 +03:00
Lauri Ojansivu 852ad1714b All Boards: the board counts line up in one column
Starred, Home, Templates and Archive have labels of four different lengths, and
on a phone the count chip sat immediately after its label - so the four numbers
landed at four different x positions and the column of counts read as ragged
rather than as a column at all.

That packing was deliberate (#6523): the number beside the text rather than
across a gap, on the grounds that a landscape phone's menu is wide enough for
the gap to look like a mistake. Reversed here, because the alignment is what a
reader is actually using - the counts are compared with each other down the
column, and four x positions is what stops that.

The row keeps the space-between and the flex: 1 label the base rule gives it, at
every width, so every count sits at the end of its own row and they line up. The
phone override is gone rather than re-tuned; there is no width at which the
ragged version was wanted.

Guarded both ways in mobileAllBoardsFit.test.cjs: the row must spread its two
children apart and the label must take the slack, AND no narrower width may take
either back - a phone override is exactly how they came to be ragged.

Thanks to xet7 !
2026-08-02 02:48:55 +03:00
Lauri Ojansivu 8dc6bc7edd Snap: a remote build that never starts now says why it did not
The v10.55 riscv64 leg failed three times in sixteen minutes, and the whole of
what it printed was:

    Git operation failed with: Could not push 'HEAD' to
      git.launchpad.net/.../+git/snapcraft-wekan-<hash>
      with refspec 'HEAD:refs/heads/main'

This is a different failure from the other two arches. It is not the staging bug
- that happens minutes into a build, on the builder. Here the build never
started: remote-build uploads the source to a Launchpad git repository first,
and the upload is what failed. ppc64el and s390x pushed fine in their own runs,
with the same credentials.

Why it is still unconfirmed: snapcraft swallows git's own error, writes it to
its execution log, names that log's path in the output, and nobody reads it. So
"rejected", "timeout", "auth" and "too big" all look identical from the job log
- which is why three runs have not narrowed it down.

Two changes, both about being able to tell next time:

The failed-attempt handler prints snapcraft's own execution log, not only the
Launchpad build log. The Launchpad one does not exist when the build never
started, which is exactly this case.

And the retry clears ~/.cache/snapcraft/remote-build before waiting. snapcraft
keeps a local clone of the Launchpad repository there, and a retry that reuses a
half-pushed one repeats the same failure - three identical attempts sixteen
minutes apart is what that looks like.

The leading suspect is recorded in ../log/snap/TODO.txt rather than guessed at
here: remote-build pushes HEAD:refs/heads/main with no force, WeKan's history
was rewritten between v10.54 and v10.55 by the release rebase, and a `main` left
behind by the previous run is then no ancestor of today's HEAD. If the log the
job now prints says "rejected", deleting that scratch repository is the fix, and
the longer-term answer is for the workflow to delete it or to use a fresh one
per run.

Thanks to xet7 !
2026-08-02 02:38:57 +03:00
Lauri Ojansivu fdd84f78ba Changelog: the platforms, the snap staging fix and the phone left menu.
The three commits since v10.55, each linking the commit it describes: the
platform expansion as a new feature, and the snap that could not finish staging
on any FerretDB-only architecture and the left menu lying over the boards on a
phone as bug fixes.

One style fix with it: the dependency bullets in v10.55 wrote their link text as
[update] where v10.54 and everything before it write [Update].

The hashes were checked rather than assumed - every commit link in the new
section resolves AND is an ancestor of HEAD, which is the property that matters
after a rebase: a hash that still exists locally but has left the branch is a
404 the moment it is pushed. The rest of the file was checked the same way and
needed nothing.

Left alone: hash LENGTH. The two newest releases happen to use 9-character
prefixes, which is what a sample of them says, but the file as a whole is 2811
forty-character hashes against 399 short ones - so the newest entries are the
outliers, not the rule, and normalising the file to match a sample of itself
would have rewritten every release ever published.

Thanks to xet7 !
2026-08-02 02:08:15 +03:00
Lauri Ojansivu 12ae908ddf Release: build every Linux platform FerretDB does, with a Node.js to match.
Every non-amd64, non-arm64 platform already used FerretDB v1 - MongoDB ships no
server for any of them - but only three of them were built, because only three
had a Node.js runtime anyone published. wekan/node now builds the rest, so the
gap is gone and the release follows FerretDB's list.

New bundles: i386, armhf and loong64, beside the ppc64le, s390x and riscv64
already there. They go into the snap (i386 and armhf; loong64 is not a snap
architecture) and into the multi-arch image (linux/386 and linux/arm/v7;
buildx and the three registries do not agree on loong64 yet).

WHERE THE NODE.JS COMES FROM is now resolved at build time rather than declared:
nodejs.org, then unofficial-builds, then the wekan/node fork, in that order, and
the log says which one served. The first two ship a tarball; the fork ships the
bare binary it built, because that is the only part missing - so when the fork
serves, npm comes from the official amd64 tarball of the same version. npm is
JavaScript and runs on whatever node executes it, so an npm built for one CPU
drives a node built for another.

THREE VOCABULARIES meet in this matrix and they disagree: Node says x86 and
armv7l where Debian, snap and FerretDB say i386 and armhf, and snap says ppc64el
where everyone else says ppc64le. Each row now names all three - arch, node_arch
and ferretdb_arch - because a row that named one of them would download another
CPU's binary and nothing would notice until somebody ran it.

The MongoDB Database Tools are per-tool tolerant now: wekan/mongo-tools does not
publish every arch, and a missing mongodump is a missing convenience rather than
a broken bundle - FerretDB is the database. It removes the inherited amd64 one
instead of shipping it, because a tool for the wrong CPU is worse than no tool.

armel is the one FerretDB target left out, and deliberately: V8 has not
supported ARMv5 for many years, so there is no runtime to put in the bundle and
the fork cannot build one either.

A new guard reads the four lists that have to agree - the bundle matrix, the
snap platforms block and its arch mapping, the snap-launchpad matrix and the
Docker platform list - and checks every snap arch has a bundle behind it, every
bundle row names all three vocabularies, and buildx builds exactly the platforms
whose push is then verified. Two older guards pinned the five-platform snap list
and now pin the seven.

Written up in docs/Design/Autoupdate/Platforms.md.

Not verified by a real release run: no workflow here can be executed, and the
first run is what will say whether the loongarch64 cross toolchain is available
on the runner and whether each platform's Node.js resolves where expected.

Thanks to xet7 !
2026-08-02 02:02:17 +03:00
Lauri Ojansivu 2d17d2627a Snap: the mongodb part cannot stage bin as a symlink any more.
The s390x snap died in the STAGE step of both its Launchpad builds, right after
"Staging mongodb":

    /build/snapcraft-wekan-<id>/stage/bin: Is a directory
    IsADirectoryError: filename: '/build/.../stage/bin'

The mongodb part's stage-packages unpack an Ubuntu 24.04 merged-/usr layout,
which puts everything under usr/ and leaves `bin` in the part as a SYMLINK to
usr/bin. On amd64 and arm64 the part's build then downloads MongoDB and copies
its binaries into $SNAPCRAFT_PART_INSTALL/bin, which replaces that symlink with
a real directory. On s390x, ppc64el and riscv64 MongoDB ships no server, so the
build exits early - "No MongoDB server for ...; FerretDB-only arch" - and the
symlink survives.

mongodb is staged LAST of the parts that carry a bin/, so stage/bin is already a
real directory by then, from wekan, caddy and helpers. Copying a symlink onto an
existing directory is the IsADirectoryError, and it fails the whole snap rather
than that one part.

An override-stage turns a `bin` SYMLINK into an empty real directory before
staging, then stages normally. It tests for a symlink specifically, so the
arches where mongod really is there are untouched; it removes before mkdir,
because mkdir -p FOLLOWS a symlink and would change nothing; and an empty real
directory merges into stage/bin without changing anything else.

ppc64el is the same FerretDB-only branch and would hit this too - its own logs
never got that far, because snapcraft's monitor loop died fetching the
in-progress build log from launchpad.net with "[Errno 101] Network is
unreachable" while the build itself was still compiling, and each retry then
submitted a NEW build instead of recovering the running one. That half is
infrastructure, not this file; it is written up with the rest in
../log/snap/TODO.txt.

riscv64 takes the same branch, so this covers it as well.

Not verified against a real Launchpad builder: the fix is read off the failure,
and the s390x leg has to be re-run to confirm it now reaches "Snapped".

Thanks to xet7 !
2026-08-02 02:02:17 +03:00
Lauri Ojansivu 5b91c99b11 The left menu fits its column on a phone, and node/ is ignored.
Spec 43 (All Boards on a phone) still failed in all three browsers with the same
numbers as before: the menu 260px wide over a 157px column, lying across the
boards.

max-width: 100% was not enough, and that is the part worth knowing: a grid
item's default min-width is `auto`, which is its content's intrinsic minimum,
and a MINIMUM BEATS A MAXIMUM. The cap did nothing until the item was allowed to
shrink to it. `min-width: 0` is the same pair the right-hand column beside it
has carried all along, for the same reason and with the same comment. Nothing
inside the menu needs the intrinsic width held open - the workspace name already
ellipses through its own min-width: 0.

Two guards followed the format rather than the other way round. Both were
written against a release with forty entries and rejected v10.55 for being four
dependabot bullets and nothing else: CLAUDE.md keeps a dependency batch as plain
bullets, so a release can legitimately have no <details> at all, and padding one
bump into a block whose body repeats its summary would be the noise that rule
exists to prevent. They count entries OR bullets now.

The empty Files report (spec 41) is NOT fixed - it is made legible. The helper
that fills that table caught every exception and returned [] in silence, which
renders exactly like "there is nothing to show": headers, "No results", and
nothing anywhere recording that it threw. It logs now. The publication says one
line when it matches nothing, whatever DEBUG is set to, because a publication
answering with nothing and a publication never running look identical from the
outside - which is why three runs have not told us which this is.

And node/ - a 2.2G clone of the Node.js 24.x fork the runtime is built from -
joins the other local-only clones in .gitignore, rooted like they are.

Thanks to xet7 !
2026-08-02 02:02:17 +03:00
dependabot[bot] 459ea80d04 Bump jquery from 3.7.1 to 4.0.0
Bumps [jquery](https://github.com/jquery/jquery) from 3.7.1 to 4.0.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Changelog](https://github.com/jquery/jquery/blob/main/changelog.md)
- [Commits](https://github.com/jquery/jquery/compare/3.7.1...4.0.0)

---
updated-dependencies:
- dependency-name: jquery
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-31 02:58:41 +00:00
dependabot[bot] 7727e51296 Bump @babel/parser from 7.29.7 to 8.0.4
Bumps [@babel/parser](https://github.com/babel/babel/tree/HEAD/packages/babel-parser) from 7.29.7 to 8.0.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.4/packages/babel-parser)

---
updated-dependencies:
- dependency-name: "@babel/parser"
  dependency-version: 8.0.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-19 01:11:02 +00:00
410 changed files with 26692 additions and 3773 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
# Project-scoped Codex configuration.
#
# Claude has no non-default tool permissions or MCP servers configured for this
# repository, so there are no settings to translate here. Project instructions
# live in AGENTS.md and the nested AGENTS.md files.
+417
View File
@@ -0,0 +1,417 @@
name: AppImage
# Build WeKan as an AppImage for every CPU an AppImage runtime exists for, and
# attach them to the newest WeKan release.
#
# WHAT IS INSIDE IS WHAT WEKAN ALREADY SHIPS. release-all.yml already produces a
# bundle that runs offline with nothing installed: the Meteor server (main.js),
# the bundled Node.js the release was built against, the wekan/FerretDB v1 fork
# with its embedded pure-Go SQLite backend, the wekan/mongo-tools-patches binaries, the
# cpu-exec/qemu-user shim from #6458, and start-wekan.sh to bring them up. That
# bundle is already relocatable and already self-contained - it is the offline
# zip people download today - so this workflow does NOT rebuild WeKan. It takes
# the wekan-<version>-<arch>.zip off the release and wraps it, which means the
# AppImage is byte-for-byte the same server, Node and FerretDB as the zip beside
# it rather than a second build that might differ.
#
# So there is no MongoDB to install, no Node to install, and no database server
# to run: FerretDB v1 keeps everything in SQLite files under the AppImage's data
# directory. One download, one file, double-click.
#
# WHICH CPUs. An AppImage needs a runtime binary for its architecture, and those
# exist for x86_64, aarch64, armhf and i686. WeKan publishes Linux bundles for
# amd64 and arm64, so those are the two that can be wrapped: x86_64 from amd64,
# aarch64 from arm64. Each is built on a runner of its own CPU, because
# appimagetool has to run on the architecture it is packing for. If a bundle for
# another architecture appears on a release, add it to the matrix and it will be
# wrapped the same way.
#
# WHERE THE DATA GOES. A .deb or a container has somewhere obvious to put its
# data; an AppImage is one file that may live on a USB stick, and the one place
# it can always write is the user's home. WRITABLE_PATH defaults to
# ~/.local/share/wekan here, so running the AppImage twice keeps the same boards,
# and setting WRITABLE_PATH yourself still overrides it - the launcher's own
# variables all still work.
on:
# Callable, so release-all-missing.yml can ask for just the architectures
# a release is short of. The build itself stays here - a second copy of the
# appimages build would drift from this one.
workflow_call:
inputs:
tag:
required: false
type: string
publish:
required: false
default: true
type: boolean
only:
description: 'Only build these architectures, as a JSON array (e.g. ["aarch64"]); empty = all'
required: false
default: ''
type: string
workflow_dispatch:
inputs:
tag:
description: 'Release tag to wrap and attach to (empty = newest release)'
required: false
type: string
publish:
description: 'Attach the AppImages to the release'
required: false
default: true
type: boolean
only:
description: 'Only build these architectures, as a JSON array (e.g. ["aarch64"]); empty = all'
required: false
default: ''
type: string
permissions:
contents: write
concurrency:
group: wekan-appimage-${{ inputs.tag || github.ref }}
cancel-in-progress: false
jobs:
release:
name: find the release
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.find.outputs.tag }}
version: ${{ steps.find.outputs.version }}
steps:
- uses: actions/checkout@v7
- id: find
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag='${{ inputs.tag }}'
# --repo on every gh call, whether or not the job happens to have
# checked out: a call that relies on an inferred remote breaks the day
# somebody removes the checkout, and the error it gives - "fatal: not
# a git repository" - names neither gh nor the workflow.
[ -n "$tag" ] || tag="$(gh release list --repo "${{ github.repository }}" \
--limit 1 --json tagName --jq '.[0].tagName')"
if [ -z "$tag" ]; then
echo "::error::No release to wrap, and no tag was given."
exit 1
fi
# The bundles are named wekan-10.57-amd64.zip against tag v10.57.
version="${tag#v}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Wrapping the bundles from $tag (version $version)"
build:
name: AppImage ${{ matrix.appimage_arch }}
needs: release
runs-on: ${{ matrix.runs_on }}
# A 32-bit job that cannot run on the runner it was given must not fail the
# release for the architectures that built fine.
continue-on-error: ${{ matrix.tolerate_failure || false }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- appimage_arch: x86_64
bundle_arch: amd64
runs_on: ubuntu-24.04
- appimage_arch: aarch64
bundle_arch: arm64
runs_on: ubuntu-24.04-arm
# The other two architectures an AppImage runtime EXISTS for. There
# are four in total - x86_64, aarch64, i686, armhf - and that is the
# whole limit: the runtime is the ELF the kernel executes, so an
# architecture with no runtime cannot have an AppImage at all. That is
# why ppc64le, s390x, riscv64 and loong64 are not here and never will
# be; they are served by the bundle zip and the .deb.
#
# i686 builds on the x86_64 runner, which runs 32-bit x86 natively -
# no emulation, no multiarch, and appimagetool-i686 runs as-is.
- appimage_arch: i686
bundle_arch: i386
runs_on: ubuntu-24.04
# armhf builds on the aarch64 runner, which runs 32-bit ARM only if
# the kernel was built with CONFIG_COMPAT and the 32-bit loader is
# installed. GitHub does not promise either, so this one is allowed to
# fail without taking the release with it - the same treatment TSC
# gives its emulated armhf job. If it turns out to work reliably,
# drop the flag; if it never does, drop the entry.
- appimage_arch: armhf
bundle_arch: armhf
runs_on: ubuntu-24.04-arm
tolerate_failure: true
# WHICH entries this run wants, decided here because it cannot be decided
# where it belongs. A job-level `if:` cannot see `matrix` - GitHub exposes
# that context to runs-on, env, name, steps and a few others, but not to a
# job's own condition - and a workflow that tries answers
# Unrecognized named-value: 'matrix'
# and refuses to load at all, taking every workflow that CALLS it with it.
# A job-level env CAN see matrix, so the answer is computed once here and
# every step below asks for it.
env:
BUILD_THIS: ${{ inputs.only == '' || contains(fromJSON(inputs.only), matrix.appimage_arch) }}
steps:
- uses: actions/checkout@v7
if: ${{ env.BUILD_THIS == 'true' }}
# An AppImage only WRAPS an existing release bundle
# (wekan-<version>-<arch>.zip); it cannot build one. When this workflow is
# asked to fill a gap for a release whose base bundle is not published yet
# (release in progress, or it failed), `gh release download` finds "no
# assets to download" and the job failed hard. Turn that into a graceful
# skip: if the bundle this arch needs is not on the release, flip BUILD_THIS
# off so every step below (all gated on it) is skipped with a notice. The
# AppImage is built normally once the base bundle exists.
- name: Skip this arch if its release bundle is not published yet
if: ${{ env.BUILD_THIS == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
zip="wekan-${{ needs.release.outputs.version }}-${{ matrix.bundle_arch }}.zip"
if gh release view '${{ needs.release.outputs.tag }}' --repo "${{ github.repository }}" \
--json assets --jq '.assets[].name' 2>/dev/null | grep -qx "$zip"; then
echo "OK: $zip is on ${{ needs.release.outputs.tag }}; building its AppImage."
else
echo "::notice::$zip is not on ${{ needs.release.outputs.tag }} yet - skipping this AppImage (it can only wrap a bundle that exists)."
echo "BUILD_THIS=false" >> "$GITHUB_ENV"
fi
- name: Fetch the release bundle for this architecture
if: ${{ env.BUILD_THIS == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
zip="wekan-${{ needs.release.outputs.version }}-${{ matrix.bundle_arch }}.zip"
gh release download '${{ needs.release.outputs.tag }}' \
--repo "${{ github.repository }}" --pattern "$zip" --clobber
unzip -q "$zip"
# A zip that exists is not a bundle that runs; these are the two files
# the AppImage is built around.
for f in bundle/main.js bundle/node bundle/ferretdb bundle/start-wekan.sh; do
if [ ! -e "$f" ]; then
echo "::error::$zip has no $f - it is not a runnable WeKan bundle."
exit 1
fi
done
chmod +x bundle/node bundle/ferretdb bundle/start-wekan.sh
du -sh bundle
- name: Build the AppDir
if: ${{ env.BUILD_THIS == 'true' }}
run: |
set -euo pipefail
version='${{ needs.release.outputs.version }}'
rm -rf AppDir
mkdir -p AppDir/usr/lib/wekan AppDir/usr/share/applications \
AppDir/usr/share/icons/hicolor/512x512/apps
cp -a bundle AppDir/usr/lib/wekan/bundle
# WeKan's own icon, from public/. 512x512 is what appimagetool wants
# for the thumbnail a desktop shows.
cp public/android-chrome-512x512.png \
AppDir/usr/share/icons/hicolor/512x512/apps/wekan.png
cp AppDir/usr/share/icons/hicolor/512x512/apps/wekan.png AppDir/wekan.png
cat > AppDir/usr/share/applications/wekan.desktop <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=WeKan
GenericName=Kanban board
Comment=Open Source kanban board with FerretDB and Node.js included
Exec=wekan
Icon=wekan
Terminal=true
Categories=Office;ProjectManagement;
Keywords=kanban;board;tasks;project;
DESKTOP
cp AppDir/usr/share/applications/wekan.desktop AppDir/wekan.desktop
# AppRun is what the AppImage runs. It does three things the launcher
# cannot do for itself: pick a writable place for the data (an AppImage
# is read-only and may sit on a USB stick), say where WeKan is once it
# has started, and then hand over to start-wekan.sh, which is the same
# launcher the offline zip uses - so every variable it documents
# (WRITABLE_PATH, PORT, ROOT_URL, MONGO_URL, WEKAN_DB, ...) still works.
cat > AppDir/AppRun <<'APPRUN'
#!/bin/sh
set -eu
HERE="$(dirname "$(readlink -f "$0")")"
BUNDLE="$HERE/usr/lib/wekan/bundle"
# An AppImage is read-only, so the data cannot live beside it.
: "${WRITABLE_PATH:=${XDG_DATA_HOME:-$HOME/.local/share}/wekan}"
export WRITABLE_PATH
: "${PORT:=8080}"
export PORT
: "${ROOT_URL:=http://localhost:$PORT}"
export ROOT_URL
mkdir -p "$WRITABLE_PATH"
echo "WeKan is starting."
echo " Open: $ROOT_URL"
echo " Data: $WRITABLE_PATH"
echo " Stop: Ctrl-C"
echo
echo "Set WRITABLE_PATH, PORT, ROOT_URL or MONGO_URL to change any of"
echo "this; WEKAN_DB=mongodb uses your own database instead of the"
echo "bundled FerretDB."
echo
exec "$BUNDLE/start-wekan.sh" "$@"
APPRUN
chmod +x AppDir/AppRun
- name: Pack the AppImage
if: ${{ env.BUILD_THIS == 'true' }}
run: |
set -euo pipefail
version='${{ needs.release.outputs.version }}'
arch='${{ matrix.appimage_arch }}'
name="WeKan-${version}-${arch}"
curl -fSL --retry 5 --retry-delay 10 -o appimagetool \
"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${arch}.AppImage"
chmod +x appimagetool
mkdir -p dist
# A runner has no FUSE, so the tool cannot mount itself; this unpacks
# it to a temporary directory and runs it from there instead.
APPIMAGE_EXTRACT_AND_RUN=1 ARCH="$arch" \
./appimagetool AppDir "dist/${name}.AppImage"
chmod +x "dist/${name}.AppImage"
# Checksum keeps the .AppImage extension in its name, matching the zip
# convention (wekan-<v>-<arch>.zip.sha256sum) and what
# releases/expected-assets.sh looks for. Naming it ${name}.sha256sum
# (extension dropped) meant "what is missing" never found the checksum,
# so every run rebuilt the AppImage even when it was already published.
( cd dist
md5sum "${name}.AppImage" > "${name}.AppImage.md5sum"
sha256sum "${name}.AppImage" > "${name}.AppImage.sha256sum" )
ls -lh dist
- name: Check that it starts
if: ${{ env.BUILD_THIS == 'true' }}
run: |
set -euo pipefail
version='${{ needs.release.outputs.version }}'
app="dist/WeKan-${version}-${{ matrix.appimage_arch }}.AppImage"
# An AppImage that packs is not an AppImage that runs. Start it, wait
# for WeKan to answer on its port, then stop it - so a broken bundle
# is caught here rather than by whoever downloads it.
export WRITABLE_PATH="$PWD/smoke-data" PORT=8080
# A CHECK THAT CANNOT RUN IS NOT A FAILED CHECK. The 32-bit builds are
# packed on a 64-bit runner, which executes them only if the kernel and
# loader for that word size are present - GitHub promises neither. If
# the binary cannot be executed AT ALL, the AppImage is as good as
# anything here knows and the release must not be withheld for it; say
# so and move on. Anything else - it starts and does not answer - is
# the bug this step exists for and still fails.
if ! APPIMAGE_EXTRACT_AND_RUN=1 "$app" --appimage-help >/dev/null 2>&1 \
&& ! APPIMAGE_EXTRACT_AND_RUN=1 "$app" --help >/dev/null 2>&1; then
if ! file "$app" | grep -q "$(uname -m)"; then
echo "::warning::this runner cannot execute a ${{ matrix.appimage_arch }} binary, so the AppImage was packed but not started. It is uploaded unchecked."
exit 0
fi
fi
APPIMAGE_EXTRACT_AND_RUN=1 "$app" > smoke.log 2>&1 &
pid=$!
ok=false
for _ in $(seq 1 90); do
if curl -fsS -o /dev/null "http://localhost:8080/sign-in"; then ok=true; break; fi
kill -0 "$pid" 2>/dev/null || break
sleep 2
done
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
if [ "$ok" != true ]; then
echo "::error::the AppImage did not answer on port 8080; its log follows"
tail -n 100 smoke.log
exit 1
fi
echo "WeKan answered on port 8080."
- uses: actions/upload-artifact@v7
if: ${{ env.BUILD_THIS == 'true' }}
with:
name: appimage-${{ matrix.appimage_arch }}
path: dist/
if-no-files-found: error
publish:
name: attach to the release
needs: [release, build]
if: ${{ always() && inputs.publish != false }}
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v8
with:
pattern: appimage-*
path: assets
merge-multiple: true
- name: Say what was built, and what was not
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ls -lh assets 2>/dev/null || true
if ls assets/*.AppImage >/dev/null 2>&1; then
# Every architecture the matrix builds. A missing one WARNS rather
# than fails: the 32-bit pair is allowed not to build, and an
# architecture whose base bundle was never published has nothing to
# wrap. What must not happen silently is one going missing unnoticed.
for want in x86_64 aarch64 i686 armhf; do
ls assets/*"-$want.AppImage" >/dev/null 2>&1 || \
echo "::warning::no AppImage for $want - see that build job's log"
done
else
# No AppImage was produced. That is only a FAILURE if there was a base
# bundle to wrap: an AppImage cannot be built without one, and the
# build jobs skip (not fail) when the base bundle is not published yet.
published="$(gh release view '${{ needs.release.outputs.tag }}' --repo "${{ github.repository }}" --json assets --jq '.assets[].name' 2>/dev/null || true)"
base_present=false
for arch in amd64 arm64 i386 armhf; do
printf '%s\n' "$published" | grep -qx "wekan-${{ needs.release.outputs.version }}-${arch}.zip" && base_present=true
done
if [ "$base_present" = false ]; then
echo "::notice::No base bundle (wekan-${{ needs.release.outputs.version }}-{amd64,arm64}.zip) is on ${{ needs.release.outputs.tag }} yet, so there is nothing to wrap - skipping. release-all.yml builds the AppImages once the base bundles are published."
exit 0
fi
echo "::error::A base bundle is published but no architecture produced an AppImage - every build job failed. Their logs say why; nothing is uploaded."
exit 1
fi
- name: Upload them
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if ! ls assets/*.AppImage >/dev/null 2>&1; then
echo "Nothing to upload (no base bundle was published to wrap)."
exit 0
fi
# --repo, because this job does NOT check the repository out - it only
# downloads artifacts - so `gh` has no git remote to infer the repo
# from and fails with "fatal: not a git repository". Every other gh
# call in this workflow already passes it; these two did not, so the
# AppImages were built, smoke-tested and uploaded as artifacts, and
# then nothing was attached to the release.
gh release upload '${{ needs.release.outputs.tag }}' assets/* \
--repo "${{ github.repository }}" --clobber
echo "Uploaded to $(gh release view '${{ needs.release.outputs.tag }}' \
--repo "${{ github.repository }}" --json url --jq .url)"
+430
View File
@@ -0,0 +1,430 @@
name: Flatpak
# Build WeKan as a Flatpak for every CPU the runtime exists for, attach the
# single-file bundles to the newest WeKan release, and publish a Flatpak
# REPOSITORY that can be added with `flatpak remote-add`.
#
# Like AppImage.yml, this does NOT rebuild WeKan. release-all.yml already
# produces a bundle that runs offline with nothing installed - the Meteor server
# (main.js), the bundled Node.js the release was built against, the
# wekan/FerretDB v1 fork with its embedded pure-Go SQLite backend, the
# wekan/mongo-tools-patches binaries and start-wekan.sh - so this takes the
# wekan-<version>-<arch>.zip off the release and packages it. The Flatpak is
# therefore the same server, Node and FerretDB as the zip and the AppImage
# beside it, not a third build that might differ.
#
# A BUNDLE AND A REPOSITORY are two different things and both come out of one
# build here. A .flatpak bundle is one file you install by hand and update by
# downloading the next one; a repository is what makes `flatpak update` work,
# and one repository serves every architecture at once. The bundles go on the
# GitHub release; the repository is uploaded as an artifact of static files, so
# it can be copied to a website with no server to run.
#
# WHICH CPUs. org.freedesktop.Platform//24.08 is built for x86_64 and aarch64,
# and WeKan publishes Linux bundles for amd64 and arm64, so those are the two.
# Each builds on a runner of its own CPU rather than under emulation.
#
# AND THAT IS THE WHOLE LIST - there is no third to add. A flatpak runs against
# a RUNTIME, not against the host's libraries, so an architecture exists here
# only if freedesktop.org publishes org.freedesktop.Platform for it. It does so
# for x86_64 and aarch64; the i386 and arm runtimes were discontinued. That is
# the difference from AppImage.yml, which was able to grow from two
# architectures to four: an AppImage carries its own runtime binary, and
# runtimes exist for i686 and armhf as well. WeKan's ppc64le, s390x, riscv64,
# loong64 and 32-bit bundles are served by the zip and the .deb.
#
# THE SANDBOX. WeKan is a server you reach in a browser, so it needs the network
# and a place to keep its data, and nothing else - no X11, no audio, no access
# to your files. --share=network and --persist=.wekan are the whole permission
# list, and FerretDB keeps everything in SQLite inside that one directory.
#
# THE REPOSITORY IS UNSIGNED. Signing needs a GPG key, which is a secret rather
# than something a workflow can invent. The README written into the repository
# says exactly how to sign it when a key exists.
on:
# Callable, so release-all-missing.yml can ask for just the architectures
# a release is short of. The build itself stays here - a second copy of the
# flatpaks build would drift from this one.
workflow_call:
inputs:
tag:
required: false
type: string
publish:
required: false
default: true
type: boolean
only:
description: 'Only build these architectures, as a JSON array (e.g. ["x86_64"]); empty = all'
required: false
default: ''
type: string
workflow_dispatch:
inputs:
tag:
description: 'Release tag to wrap and attach to (empty = newest release)'
required: false
type: string
publish:
description: 'Attach the bundles to the release'
required: false
default: true
type: boolean
publish_repo:
description: 'Build the Flatpak repository for the website'
required: false
default: true
type: boolean
only:
description: 'Only build these architectures, as a JSON array (e.g. ["x86_64"]); empty = all'
required: false
default: ''
type: string
permissions:
contents: write
concurrency:
group: wekan-flatpak-${{ inputs.tag || github.ref }}
cancel-in-progress: false
jobs:
release:
name: find the release
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.find.outputs.tag }}
version: ${{ steps.find.outputs.version }}
steps:
- uses: actions/checkout@v7
- id: find
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag='${{ inputs.tag }}'
# --repo on every gh call, whether or not the job happens to have
# checked out: one that relies on an inferred remote breaks the day
# somebody removes the checkout.
[ -n "$tag" ] || tag="$(gh release list --repo "${{ github.repository }}" \
--limit 1 --json tagName --jq '.[0].tagName')"
if [ -z "$tag" ]; then
echo "::error::No release to wrap, and no tag was given."
exit 1
fi
version="${tag#v}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Wrapping the bundles from $tag (version $version)"
build:
name: flatpak ${{ matrix.flatpak_arch }}
needs: release
runs-on: ${{ matrix.runs_on }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- flatpak_arch: x86_64
bundle_arch: amd64
runs_on: ubuntu-24.04
- flatpak_arch: aarch64
bundle_arch: arm64
runs_on: ubuntu-24.04-arm
# WHICH entries this run wants, decided here because it cannot be decided
# where it belongs. A job-level `if:` cannot see `matrix` - GitHub exposes
# that context to runs-on, env, name, steps and a few others, but not to a
# job's own condition - and a workflow that tries answers
# Unrecognized named-value: 'matrix'
# and refuses to load at all, taking every workflow that CALLS it with it.
# A job-level env CAN see matrix, so the answer is computed once here and
# every step below asks for it.
env:
BUILD_THIS: ${{ inputs.only == '' || contains(fromJSON(inputs.only), matrix.flatpak_arch) }}
steps:
- uses: actions/checkout@v7
if: ${{ env.BUILD_THIS == 'true' }}
- name: Install flatpak
if: ${{ env.BUILD_THIS == 'true' }}
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends flatpak ostree zip
flatpak remote-add --if-not-exists --user flathub \
https://flathub.org/repo/flathub.flatpakrepo
flatpak install --user -y --noninteractive flathub \
org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08
- name: Fetch the release bundle for this architecture
if: ${{ env.BUILD_THIS == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
zip="wekan-${{ needs.release.outputs.version }}-${{ matrix.bundle_arch }}.zip"
gh release download '${{ needs.release.outputs.tag }}' \
--repo "${{ github.repository }}" --pattern "$zip" --clobber
unzip -q "$zip"
for f in bundle/main.js bundle/node bundle/ferretdb bundle/start-wekan.sh; do
if [ ! -e "$f" ]; then
echo "::error::$zip has no $f - it is not a runnable WeKan bundle."
exit 1
fi
done
chmod +x bundle/node bundle/ferretdb bundle/start-wekan.sh
du -sh bundle
# flatpak-builder is for building from source. The bundle IS the build, so
# this uses `flatpak build` directly: make a build directory from the
# runtime, copy the bundle into it, finish it with its permissions, and
# export it into a repository.
- name: Build the Flatpak
if: ${{ env.BUILD_THIS == 'true' }}
run: |
set -euo pipefail
version='${{ needs.release.outputs.version }}'
arch='${{ matrix.flatpak_arch }}'
rm -rf build-dir repo
flatpak build-init --arch="$arch" build-dir \
io.github.wekan.WeKan org.freedesktop.Sdk org.freedesktop.Platform 24.08
mkdir -p build-dir/files/lib/wekan build-dir/files/bin \
build-dir/files/share/applications \
build-dir/files/share/icons/hicolor/512x512/apps \
build-dir/files/share/metainfo
cp -a bundle build-dir/files/lib/wekan/bundle
cp public/android-chrome-512x512.png \
build-dir/files/share/icons/hicolor/512x512/apps/io.github.wekan.WeKan.png
# The command the Flatpak runs. --persist=.wekan gives the sandbox one
# writable directory that survives restarts, and that is where
# WRITABLE_PATH points, so FerretDB's SQLite files and the attachments
# live there. Everything start-wekan.sh documents still overrides.
cat > build-dir/files/bin/wekan <<'SH'
#!/bin/sh
set -eu
BUNDLE=/app/lib/wekan/bundle
: "${WRITABLE_PATH:=$HOME/.wekan}"
export WRITABLE_PATH
: "${PORT:=8080}"
export PORT
: "${ROOT_URL:=http://localhost:$PORT}"
export ROOT_URL
mkdir -p "$WRITABLE_PATH"
echo "WeKan is starting."
echo " Open: $ROOT_URL"
echo " Data: $WRITABLE_PATH (inside the Flatpak sandbox)"
echo " Stop: Ctrl-C"
echo
exec "$BUNDLE/start-wekan.sh" "$@"
SH
chmod +x build-dir/files/bin/wekan
cat > build-dir/files/share/applications/io.github.wekan.WeKan.desktop <<'DESKTOP'
[Desktop Entry]
Type=Application
Name=WeKan
GenericName=Kanban board
Comment=Open Source kanban board with FerretDB and Node.js included
Exec=wekan
Icon=io.github.wekan.WeKan
Terminal=true
Categories=Office;ProjectManagement;
Keywords=kanban;board;tasks;project;
DESKTOP
cat > build-dir/files/share/metainfo/io.github.wekan.WeKan.metainfo.xml <<XML
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>io.github.wekan.WeKan</id>
<name>WeKan</name>
<summary>Open Source kanban board</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<description>
<p>
WeKan is an Open Source kanban board. This build carries everything
it needs to run: the WeKan server, Node.js, and FerretDB v1 with an
embedded SQLite backend, so no separate database is required.
</p>
</description>
<launchable type="desktop-id">io.github.wekan.WeKan.desktop</launchable>
<url type="homepage">https://wekan.fi</url>
<releases><release version="${version}"/></releases>
</component>
XML
# A server reached in a browser needs the network and its own data
# directory, and nothing else.
flatpak build-finish build-dir \
--share=network \
--persist=.wekan \
--command=wekan
flatpak build-export --arch="$arch" repo build-dir stable
- name: Make the single-file bundle
if: ${{ env.BUILD_THIS == 'true' }}
run: |
set -euo pipefail
version='${{ needs.release.outputs.version }}'
name="WeKan-${version}-${{ matrix.flatpak_arch }}"
mkdir -p dist
flatpak build-bundle repo "dist/${name}.flatpak" \
--arch='${{ matrix.flatpak_arch }}' io.github.wekan.WeKan stable
# Checksum keeps the .flatpak extension in its name, matching the zip
# convention and what releases/expected-assets.sh looks for; dropping it
# meant "what is missing" never found the checksum and rebuilt the
# Flatpak every run even when already published.
( cd dist
md5sum "${name}.flatpak" > "${name}.flatpak.md5sum"
sha256sum "${name}.flatpak" > "${name}.flatpak.sha256sum" )
ls -lh dist
- uses: actions/upload-artifact@v7
if: ${{ env.BUILD_THIS == 'true' }}
with:
name: flatpak-${{ matrix.flatpak_arch }}
path: dist/
if-no-files-found: error
- uses: actions/upload-artifact@v7
if: ${{ env.BUILD_THIS == 'true' }}
with:
name: flatpak-repo-${{ matrix.flatpak_arch }}
path: repo/
if-no-files-found: warn
publish:
name: attach to the release, and build the repository
needs: [release, build]
if: ${{ always() }}
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v8
with:
pattern: flatpak-[0-9a-z]*
path: assets
merge-multiple: true
- name: Say what was built, and what was not
run: |
set -euo pipefail
ls -lh assets 2>/dev/null || true
if ! ls assets/*.flatpak >/dev/null 2>&1; then
echo "::error::No architecture produced a Flatpak bundle; nothing is uploaded."
exit 1
fi
for want in x86_64 aarch64; do
ls assets/*"-$want.flatpak" >/dev/null 2>&1 || \
echo "::warning::no Flatpak for $want - see that build job's log"
done
- name: Upload the bundles
if: ${{ inputs.publish != false }}
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# --repo, because this job does NOT check the repository out - it
# only downloads artifacts - so `gh` has no git remote to infer the
# repo from and fails with "fatal: not a git repository". The same
# line was wrong in AppImage.yml for the same reason.
#
# And the bundles BY NAME, not assets/*: the artifacts also carry the
# ostree repository the flatpak was exported through - config,
# objects/, refs/, summaries/, summary, summary.idx - which is build
# scaffolding, not something to attach to a release. TSC hit this
# exact thing ("upload only the bundle files, not the ostree repo").
gh release upload '${{ needs.release.outputs.tag }}' \
assets/*.flatpak assets/*.flatpak.md5sum assets/*.flatpak.sha256sum \
--repo "${{ github.repository }}" --clobber
echo "Uploaded to $(gh release view '${{ needs.release.outputs.tag }}' \
--repo "${{ github.repository }}" --json url --jq .url)"
# ---- the repository -------------------------------------------------
- uses: actions/download-artifact@v8
if: ${{ inputs.publish_repo != false }}
with:
pattern: flatpak-repo-*
path: repos
- name: Build the multi-architecture repository
if: ${{ inputs.publish_repo != false }}
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends flatpak ostree
shopt -s nullglob
parts=(repos/flatpak-repo-*)
if [ ${#parts[@]} -eq 0 ]; then
echo "::warning::no repository halves were produced; skipping"
exit 0
fi
ostree init --repo=flatpak-repo --mode=archive-z2
for part in "${parts[@]}"; do
name="$(basename "$part")"
echo "Pulling $name"
ostree --repo=flatpak-repo remote add --no-gpg-verify --if-not-exists \
"$name" "file://$PWD/$part"
ostree --repo=flatpak-repo pull --mirror "$name" || true
done
flatpak build-update-repo --prune flatpak-repo
cat > flatpak-repo/wekan.flatpakrepo <<'REPO'
[Flatpak Repo]
Title=WeKan
Url=https://wekan.fi/flatpak/
Homepage=https://wekan.fi
Comment=WeKan releases, built by the Flatpak workflow in wekan/wekan
Description=Flatpak repository for WeKan, the Open Source kanban board
REPO
cat > flatpak-repo/README.txt <<'TXT'
Flatpak repository for WeKan.
Copy this whole directory to the website under /flatpak/, then:
flatpak remote-add --if-not-exists wekan \
https://wekan.fi/flatpak/wekan.flatpakrepo
flatpak install wekan io.github.wekan.WeKan
flatpak run io.github.wekan.WeKan
Then open http://localhost:8080 . Everything is inside: the WeKan
server, Node.js, and FerretDB v1 with an embedded SQLite backend, so
there is no database to install. The data lives in the sandbox's
persistent .wekan directory.
THIS REPOSITORY IS NOT SIGNED. Signing needs a GPG key, which is a
secret rather than something a workflow can invent. To sign it:
1. Make a key, once, on a machine you trust:
gpg --quick-generate-key "WeKan <security@wekan.fi>" default default never
gpg --export KEYID > wekan-flatpak.gpg # the public half
gpg --export-secret-keys --armor KEYID # the private half
2. Put the private half in the repository's Actions secrets as
GPG_PRIVATE_KEY.
3. In Flatpak.yml, import it before the export and add the two flags:
flatpak build-export --gpg-sign=KEYID ...
flatpak build-update-repo --gpg-sign=KEYID ...
4. Put wekan-flatpak.gpg beside this file on the website and add
GPGKey=<base64 of wekan-flatpak.gpg>
to wekan.flatpakrepo.
Until then flatpak will say the remote is unsigned, and installing
from it trusts whoever serves the files.
TXT
du -sh flatpak-repo
- uses: actions/upload-artifact@v7
if: ${{ inputs.publish_repo != false }}
with:
name: flatpak-repository
path: flatpak-repo/
if-no-files-found: warn
+30
View File
@@ -34,6 +34,31 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v7
# The Dockerfile DOWNLOADS the prebuilt release zip that release-all.yml
# publishes (and `exit 8`s if it is missing), so this image can only be
# built once the release for the version pinned in the Dockerfile is up. On
# a tag push (the tag is created early in the release, ~30 min before the
# zips are attached), during the release window, or if the release failed,
# the asset 404s and this job failed hard. Check first and SKIP the build
# (a notice, green run) when the release is not published yet; the release
# image itself is built + pushed by release-all.yml's docker job.
- name: Is the release this Dockerfile needs published yet?
id: rel
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
VERSION="$(sed -n 's/^ARG VERSION=//p' Dockerfile | head -1)"
asset="wekan-${VERSION}-amd64.zip"
if gh release view "v${VERSION}" --repo "${{ github.repository }}" \
--json assets --jq '.assets[].name' 2>/dev/null | grep -qx "$asset"; then
echo "build=yes" >> "$GITHUB_OUTPUT"
echo "OK: $asset is on the v${VERSION} release; building the image."
else
echo "build=no" >> "$GITHUB_OUTPUT"
echo "::notice::$asset is not on the v${VERSION} release yet - skipping. release-all.yml builds and pushes the image once the release is published."
fi
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
@@ -55,9 +80,14 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
if: steps.rel.outputs.build == 'yes'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Full build provenance (SLSA), not the minimal default that BuildKit
# attaches on its own. See release-all.yml for what it is and why the
# manifest list gains an `unknown/unknown` entry per platform.
provenance: mode=max
+28
View File
@@ -16,5 +16,33 @@ jobs:
steps:
- uses: actions/checkout@v7
# The Dockerfile does not build WeKan from source — it DOWNLOADS the prebuilt
# release zip (wekan-<VERSION>-amd64.zip) that release-all.yml publishes, and
# `exit 8`s if it is missing. So this image can only be built once the release
# for the version pinned in the Dockerfile is up. On a push during the release
# window (the version was just bumped but the release job has not attached the
# zip yet), or if the release failed, the asset 404s and this job failed hard
# even though nothing here is wrong. Check first and SKIP (a notice, green run)
# when the release is not published yet; the real image is built by
# release-all.yml's docker job once the release is up.
- name: Is the release this Dockerfile needs published yet?
id: rel
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
VERSION="$(sed -n 's/^ARG VERSION=//p' Dockerfile | head -1)"
asset="wekan-${VERSION}-amd64.zip"
if gh release view "v${VERSION}" --repo "${{ github.repository }}" \
--json assets --jq '.assets[].name' 2>/dev/null | grep -qx "$asset"; then
echo "build=yes" >> "$GITHUB_OUTPUT"
echo "OK: $asset is on the v${VERSION} release; building the image."
else
echo "build=no" >> "$GITHUB_OUTPUT"
echo "::notice::$asset is not on the v${VERSION} release yet - skipping. release-all.yml builds the image once the release is published."
fi
- name: Build the Docker image
if: steps.rel.outputs.build == 'yes'
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
+318
View File
@@ -0,0 +1,318 @@
name: Release all missing
# Build only the bundles a release does not already carry.
#
# WHY: release-all.yml is one run that bumps versions, tags, builds every
# platform, publishes Docker images, snaps, the website and the charts. It is
# the right thing for MAKING a release and the wrong thing for FINISHING one.
# When the v10.57 run ended with the six extra architectures missing - four of
# them on a shell-quoting bug and two on a base image - there was no way to get
# those six without running the whole thing again, version bump and all.
#
# This finishes a release that already exists. It never bumps a version, never
# tags, never touches Docker, snap, the website or the charts.
#
# WHAT IT CAN BUILD, and why that is not everything. Only build-amd64 runs
# Meteor; every other bundle WeKan ships is that same bundle repacked. This job
# downloads the PUBLISHED wekan-<v>-amd64.zip and repacks it, so it can rebuild:
#
# the six extra architectures s390x ppc64le riscv64 i386 armhf loong64
# the AppImages and Flatpaks by calling their own workflows
#
# It cannot rebuild amd64 itself (that is the Meteor build), nor arm64, win64
# and mac-arm64 (each needs its own kind of runner and its own steps), nor the
# Sandstorm .spk. When one of those is missing it says so and names
# release-all.yml, rather than pretending.
#
# The repack is releases/repack-bundle-for-arch.sh - the SAME script
# release-all.yml's build-extra-arches runs, so a bundle added to a release
# months later is built exactly like the ones already on it.
#
# What a complete release looks like is releases/expected-assets.sh, in one
# place. An asset counts as present only when its .sha256sum is there too; the
# Sandstorm .spk is signed rather than checksummed and is marked nosums.
#
# Usage:
# gh workflow run release-all-missing.yml -f version=10.58
on:
workflow_dispatch:
inputs:
version:
description: 'Release to complete, without the v (e.g. 10.58). Empty = the newest release.'
required: false
type: string
permissions:
contents: write
env:
# Must match release-all.yml: the Node.js major the bundles carry.
NODE_VERSION: '24'
# Never at the same time as a full release run, which would be uploading to the
# same assets.
concurrency:
group: wekan-release-${{ inputs.version || github.ref }}
cancel-in-progress: false
jobs:
plan:
name: what the release is missing
runs-on: ubuntu-latest
outputs:
version: ${{ steps.plan.outputs.version }}
extra: ${{ steps.plan.outputs.extra }}
appimage: ${{ steps.plan.outputs.appimage }}
flatpak: ${{ steps.plan.outputs.flatpak }}
manual: ${{ steps.plan.outputs.manual }}
total: ${{ steps.plan.outputs.total }}
steps:
- uses: actions/checkout@v7
- id: plan
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
V='${{ inputs.version }}'
V="${V#v}"
if [ -z "$V" ]; then
V="$(gh release view --json tagName --jq .tagName)"
V="${V#v}"
echo "No version given; the newest release is v$V."
fi
echo "version=$V" >> "$GITHUB_OUTPUT"
if gh release view "v$V" >/dev/null 2>&1; then
gh release view "v$V" --json assets --jq '.assets[].name' | sort > existing.txt
else
echo "::error::There is no release v$V to complete. This workflow FINISHES a release; release-all.yml makes one."
exit 1
fi
echo "v$V carries $(wc -l < existing.txt | tr -d ' ') asset(s)."
bash releases/expected-assets.sh "$V" > expected.txt
: > missing.txt
present=0
while read -r kind selector asset sums; do
need="$asset"
[ "$sums" = "sums" ] && need="$need ${asset}.sha256sum"
have=yes
for n in $need; do grep -qxF "$n" existing.txt || have=no; done
if [ "$have" = yes ]; then present=$((present + 1)); else echo "$kind $selector" >> missing.txt; fi
done < expected.txt
total="$(wc -l < missing.txt | tr -d ' ')"
echo "total=$total" >> "$GITHUB_OUTPUT"
for kind in extra appimage flatpak; do
sels="$(awk -v k="$kind" '$1 == k { print $2 }' missing.txt)"
json="$(printf '%s\n' $sels | sed '/^$/d' | jq -R . | jq -sc .)"
[ -n "$json" ] || json='[]'
echo "$kind=$json" >> "$GITHUB_OUTPUT"
echo " $kind: $json"
done
# The ones this workflow cannot make. Named, not silently skipped.
manual="$(awk '$1 == "meteor" || $1 == "repack" || $1 == "sandstorm" { printf "%s/%s ", $1, $2 }' missing.txt)"
echo "manual=${manual% }" >> "$GITHUB_OUTPUT"
{
echo "### v$V"
echo ""
echo "$present of $((present + total)) assets already published."
echo ""
if [ "$total" -eq 0 ]; then
echo "Nothing is missing - the release is complete."
else
echo "Missing:"
echo ""
echo '```'
cat missing.txt
echo '```'
if [ -n "$manual" ]; then
echo ""
echo "Not buildable here (run release-all.yml): \`$manual\`"
fi
fi
} >> "$GITHUB_STEP_SUMMARY"
echo "Missing $total. Needs release-all.yml: ${manual:-none}"
- name: Say plainly what this run will not do
if: ${{ steps.plan.outputs.manual != '' }}
run: |
echo "::warning::v${{ steps.plan.outputs.version }} is missing ${{ steps.plan.outputs.manual }}, which this workflow cannot build: amd64 is the Meteor build, arm64/win64/mac-arm64 each need their own runner, and the Sandstorm .spk is signed. Run release-all.yml for those."
# The six architectures nobody else builds for, repacked from the PUBLISHED
# amd64 bundle - which is what makes this possible without a Meteor build.
extra-arches:
name: ${{ matrix.arch }}
needs: plan
if: ${{ needs.plan.outputs.extra != '[]' }}
runs-on: ubuntu-24.04
timeout-minutes: 300
strategy:
fail-fast: false
matrix:
include:
# The same table as release-all.yml's build-extra-arches, and it has
# to stay the same: `node_arch` is what the Node.js download is
# called (Node says x86 where we say i386, armv7l where we say
# armhf), `ferretdb_arch` is the asset name in the wekan/FerretDB
# release, and `image` must be a base image that PUBLISHES this
# platform - ubuntu:26.04 does not build 386, debian:trixie does.
- { arch: s390x, platform: linux/s390x, image: debian:trixie, node_arch: s390x, ferretdb_arch: s390x }
- { arch: ppc64le, platform: linux/ppc64le, image: debian:trixie, node_arch: ppc64le, ferretdb_arch: ppc64le }
- { arch: riscv64, platform: linux/riscv64, image: debian:trixie, node_arch: riscv64, ferretdb_arch: riscv64 }
- { arch: i386, platform: linux/386, image: debian:trixie, node_arch: x86, ferretdb_arch: i386 }
- { arch: armhf, platform: linux/arm/v7, image: debian:trixie, node_arch: armv7l, ferretdb_arch: armhf }
# loong64 has NO base image on Docker Hub at any tag, so there is no
# loong64 userland to rebuild the native modules in. Its Node.js and
# its FerretDB both exist; the container does not. The preflight
# inside the script stops it saying exactly that, and it stays in the
# matrix so it is visible rather than quietly absent.
- { arch: loong64, platform: linux/loong64, image: debian:trixie, node_arch: loong64, ferretdb_arch: loong64 }
env:
VERSION: ${{ needs.plan.outputs.version }}
steps:
- name: Skip the architectures this release already has
id: want
run: |
if printf '%s' '${{ needs.plan.outputs.extra }}' | jq -e --arg a '${{ matrix.arch }}' 'index($a)' >/dev/null; then
echo "build=yes" >> "$GITHUB_OUTPUT"
else
echo "build=no" >> "$GITHUB_OUTPUT"
echo "v${VERSION} already has ${{ matrix.arch }}; nothing to do."
fi
- name: Install dependencies
if: ${{ steps.want.outputs.build == 'yes' }}
run: |
sudo apt-get update
sudo apt-get install -y curl unzip zip
- name: Set up QEMU for ${{ matrix.arch }} emulation
if: ${{ steps.want.outputs.build == 'yes' }}
uses: docker/setup-qemu-action@v4
# Before the download, because actions/checkout cleans the workspace.
- uses: actions/checkout@v7
if: ${{ steps.want.outputs.build == 'yes' }}
# THE DIFFERENCE from release-all.yml, and the whole point of this
# workflow: the amd64 bundle comes from the RELEASE, not from an artifact
# of a build that is not running. Its checksum is verified when the
# release has one - a bundle every other architecture is cut from is worth
# checking before it is cut.
- name: Download the published amd64 bundle
if: ${{ steps.want.outputs.build == 'yes' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
zip="wekan-${VERSION}-amd64.zip"
gh release download "v${VERSION}" --pattern "$zip" --clobber
if gh release download "v${VERSION}" --pattern "${zip}.sha256sum" --clobber 2>/dev/null; then
sha256sum -c "${zip}.sha256sum"
echo "amd64 bundle checksum verified."
else
echo "::warning::v${VERSION} publishes no ${zip}.sha256sum, so the bundle every other architecture is cut from could not be verified."
fi
unzip -q "$zip"
[ -d bundle ] || { echo "::error::$zip did not contain a bundle/ directory."; exit 1; }
rm -f "$zip" "${zip}.sha256sum"
- name: Repack it for ${{ matrix.arch }}
if: ${{ steps.want.outputs.build == 'yes' }}
env:
ARCH: ${{ matrix.arch }}
NODE_ARCH: ${{ matrix.node_arch }}
FERRETDB_ARCH: ${{ matrix.ferretdb_arch }}
IMAGE: ${{ matrix.image }}
PLATFORM: ${{ matrix.platform }}
run: bash releases/repack-bundle-for-arch.sh
- name: Attach ${{ matrix.arch }} to the release
if: ${{ steps.want.outputs.build == 'yes' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
asset="wekan-${VERSION}-${{ matrix.arch }}.zip"
# No --clobber: this architecture was absent, so an upload that would
# overwrite something means the release changed under this run.
gh release upload "v${VERSION}" "$asset" "${asset}.sha256sum"
# Confirm from the other side. An upload that reports success and
# leaves no asset behind is the failure nobody notices until a
# download 404s.
gh release view "v${VERSION}" --json assets --jq '.assets[].name' > release-assets.txt
grep -qxF "$asset" release-assets.txt || {
echo "::error::$asset is not on release v${VERSION} although the upload reported success."
exit 1
}
echo "OK: $asset attached ($(($(wc -c < "$asset")/1024/1024)) MiB)."
appimage:
name: AppImages
needs: plan
if: ${{ needs.plan.outputs.appimage != '[]' }}
uses: ./.github/workflows/AppImage.yml
permissions:
contents: write
with:
tag: v${{ needs.plan.outputs.version }}
publish: true
only: ${{ needs.plan.outputs.appimage }}
flatpak:
name: Flatpaks
needs: plan
if: ${{ needs.plan.outputs.flatpak != '[]' }}
uses: ./.github/workflows/Flatpak.yml
permissions:
contents: write
with:
tag: v${{ needs.plan.outputs.version }}
publish: true
only: ${{ needs.plan.outputs.flatpak }}
done:
name: what is still missing
needs: [plan, extra-arches, appimage, flatpak]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
V: ${{ needs.plan.outputs.version }}
BEFORE: ${{ needs.plan.outputs.total }}
run: |
set -euo pipefail
if [ "$BEFORE" = "0" ]; then
echo "::notice::v$V was already complete - nothing was built."
exit 0
fi
gh release view "v$V" --json assets --jq '.assets[].name' | sort > now.txt
bash releases/expected-assets.sh "$V" > expected.txt
still=""
while read -r kind selector asset sums; do
need="$asset"
[ "$sums" = "sums" ] && need="$need ${asset}.sha256sum"
for n in $need; do
grep -qxF "$n" now.txt || { still="$still $kind/$selector"; break; }
done
done < expected.txt
echo "" >> "$GITHUB_STEP_SUMMARY"
if [ -z "$still" ]; then
echo "v$V is complete."
echo "**v$V is complete.**" >> "$GITHUB_STEP_SUMMARY"
else
# A warning, not a failure: what is left may be the parts this
# workflow never claimed to build.
echo "::warning::v$V is still missing:${still}"
echo "Still missing:\`${still# }\`" >> "$GITHUB_STEP_SUMMARY"
fi
File diff suppressed because it is too large. Load diff
+29 -14
View File
@@ -67,7 +67,14 @@ test-results/
# Claude AI
.claude/
# Tools like mongosh etc so that they work inside Flatpak sandbox
# Companion repositories and tools, cloned INSIDE this checkout so they work in a
# Flatpak sandbox that shares only this directory. One ignored directory for all
# of them - it replaced an entry per clone (/FerretDB/, /node/, /mongo-tools/,
# /mongosh/, /sandstorm/, /projects/, and the WeKan variants), which had to be
# extended by hand for every new one. build.sh's ensure_tool_repo clones what a
# test run needs into here: .tools/FerretDB, .tools/node-patches,
# .tools/mongo-tools-patches. Nothing in here is WeKan's source, and Meteor is
# told the same by /.tools/ in .meteorignore.
.tools/
# Meteor custom local directory (METEOR_LOCAL_DIR)
@@ -75,19 +82,18 @@ _build-local-test
*/build-assets-local-test
*/build-chunks-local-test
# Local-only working copies: other wekan repos cloned under this repo for editing,
# and build inputs. These are separate git repos / large machine-specific dirs;
# never commit them.
# - projects/ : unarchived meteor-spk fork (Node/Mongo/FerretDB binaries)
# - sandstorm/ : clone of sandstorm-io/sandstorm (platform source, for reference)
# - FerretDB/ : clone of wekan/FerretDB
# - mongo-tools/ : clone of wekan/mongo-tools
# - mongosh/ : clone of wekan/mongosh
/projects/
/sandstorm/
/FerretDB/
/mongo-tools/
/mongosh/
# Test and build logs, when they land INSIDE the repo. build.sh writes to
# ../log/<datetime>/ by preference - one level up, so a test run does not show
# up in git status - but the parent of the repo is not writable in a Flatpak
# sandbox, which shares only the repository. There it falls back to ./log/,
# which must not be committed.
/log/
# V8 heap snapshots from WEKAN_BUILD_HEAP_SNAPSHOT=1. Named
# Heap.<date>.<pid>.<n>.heapsnapshot by Node, written into the repo root
# because that is the build's working directory, and as large as the heap that
# produced them - gigabytes.
Heap.*.heapsnapshot
# Linux core dumps (core.<pid>), e.g. from a crashing Playwright/WebKit browser
# process. Written into the crashing process CWD (tests/playwright/) by
@@ -100,3 +106,12 @@ core.[0-9]*
# they should not show up as untracked noise either.
/wekan-ondra/
/wekan-gantt-gpl/
# node-patches: a SEPARATE repo (github.com/wekan/node-patches) checked out here.
node-patches/
# mongo-tools-patches: a SEPARATE repo (github.com/wekan/mongo-tools-patches)
# checked out here. Same story as node-patches: the PATCHES to the upstream
# MongoDB Database Tools that WeKan's bundled bsondump/mongodump/... are built
# from, replacing the wekan/mongo-tools source fork.
mongo-tools-patches/
+4 -4
View File
@@ -6,9 +6,9 @@
meteor-base@1.5.2
# Build system
ecmascript@0.19.0-beta351.0
ecmascript@0.19.0
standard-minifier-js@3.2.0
rspack@1.2.0-beta351.0
rspack@1.2.0
# Declared explicitly because server/modernBrowsers.js imports it, to say that
# Yandex Browser is modern and must not be served the legacy bundle (#6557).
modern-browsers@0.2.3
@@ -19,10 +19,10 @@ blaze@3.0.2
aldeed:collection2
aldeed:schema-index@4.0.0-beta.5
reywood:publish-composite
mongo@2.5.0-beta351.0
mongo@2.5.0
# Account system
accounts-password@3.3.1-beta351.0
accounts-password@3.3.1
accounts-express@1.0.0
communitypackages:core
communitypackages:unstyled
+1 -1
View File
@@ -1 +1 @@
METEOR@3.5.1-beta.0
METEOR@3.5.1
+14 -14
View File
@@ -1,14 +1,14 @@
accounts-base@3.3.0
accounts-express@1.0.0
accounts-oauth@1.4.7
accounts-password@3.3.1-beta351.0
accounts-password@3.3.1
aldeed:collection2@4.2.1
aldeed:schema-index@4.0.0
aldeed:simple-schema@1.13.1
allow-deny@2.1.0
audit-argument-checks@1.0.8
autoupdate@2.0.1
babel-compiler@7.15.0-beta351.0
babel-compiler@7.15.0
babel-runtime@1.5.2
base64@1.0.14
binary-heap@1.0.13
@@ -24,13 +24,13 @@ communitypackages:flow-routing@2.1.0
communitypackages:unstyled@2.0.1
core-runtime@1.0.0
ddp@1.4.2
ddp-client@3.4.0-beta351.0
ddp-client@3.4.0
ddp-common@1.4.5
ddp-rate-limiter@1.3.0
ddp-server@3.4.0-beta351.0
ddp-server@3.4.0
diff-sequence@1.1.3
dynamic-import@0.7.4
ecmascript@0.19.0-beta351.0
ecmascript@0.19.0
ecmascript-runtime@0.8.3
ecmascript-runtime-client@0.13.0
ecmascript-runtime-server@0.11.1
@@ -44,7 +44,7 @@ hot-code-push@1.0.5
html-tools@2.0.1
htmljs@2.0.2
id-map@1.2.0
inter-process-messaging@0.1.3-beta351.0
inter-process-messaging@0.1.3
jquery@3.0.2
lai:collection-extensions@1.0.0
localstorage@1.2.1
@@ -56,16 +56,16 @@ meteortesting:browser-tests@1.8.0
meteortesting:mocha@3.3.0
meteortesting:mocha-core@8.2.0
minifier-css@2.0.2
minifier-js@3.3.0-beta351.0
minifier-js@3.3.0
minimongo@2.2.0
modern-browsers@0.2.3
modules@0.20.3
modules-runtime@0.13.2
mongo@2.5.0-beta351.0
mongo@2.5.0
mongo-decimal@0.2.0
mongo-dev-server@1.1.1
mongo-id@1.0.9
npm-mongo@6.16.2-beta351.0
npm-mongo@6.16.2
oauth@3.0.4
oauth2@1.3.3
observe-sequence@2.0.1
@@ -87,12 +87,12 @@ reload@1.3.2
retry@1.1.1
reywood:publish-composite@1.9.0
routepolicy@1.1.2
rspack@1.2.0-beta351.0
rspack@1.2.0
service-configuration@1.3.5
session@1.2.3
sha@1.0.10
shell-server@0.7.0
socket-stream-client@0.7.1-beta351.0
socket-stream-client@0.7.1
spacebars@2.0.1
spacebars-compiler@2.0.1
standard-minifier-js@3.2.0
@@ -100,11 +100,11 @@ templating@1.4.5
templating-compiler@2.0.1
templating-runtime@2.0.2
templating-tools@2.0.1
tools-core@1.2.0-beta351.0
tools-core@1.2.0
tracker@1.3.4
typescript@5.11.0-beta351.0
typescript@5.11.0
url@1.3.6
webapp@2.3.0-beta351.0
webapp@2.3.0
webapp-hashing@1.1.2
wekan-accounts-cas@0.2.0
wekan-accounts-lockout@1.1.0
+21 -6
View File
@@ -19,9 +19,24 @@ npm-packages/
# ENOSPC from inotify_add_watch means "watch limit reached", NOT a full disk.
/.tools/
# The FerretDB v1 fork checkout: a complete Go project + its docs, 9,148 more
# directories. Nothing in WeKan imports from it - the FerretDB binary is
# downloaded from its GitHub releases at build time - so Meteor has no reason to
# watch or scan it. Both are already in .gitignore, but the watcher only honours
# .meteorignore.
/FerretDB/
# DO NOT ADD /_build/ OR /_build-local-test/ HERE. They look like build output
# that should be ignored - they are gitignored, and they are the first two
# entries of the 296 KB pattern list above - and excluding them breaks the
# build outright:
#
# error: Could not find mainModule for 'os' architecture:
# _build/main-prod/server-meteor.js
# Check the "meteor" section of your package.json file?
#
# They are not leftovers, they are the HANDOFF. rspack compiles the app into
# _build/main-prod/ (and _build/test/ for a test run), and Meteor then reads
# _build/main-prod/server-meteor.js and client-meteor.js as the application's
# main modules - package.json names client/main.js and server/main.js, and the
# rspack integration repoints mainModule at its own output. Ignoring them hides
# the files Meteor is about to be handed. They cost 3 directories each, so there
# was nothing to win here anyway.
# Local build and test logs. WEKAN_LOG_ROOT prefers ../log/<datetime>/, but
# falls back to ./log when the parent is not writable - which is exactly the
# case inside the Flatpak sandbox, so this directory does appear here.
/log/
+453
View File
@@ -0,0 +1,453 @@
# AGENTS.md — instructions Codex reads first
Codex reads this file at the repo root before doing work here. Follow it.
## First: are you the maintainer or a contributor?
Check the current git identity before committing or releasing:
```
git config user.name && git config user.email
```
- **Maintainer mode** — ONLY when the identity is exactly
`Lauri Ojansivu <x@xet7.org>` (name `Lauri Ojansivu`, email `x@xet7.org`). Then, and
only then: commit **directly to the current branch** as `Lauri Ojansivu <x@xet7.org>`
with no AI trailer and no pull request, and the **publishing / release steps** below
are available.
- **Contributor mode** — any other git identity. Then: do **not** commit directly to
the branch and do **not** run any release/publishing step. Make changes on a branch
and open a **pull request** for the maintainer to review. The "commit as Lauri
Ojansivu", "commit directly", and all release instructions below are **maintainer-only
and do not apply to you**.
Everything below marked as maintainer-specific (committing directly, the exact commit
author, and the entire "Making a release" / publishing flow) applies only in maintainer
mode. The rest (translation policy, CHANGELOG structure, tests, validating from code) is
good practice for everyone.
## Process: "Fix open issues"
When asked to fix an open issue (one issue at a time):
1. **Read the whole issue** — the description AND all comments (e.g.
`gh issue view <n> --comments`, or the GitHub API `.../issues/<n>/comments`).
2. **If it is a bug, first check whether it is already fixed** in the current WeKan
AND FerretDB source code (read the actual code, not just changelogs). It may have
been fixed already.
3. **If it is NOT fixed yet, fix it**, using the newest documentation and issues of
the dependencies involved, checking where the newest MAINTAINED dependency lives and
reading its source and issues:
- Meteor 3 docs — https://docs.meteor.com/
- Meteor 3 source + issues — https://github.com/meteor/meteor/issues
- other Meteor software docs/code (e.g. Rocket.Chat) for how they solved it
- Meteor forums — https://forums.meteor.com
- AtmosphereJS — https://atmospherejs.com/ ; Packosphere — https://packosphere.com/
- Meteor Community Packages — https://github.com/meteor-community-packages
- Meteor-Files — https://github.com/veliovgroup/Meteor-Files
- npm — https://www.npmjs.com
4. **After fixing, add tests** — unit tests, negative tests, and UI tests where
appropriate — and **run the new tests to verify they pass** (see Tests / the sandbox
build+test instructions in `docs/Security/Sandboxes/vscode/README.md`).
5. **Commit** (maintainer only — when the current git user is `Lauri Ojansivu
<x@xet7.org>`), with no "Co-Authored" / AI trailer, a message body ending:
```
Thanks to (GitHub nickname of the issue creator) and xet7 !
Fixes #1234,
```
6. **If the bug is already fixed**, still add a commit that CLOSES the issue (a commit
whose message ends `Fixes #1234,`), noting where/when it was fixed.
Fix from source and test — do not guess. If the environment cannot run the relevant
test (e.g. Docker is unavailable in this sandbox), reproduce as closely as possible
from source and say clearly what was and was not verified.
## Translations (Transifex ↔ direct LLM fill, no external service)
WeKan translations live in `imports/i18n/data/<lang>.i18n.json` (flat
`key -> string`, 2-space indent, key order matches `en.i18n.json`). Transifex holds
the human translations. **The policy is: never overwrite a human translation with a
filled (or English) one, but always take the newest translations from Transifex.**
- Pull with `releases/translations/pull-translations.sh`. It runs `tx pull -a -f`
(which fills every string that is UNtranslated on Transifex with the English source)
and then a **per-key merge** (`releases/translations/merge-translations.mjs`) that,
for every language file and every key:
- Transifex has a real translation (pulled value differs from English) → **keep it**
(the newest human translation always wins);
- the pull reverted it to English but a human translation is committed in git →
**restore the committed translation** (a pull never reverts a human translation,
even in files that also received real new Transifex translations);
- **no translation anywhere** (untranslated on Transifex AND never committed) → leave
the English source as a placeholder. **This is the only case a non-human value is
used.** A separate fill step may fill *only* these English placeholders, so a filled
string can never overwrite a human translation.
- Restored languages are pushed back to Transifex so they stop reverting.
### Filling the remaining untranslated strings — directly, no translation service
The strings still equal to the English source after the merge are the ones untranslated
**everywhere** (Transifex + git). Translate these **directly** — the maintainer or the
assistant (an LLM) writes the translation itself, using that language's **existing
translations** and general **kanban terminology** for the language as the reference.
**Do NOT wire up any external translation service, API, endpoint, key or password** — an
earlier `machine-translate.mjs` that called LibreTranslate/DeepL is removed on purpose
(it did not work and needed a password). There is **no `WEKAN_MT*` env var** anymore.
- `node releases/translations/fill-translations.mjs --missing` — per-language count of
strings still needing translation (English + `en-*` variants are skipped: English by
design). Also printed at the end of `pull-translations.sh`.
- `node releases/translations/fill-translations.mjs --list <lang> [--limit N]` — dump the
untranslated keys of a language as `{ key: englishSource }` for the translator to fill.
- `node releases/translations/fill-translations.mjs --apply <lang> <translated.json>` —
merge the translations back. It writes **only** into placeholder keys, so it can
**never** overwrite a human translation, and a value still equal to English/empty is
ignored. Key order and 2-space indent are preserved.
**Both directions are safe, and it is verified:**
`node releases/translations/verify-human-preference.mjs` proves (pure-logic, no network)
that the pull-merge keeps/restores human translations and that a fill only touches
placeholders. Filled strings stay **local** — they are **NOT** pushed to Transifex (only
the merge-restored human languages are pushed), so a filled string can never masquerade
as human there. So: **only missing strings are ever filled, only when missing
everywhere, human strings are always preferred and merged**, and nothing you fill is
pushed to Transifex as if it were human.
## General practices (from ../log/v10/Claude.txt)
- **[maintainer only]** Commit as `Lauri Ojansivu <x@xet7.org>`, with **no**
"Co-Authored-By" or any other AI trailer, directly to the `main` branch of WeKan and
the `main-v1` branch of the FerretDB fork. **Do not make pull requests.** (Contributors
do the opposite: work on a branch and open a pull request — see the top section.)
- Lauri Ojansivu (xet7) maintains WeKan (https://wekan.fi) and the FerretDB v1 fork.
- Directory structure:
- `wekan` — this repo (https://github.com/wekan/wekan); see
`docs/DeveloperDocs/Directory-Structure.md`; `CHANGELOG.md` at root.
- `../w/wekan.fi` — the WeKan website.
- `FerretDB` subdirectory (when present) — https://github.com/wekan/FerretDB, its own
`CHANGELOG.md`. FerretDB `.go` files must contain **no** application-specific names
(say "the client" / "a Meteor 3 driver" / a bare `#NNNN`); its `CHANGELOG.md` may
use `wekan/wekan#NNNN`.
- `sandstorm` subdirectory (when present) — https://github.com/sandstorm-io/sandstorm.
### CHANGELOG
- During development, add entries under a new `# Upcoming WeKan ® release` section above
the newest release (FerretDB uses `## Upcoming FerretDB release`). Do **not** hand-edit
`package.json` or any other version reference — the release workflow bumps those.
- **The file's shape, top to bottom** — keep it exactly as it is now:
1. `# Platforms` — the line `Newest WeKan at these platforms:` and the Install /
Upgrade / Docs / Mac ChangeLog bullets, then a `<details>` whose `<summary>` is
`Version` holding "which WeKan version uses what". There is no `# Version`
heading of its own.
2. `# TODO Later` — a `<details>` whose `<summary>` is `Carried to a future
release.` explaining the list, then one `<details>` per category (below).
3. The releases, newest first, each `# v<MAJOR>.<MINOR> YYYY-MM-DD WeKan ® release`.
Nothing else is an `#` heading. A `##`/`###` inside a release would break the
version list, and a wrapped line that BEGINS with `#` (e.g. an issue number such
as `#6514`) becomes a heading too — escape it `\#6514` or keep it off the line
start.
- **A release's order of subsections** — CRITICAL SECURITY ISSUES first, then new
features, then the Admin Panel / UI reorganisation if there is one, then dependency
updates (`Thanks to dependabot.`), then bug fixes, then developer-facing changes,
documentation and translations. The section ends with the line `Thanks to above
GitHub users for their contributions and translators for their translations.`
- **Every entry is a `<details>` block, and they are what a release is made of.**
The `<summary>` is the SHORT description of what was done and IS the link to the
commit — the hash lives in the `href` and is never on the page — followed by the
`Thanks to …` sentence. Clicking it reveals the long description:
```
<details>
<summary><a href="https://github.com/wekan/wekan/commit/<hash>">Short description of
what was done</a>. Thanks to (issue creator's GitHub nickname) and xet7.</summary>
The long description: what was wrong, why, what it does now, what the test pins.
Word-wrapped at 80 characters, ordinary markdown - links, `code`, emphasis.
</details>
```
Exactly as above: `<details>` and `</details>` each on their own line, the whole
`<summary>…</summary>` on ONE line, a blank line under it, the body, a blank line,
the close — and a blank line between two blocks.
- **The summary is one line at a glance** — aim for ≤ 110 characters — and PLAIN text:
no `[text](url)`, no `**bold**`, no backticks inside `<summary>`, because a link
cannot nest inside the `<a>` and the rest renders literally. It ends with a full
stop, then `Thanks to …`. Every summary in a release links its own commit.
- **A change with nothing more to say stays a plain bullet** —
`- [Short description](https://github.com/wekan/wekan/commit/<hash>). Thanks to xet7.`
— and a dependency batch keeps its `- **package 1.2.3 → 1.2.4** — one line on what
it is` bullets, closing with `Thanks to dependabot.` A `<details>` whose body only
repeats its summary is noise; use one when there IS a longer story to reveal, which
is most fixes.
- **`# TODO Later` blocks are the same shape with two differences:** the `<summary>` is
the short category text (no `<a>`, because nothing was committed), and there is **no
`Thanks to`** — nothing is done yet, so there is nobody to thank. The body lists the
issues as `[#NNNN](https://github.com/wekan/wekan/issues/NNNN) (one-line reason)`.
- **The hash is never the link text.** `[f1c89548e](…)` shows a hash to a reader who
cannot do anything with it; the link text says what changed. Same for `[merge
commit](…)` and for a bare URL — see the next rule.
- **Never show a long URL as visible text.** A link is always
`[short text](url)` — an issue is `[#6524](…/issues/6524)`, an advisory is
`[GHSA-xxxx](…)`, a security page is `[ZipBleed](https://wekan.fi/hall-of-fame/zipbleed/)`.
- **Subsection headers read as ONE flowing sentence.** The FIRST subsection of a release
starts with `This release ` (e.g. `This release fixes the following bugs:`); every LATER
subsection in the SAME release starts with a lowercase `and ` instead of repeating
`This release ` — e.g.
`This release fixes the following bugs:` … `and updates the following dependencies:` …
`and has the following developer-tooling fix:` … `and improves the translation workflow:`
… then the closing `Thanks to above GitHub users …` line. A release with only one
subsection just keeps its single `This release …:` header. Use `and adds the following
new features:`, `and fixes the following bugs:`, `and updates the following
dependencies:`, etc., matching the verb to the subsection.
- **The Upcoming section opens with an `**In short:**` paragraph.** One paragraph,
right under the `# Upcoming WeKan ® release` line and above the first
`This release …:` header, saying what the whole release amounts to — the areas
that changed and what changed about them, with the notable names in `**bold**`
so it can be skimmed. It is a SUMMARY, not a list: it does not link commits, it
does not repeat an entry's wording, and it ends by accounting for the rest in a
clause (`Below that: dependency updates, nine bug fixes … and the usual
documentation and translation work.`). Keep it current as entries are added —
it is the first thing a reader sees, so a stale one misdescribes the release.
A finished release keeps the paragraph it was written with.
- **Under the summary comes the BINARIES TABLE: what each platform ships.** So the
top of a release section is, in order, (1) the `**In short:**` paragraph and
(2) this table, and only then the `This release …:` subsections. A WeKan bundle
is not only WeKan — it carries a Node.js, a FerretDB and the MongoDB Database
Tools that other projects publish, and WHICH source has a given CPU changes
from release to release: nodejs.org builds some architectures,
unofficial-builds others, and [wekan/node-patches](https://github.com/wekan/node-patches)
the ones neither of them does. "Which Node.js is in the arm64 bundle of 10.69,
and was it checked" must be answerable from the CHANGELOG, not from a build log
that expires.
```
| Platform | Binary | From | Version | SHA256 |
| --- | --- | --- | --- | --- |
| amd64 | Node.js | [nodejs.org](https://nodejs.org/dist/v24.19.0/node-v24.19.0-linux-x64.tar.xz) | v24.19.0 | `a1b2…` |
| amd64 | FerretDB | [wekan/FerretDB](https://github.com/wekan/FerretDB/releases/download/v1.24.0/ferretdb-amd64) | v1.24.0 | `c3d4…` |
| arm64 | Node.js | [nodejs.org](https://nodejs.org/dist/v24.19.0/node-v24.19.0-linux-arm64.tar.xz) | v24.19.0 | `e5f6…` |
```
**GROUPED BY PLATFORM**: rows are sorted by platform and then by binary, so one
platform's binaries stay together and the table is read a platform at a time.
The URL is the LINK ON THE "From" CELL — never a bare URL as visible text, the
same rule as everywhere else — and it is the exact file that was downloaded, not
the project's front page. The SHA256 is the checksum the source published and the
build verified, in backticks; a source that publishes none says *no checksum
published*, which is not a failed check but a source worth fixing. Table rows
carry links, so the 80-character wrap does not apply to them.
It is the same table `releases/provenance-table.sh` puts at the top of the GitHub
release notes, from the `provenance.tsv` rows each build job records — so the two
are filled from the same data and cannot disagree. **A platform that was NOT built
has no rows**, which is how the table also answers "why is there no i386 bundle
this time": no source published a Node.js for it (see
`releases/resolve-node-source.sh`).
- **Inside a subsection, entries are GROUPED BY AREA.** A release touches a handful
of areas and repeating the area's name in every summary is the noise this
removes — twelve entries that each begin "All Boards:" say "All Boards" twelve
times and the part that differs starts halfway through the line. Instead the area
is named ONCE, as a bold line with a short description of what the group covers,
and every entry under it drops the prefix:
```
and fixes the following bugs:
**The first header bar** - how it lays itself out, and what sits under it.
<details>
<summary><a href="…">It fills each row before starting the next one</a>. Thanks to xet7.</summary>
…
</details>
<details>
<summary><a href="…">It wraps to a second row instead of hiding the buttons that do not fit</a>. Thanks to xet7.</summary>
…
</details>
**All Boards** - the overview and its search.
<details>
…
```
The group line is `**Area** - short description.` on ONE line, wrapped at 80 like
everything else, with a blank line under it. It is NOT a heading: a `##` inside a
release breaks the version list (see above). Group labels are the areas of the
app — `All Boards`, `The first header bar`, `The left menus`, `The Admin Panel`,
`Board views`, `Public Boards`, `Member Settings`, `Board roles` — and the same
label is reused across subsections when an area has both a feature and a fix.
EVERY entry of a grouped subsection belongs to a group, including a group with
one entry: a section that is half grouped and half loose reads as a mistake. A
subsection with only one entry, and the dependency bullets, stay flat.
- **CRITICAL security header — match the previous releases' wording.** A security release
leads with `This release fixes the following CRITICAL SECURITY ISSUE of
[Name](https://wekan.fi/hall-of-fame/namebleed/):` for a single named *Bleed, or
`This release fixes the following CRITICAL SECURITY ISSUES:` for several — NOT
`CRITICAL VULNERABILITIES`. Each security bullet still links its own
`https://wekan.fi/hall-of-fame/...bleed/`. CodeQL-only releases use the established
`This release fixes the following SECURITY ISSUES found by GitHub CodeQL code scanning:`.
Because CRITICAL comes first, it keeps the `This release ` prefix; a following non-security
subsection becomes `and …` per the rule above.
- **`# TODO Later` section** — a triage backlog near the TOP of `CHANGELOG.md` (above the
version sections), for open issues that were **investigated but not fixed here**, each
recorded with a concrete REASON so whoever picks it up next knows why. Use it when working
through open issues (the "Fix open issues" process): for each issue, either **fix it** (commit
ending `Fixes #NNNN,`), **close it** if already fixed in current code (commit `Close #NNNN` /
`Fixes #NNNN,`), or — when it can't be fixed/verified in this environment — **add it to `TODO
Later` under the matching category with the reason**. Group issues by category bullet, e.g.:
*Need specific infrastructure / a running server stack* (LDAP/WebHooks/Sandstorm/proxy —
environment owners), *Need the running app to reproduce/verify* (runtime UI / publication /
mergebox / router state, not unit-testable), *Already correct in the current code* (verified by
reading; could not reproduce), *Feature requests / behaviour-by-design rather than bugs*, and
*Needs a maintainer decision on the intended contract*. Each category is a `<details>`
whose `<summary>` is the SHORT category text and whose body lists the issues with their
reasons — and with **no `Thanks to` line**, because nothing is done yet, so there is
nobody to thank. Each entry is a normal issue link
`[#NNNN](https://github.com/wekan/wekan/issues/NNNN) (one-line reason)` — issue links here, NOT
commit links (nothing was committed). **Keep it current:** when an issue in `TODO Later` gets
fixed, REMOVE it from the list (its fix commit's `Fixes #NNNN` closes it); do not leave fixed
issues in the backlog. This is the only CHANGELOG place that uses issue links and lives above
the releases; everything else uses the per-release commit-link bullets above.
- FerretDB Upcoming structure — `### New Features 🎉`, `### Fixed 🐛`, `### Other Changes
🤖`; entries end `... by @xet7. Thanks to xet7.`
- Word-wrap both CHANGELOGs at 80 chars, but never break a long link across lines (a
`<summary>` line, or any other line carrying a link, may be longer). Continuation
lines of a plain bullet are indented by two spaces.
### Commit message structure
```
Do something.
Thanks to (original creator of issue) and xet7 !
Fixes #1234,
Fixes #1235.
```
### Making a release — no version number needed **[maintainer only]**
All publishing / release steps below are maintainer-only. Contributors never run them.
- WeKan: run `./releases/release-all.sh` (no arguments). It renames
`# Upcoming WeKan ® release` to the next version (same increment as the last release;
9.99 → 10.00) dated today, commits + pushes, and triggers
`.github/workflows/release-all.yml` (its `bump` job bumps `package.json` and every
version reference, then the release jobs tag `v<new>` and publish the GitHub Release).
An explicit `oldversion newversion` pair still overrides. Adding entries under Upcoming
is the only hand step.
- FerretDB: run `./build.sh release-ferretdb` from the `FerretDB` subdirectory (no
version). It renames `## Upcoming FerretDB release` to the next version with the
correct git-tag link, commits + tags + pushes, then triggers `release-all.yml` (which
in turn triggers `docker.yml` for the multi-arch image). It refuses to re-release an
already-tagged version.
### Security issues
- Fix the vulnerability, add a CRITICAL section to the WeKan CHANGELOG like previous
entries, and update `../w/wekan.fi/hall-of-fame/index.html` and the vuln-name
subdirectory `index.html` like previous security issues.
- **A Hall of Fame row has EIGHT cells**, in this order: CVE, Icon, Vulnerability
name, Date, Responsible Security Disclosure by, Stars, Process, Vulnerabilities.
One thing per cell — the name without its icons, the Font Awesome icons alone in
Icon (still wrapped in `<h2>`), the reporter alone in "by", the `GoldStar.png`
images alone in Stars, and the sentence about how the report arrived (coordinated
disclosure, GitHub advisory, code scanning, found while reviewing …) in Process.
In "by", the reporter's NICKNAME is the link —
`<b><a href="https://github.com/nick">nick</a></b>`, never the name followed by a
separate "(GitHub)" link. **Check the account exists before linking it** (a 404 or,
worse, somebody else's profile is the failure here); a real name, a company or a
tool such as GitHub CodeQL is not a GitHub user and stays plain text. Do not write
a role note after a name — no `(fix)`, `(found)` or `(found and fix)`: this column
is the credit, and the Process cell beside it already says how the report arrived.
Stars is a column so it can be compared down the page; do not put a star, an icon
or the process sentence back into another cell. A page-local `<style>` in
`index.html` keeps a row's stars on ONE line from 900px up (they are a count) and
sizes them at 24px, and keeps the icon and its red drop on one line at every width
(they are one icon) — the table itself stays percentage-sized with no fixed or
minimum width, so it fills the page at every browser width.
- **On the Hall of Fame index page, the Process and Vulnerabilities cells are
collapsed.** Those two columns carry the prose — how the report arrived, and the
whole story of the vulnerability — so the page would be one wall of text and the
columns that identify a row (CVE, name, date, reporter, stars) would be far apart.
In `hall-of-fame/index.html`, and ONLY in those two `<td>`s, the cell's content
sits inside a `<details>` whose summary is the column's own name — `Process` for
the Process cell, `Details` for the Vulnerabilities cell:
```
<td valign="top">
<details>
<summary>Details</summary>
<ul>
<li>… the same list as before …</li>
</ul>
</details>
</td>
```
The summary is that plain word, there is a blank line under it, and the cell's
contents are otherwise unchanged. The other six cells of the row stay as they are
— they are what a reader scans — and so does the header row.
- **The vuln-name subdirectory page is NOT collapsed.** `hall-of-fame/<name>bleed/index.html`
is the page a reader opened on purpose, for one vulnerability: its table cell stays
open and its `<h2 class="hof">Details</h2>` prose section below the table stays as
it is. Only the index, which lists them all, hides them behind the summary.
### Tests
- `build.sh` / `build.bat` have a menu to install dependencies, build
WeKan and run tests.
- Add tests, negative tests and UI tests for all new features and fixes that do not yet
have tests. When adding a test, run or validate it and fix it until it works.
- **"Check newest test logs":** test logs go into a `log/<datetime>/` directory,
one per run (e.g. `2026-07-21_20-58-09/`). That is **`../log/`**, one level up
from the repo, so a run does not show up in `git status` - **unless the parent
is not writable**, as in a Flatpak sandbox that shares only the repository;
there it is **`./log/`** inside the repo (gitignored). `build.sh` prints which
one when a run starts. The
newest datetime directory is the latest run. Each holds the Playwright per-browser
logs (`wekan-alltests-chromium.log`, `-firefox.log`, `-webkit.log`), the mocha/unit
log (`wekan-alltests-mocha.log`), the e2e/import logs, and `wekan-test-server.log`
(the WeKan test server + database output). A whole-run directory also holds the
database-conformance logs (`db-conformance-*.log`, `-report.md`, `-summary.txt`)
and FerretDB's own (`ferretdb-unit.log`, `-vet.log`, `-integration.log`). "Check
the newest test logs" means: open the most recent `log/<datetime>/` - look in
`../log/` first, then `./log/` - and read those.
- **Check and fix WHILE the tests are still running.** A full run takes a long time
(three browsers, then every database with an image for this CPU, then FerretDB's
own suites), and its stages finish one at a time. Do not wait for the end: read
the logs that are already written, fix what they show, and then look again for the
stages that have finished since — repeat until every stage has run and everything
found is fixed. Two things make this work:
- A stage's log file is complete when its `===== ... finished` line is there; a
file whose mtime is still moving is a stage in progress, and its failures so far
are already real and worth fixing.
- The node suites are run by `tests/run-node-suites.cjs`, which runs ALL of them
and lists every failure at the end (`===== node suites: N run, M failed`). They
are the fastest signal - about 15 seconds for 260 suites - so they are usually
what to fix first while the browsers are still going.
- **A failing guard is not automatically a broken app.** Most of these suites read
the source and pin a behaviour. When one fails, decide which side is wrong: fix
the CODE when the guard still describes what WeKan should do, and fix the GUARD
when the behaviour deliberately changed - and then say in the test WHY, so the
next reader knows it was a decision and not a slip. Every fixed test keeps the
assertion that made it valuable; do not delete a test to make a run green.
## Environment
- The editor (VSCode) runs inside a **Flatpak sandbox**, launched by
`docs/Security/Sandboxes/vscode/vscode-sandbox.sh`. What the sandbox allows/blocks and
how it is set up is documented in `docs/Security/Sandboxes/vscode/README.md` in that
same directory — read it when something behaves differently than a normal host (file
access, network, running services).
### Always validate from the actual code
- When doing anything, check how it actually works in the code first.
+4147 -21
View File
File diff suppressed because it is too large. Load diff
+51 -10
View File
@@ -14,8 +14,7 @@ git config user.name && git config user.email
`Lauri Ojansivu <x@xet7.org>` (name `Lauri Ojansivu`, email `x@xet7.org`). Then, and
only then: commit **directly to the current branch** as `Lauri Ojansivu <x@xet7.org>`
with no AI trailer and no pull request, and the **publishing / release steps** below
are available. (Per the standing rule you still **commit only; do not push** unless
explicitly asked.)
are available.
- **Contributor mode** — any other git identity. Then: do **not** commit directly to
the branch and do **not** run any release/publishing step. Make changes on a branch
and open a **pull request** for the maintainer to review. The "commit as Lauri
@@ -122,17 +121,21 @@ pushed to Transifex as if it were human.
"Co-Authored-By" or any other AI trailer, directly to the `main` branch of WeKan and
the `main-v1` branch of the FerretDB fork. **Do not make pull requests.** (Contributors
do the opposite: work on a branch and open a pull request — see the top section.)
- **[maintainer only]** **Commit only. Do not push** (unless explicitly asked).
- Lauri Ojansivu (xet7) maintains WeKan (https://wekan.fi) and the FerretDB v1 fork.
- Directory structure:
- `wekan` — this repo (https://github.com/wekan/wekan); see
`docs/DeveloperDocs/Directory-Structure.md`; `CHANGELOG.md` at root.
- `../w/wekan.fi` — the WeKan website.
- `FerretDB` subdirectory (when present) — https://github.com/wekan/FerretDB, its own
- `.tools/` — companion repos cloned INSIDE this checkout, in one directory
that `.gitignore` and `.meteorignore` already exclude (they used to be one
ignored subdirectory each at the repo root). `build.sh`'s `ensure_tool_repo`
clones one on demand, so a fresh checkout needs no manual setup:
`.tools/FerretDB`, `.tools/node-patches`, `.tools/mongo-tools-patches`.
- `.tools/FerretDB` (cloned on demand) — https://github.com/wekan/FerretDB, its own
`CHANGELOG.md`. FerretDB `.go` files must contain **no** application-specific names
(say "the client" / "a Meteor 3 driver" / a bare `#NNNN`); its `CHANGELOG.md` may
use `wekan/wekan#NNNN`.
- `sandstorm` subdirectory (when present) — https://github.com/sandstorm-io/sandstorm.
- `.tools/sandstorm` (when present) — https://github.com/sandstorm-io/sandstorm.
### CHANGELOG
@@ -216,6 +219,40 @@ pushed to Transifex as if it were human.
documentation and translation work.`). Keep it current as entries are added —
it is the first thing a reader sees, so a stale one misdescribes the release.
A finished release keeps the paragraph it was written with.
- **Under the summary comes the BINARIES TABLE: what each platform ships.** So the
top of a release section is, in order, (1) the `**In short:**` paragraph and
(2) this table, and only then the `This release …:` subsections. A WeKan bundle
is not only WeKan — it carries a Node.js, a FerretDB and the MongoDB Database
Tools that other projects publish, and WHICH source has a given CPU changes
from release to release: nodejs.org builds some architectures,
unofficial-builds others, and [wekan/node-patches](https://github.com/wekan/node-patches)
the ones neither of them does. "Which Node.js is in the arm64 bundle of 10.69,
and was it checked" must be answerable from the CHANGELOG, not from a build log
that expires.
```
| Platform | Binary | From | Version | SHA256 |
| --- | --- | --- | --- | --- |
| amd64 | Node.js | [nodejs.org](https://nodejs.org/dist/v24.19.0/node-v24.19.0-linux-x64.tar.xz) | v24.19.0 | `a1b2…` |
| amd64 | FerretDB | [wekan/FerretDB](https://github.com/wekan/FerretDB/releases/download/v1.24.0/ferretdb-amd64) | v1.24.0 | `c3d4…` |
| arm64 | Node.js | [nodejs.org](https://nodejs.org/dist/v24.19.0/node-v24.19.0-linux-arm64.tar.xz) | v24.19.0 | `e5f6…` |
```
**GROUPED BY PLATFORM**: rows are sorted by platform and then by binary, so one
platform's binaries stay together and the table is read a platform at a time.
The URL is the LINK ON THE "From" CELL — never a bare URL as visible text, the
same rule as everywhere else — and it is the exact file that was downloaded, not
the project's front page. The SHA256 is the checksum the source published and the
build verified, in backticks; a source that publishes none says *no checksum
published*, which is not a failed check but a source worth fixing. Table rows
carry links, so the 80-character wrap does not apply to them.
It is the same table `releases/provenance-table.sh` puts at the top of the GitHub
release notes, from the `provenance.tsv` rows each build job records — so the two
are filled from the same data and cannot disagree. **A platform that was NOT built
has no rows**, which is how the table also answers "why is there no i386 bundle
this time": no source published a Node.js for it (see
`releases/resolve-node-source.sh`).
- **Inside a subsection, entries are GROUPED BY AREA.** A release touches a handful
of areas and repeating the area's name in every summary is the noise this
removes — twelve entries that each begin "All Boards:" say "All Boards" twelve
@@ -310,7 +347,7 @@ All publishing / release steps below are maintainer-only. Contributors never run
version reference, then the release jobs tag `v<new>` and publish the GitHub Release).
An explicit `oldversion newversion` pair still overrides. Adding entries under Upcoming
is the only hand step.
- FerretDB: run `./build.sh release-ferretdb` from the `FerretDB` subdirectory (no
- FerretDB: run `./build.sh release-ferretdb` from `.tools/FerretDB` (no
version). It renames `## Upcoming FerretDB release` to the next version with the
correct git-tag link, commits + tags + pushes, then triggers `release-all.yml` (which
in turn triggers `docker.yml` for the multi-arch image). It refuses to re-release an
@@ -374,16 +411,20 @@ All publishing / release steps below are maintainer-only. Contributors never run
WeKan and run tests.
- Add tests, negative tests and UI tests for all new features and fixes that do not yet
have tests. When adding a test, run or validate it and fix it until it works.
- **"Check newest test logs":** test logs are written outside this repo, in
`../log/<datetime>/` (one directory per run, e.g. `../log/2026-07-21_20-58-09/`). The
- **"Check newest test logs":** test logs go into a `log/<datetime>/` directory,
one per run (e.g. `2026-07-21_20-58-09/`). That is **`../log/`**, one level up
from the repo, so a run does not show up in `git status` - **unless the parent
is not writable**, as in a Flatpak sandbox that shares only the repository;
there it is **`./log/`** inside the repo (gitignored). `build.sh` prints which
one when a run starts. The
newest datetime directory is the latest run. Each holds the Playwright per-browser
logs (`wekan-alltests-chromium.log`, `-firefox.log`, `-webkit.log`), the mocha/unit
log (`wekan-alltests-mocha.log`), the e2e/import logs, and `wekan-test-server.log`
(the WeKan test server + database output). A whole-run directory also holds the
database-conformance logs (`db-conformance-*.log`, `-report.md`, `-summary.txt`)
and FerretDB's own (`ferretdb-unit.log`, `-vet.log`, `-integration.log`). "Check
the newest test logs" means: open the most recent `../log/<datetime>/` and read
those.
the newest test logs" means: open the most recent `log/<datetime>/` - look in
`../log/` first, then `./log/` - and read those.
- **Check and fix WHILE the tests are still running.** A full run takes a long time
(three browsers, then every database with an image for this CPU, then FerretDB's
own suites), and its stages finish one at a time. Do not wait for the end: read
+80 -30
View File
@@ -1,20 +1,25 @@
FROM ubuntu:26.04
# debian:trixie (Debian 13), not ubuntu:26.04: Ubuntu publishes no linux/386
# image, which stopped the image from ever building for i386. Debian ships every
# arch this image targets - amd64, arm64, 386, arm/v7, ppc64le, riscv64, s390x -
# so ONE base covers them all, and it is the same base WeKan's per-arch .zip
# bundles are already built in (releases/... build-extra-arches).
FROM debian:trixie
LABEL maintainer="wekan"
LABEL org.opencontainers.image.ref.name="ubuntu"
LABEL org.opencontainers.image.version="26.04"
LABEL org.opencontainers.image.ref.name="debian"
LABEL org.opencontainers.image.version="trixie"
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=10.55
ARG VERSION=10.78
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
ENV \
DEBUG=false \
NODE_VERSION=v24.18.1 \
NODE_VERSION=v24.19.0 \
METEOR_RELEASE=METEOR@3.5-rc.2 \
USE_EDGE=false \
NPM_VERSION=11.12.1 \
@@ -169,6 +174,13 @@ ENV \
MONGO_PASSWORD_FILE="" \
S3_SECRET_FILE=""
# Where this image's Node.js comes from is decided by the SAME script the .zip
# bundles and the snap use - official nodejs.org, then unofficial-builds, then
# wekan/node-patches - so the image and the bundle of the same architecture can
# never end up on Node.js from different places. It is copied in rather than
# reimplemented here; a second copy of that order would drift from the first.
COPY --chmod=755 releases/resolve-node-source.sh /tmp/resolve-node-source.sh
RUN <<EOR
set -o xtrace
# Fail hard on any error so a missing release zip / failed download can never
@@ -185,35 +197,73 @@ apt-get update --assume-yes
apt-get upgrade --assume-yes
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# Multi-arch mapping: Docker TARGETARCH -> Node.js arch name + WeKan bundle name.
# amd64/arm64 have MongoDB Community; ppc64le/s390x/riscv64 have no MongoDB server
# and ship FerretDB v1 instead (the ferretdb binary is baked into their .zip and
# started by wekan-entrypoint.sh). riscv64's Node.js 24 comes from
# unofficial-builds.nodejs.org (nodejs.org ships no riscv64). armv7l is excluded:
# there is no Node.js 24 build for it anywhere.
NODE_BASE="official"
# Multi-arch mapping: Docker TARGETARCH -> WeKan's own platform name, which is
# both the bundle .zip's name and what resolve-node-source.sh is asked about.
# amd64/arm64 have MongoDB Community; ppc64le/s390x/riscv64 have no MongoDB
# server and ship FerretDB v1 instead (the ferretdb binary is baked into their
# .zip and started by wekan-entrypoint.sh). Debian's 32-bit ARM port is armhf
# (ARMv7, VFPv3-D16), which is what linux/arm/v7 runs.
case "${TARGETARCH}" in
"amd64") NODE_ARCH="x64" WEKAN_ARCH="amd64" ;;
"arm64") NODE_ARCH="arm64" WEKAN_ARCH="arm64" ;;
"ppc64le") NODE_ARCH="ppc64le" WEKAN_ARCH="ppc64le" ;;
"s390x") NODE_ARCH="s390x" WEKAN_ARCH="s390x" ;;
"riscv64") NODE_ARCH="riscv64" WEKAN_ARCH="riscv64" NODE_BASE="unofficial" ;;
*) echo "Unsupported architecture: ${TARGETARCH}"; exit 1 ;;
"amd64") WEKAN_ARCH="amd64" ;;
"arm64") WEKAN_ARCH="arm64" ;;
"ppc64le") WEKAN_ARCH="ppc64le" ;;
"s390x") WEKAN_ARCH="s390x" ;;
"riscv64") WEKAN_ARCH="riscv64" ;;
"386") WEKAN_ARCH="i386" ;;
"arm") WEKAN_ARCH="armhf" ;;
*) echo "Unsupported architecture: ${TARGETARCH}${TARGETVARIANT:+/${TARGETVARIANT}}"; exit 1 ;;
esac
# Node.js installation. Official nodejs.org builds for amd64/arm64/ppc64le/s390x;
# unofficial-builds.nodejs.org for riscv64 (nodejs.org ships no riscv64 binary).
# Node.js installation - official nodejs.org, then unofficial-builds.nodejs.org,
# then wekan/node-patches, in that order, for EVERY architecture.
#
# The choice is made by releases/resolve-node-source.sh, copied in above and used
# by the .zip bundles and the snap as well, so the image and the bundle of one
# architecture always carry the same Node.js from the same place. It is asked
# about the MAJOR, so a CPU whose newest build lags a release still gets its
# newest: it answers with the exact file, what shape that file is, and the
# SHA256 the source published for it.
#
# npm is arch-independent JavaScript, so it is grafted from the official amd64
# tarball of the version that was resolved - a build-time tool, not the shipped
# node. (node-patches publishes a bare node binary and no npm at all, which is
# why npm is fetched separately rather than taken from the archive above.)
cd /tmp
if [ "${NODE_BASE}" = "unofficial" ]; then
NODE_DIST="https://unofficial-builds.nodejs.org/download/release/${NODE_VERSION}"
else
NODE_DIST="https://nodejs.org/dist/${NODE_VERSION}"
NODE_MAJOR="${NODE_VERSION#v}"; NODE_MAJOR="${NODE_MAJOR%%.*}"
if ! NODE_META="$(bash /tmp/resolve-node-source.sh "${WEKAN_ARCH}" "${NODE_MAJOR}")"; then
echo "No Node.js ${NODE_MAJOR}.x for ${WEKAN_ARCH} at nodejs.org, unofficial-builds.nodejs.org or wekan/node-patches, so this image cannot be built for it. Build node-${WEKAN_ARCH} in wekan/node-patches, or drop ${TARGETARCH} from the image's platform list." >&2
exit 1
fi
wget "${NODE_DIST}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
wget "${NODE_DIST}/SHASUMS256.txt"
grep " node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz\$" SHASUMS256.txt | shasum -a 256 -c -
tar xzf "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt
eval "${NODE_META}"
echo "Node.js ${node_full} for ${WEKAN_ARCH}: ${node_from} (${node_url})"
wget --tries=20 --waitretry=20 --retry-on-http-error=403,500,502,503 -O node-download "${node_url}"
echo "${node_sha256} node-download" | sha256sum -c -
case "${node_kind}" in
binary)
cp node-download /usr/local/bin/node
;;
tar.xz|tar.gz|tar)
# bsdtar (libarchive-tools) is installed below for the bundle; plain tar
# here, letting it detect the compression rather than naming it.
tar -xf node-download --no-same-owner "${node_member}"
cp "${node_member}" /usr/local/bin/node
;;
*)
echo "resolve-node-source.sh returned an unknown kind '${node_kind}'." >&2
exit 1
;;
esac
chmod +x /usr/local/bin/node
# npm + npx from the official amd64 tarball of the SAME version; extract only
# those paths, not the amd64 node binary.
wget "https://nodejs.org/dist/${node_full}/node-${node_full}-linux-x64.tar.gz"
wget "https://nodejs.org/dist/${node_full}/SHASUMS256.txt"
grep " node-${node_full}-linux-x64.tar.gz\$" SHASUMS256.txt | sha256sum -c -
tar xzf "node-${node_full}-linux-x64.tar.gz" -C /usr/local --strip-components=1 --no-same-owner \
"node-${node_full}-linux-x64/lib/node_modules/npm" \
"node-${node_full}-linux-x64/bin/npm" \
"node-${node_full}-linux-x64/bin/npx"
rm -rf node-download "node-${node_full}-linux-x64.tar.gz" "node-${node_full}-linux-x64" SHASUMS256.txt
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
# NPM configuration
@@ -244,7 +294,7 @@ mv /home/wekan/app/bundle /build
# offline downloads; the Docker image installs its own Node and uses
# wekan-entrypoint.sh, so drop the redundant bundled node + launchers. Keeps
# /build/ferretdb (used by the entrypoint) and the per-arch MongoDB Database Tools
# (bsondump, mongodump, mongorestore, … from wekan/mongo-tools, embedded in the
# (bsondump, mongodump, mongorestore, … from wekan/mongo-tools-patches, embedded in the
# bundle) for backup/restore inside the container. Saves ~80 MB per arch.
rm -f /build/node /build/start-wekan.sh /build/start-wekan.bat
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v10.55.0"
appVersion: "v10.78.0"
files:
userUploads:
- README.md
+171 -87
View File
@@ -100,12 +100,14 @@ echo.
echo -- Setup -- ^(0 = Back^)
echo 1^) Install dependencies
echo 2^) Build WeKan
echo 3^) Update git ^(fetch + rebase onto origin, fix CHANGELOG hashes, status^)
echo 3^) git pull ^(fetch, fast-forward or rebase, repoint moved CHANGELOG links^)
echo 4^) git push ^(check the CHANGELOG links, push, pull-and-retry once if rejected^)
set "choice="
set /p "choice=Choose: "
if "%choice%"=="1" goto install
if "%choice%"=="2" goto build
if "%choice%"=="3" goto updategit
if "%choice%"=="3" goto gitpull
if "%choice%"=="4" goto gitpush
if "%choice%"=="0" goto menu
goto menu_setup
@@ -138,8 +140,10 @@ REM ===========================================================================
:menu_tests
echo.
echo -- Tests -- ^(0 = Back^)
echo 1^) EVERYTHING ^(sequential^): WeKan's own tests, then every database with an
echo image for this CPU, then all FerretDB tests. Logs in ..\log\^<datetime^>\
echo 1^) EVERYTHING ^(sequential^): the floating-promises guard, then WeKan's own
echo tests ^(mocha, node suites, import, node E2E, three browsers^), then every
echo database with an image for this CPU, then all FerretDB tests ^(unit, vet,
echo integration^). Logs in ..\log\^<datetime^>\
echo 2^) WeKan's own tests only, parallel: Mocha, node unit suites, import, node
echo E2E and all three browsers, concurrently. No databases, no FerretDB
echo 3^) WeKan's own tests only, sequential: the same suite, one job at a time
@@ -299,6 +303,7 @@ if "%choice%"=="3" call :rel_run "releases/snap-push-to-store.sh" "Path to the .
if "%choice%"=="4" call :rel_run "releases/release-snap.sh" "WeKan version, e.g. 10.50"
if "%choice%"=="5" call :rel_run "releases/snap-store-revisions.sh" ""
if "%choice%"=="6" call :rel_run "releases/snap-store-release-revision-to-channels.sh" "Snap Store revision number"
if "%choice%"=="7" call :rel_run "releases/snap-release-all-channels.sh" "Version, or empty for the newest"
if "%choice%"=="7" call :rel_run "releases/snap-edge.sh" ""
if "%choice%"=="8" call :rel_run "releases/snap-stable.sh" ""
if "%choice%"=="9" call :rel_run "releases/snapcraft-help.sh" ""
@@ -556,6 +561,7 @@ echo snap-push-to-store Push one .snap to the Snap Store ^<P
echo release-snap Release the snap for one version ^<WeKan version, e.g. 10.50^>
echo snap-store-revisions List the newest Snap Store revisions
echo snap-store-release-revision-to-channels Release one store revision to edge, beta and candidate ^<Snap Store revision number^>
echo snap-release-all-channels Release every snap, every architecture, to all four channels ^<Version, or empty for the newest^>
echo snap-edge Switch the installed snap to the edge channel
echo snap-stable Switch the installed snap to the stable channel
echo snapcraft-help snapcraft help topics
@@ -638,6 +644,7 @@ if /I "%K%"=="snap-push-to-store" (set "CMD=bash releases/snap-push-to-store.sh"
if /I "%K%"=="release-snap" (set "CMD=bash releases/release-snap.sh" ^& goto cli_go)
if /I "%K%"=="snap-store-revisions" (set "CMD=bash releases/snap-store-revisions.sh" ^& goto cli_go)
if /I "%K%"=="snap-store-release-revision-to-channels" (set "CMD=bash releases/snap-store-release-revision-to-channels.sh" ^& goto cli_go)
if /I "%K%"=="snap-release-all-channels" (set "CMD=bash releases/snap-release-all-channels.sh" ^& goto cli_go)
if /I "%K%"=="snap-edge" (set "CMD=bash releases/snap-edge.sh" ^& goto cli_go)
if /I "%K%"=="snap-stable" (set "CMD=bash releases/snap-stable.sh" ^& goto cli_go)
if /I "%K%"=="snapcraft-help" (set "CMD=bash releases/snapcraft-help.sh" ^& goto cli_go)
@@ -672,6 +679,11 @@ if /I "%K%"=="push-copy-en-gb-translation" (set "CMD=bash releases/translations/
if /I "%K%"=="report-english-regressions" (set "CMD=node releases/translations/report-english-regressions.mjs" ^& goto cli_go)
if /I "%K%"=="verify-human-preference" (set "CMD=node releases/translations/verify-human-preference.mjs" ^& goto cli_go)
if /I "%K%"=="merge-translations" (set "CMD=node releases/translations/merge-translations.mjs" ^& goto cli_go)
REM `git pull` and `git push` are shell functions in build.sh, not scripts in
REM releases/, so this answers to the same two names with its own labels - the
REM ones the interactive menu already uses.
if /I "%K%"=="git-pull" (goto gitpull)
if /I "%K%"=="git-push" (goto gitpush)
if /I "%K%"=="commit" (set "CMD=bash releases/commit.sh" ^& goto cli_go)
if /I "%K%"=="git-add-revert" (set "SHCMD=git restore --staged" ^& goto cli_go)
if /I "%K%"=="delete-branch-local-and-remote" (set "CMD=bash releases/delete-branch-local-and-remote.sh" ^& goto cli_go)
@@ -799,51 +811,32 @@ echo Done.
goto end
REM ===========================================================================
REM Make the working copy current in one step: fetch + rebase the current branch
REM onto its upstream, repoint any CHANGELOG commit links the rebase made stale
REM (the same shared releases\fix-changelog-hashes.sh release-all.sh uses), and
REM show the status. Parity with build.sh's Setup -> "Update git ...".
:updategit
echo Updating git: fetch + rebase onto origin, fix CHANGELOG hashes, show status.
git rev-parse --git-dir >nul 2>&1
if errorlevel 1 ( echo Not a git repository. & goto end )
set "BRANCH="
for /f "delims=" %%b in ('git rev-parse --abbrev-ref HEAD 2^>nul') do set "BRANCH=%%b"
echo Branch: %BRANCH%
echo (A dirty tree is auto-stashed for the rebase and re-applied after.)
echo --- git fetch --all --prune ---
git fetch --all --prune
git rev-parse --verify --quiet "origin/%BRANCH%" >nul 2>&1
if errorlevel 1 (
echo No upstream origin/%BRANCH% - skipping rebase.
) else (
echo --- git pull --rebase --autostash origin %BRANCH% ---
git pull --rebase --autostash origin "%BRANCH%"
if errorlevel 1 (
echo.
echo Rebase stopped ^(conflicts^). Resolve them, then:
echo git rebase --continue ^(or: git rebase --abort^)
echo and run this option again to finish the hash fix + status.
goto end
)
)
echo --- Fixing CHANGELOG commit links ^(releases\fix-changelog-hashes.sh^) ---
echo ^(whole file, including released sections: bash releases/fix-changelog-hashes.sh --all-sections^)
:gitpull
REM Both git actions are build.sh's git_pull / git_push, run through bash rather
REM than written a second time here. What they do - fast-forward or rebase,
REM repoint the CHANGELOG links a rebase moved, abort a conflicting rebase so the
REM repo is left exactly as it was, pull-and-retry once when a push is rejected -
REM is shell logic, and the batch copy of the old "Update git" that used to live
REM here is why this file drifted: it reimplemented the same steps, so a fix to
REM one never reached the other.
where bash >nul 2>&1
if errorlevel 1 (
echo bash not found - the CHANGELOG hash fix needs Git Bash, bundled with Git for Windows.
echo Skipping it. Run it yourself in Git Bash: bash releases/fix-changelog-hashes.sh
) else (
bash releases/fix-changelog-hashes.sh
echo ERROR: bash was not found. It comes with Git for Windows ^(Git Bash^) and with WSL.
goto end
)
echo --- git status ---
git status
echo.
echo NOTE: if CHANGELOG.md was changed above, review 'git diff CHANGELOG.md' and
echo commit it - this script never commits for you.
bash ./build.sh git-pull
goto end
REM ===========================================================================
:gitpush
where bash >nul 2>&1
if errorlevel 1 (
echo ERROR: bash was not found. It comes with Git for Windows ^(Git Bash^) and with WSL.
goto end
)
bash ./build.sh git-push
goto end
:dev_local
call :ensure_dirs
call :set_dev_env
@@ -949,8 +942,10 @@ call :rebuild_for_tests
if errorlevel 1 goto end
set "FAILED=0"
set "S_mocha=RUN" & set "S_unit=RUN" & set "S_import=RUN" & set "S_e2e=RUN" & set "S_browsers=RUN"
set "C_mocha=0" & set "C_unit=0" & set "C_import=0" & set "C_e2e=0" & set "C_browsers=0"
set "S_mocha=RUN" & set "S_unit=RUN" & set "S_import=RUN" & set "S_e2e=RUN"
set "S_chromium=RUN" & set "S_firefox=RUN" & set "S_webkit=RUN"
set "C_mocha=0" & set "C_unit=0" & set "C_import=0" & set "C_e2e=0"
set "C_chromium=0" & set "C_firefox=0" & set "C_webkit=0"
REM Each run gets its own ..\log\<timestamp>\ dir (stamped once at run start), so
REM logs are never overwritten and previous runs are kept. PowerShell gives a
REM locale-independent yyyy-MM-dd_HH-mm-ss; %RUN_LOGDIR% is absolute so it works
@@ -960,7 +955,7 @@ set "RUN_LOGDIR=%REPO%\..\log\%RUN_TS%"
if not exist "%RUN_LOGDIR%" md "%RUN_LOGDIR%"
echo Logs for this run: %RUN_LOGDIR%\ - previous runs are kept
REM Clear completion flags from any previous run.
del /q ".done-mocha" ".done-unit" ".done-import" ".done-e2e" ".done-browsers" 2>nul
del /q ".done-mocha" ".done-unit" ".done-import" ".done-e2e" ".done-chromium" ".done-firefox" ".done-webkit" 2>nul
REM Start the :3000 server FIRST and let it build alone. Mocha runs its own
REM Meteor build (.meteor\local-test); launching it here would make two full
@@ -982,11 +977,19 @@ start "Wekan import" /MIN /D "%REPO%" cmd /c "(echo ===== Import regression [no
if "!SERVER_READY!"=="0" (
echo FAIL: server did not become ready on http://localhost:3000 ^(see %RUN_LOGDIR%\wekan-test-server.log^)
set "S_e2e=SKIP" & set "S_browsers=SKIP" & set "FAILED=1"
set "S_e2e=SKIP" & set "S_chromium=SKIP" & set "S_firefox=SKIP" & set "S_webkit=SKIP" & set "FAILED=1"
) else (
echo ==^> Server is up: starting Node E2E and Playwright ^(Chromium + Firefox + WebKit, --workers=3^) in parallel.
echo ==^> Server is up: starting Node E2E and Playwright ^(Chromium, Firefox and WebKit as three separate jobs^) in parallel.
start "Wekan e2e" /MIN /D "%REPO%" cmd /c "(echo ===== Node E2E [M1 node:3000 db:3001] test run: %DATE% %TIME% =====) 1>%RUN_LOGDIR%\wekan-alltests-e2e.log 2>&1 & call meteor npm run test:e2e 1>>%RUN_LOGDIR%\wekan-alltests-e2e.log 2>&1 & if errorlevel 1 (echo FAIL>.done-e2e) else (echo PASS>.done-e2e)"
start "Wekan browsers" /MIN /D "%REPO%\tests\playwright" cmd /c "set WEKAN_PLAYWRIGHT_ALL=1&& (echo ===== Playwright browsers [M1 node:3000 db:3001] test run: %DATE% %TIME% =====) 1>%RUN_LOGDIR%\wekan-alltests-browsers.log 2>&1 & call meteor npm exec playwright test -- --project=chromium --project=firefox --project=webkit --workers=3 --reporter=list 1>>%RUN_LOGDIR%\wekan-alltests-browsers.log 2>&1 & if errorlevel 1 (echo FAIL>..\..\.done-browsers) else (echo PASS>..\..\.done-browsers)"
REM One job, one log, one summary row PER BROWSER - the same shape build.sh
REM produces. A single combined "browsers" job wrote one wekan-alltests-browsers.log
REM for all three, so "which browser failed, and what did WebKit print" could not
REM be answered from the logs, and CLAUDE.md's "check the newest test logs" names
REM the per-browser files. --output keeps each browser's artifacts in its own
REM directory, or the three would clear each other's at startup.
call :start_browser_job chromium
call :start_browser_job firefox
call :start_browser_job webkit
)
REM Live progress: re-print a status line every ~3s until all expected jobs
@@ -1001,18 +1004,25 @@ call :jcount C_unit unit "%RUN_LOGDIR%\wekan-alltests-unit.log"
call :jcount C_import check "%RUN_LOGDIR%\wekan-alltests-import.log"
if "!SERVER_READY!"=="1" (
call :jstate e2e
call :jstate browsers
call :jstate chromium
call :jstate firefox
call :jstate webkit
call :jcount C_e2e e2e "%RUN_LOGDIR%\wekan-alltests-e2e.log"
call :jcount C_browsers check "%RUN_LOGDIR%\wekan-alltests-browsers.log"
call :jcount C_chromium check "%RUN_LOGDIR%\wekan-alltests-chromium.log"
call :jcount C_firefox check "%RUN_LOGDIR%\wekan-alltests-firefox.log"
call :jcount C_webkit check "%RUN_LOGDIR%\wekan-alltests-webkit.log"
)
echo mocha [M2 :3100/db:3101] !S_mocha! tests:!C_mocha! ^| unit [no server] !S_unit! tests:!C_unit! ^| import [no server] !S_import! tests:!C_import! ^| e2e [M1 :3000/db:3001] !S_e2e! tests:!C_e2e! ^| browsers [M1 :3000/db:3001] !S_browsers! tests:!C_browsers!
echo mocha [M2 :3100/db:3101] !S_mocha! tests:!C_mocha! ^| unit [no server] !S_unit! tests:!C_unit! ^| import [no server] !S_import! tests:!C_import! ^| e2e [M1 :3000/db:3001] !S_e2e! tests:!C_e2e!
echo chromium !S_chromium! tests:!C_chromium! ^| firefox !S_firefox! tests:!C_firefox! ^| webkit !S_webkit! tests:!C_webkit! [all three on M1 :3000/db:3001]
set "ALLDONE=1"
if not exist ".done-mocha" set "ALLDONE=0"
if not exist ".done-unit" set "ALLDONE=0"
if not exist ".done-import" set "ALLDONE=0"
if "!SERVER_READY!"=="1" (
if not exist ".done-e2e" set "ALLDONE=0"
if not exist ".done-browsers" set "ALLDONE=0"
if not exist ".done-chromium" set "ALLDONE=0"
if not exist ".done-firefox" set "ALLDONE=0"
if not exist ".done-webkit" set "ALLDONE=0"
)
if "!ALLDONE!"=="0" (
ping -n 4 127.0.0.1 >nul
@@ -1029,7 +1039,9 @@ if "!S_mocha!"=="FAIL" set "FAILED=1"
if "!S_unit!"=="FAIL" set "FAILED=1"
if "!S_import!"=="FAIL" set "FAILED=1"
if "!S_e2e!"=="FAIL" set "FAILED=1"
if "!S_browsers!"=="FAIL" set "FAILED=1"
if "!S_chromium!"=="FAIL" set "FAILED=1"
if "!S_firefox!"=="FAIL" set "FAILED=1"
if "!S_webkit!"=="FAIL" set "FAILED=1"
echo.
echo ==================== TEST SUMMARY ====================
@@ -1038,9 +1050,11 @@ call :report "!S_unit!" "Unit tests (node)" "[no server]
call :report "!S_import!" "Import regression" "[no server] tests:!C_import!"
if "!SERVER_READY!"=="1" ( call :report "PASS" "Server startup" "[M1 :3000/db:3001]" ) else ( call :report "FAIL" "Server startup" "[M1 :3000/db:3001]" )
call :report "!S_e2e!" "Node E2E regressions" "[M1 :3000/db:3001] tests:!C_e2e!"
call :report "!S_browsers!" "Playwright (Chromium+Firefox+WebKit)" "[M1 :3000/db:3001] tests:!C_browsers!"
call :report "!S_chromium!" "Playwright Chromium" "[M1 :3000/db:3001] tests:!C_chromium!"
call :report "!S_firefox!" "Playwright Firefox" "[M1 :3000/db:3001] tests:!C_firefox!"
call :report "!S_webkit!" "Playwright WebKit" "[M1 :3000/db:3001] tests:!C_webkit!"
echo =====================================================
echo (per-job logs in: %RUN_LOGDIR%\ as wekan-alltests-^<mocha^|unit^|import^|e2e^|browsers^>.log and wekan-test-server.log)
echo (per-job logs in: %RUN_LOGDIR%\ as wekan-alltests-^<mocha^|unit^|import^|e2e^|chromium^|firefox^|webkit^>.log and wekan-test-server.log)
if "!FAILED!"=="0" ( echo RESULT: All tests passed. ) else ( echo RESULT: Some tests FAILED ^(see details above^). )
goto end
@@ -1066,8 +1080,10 @@ call :rebuild_for_tests
if errorlevel 1 goto end
set "FAILED=0"
set "S_mocha=RUN" & set "S_unit=RUN" & set "S_import=RUN" & set "S_e2e=RUN" & set "S_browsers=RUN"
set "C_mocha=0" & set "C_unit=0" & set "C_import=0" & set "C_e2e=0" & set "C_browsers=0"
set "S_mocha=RUN" & set "S_unit=RUN" & set "S_import=RUN" & set "S_e2e=RUN"
set "S_chromium=RUN" & set "S_firefox=RUN" & set "S_webkit=RUN"
set "C_mocha=0" & set "C_unit=0" & set "C_import=0" & set "C_e2e=0"
set "C_chromium=0" & set "C_firefox=0" & set "C_webkit=0"
REM Each run gets its own ..\log\<timestamp>\ dir (stamped once at run start), so
REM logs are never overwritten and previous runs are kept. PowerShell gives a
REM locale-independent yyyy-MM-dd_HH-mm-ss; %RUN_LOGDIR% is absolute so it works
@@ -1077,7 +1093,7 @@ set "RUN_LOGDIR=%REPO%\..\log\%RUN_TS%"
if not exist "%RUN_LOGDIR%" md "%RUN_LOGDIR%"
echo Logs for this run: %RUN_LOGDIR%\ - previous runs are kept
REM Clear completion flags from any previous run.
del /q ".done-mocha" ".done-unit" ".done-import" ".done-e2e" ".done-browsers" 2>nul
del /q ".done-mocha" ".done-unit" ".done-import" ".done-e2e" ".done-chromium" ".done-firefox" ".done-webkit" 2>nul
REM Start the :3000 server FIRST and let it build alone. Mocha runs its own
REM Meteor build (.meteor\local-test); launching it here would make two full
@@ -1116,15 +1132,23 @@ echo ==^> Running Node E2E regressions on Meteor #1 [Node.js :3000, MongoDB :300
start "Wekan e2e" /MIN /D "%REPO%" cmd /c "(echo ===== Node E2E [M1 node:3000 db:3001] test run: %DATE% %TIME% =====) 1>%RUN_LOGDIR%\wekan-alltests-e2e.log 2>&1 & call meteor npm run test:e2e 1>>%RUN_LOGDIR%\wekan-alltests-e2e.log 2>&1 & if errorlevel 1 (echo FAIL>.done-e2e) else (echo PASS>.done-e2e)"
call :seq_run_wait e2e e2e C_e2e "%RUN_LOGDIR%\wekan-alltests-e2e.log"
echo ==^> Running Playwright Chromium, Firefox and WebKit one at a time ^(--workers=1^) on Meteor #1 [Node.js :3000, MongoDB :3001]. Full log: %RUN_LOGDIR%\wekan-alltests-browsers.log
start "Wekan browsers" /MIN /D "%REPO%\tests\playwright" cmd /c "set WEKAN_PLAYWRIGHT_ALL=1&& (echo ===== Playwright browsers [M1 node:3000 db:3001] test run: %DATE% %TIME% =====) 1>%RUN_LOGDIR%\wekan-alltests-browsers.log 2>&1 & call :onelog playwright-all
call meteor npm exec playwright test -- --project=chromium --project=firefox --project=webkit --workers=1 --reporter=list 2>&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath '%ONELOG%'" 1>>%RUN_LOGDIR%\wekan-alltests-browsers.log 2>&1 & if errorlevel 1 (echo FAIL>..\..\.done-browsers) else (echo PASS>..\..\.done-browsers)"
call :seq_run_wait browsers check C_browsers "%RUN_LOGDIR%\wekan-alltests-browsers.log"
echo ==^> Running Playwright Chromium, Firefox and WebKit one browser at a time on Meteor #1 [Node.js :3000, MongoDB :3001].
REM One job, one log and one summary row per browser, as build.sh does. The
REM combined run wrote a single wekan-alltests-browsers.log for all three, so
REM neither "which browser failed" nor "what did WebKit print" was answerable
REM from the logs afterwards - and CLAUDE.md's "check the newest test logs"
REM names wekan-alltests-chromium.log, -firefox.log and -webkit.log.
call :start_browser_job chromium
call :seq_run_wait chromium check C_chromium "%RUN_LOGDIR%\wekan-alltests-chromium.log"
call :start_browser_job firefox
call :seq_run_wait firefox check C_firefox "%RUN_LOGDIR%\wekan-alltests-firefox.log"
call :start_browser_job webkit
call :seq_run_wait webkit check C_webkit "%RUN_LOGDIR%\wekan-alltests-webkit.log"
goto server_jobs_done
:skip_server_jobs
echo FAIL: server did not become ready on http://localhost:3000 ^(see %RUN_LOGDIR%\wekan-test-server.log^)
set "S_e2e=SKIP" & set "S_browsers=SKIP" & set "FAILED=1"
set "S_e2e=SKIP" & set "S_chromium=SKIP" & set "S_firefox=SKIP" & set "S_webkit=SKIP" & set "FAILED=1"
:server_jobs_done
@@ -1138,14 +1162,18 @@ if "!S_mocha!"=="FAIL" set "FAILED=1"
if "!S_unit!"=="FAIL" set "FAILED=1"
if "!S_import!"=="FAIL" set "FAILED=1"
if "!S_e2e!"=="FAIL" set "FAILED=1"
if "!S_browsers!"=="FAIL" set "FAILED=1"
if "!S_chromium!"=="FAIL" set "FAILED=1"
if "!S_firefox!"=="FAIL" set "FAILED=1"
if "!S_webkit!"=="FAIL" set "FAILED=1"
REM Count passing tests per job from each log (advances shown in the summary).
call :jcount C_mocha check "%RUN_LOGDIR%\wekan-alltests-mocha.log"
call :jcount C_unit unit "%RUN_LOGDIR%\wekan-alltests-unit.log"
call :jcount C_import check "%RUN_LOGDIR%\wekan-alltests-import.log"
call :jcount C_e2e e2e "%RUN_LOGDIR%\wekan-alltests-e2e.log"
call :jcount C_browsers check "%RUN_LOGDIR%\wekan-alltests-browsers.log"
call :jcount C_chromium check "%RUN_LOGDIR%\wekan-alltests-chromium.log"
call :jcount C_firefox check "%RUN_LOGDIR%\wekan-alltests-firefox.log"
call :jcount C_webkit check "%RUN_LOGDIR%\wekan-alltests-webkit.log"
echo.
echo ==================== TEST SUMMARY ====================
@@ -1154,9 +1182,11 @@ call :report "!S_unit!" "Unit tests (node)" "[no server]
call :report "!S_import!" "Import regression" "[no server] tests:!C_import!"
if "!SERVER_READY!"=="1" ( call :report "PASS" "Server startup" "[M1 :3000/db:3001]" ) else ( call :report "FAIL" "Server startup" "[M1 :3000/db:3001]" )
call :report "!S_e2e!" "Node E2E regressions" "[M1 :3000/db:3001] tests:!C_e2e!"
call :report "!S_browsers!" "Playwright (Chromium+Firefox+WebKit)" "[M1 :3000/db:3001] tests:!C_browsers!"
call :report "!S_chromium!" "Playwright Chromium" "[M1 :3000/db:3001] tests:!C_chromium!"
call :report "!S_firefox!" "Playwright Firefox" "[M1 :3000/db:3001] tests:!C_firefox!"
call :report "!S_webkit!" "Playwright WebKit" "[M1 :3000/db:3001] tests:!C_webkit!"
echo =====================================================
echo (per-job logs in: %RUN_LOGDIR%\ as wekan-alltests-^<mocha^|unit^|import^|e2e^|browsers^>.log and wekan-test-server.log)
echo (per-job logs in: %RUN_LOGDIR%\ as wekan-alltests-^<mocha^|unit^|import^|e2e^|chromium^|firefox^|webkit^>.log and wekan-test-server.log)
if "!FAILED!"=="0" ( echo RESULT: All tests passed. ) else ( echo RESULT: Some tests FAILED ^(see details above^). )
goto end
@@ -1252,17 +1282,34 @@ if /i "%INSTALL_DEPS%"=="y" (
popd
)
REM All three browsers run natively on Windows. Use --workers=1 so a single
REM Playwright run executes Chromium, Firefox and WebKit tests one at a time
REM (sequentially) rather than concurrently: running all three at once uses too
REM much RAM/swap and can crash the machine. WEKAN_PLAYWRIGHT_ALL=1 enables all
REM projects.
cd /d "%REPO%\tests\playwright"
set "WEKAN_PLAYWRIGHT_ALL=1"
call meteor npm exec playwright test -- --project=chromium --project=firefox --project=webkit --workers=1 --reporter=list
if errorlevel 1 ( echo RESULT: Some Playwright browsers FAILED ^(see details above^). ) else ( echo RESULT: All Playwright browsers passed. )
REM One browser at a time, each with its own log - the same as build.sh's
REM run_playwright_parallel, and the same per-browser logs the whole-suite runs
REM write. Running all three concurrently against one dev server uses too much
REM RAM/swap on smaller machines and can crash it, so this stays sequential.
REM
REM It used to be a single `playwright test --project=... --project=...` call
REM with no log at all: everything went to the terminal and there was nothing
REM left to read afterwards, which is what tests/dbConformanceWiring.test.cjs
REM means by "every Tests option writes its log to ../log/<datetime>/".
set "PW_ALL_FAILED=0"
for %%B in (chromium firefox webkit) do call :pw_one_browser %%B
if "%PW_ALL_FAILED%"=="1" ( echo RESULT: Some Playwright browsers FAILED ^(see the logs above^). ) else ( echo RESULT: All Playwright browsers passed. )
goto end
:pw_one_browser
REM One browser of the "ALL browsers" option: %1 = chromium^|firefox^|webkit.
REM Streams live AND writes wekan-playwright-^<browser^>.log, via the same
REM :onelog helper every other Tests option uses.
set "PW_PROJECT=%~1"
call :onelog playwright-%PW_PROJECT%
echo ==^> Playwright %PW_PROJECT% ^(one browser at a time^). Log: %ONELOG%
pushd "%REPO%\tests\playwright"
set "WEKAN_PLAYWRIGHT_ALL=1"
call meteor npm exec playwright test -- --project=%PW_PROJECT% --output=test-results\%PW_PROJECT% --reporter=list 2>&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath '%ONELOG%'"
if errorlevel 1 set "PW_ALL_FAILED=1"
popd
exit /b 0
REM ===========================================================================
:check_floating
echo Ensuring missing ESLint dependencies for the no-floating-promises rule.
@@ -1724,6 +1771,25 @@ REM 6) Wait for :3000 to answer (bundle boots in seconds; curl-timeout poll).
call :wait_server_ready
exit /b 0
:start_browser_job
REM Start ONE Playwright browser as its own job: %1 = chromium^|firefox^|webkit.
REM
REM One job, one log, one .done flag and one summary row per browser - the same
REM shape build.sh's run_pw_all_browser produces, and what CLAUDE.md means by the
REM per-browser logs in log\^<datetime^>\. The combined three-project run this
REM replaced wrote everything into one wekan-alltests-browsers.log, where a
REM failure could not be attributed to a browser without reading the whole file.
REM
REM --output is per browser as well: Playwright CLEARS its output directory at
REM startup, so three jobs sharing test-results\ would delete each other's traces
REM and screenshots - exactly the artifacts wanted after a failure. The job runs
REM in tests\playwright, so the .done flag is written two levels up, in the repo
REM root, where the wait loops look for it.
if "%~1"=="" exit /b 1
echo ==^> Playwright %~1 on Meteor #1 [Node.js :3000, MongoDB :3001]. Log: %RUN_LOGDIR%\wekan-alltests-%~1.log
start "Wekan %~1" /MIN /D "%REPO%\tests\playwright" cmd /c "set WEKAN_PLAYWRIGHT_ALL=1&& (echo ===== Playwright %~1 [M1 node:3000 db:3001] test run: %DATE% %TIME% =====) 1>%RUN_LOGDIR%\wekan-alltests-%~1.log 2>&1 & call meteor npm exec playwright test -- --project=%~1 --output=test-results\%~1 --reporter=list 1>>%RUN_LOGDIR%\wekan-alltests-%~1.log 2>&1 & if errorlevel 1 (echo FAIL>..\..\.done-%~1) else (echo PASS>..\..\.done-%~1)"
exit /b 0
:seq_run_wait
REM Live progress for a sequential job that runs in its own minimized window and
REM writes .done-<key> when finished. %1=key %2=count kind (check^|e2e) %3=count var
@@ -1761,8 +1827,8 @@ exit /b 0
REM ===========================================================================
:test_all_databases
REM The same thing build.sh's "All databases (sequential)" runs: build the newest
REM FerretDB v1 from the FerretDB subdirectory (cloning wekan/FerretDB if it is
REM not there, and installing Go and the module dependencies if they are
REM FerretDB v1 from .tools\FerretDB (cloning wekan/FerretDB there if it is not
REM there, and installing Go and the module dependencies if they are
REM missing), then run the whole FerretDB v1 query catalogue against every
REM database that has a Docker image for THIS CPU - one at a time, because they
REM all use the same FerretDB port - and compare that they all answered the same.
@@ -1782,9 +1848,13 @@ goto end
REM ===========================================================================
:test_ferretdb
REM All of FerretDB's own tests, one at a time: unit, vet, integration. FerretDB
REM is expected to be a subdirectory of this repo - the "All databases" option
REM clones it if it is not there. Its build.sh installs Go and the Go modules
REM All of FerretDB's own tests, one at a time: unit, vet, integration.
REM
REM FerretDB lives in .tools\FerretDB - companion repos are kept in one directory
REM that .gitignore and .meteorignore already exclude, instead of one ignored
REM subdirectory each at the repo root. It is cloned here when it is not there,
REM the same as build.sh's ensure_tool_repo does, so neither script depends on
REM the other having been run first. Its build.sh installs Go and the Go modules
REM when they are missing, and writes its logs to ..\log\<datetime>\ with every
REM other test run's.
where bash >nul 2>&1
@@ -1792,13 +1862,27 @@ if errorlevel 1 (
echo ERROR: bash was not found. It comes with Git for Windows ^(Git Bash^) and with WSL.
goto end
)
if not exist "FerretDB\build.sh" (
echo FerretDB\build.sh is missing. Clone it first:
echo git clone git@github.com:wekan/FerretDB
echo ^(or run Tests - All databases, which clones it before building.^)
if not exist "%REPO%\.tools\FerretDB\build.sh" (
where git >nul 2>&1
if errorlevel 1 (
echo ERROR: git was not found, so .tools\FerretDB cannot be cloned.
echo Install Git for Windows, or clone it by hand:
echo git clone git@github.com:wekan/FerretDB .tools/FerretDB
goto end
)
if not exist "%REPO%\.tools" md "%REPO%\.tools"
echo ==^> FerretDB is not in .tools\ yet; cloning wekan/FerretDB
git clone git@github.com:wekan/FerretDB "%REPO%\.tools\FerretDB"
if errorlevel 1 (
echo ==^> SSH clone failed ^(no key for github.com?^); trying HTTPS.
git clone https://github.com/wekan/FerretDB "%REPO%\.tools\FerretDB"
)
)
if not exist "%REPO%\.tools\FerretDB\build.sh" (
echo ERROR: .tools\FerretDB\build.sh is still missing after cloning.
goto end
)
bash -c "cd FerretDB && ./build.sh test-all"
bash -c "cd .tools/FerretDB && ./build.sh test-all"
goto end
REM ===========================================================================
+539 -144
View File
@@ -4,8 +4,11 @@ echo "Recommended for development: Newest Debian or Ubuntu amd64 based distro, d
echo "Note1: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly."
echo " You can still use any other locale as your main locale."
echo "Note2: Console output is also logged to ../log/wekan-log.log"
echo "Note3: All logs this script produces go into the ../log/ directory."
echo "Note2: Console output is also logged to <logs>/wekan-log.log"
echo "Note3: All logs this script produces go into a log/<datetime>/ directory -"
echo " ../log/ when the parent of the repo is writable, otherwise ./log/"
echo " inside it (a sandbox that shares only this repository). The path is"
echo " printed when a run starts."
# Give the Meteor build tool and Node processes a larger heap so long
# development sessions and test runs don't crash with
@@ -14,12 +17,117 @@ echo "Note3: All logs this script produces go into the ../log/ directory."
# (the one that hits the limit during `meteor run`/`meteor test`/`meteor build`);
# NODE_OPTIONS covers the child Node/rspack processes. Both default to 8 GB here
# and honor any value you already exported. Lower it if your machine has less RAM.
export TOOL_NODE_FLAGS="${TOOL_NODE_FLAGS:---max-old-space-size=8192}"
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=8192}"
# The size is worked out from the machine rather than fixed at 8192, because
# 8192 is where a build died:
#
# FATAL ERROR: Ineffective mark-compacts near heap limit
# Allocation failed - JavaScript heap out of memory
#
# at 8146 MB of an 8192 MB limit, on a machine with 30 GiB of RAM. The build had
# not run out of memory, it had run out of the ceiling this line gave it - and
# because that ceiling was a constant, the same number was too small on a large
# machine and too large on a small one.
#
# Half of total RAM, clamped to [4096, 16384]. Half is the share that leaves the
# rest of the machine usable while a build runs, the floor keeps a small machine
# from being given something unusable, and the ceiling is there because a heap
# larger than that means something is wrong rather than something is big. At
# 16 GiB this works out to 8192, which is exactly what was hard-coded here
# before, so nothing changes on the machine that value was chosen for.
#
# Anything already exported still wins, which is how to override it.
# Always computed, never conditionally: if only ONE of the two variables were
# already set, skipping this would leave the other with an empty size and Node
# would be handed "--max-old-space-size=".
_mem_total_mb=$(awk '/^MemTotal:/ { print int($2 / 1024) }' /proc/meminfo 2>/dev/null)
# macOS has no /proc; hw.memsize is bytes.
[ -n "${_mem_total_mb:-}" ] || _mem_total_mb=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1048576 ))
[ "${_mem_total_mb:-0}" -gt 0 ] 2>/dev/null || _mem_total_mb=16384
_heap_mb=$(( _mem_total_mb / 2 ))
[ "$_heap_mb" -lt 4096 ] && _heap_mb=4096
[ "$_heap_mb" -gt 16384 ] && _heap_mb=16384
if [ -z "${TOOL_NODE_FLAGS:-}${NODE_OPTIONS:-}" ]; then
echo "Node heap limit for builds: ${_heap_mb} MB (half of ${_mem_total_mb} MB of RAM)."
echo " Override by exporting TOOL_NODE_FLAGS and NODE_OPTIONS yourself."
fi
export TOOL_NODE_FLAGS="${TOOL_NODE_FLAGS:---max-old-space-size=$_heap_mb}"
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=$_heap_mb}"
# Every log this script writes goes into ../log/ (one directory up from the
# repo). Create it up front so redirections never fail on a missing directory.
mkdir -p ../log
# Every log this script writes goes into a `log/<datetime>/` directory, and
# WEKAN_LOG_ROOT is where those directories live.
#
# ../log by preference - one level up from the repo, so a test run's output is
# not mixed into the working tree and `git status` after a run is about the code
# rather than about the run. That is what the docs and the release process say
# to read.
#
# But the parent of the repo is not always there to write into. In the Flatpak
# sandbox only the repository directory itself is shared, so `..` is either
# missing or read-only, and `mkdir -p ../log` fails - after which every
# redirection in this script either failed or quietly dropped its log into the
# repo root. So: try the parent, and fall back to `log/` INSIDE the repo, which
# is always writable because the repo is what the sandbox shares. Same
# `log/<datetime>/` shape either way, so nothing that reads these logs has to
# care which happened, and the path is printed whenever a run starts.
#
# WEKAN_LOG_ROOT can be set to put them somewhere else entirely.
if [ -z "${WEKAN_LOG_ROOT:-}" ]; then
if mkdir -p ../log 2>/dev/null && [ -w ../log ]; then
WEKAN_LOG_ROOT="../log"
else
WEKAN_LOG_ROOT="log"
echo "Note: the parent directory is not writable (a sandbox shares only"
echo " this repository), so logs go into $(pwd)/log/ instead of ../log/."
fi
fi
export WEKAN_LOG_ROOT
mkdir -p "$WEKAN_LOG_ROOT"
# ── Companion repositories: wekan/.tools/<name> ──────────────────────────────
#
# The repos WeKan needs to run everything - wekan/FerretDB for the database
# conformance run and FerretDB's own suites, and the patch repos - are separate
# git repositories, not part of this one. They live in .tools/ inside this
# checkout, which .gitignore and .meteorignore already exclude, so a clone of
# them can never end up in a commit or in a Meteor rebuild.
#
# They used to be cloned as subdirectories of the repo root (wekan/FerretDB),
# each needing its own ignore entry; one ignored directory holds all of them.
# The repo directory, from the script's own path rather than the caller's cwd:
# ./build.sh is normally run from the checkout, but a clone must not land in
# whatever directory somebody happened to be in.
WEKAN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WEKAN_TOOLS_DIR="$WEKAN_DIR/.tools"
export WEKAN_DIR WEKAN_TOOLS_DIR
# ensure_tool_repo <name> [git-url] - the path to .tools/<name>, cloning it if it
# is not there yet. Prints the path on stdout; everything else goes to stderr, so
# a caller can do `dir="$(ensure_tool_repo FerretDB)"`.
#
# SSH first, HTTPS second: a maintainer has a key and wants to push, and anyone
# else still gets a working clone. Returns non-zero when neither works, so the
# caller can say what it cannot do rather than cd into nothing.
function ensure_tool_repo(){
local name="$1"
local url="${2:-git@github.com:wekan/$name}"
local https_url="${url/git@github.com:/https://github.com/}"
local dir="$WEKAN_TOOLS_DIR/$name"
if [ -d "$dir/.git" ]; then
printf '%s\n' "$dir"
return 0
fi
mkdir -p "$WEKAN_TOOLS_DIR" || { echo "ERROR: cannot create $WEKAN_TOOLS_DIR" >&2; return 1; }
echo "==> $name is not in .tools/ yet; cloning $url" >&2
if ! git clone "$url" "$dir" >&2; then
echo "==> SSH clone failed (no key for github.com?); trying HTTPS." >&2
if ! git clone "$https_url" "$dir" >&2; then
echo "ERROR: could not clone $name from $url or $https_url" >&2
return 1
fi
fi
printf '%s\n' "$dir"
}
function pause(){
read -p "$*"
@@ -195,9 +303,114 @@ function kill_all_dev_servers(){
# next `meteor run` recompiles from scratch instead of serving stale modules.
function build_wekan(){
echo "Building WeKan."
rm -rf node_modules node_modules/.cache .meteor/local .build _build
(meteor update --npm 2>/dev/null || true) && meteor npm install
meteor build .build --directory
# The build's output goes to the run's log directory, not only to the
# terminal. A test run that fails IN THE BUILD used to leave nothing behind
# to read: log/<datetime>/ held the FerretDB and conformance logs and not
# one line about why WeKan itself never got as far as a test, so "check the
# newest test logs" could not answer the question the run had just raised.
# It is teed, so an interactive build still scrolls past as it always did.
local buildlog
# What the build is actually given - the computed limit, unless the
# snapshot mode below caps it. The failure message quoted the computed one
# either way, which read as "allowed 15542 MB and reached 4280 MB".
_effective_heap_mb="$_heap_mb"
buildlog="$(one_log build)"
echo "Build log: $buildlog"
# WEKAN_BUILD_HEAP_SNAPSHOT=1 makes the build write a heap snapshot just
# before it would run out of memory, instead of only dying.
#
# This exists because guessing at the cause did not work. The build has
# exhausted its heap three runs running - 8146 MB of 8192, then 15526 of
# 15542, then 15520 with the second JS minifier removed on the theory that
# it was the consumer. The last of those settled the theory: taking the
# minifier out changed the peak by 6 MB, which is noise. Something else is
# holding 15 GB, and the only way to find out what is to look.
#
# Node writes Heap.<pid>.<seq>.heapsnapshot into the working directory when
# it gets near the limit. Open it in Chrome DevTools (Memory -> Load) and
# sort by retained size. It is roughly as large as the heap, so this is
# off by default rather than something every build pays for.
if [ "${WEKAN_BUILD_HEAP_SNAPSHOT:-0}" = "1" ]; then
# A SMALLER heap on purpose, not the computed one.
#
# The first attempt at this kept the full limit and the run was killed
# outright - "Päätetty" from the kernel, and a 0-byte
# Heap.*.heapsnapshot left behind. Writing a snapshot costs memory on
# top of the heap being dumped, so asking a 15.5 GB heap to write a
# 15.5 GB snapshot on a 30 GB machine that already had 12 GB in use
# does not fit, and the OOM killer arrives before the file does.
#
# A snapshot is not more useful for being bigger. What is wanted is
# WHAT is retaining memory, and whatever grows to 15 GB is already the
# largest thing on the heap at 4 GB - it just gets there sooner and
# writes a file that fits. The build still fails; that is expected.
local snap_mb="${WEKAN_BUILD_HEAP_SNAPSHOT_MB:-4096}"
_effective_heap_mb="$snap_mb"
export TOOL_NODE_FLAGS="--max-old-space-size=$snap_mb --heapsnapshot-near-heap-limit=1"
export NODE_OPTIONS="--max-old-space-size=$snap_mb --heapsnapshot-near-heap-limit=1"
echo "Heap snapshot: ON, with the heap capped at ${snap_mb} MB."
echo " The build WILL fail sooner than usual - that is the point: it"
echo " writes $(pwd)/Heap.<date>.<pid>.<n>.heapsnapshot, about ${snap_mb} MB,"
echo " just before it dies. A snapshot at the full ${_heap_mb} MB limit"
echo " cannot be written on this machine - the last attempt was killed"
echo " by the kernel and left a 0-byte file."
echo " Open it in Chrome DevTools: Memory -> Load, sort by retained size."
echo " Raise it with WEKAN_BUILD_HEAP_SNAPSHOT_MB if 4096 is too early."
fi
{
echo "===== wekan build started $(date '+%F %T') ====="
rm -rf node_modules node_modules/.cache .meteor/local .build _build
(meteor update --npm 2>/dev/null || true) && meteor npm install
meteor build .build --directory
local rc=$?
echo "===== wekan build finished $(date '+%F %T') (exit $rc) ====="
return $rc
} 2>&1 | tee "$buildlog"
# The exit status of the pipeline is tee's; take the build's.
local rc="${PIPESTATUS[0]}"
if [ "$rc" -ne 0 ] || [ ! -d .build/bundle ]; then
echo "ERROR: the WeKan build failed. Its output is in $buildlog"
# Name the failure when it is one we can recognise, rather than leaving
# a V8 stack trace as the last word. Running out of heap and failing to
# compile look identical at this level and have nothing in common.
if grep -q "JavaScript heap out of memory" "$buildlog" 2>/dev/null; then
local peak
peak="$(grep -ao 'Mark-Compact ([a-z ]*) [0-9.]*' "$buildlog" \
| tail -1 | awk '{print $NF}')"
# Appended to the log as well as printed. The whole point of the
# build log is that "check the newest test logs" answers the
# question, and a diagnosis that exists only in terminal scrollback
# is one the next person does not have.
{
echo
echo " The build ran out of JavaScript heap. It was allowed ${_effective_heap_mb} MB${peak:+ and reached ${peak} MB}."
echo
echo " Raising that number is NOT the next step. It has been raised once"
echo " already - 8192 to ${_heap_mb} - and the build simply used the extra;"
echo " removing the second JS minifier on the theory that it was the"
echo " consumer moved the peak by 6 MB. The build really is holding that"
echo " much, in the phase after both rspack compiles report done."
echo
if [ "${WEKAN_BUILD_HEAP_SNAPSHOT:-0}" = "1" ]; then
echo " A heap snapshot was requested, so look for"
echo " $(pwd)/Heap.*.heapsnapshot and read it with:"
echo
echo " python3 releases/analyse-heapsnapshot.py Heap.*.heapsnapshot"
else
echo " To find out WHAT is holding it, run once with a heap snapshot:"
echo
echo " WEKAN_BUILD_HEAP_SNAPSHOT=1 ./build.sh"
echo
echo " That caps the heap so the snapshot can be written at all, and"
echo " leaves Heap.*.heapsnapshot here. Read it with:"
echo
echo " python3 releases/analyse-heapsnapshot.py Heap.*.heapsnapshot"
fi
} | tee -a "$buildlog"
fi
return 1
fi
echo Done.
}
@@ -421,7 +634,7 @@ function run_playwright_parallel(){
# This run's own ../log/<timestamp>/ dir, so logs are never overwritten.
local RUN_LOGDIR
RUN_LOGDIR="../log/$(date '+%Y-%m-%d_%H-%M-%S')"
RUN_LOGDIR="$WEKAN_LOG_ROOT/$(date '+%Y-%m-%d_%H-%M-%S')"
mkdir -p "$RUN_LOGDIR"
echo "Running Chromium, Firefox and WebKit Playwright suites sequentially (one browser at a time)."
@@ -488,14 +701,14 @@ function run_playwright_parallel(){
}
# Run one Playwright browser project interactively (single-browser menu items).
# one_log <name> — a fresh ../log/<datetime>/ for a single test option, and the
# one_log <name> — a fresh log/<datetime>/ for a single test option, and the
# path of the file to tee into. Every option in the Tests menu writes there, so
# "the newest test logs" is one directory whichever option produced them. When a
# larger run is driving this (EVERYTHING), WEKAN_LOGDIR is already set and is used
# instead, so one run stays in one directory.
one_log() {
local name="$1" dir
dir="${WEKAN_LOGDIR:-../log/$(date '+%Y-%m-%d_%H-%M-%S')}"
dir="${WEKAN_LOGDIR:-$WEKAN_LOG_ROOT/$(date '+%Y-%m-%d_%H-%M-%S')}"
mkdir -p "$dir" 2>/dev/null || dir="."
printf '%s/wekan-%s.log' "$(cd "$dir" && pwd)" "$name"
}
@@ -538,9 +751,21 @@ function run_all_tests(){
# (stamped once, when the run starts), so logs are never overwritten and
# previous runs are kept.
local RUN_TS RUN_LOGDIR
RUN_TS="$(date '+%Y-%m-%d_%H-%M-%S')"
RUN_LOGDIR="../log/$RUN_TS"
# WEKAN_LOGDIR is set when a larger run is driving this (EVERYTHING), and
# then it is THE directory for the whole run - minting a second one here
# would split one run's logs across two directories, which is the thing the
# per-run directory exists to prevent. Standalone, this stamps its own and
# exports it, so every job of this run agrees on it rather than each calling
# date() again and landing in a different second.
if [ -n "${WEKAN_LOGDIR:-}" ]; then
RUN_LOGDIR="$WEKAN_LOGDIR"
else
RUN_TS="$(date '+%Y-%m-%d_%H-%M-%S')"
RUN_LOGDIR="$WEKAN_LOG_ROOT/$RUN_TS"
fi
mkdir -p "$RUN_LOGDIR"
RUN_LOGDIR="$(cd "$RUN_LOGDIR" && pwd)"
export WEKAN_LOGDIR="$RUN_LOGDIR"
echo "Logs for this run: $RUN_LOGDIR/ (previous runs are kept)"
# Tests ALWAYS run against a freshly built bundle. The :3000 test server runs
# the precompiled .build/bundle, so a stale bundle means the suite passes or
@@ -947,41 +1172,101 @@ function run_all_tests(){
return "$FAILED"
}
# floating_promises_checks — the guard's CHECKS, with nothing that installs or
# edits anything.
#
# The menu option of the same name may install ripgrep and the @typescript-eslint
# packages and write the rule into .eslintrc.json: that is a person choosing to
# set the guard up, interactively. EVERYTHING runs unattended, must not call
# sudo, and must not modify the tree it is testing - a run that edits
# .eslintrc.json is no longer testing the commit it started from. So the checks
# themselves live here, are pure reads, and are what both callers run:
#
# * the no-floating-promises rule is configured in .eslintrc.json;
# * every Authentication.checkBoardAccess / checkBoardWriteAccess in
# server/models is awaited. An unawaited permission check returns a pending
# promise, and a promise is truthy, so the call site passes a check that
# never ran - which is a permission bug that looks like working code.
#
# grep, not ripgrep: this must work on a machine where rg is not installed and
# where nothing may be installed to make it work.
function floating_promises_checks(){
local rc=0 calls
echo "Checking that @typescript-eslint/no-floating-promises is configured in .eslintrc.json"
if grep -q '"@typescript-eslint/no-floating-promises"' .eslintrc.json 2>/dev/null; then
echo "OK: the rule is configured."
else
echo "FAIL: @typescript-eslint/no-floating-promises is NOT configured in .eslintrc.json."
echo " Tests -> 'Floating-promises guard' installs the packages and writes it."
rc=1
fi
echo
echo "Scanning server/models for unawaited Authentication.checkBoardAccess / checkBoardWriteAccess"
calls="$(grep -RInE 'Authentication\.(checkBoardAccess|checkBoardWriteAccess)\(' server/models 2>/dev/null \
| grep -vE 'await Authentication\.(checkBoardAccess|checkBoardWriteAccess)\(' || true)"
if [ -n "$calls" ]; then
echo "FAIL: these board auth checks are not awaited:"
printf '%s\n' "$calls"
rc=1
else
echo "OK: every board auth check in server/models is awaited."
fi
return $rc
}
# run_everything — every test WeKan and FerretDB have, one after another.
#
# Three stages, sequential on purpose: the WeKan suite (which builds a fresh
# bundle and starts a server), then the database conformance run (which builds
# FerretDB from source and runs one query catalogue against every database with an
# image for this CPU), then FerretDB's own tests (unit, vet, integration). They
# share one ../log/<datetime>/ directory, so "the newest test logs" is one place.
# Four stages, sequential on purpose: the static guard (seconds, so a broken
# permission check is reported before an hour of browsers), then the WeKan suite
# (which builds a fresh bundle and starts a server, and runs mocha, the node
# suites, the import regression, the node E2E and all three browsers), then the
# database conformance run (which builds FerretDB from source and runs one query
# catalogue against every database with an image for this CPU), then FerretDB's
# own tests (unit, vet - which includes its no-LFS guard - and integration).
# They share one log/<datetime>/ directory, so "the newest test logs" is one
# place.
#
# FerretDB is expected to be a subdirectory of this repo. releases/db-conformance.sh
# clones it if it is not there, so the only thing this checks is that the clone
# worked before handing it the FerretDB stage.
# Everything in the Tests menu that is a TEST is in here. What is deliberately
# not: "Install Playwright browsers" (setup, and the browser stages install what
# they need), and "Count tests by category" (a report, not a check).
#
# FerretDB lives in .tools/FerretDB, cloned on demand by ensure_tool_repo (and by
# releases/db-conformance.sh, which needs it a stage earlier). .tools/ is ignored
# by git and by Meteor, so a companion repo cannot reach a commit or a rebuild.
function run_everything(){
local RUN_TS RUN_LOGDIR FAILED=0
RUN_TS="$(date '+%Y-%m-%d_%H-%M-%S')"
RUN_LOGDIR="../log/$RUN_TS"
RUN_LOGDIR="$WEKAN_LOG_ROOT/$RUN_TS"
mkdir -p "$RUN_LOGDIR"
RUN_LOGDIR="$(cd "$RUN_LOGDIR" && pwd)"
export WEKAN_LOGDIR="$RUN_LOGDIR"
echo "=============================================================================="
echo "EVERYTHING, one stage at a time. Logs: $RUN_LOGDIR/"
echo " 1/3 WeKan's own tests (builds the bundle, starts a server)"
echo " 2/3 Database conformance (builds FerretDB, every database this CPU runs)"
echo " 3/3 FerretDB's own tests (unit, vet, integration)"
echo " 1/4 Floating-promises guard (seconds: the rule, and unawaited auth checks)"
echo " 2/4 WeKan's own tests (builds the bundle, starts a server; mocha, the"
echo " node suites, import, node E2E, three browsers)"
echo " 3/4 Database conformance (builds FerretDB, every database this CPU runs)"
echo " 4/4 FerretDB's own tests (unit, vet, integration)"
echo "This takes a long time. Nothing runs concurrently, so a failure is readable."
echo "=============================================================================="
echo
local wekan_rc=0 conf_rc=0 ferret_rc=0
local guard_rc=0 wekan_rc=0 conf_rc=0 ferret_rc=0
echo "### 1/3 WeKan tests ###########################################################"
echo "### 1/4 Floating-promises guard ###############################################"
# Checks only - no installing, no editing of .eslintrc.json. It takes seconds,
# so it runs first: an unawaited permission check should not be reported after
# an hour of browser tests.
floating_promises_checks 2>&1 | tee "$RUN_LOGDIR/wekan-floating-promises.log"
guard_rc=${PIPESTATUS[0]}
echo
echo "### 2/4 WeKan tests ###########################################################"
run_all_tests sequential || wekan_rc=$?
echo
echo "### 2/3 Database conformance ##################################################"
echo "### 3/4 Database conformance ##################################################"
if [ -x ./releases/db-conformance.sh ]; then
./releases/db-conformance.sh || conf_rc=$?
else
@@ -989,24 +1274,29 @@ function run_everything(){
fi
echo
echo "### 3/3 FerretDB tests ########################################################"
if [ -x FerretDB/build.sh ]; then
( cd FerretDB && WEKAN_LOGDIR="$RUN_LOGDIR" ./build.sh test-all ) || ferret_rc=$?
echo "### 4/4 FerretDB tests ########################################################"
# .tools/FerretDB, cloned here if this is the first run on this machine -
# stage 3 usually got there first, but EVERYTHING must not depend on the
# order of its own stages.
ferret_dir="$(ensure_tool_repo FerretDB)" || ferret_dir=""
if [ -n "$ferret_dir" ] && [ -x "$ferret_dir/build.sh" ]; then
( cd "$ferret_dir" && WEKAN_LOGDIR="$RUN_LOGDIR" ./build.sh test-all ) || ferret_rc=$?
else
echo "ERROR: FerretDB/build.sh is missing - db-conformance.sh clones wekan/FerretDB"
echo " into this repo; run stage 2 first, or clone it by hand."
echo "ERROR: .tools/FerretDB/build.sh is missing and could not be cloned."
echo " Clone it by hand: git clone git@github.com:wekan/FerretDB .tools/FerretDB"
ferret_rc=1
fi
echo
echo "=============================== EVERYTHING ==================================="
printf ' %-6s %s\n' "$([ $wekan_rc -eq 0 ] && echo PASS || echo FAIL)" "WeKan tests (sequential)"
printf ' %-6s %s\n' "$([ $guard_rc -eq 0 ] && echo PASS || echo FAIL)" "Floating-promises guard (rule + unawaited board auth checks)"
printf ' %-6s %s\n' "$([ $wekan_rc -eq 0 ] && echo PASS || echo FAIL)" "WeKan tests (mocha, node suites, import, E2E, three browsers)"
printf ' %-6s %s\n' "$([ $conf_rc -eq 0 ] && echo PASS || echo FAIL)" "Database conformance (all databases for this CPU)"
printf ' %-6s %s\n' "$([ $ferret_rc -eq 0 ] && echo PASS || echo FAIL)" "FerretDB tests (unit, vet, integration)"
echo "=============================================================================="
echo "All logs: $RUN_LOGDIR/"
unset WEKAN_LOGDIR
[ $wekan_rc -eq 0 ] && [ $conf_rc -eq 0 ] && [ $ferret_rc -eq 0 ] || FAILED=1
[ $guard_rc -eq 0 ] && [ $wekan_rc -eq 0 ] && [ $conf_rc -eq 0 ] && [ $ferret_rc -eq 0 ] || FAILED=1
if [ "$FAILED" -eq 0 ]; then echo "RESULT: EVERYTHING passed."; else echo "RESULT: something FAILED - see the stage summaries above."; fi
return "$FAILED"
}
@@ -1347,63 +1637,161 @@ function ask_dev_url(){
esac
}
# ── Update git: make the working copy current in one step ────────────────────
# ── Keeping the checkout in step with origin ─────────────────────────────────
# Fetch + rebase the current branch onto its upstream, then repoint any CHANGELOG
# commit links the rebase made stale (same shared script release-all.sh uses), and
# show the resulting status. One menu action instead of the fetch/pull/rebase/
# hash-fix/status dance by hand.
function update_git(){
git rev-parse --git-dir >/dev/null 2>&1 || { echo "Not a git repository."; return 0; }
local branch upstream here
here="$(cd "$(dirname "$0")" && pwd)"
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
echo "== Update git — branch: ${branch:-?} =="
# ── git pull / git push, doing the whole job ─────────────────────────────────
#
# These replace the old "Update git", which did a fetch+rebase, ran the CHANGELOG
# hash repair, printed `git status` and left the rest to the reader. The rest is
# where the mistakes were: a rebase rewrites local commits, so the CHANGELOG links
# written before it point at commits that no longer exist; the repair fixes the
# file but commits nothing, so the next push carries a dirty tree; and a push
# rejected as non-fast-forward leaves a repo that needs a pull the caller has to
# know to run.
#
# So: one action per direction, each ending in a state that is either correct or
# unchanged. Conflicts are the one thing a script must not paper over - a rebase
# that stops half-applied IS the wrong state - so on conflict the rebase is
# aborted, the working tree is exactly what it was, and the message says what to
# do by hand.
#
# Nothing here force-pushes, ever.
# Do not surprise anyone: a dirty tree is stashed by --autostash below, but say
# so first and let the user opt out.
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
echo "Working tree has uncommitted changes:"
git status --short
printf "They will be auto-stashed for the rebase and re-applied after. Continue? [y/N] "
read -r ans
case "$ans" in y|Y|yes|YES) : ;; *) echo "Aborted (nothing changed)."; return 0 ;; esac
fi
echo "--- git fetch --all --prune ---"
git fetch --all --prune || { echo "fetch failed - check network/remote and retry."; return 0; }
upstream="origin/$branch"
if git rev-parse --verify --quiet "$upstream" >/dev/null 2>&1; then
echo "--- git pull --rebase --autostash origin $branch ---"
if ! git pull --rebase --autostash origin "$branch"; then
echo
echo "Rebase stopped (conflicts or autostash conflict). Resolve them, then:"
echo " git rebase --continue # or: git rebase --abort"
echo "and run this option again to finish the hash fix + status."
return 0
fi
else
echo "No upstream '$upstream' - skipping rebase (nothing to pull for this branch)."
fi
# The rebase may have rewritten commits the CHANGELOG links to; repoint the
# stale links in the unreleased section (shared with releases/release-all.sh).
# A link is only repointed when NO ref in this clone reaches its commit, so a
# link into an old release tag - which GitHub serves fine - is left alone.
echo "--- Fixing CHANGELOG commit links (releases/fix-changelog-hashes.sh) ---"
bash "$here/releases/fix-changelog-hashes.sh" || true
echo " (whole file, including released sections:"
echo " bash releases/fix-changelog-hashes.sh --all-sections [--dry-run])"
echo "--- git status ---"
git status
# The CHANGELOG links after history moved. The repair only repoints a link whose
# commit is reachable from NO ref in this clone (a link into an old release tag
# is fine and is left alone), so running it when nothing moved is a no-op.
# Returns 0 when the file was changed and committed, 1 when there was nothing to do.
function git_fix_changelog_links(){
local script="$WEKAN_DIR/releases/fix-changelog-hashes.sh"
[ -f "$script" ] || return 1
bash "$script" || true
if [ -n "$(git status --porcelain -- CHANGELOG.md 2>/dev/null)" ]; then
echo
echo "NOTE: CHANGELOG.md was updated by the hash fix above."
echo " Review 'git diff CHANGELOG.md' and commit it (this script never commits for you)."
git add CHANGELOG.md
git commit -q -m "CHANGELOG: repoint commit links after history moved.
A rebase rewrote the commits these entries link to, so the links named commits
that no longer exist and would have 404ed on GitHub. Repointed by
releases/fix-changelog-hashes.sh, which only touches a link no ref in this clone
can reach.
Thanks to xet7 !"
echo "==> CHANGELOG commit links repointed and committed."
return 0
fi
return 1
}
# git pull: fast-forward when that is all it takes, rebase when the branch has
# diverged, and never end half-way through either.
function git_pull(){
git rev-parse --git-dir >/dev/null 2>&1 || { echo "Not a git repository."; return 1; }
local branch upstream before ahead behind
branch="$(git rev-parse --abbrev-ref HEAD)"
upstream="origin/$branch"
echo "== git pull - branch $branch =="
# The exact commit to return to if anything goes wrong.
before="$(git rev-parse HEAD)"
if [ -n "$(git status --porcelain)" ]; then
echo "==> Working tree is not clean; the changes are stashed for the pull and"
echo " re-applied afterwards (git's own --autostash)."
fi
echo "--- git fetch origin $branch ---"
git fetch origin "$branch" || { echo "ERROR: fetch failed - network or remote. Nothing changed."; return 1; }
git rev-parse --verify --quiet "$upstream" >/dev/null || {
echo "No $upstream yet - nothing to pull. Push first to create it."; return 0; }
behind="$(git rev-list --count HEAD.."$upstream")"
ahead="$(git rev-list --count "$upstream"..HEAD)"
echo " $ahead commit(s) here that origin does not have, $behind the other way."
if [ "$behind" -eq 0 ]; then
echo "==> Already up to date with $upstream."
elif [ "$ahead" -eq 0 ]; then
# Nothing of ours to replay: a fast-forward moves the branch pointer and
# rewrites no commit, so no link can go stale.
echo "--- fast-forward (no local commits to replay) ---"
git merge --ff-only "$upstream" || { echo "ERROR: fast-forward failed. Nothing changed."; return 1; }
else
echo "--- rebase: replaying $ahead local commit(s) onto $upstream ---"
echo " This gives them NEW hashes, which is why the CHANGELOG links are"
echo " repaired straight after."
if ! git -c rebase.autoStash=true rebase "$upstream"; then
git rebase --abort 2>/dev/null
git stash list >/dev/null 2>&1
echo
echo "ERROR: the rebase hit a conflict, so it was ABORTED - this repo is"
echo " exactly as it was before ($(git rev-parse --short "$before"))."
echo " Resolve it by hand:"
echo " git rebase $upstream # then fix the conflicts"
echo " git rebase --continue # or: git rebase --abort"
echo " and run this option again afterwards."
return 1
fi
fi
git_fix_changelog_links || echo "==> CHANGELOG commit links all resolve; nothing to repoint."
echo "--- git status ---"
git status --short --branch
return 0
}
# git push: make sure what is about to be published is correct first, and turn
# the one rejection that has an obvious answer into that answer.
function git_push(){
git rev-parse --git-dir >/dev/null 2>&1 || { echo "Not a git repository."; return 1; }
local branch ahead
branch="$(git rev-parse --abbrev-ref HEAD)"
echo "== git push - branch $branch =="
if [ -n "$(git status --porcelain)" ]; then
echo "==> NOTE: there are uncommitted changes; a push publishes commits only."
git status --short
fi
# Before publishing, not after: a stale link that reaches GitHub 404s for
# everyone who reads the release notes.
git_fix_changelog_links || echo "==> CHANGELOG commit links all resolve."
git fetch origin "$branch" >/dev/null 2>&1 || true
if git rev-parse --verify --quiet "origin/$branch" >/dev/null; then
ahead="$(git rev-list --count "origin/$branch"..HEAD)"
[ "$ahead" -eq 0 ] && { echo "==> Nothing to push; origin/$branch is already at this commit."; return 0; }
echo " $ahead commit(s) to push."
fi
echo "--- git push origin $branch ---"
if git push origin "$branch"; then
echo "==> Pushed."
return 0
fi
# The common rejection: origin moved while this was being written. That has
# one right answer - pull, then push again - so do it once, rather than
# printing the advice and leaving the caller to it. Once only: a second
# rejection is something else, and retrying a loop is not a fix.
echo
echo "==> Push was rejected. Pulling first, then trying once more."
git_pull || { echo "ERROR: the pull did not finish, so nothing was pushed."; return 1; }
echo "--- git push origin $branch (retry) ---"
if git push origin "$branch"; then
echo "==> Pushed."
return 0
fi
echo
echo "ERROR: still rejected. This is NOT a case to force - force-pushing a"
echo " branch somebody has pulled rewrites their history too. Read what"
echo " git said above; if origin has commits this clone should keep,"
echo " run this option's pull first and look at 'git log --oneline'."
return 1
}
echo
PS3='Please enter your choice: '
@@ -1485,6 +1873,7 @@ RELEASE_SCRIPTS=( "Release|Release ALL platforms: push CHANGELOG, trigger releas
"Snap|Release the snap for one version|releases/release-snap.sh|WeKan version, e.g. 10.50||"
"Snap|List the newest Snap Store revisions|releases/snap-store-revisions.sh|||"
"Snap|Release one store revision to edge, beta and candidate|releases/snap-store-release-revision-to-channels.sh|Snap Store revision number||"
"Snap|Release every snap, every architecture, to all four channels|releases/snap-release-all-channels.sh|Version, or empty for the newest||snap-release-all-channels"
"Snap|Switch the installed snap to the edge channel|releases/snap-edge.sh|||"
"Snap|Switch the installed snap to the stable channel|releases/snap-stable.sh|||"
"Snap|snapcraft help topics|releases/snapcraft-help.sh|||"
@@ -1523,6 +1912,8 @@ RELEASE_SCRIPTS=( "Release|Release ALL platforms: push CHANGELOG, trigger releas
"Translations|Report English strings that regressed|releases/translations/report-english-regressions.mjs|||"
"Translations|Prove a pull keeps human translations (no network)|releases/translations/verify-human-preference.mjs|||"
"Translations|Merge a finished pull by hand|releases/translations/merge-translations.mjs|||"
"Git and repo|git pull - fetch, fast-forward or rebase, repoint moved CHANGELOG links|!git_pull|||git-pull"
"Git and repo|git push - verify the CHANGELOG links, push, pull-and-retry once if origin moved|!git_push|||git-push"
"Git and repo|Commit with the editor open for a multi-line message|releases/commit.sh|||"
"Git and repo|Add everything, then revert it again|!git restore --staged|Path to unstage||git-add-revert"
"Git and repo|Delete a branch, locally and on the remote|releases/delete-branch-local-and-remote.sh|Branch name||"
@@ -1731,7 +2122,8 @@ while [ -z "$opt" ]; do
choose "Setup" \
"Install dependencies|Install WeKan dependencies" \
"Build WeKan|Build WeKan" \
"Update git|Update git: fetch + rebase onto origin, fix CHANGELOG hashes, show status" ;;
"git pull|git pull: fetch, fast-forward or rebase onto origin, repoint the CHANGELOG commit links the rebase moved, and leave the repo unchanged if anything conflicts" \
"git push|git push: check the CHANGELOG commit links resolve before publishing them, push this branch to origin, and pull-then-retry once if origin moved meanwhile" ;;
"Dev server")
choose "Dev server" \
"localhost:3000|Run Meteor for dev on http://localhost:3000" \
@@ -1744,9 +2136,9 @@ while [ -z "$opt" ]; do
"Kill all dev servers|Kill all dev servers (free ports 3000/3001/3100/3101/4000/4001/8080)" ;;
"Tests")
choose "Tests" \
"EVERYTHING (sequential): WeKan tests + all databases + FerretDB tests|Run EVERYTHING sequentially: WeKan's own tests, then the database conformance run for every database with a Docker image for this CPU, then all of FerretDB's own tests (unit, vet, integration) - one stage at a time, all logs in ../log/<datetime>/" \
"WeKan's own tests only, parallel|Run WeKan's own tests in parallel on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, concurrently. No database conformance and no FerretDB tests - logs in ../log/<datetime>/" \
"WeKan's own tests only, sequential|Run WeKan's own tests sequentially on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, one job at a time. No database conformance and no FerretDB tests - logs in ../log/<datetime>/" \
"EVERYTHING (sequential): the guard + WeKan tests + all databases + FerretDB tests|Run EVERYTHING sequentially: the floating-promises guard (seconds), then WeKan's own tests (mocha, the node suites, import regression, node E2E and all three browsers), then the database conformance run for every database with a Docker image for this CPU, then all of FerretDB's own tests (unit, vet, integration) - one stage at a time, all logs in log/<datetime>/" \
"WeKan's own tests only, parallel|Run WeKan's own tests in parallel on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, concurrently. No database conformance and no FerretDB tests - logs in log/<datetime>/" \
"WeKan's own tests only, sequential|Run WeKan's own tests sequentially on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, one job at a time. No database conformance and no FerretDB tests - logs in log/<datetime>/" \
"Mocha (server-side)|Test Mocha unit + security + API-logic tests (server-side only, no browser)" \
"Import regression|Test import regression (tests/wekanCreator.import.test.js, fast, no server)" \
"Node E2E regressions|Test Node E2E regressions (tests/e2e/list-regressions.js, needs running server)" \
@@ -1757,8 +2149,8 @@ while [ -z "$opt" ]; do
"Playwright ALL browsers|Test Playwright ALL browsers sequentially (Chromium + Firefox + WebKit, one at a time), server already running on :3000" \
"Floating-promises guard|Check floating promises guard (@typescript-eslint/no-floating-promises + auth await scan)" \
"Count tests by category|Count amount of tests by category" \
"Run all FerretDB tests - SEQUENTIAL|Run all FerretDB tests - SEQUENTIAL: unit, vet and the integration suite of the FerretDB subdirectory of this repo, one at a time, logs in ../log/<datetime>/" \
"All databases (sequential)|Test all databases that have a Docker image for this CPU, SEQUENTIALLY: build newest FerretDB v1 from source, then run every FerretDB v1 query type against each database and compare that they all answer the same (results in ../log/<datetime>/)" ;;
"Run all FerretDB tests - SEQUENTIAL|Run all FerretDB tests - SEQUENTIAL: unit, vet and the integration suite of the FerretDB subdirectory of this repo, one at a time, logs in log/<datetime>/" \
"All databases (sequential)|Test all databases that have a Docker image for this CPU, SEQUENTIALLY: build newest FerretDB v1 from source, then run every FerretDB v1 query type against each database and compare that they all answer the same (results in log/<datetime>/)" ;;
"Tools")
choose "Tools" \
"Save Meteor deps list|Save Meteor dependency chain to ../meteor-deps.txt" \
@@ -1863,12 +2255,23 @@ for _once in 1; do
;;
"Build WeKan")
build_wekan
# || exit 1, because build_wekan returns 1 on failure and a bare call
# throws that away: the menu breaks, the script falls off the end and
# exits 0. A build that printed "ERROR: the WeKan build failed" while
# reporting success to its caller is worse than one that just fails -
# anything driving this non-interactively (`printf '1\n2\n' | ./build.sh`,
# or CI) sees a green run and a missing bundle.
build_wekan || exit 1
break
;;
"Update git: fetch + rebase onto origin, fix CHANGELOG hashes, show status")
update_git
"git pull: fetch, fast-forward or rebase onto origin, repoint the CHANGELOG commit links the rebase moved, and leave the repo unchanged if anything conflicts")
git_pull
break
;;
"git push: check the CHANGELOG commit links resolve before publishing them, push this branch to origin, and pull-then-retry once if origin moved meanwhile")
git_push
break
;;
@@ -1877,9 +2280,9 @@ for _once in 1; do
kill_meteor_on_port 3000 || break
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
# Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee ../log/wekan-log.log
# Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1890,8 +2293,8 @@ for _once in 1; do
kill_meteor_on_port 3000 || break
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
# Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings --max-old-space-size=8192" WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 2>&1 | tee ../log/wekan-log.log
# Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings --max-old-space-size=$_heap_mb" WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1901,9 +2304,9 @@ for _once in 1; do
kill_meteor_on_port 3000 || break
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee ../log/wekan-log.log
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 --extra-packages bundle-visualizer --production 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 --extra-packages bundle-visualizer --production 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1920,9 +2323,9 @@ for _once in 1; do
#---------------------------------------------------------------------
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee ../log/wekan-log.log
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:3000 meteor run --port 3000 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:3000 meteor run --port 3000 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1939,9 +2342,9 @@ for _once in 1; do
#---------------------------------------------------------------------
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee ../log/wekan-log.log
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true MONGO_URL=mongodb://127.0.0.1:27019/wekan WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:3000 meteor run --port 3000 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true MONGO_URL=mongodb://127.0.0.1:27019/wekan WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:3000 meteor run --port 3000 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1953,9 +2356,9 @@ for _once in 1; do
#---------------------------------------------------------------------
# Same environment as the plain localhost:3000 option; only the port and
# ROOT_URL differ. Logging of terminal output to console and to
# ../log/wekan-log.log at the end of the line: 2>&1 | tee ../log/wekan-log.log
# ../log/wekan-log.log at the end of the line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL="$DEV_ROOT_URL" meteor run --port "$DEV_PORT" 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL="$DEV_ROOT_URL" meteor run --port "$DEV_PORT" 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1972,9 +2375,9 @@ for _once in 1; do
#---------------------------------------------------------------------
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee ../log/wekan-log.log
#Logging of terminal output to console and to ../log/wekan-log.log at end of this line: 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --port $PORT 2>&1 | tee ../log/wekan-log.log
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=uws DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --port $PORT 2>&1 | tee "$WEKAN_LOG_ROOT/wekan-log.log"
#---------------------------------------------------------------------
break
;;
@@ -1991,12 +2394,12 @@ for _once in 1; do
break
;;
"Run WeKan's own tests in parallel on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, concurrently. No database conformance and no FerretDB tests - logs in ../log/<datetime>/")
"Run WeKan's own tests in parallel on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, concurrently. No database conformance and no FerretDB tests - logs in log/<datetime>/")
run_all_tests parallel
break
;;
"Run WeKan's own tests sequentially on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, one job at a time. No database conformance and no FerretDB tests - logs in ../log/<datetime>/")
"Run WeKan's own tests sequentially on http://localhost:3000: Mocha, the node unit suites, import regression, node E2E and all three browsers, one job at a time. No database conformance and no FerretDB tests - logs in log/<datetime>/")
run_all_tests sequential
break
;;
@@ -2076,39 +2479,15 @@ for _once in 1; do
echo "Ensuring .eslintrc.json includes @typescript-eslint plugin and no-floating-promises rule"
node -e "const fs=require('fs');const p='.eslintrc.json';const c=JSON.parse(fs.readFileSync(p,'utf8'));c.plugins=Array.isArray(c.plugins)?c.plugins:[];if(!c.plugins.includes('@typescript-eslint'))c.plugins.push('@typescript-eslint');c.rules=c.rules||{};c.rules['@typescript-eslint/no-floating-promises']='error';fs.writeFileSync(p,JSON.stringify(c,null,2)+'\\n');"
echo "Checking whether @typescript-eslint/no-floating-promises is configured in .eslintrc.json"
if command -v rg >/dev/null 2>&1; then
RULE_CHECK_CMD='rg -n'
else
RULE_CHECK_CMD='grep -nE'
fi
if $RULE_CHECK_CMD '"@typescript-eslint/no-floating-promises"' .eslintrc.json >/dev/null 2>&1; then
echo "OK: Rule @typescript-eslint/no-floating-promises is configured in .eslintrc.json"
else
echo "WARNING: Rule @typescript-eslint/no-floating-promises is NOT configured in .eslintrc.json"
echo "Suggested: add @typescript-eslint/eslint-plugin and set '@typescript-eslint/no-floating-promises': 'error'"
fi
echo "Quick note: @typescript-eslint/no-floating-promises is a type-aware rule and may require further parser/project setup for full enforcement in all files."
echo
echo "Scanning for unawaited Authentication.checkBoardAccess/checkBoardWriteAccess in server/models"
AUTH_CALL_PATTERN='Authentication\.checkBoardAccess\(|Authentication\.checkBoardWriteAccess\('
AUTH_AWAIT_PATTERN='await Authentication\.checkBoardAccess\(|await Authentication\.checkBoardWriteAccess\('
if command -v rg >/dev/null 2>&1; then
if rg -n "$AUTH_CALL_PATTERN" server/models | rg -v "$AUTH_AWAIT_PATTERN"; then
echo "WARNING: Found possible unawaited board auth checks above"
else
echo "OK: No unawaited board auth checks found"
fi
else
if grep -RInE "$AUTH_CALL_PATTERN" server/models | grep -vE "$AUTH_AWAIT_PATTERN"; then
echo "WARNING: Found possible unawaited board auth checks above"
else
echo "OK: No unawaited board auth checks found"
fi
fi
# The checks themselves - the same function EVERYTHING runs, so the
# menu option and the whole-run stage can never drift apart. Above
# this line is the setting-up half, which only a person asking for
# this option gets: it installs packages and writes the rule into
# .eslintrc.json, and a test run must do neither.
floating_promises_checks
} 2>&1 | tee "$LOG"
break
;;
@@ -2123,7 +2502,7 @@ for _once in 1; do
break
;;
"Run all FerretDB tests - SEQUENTIAL: unit, vet and the integration suite of the FerretDB subdirectory of this repo, one at a time, logs in ../log/<datetime>/")
"Run all FerretDB tests - SEQUENTIAL: unit, vet and the integration suite of the FerretDB subdirectory of this repo, one at a time, logs in log/<datetime>/")
# FerretDB is a subdirectory of this repo; releases/db-conformance.sh clones it
# when it is not there, and its own build.sh installs Go and the modules.
if [ ! -x FerretDB/build.sh ]; then
@@ -2135,11 +2514,11 @@ for _once in 1; do
fi
;;
"Run EVERYTHING sequentially: WeKan's own tests, then the database conformance run for every database with a Docker image for this CPU, then all of FerretDB's own tests (unit, vet, integration) - one stage at a time, all logs in ../log/<datetime>/")
"Run EVERYTHING sequentially: the floating-promises guard (seconds), then WeKan's own tests (mocha, the node suites, import regression, node E2E and all three browsers), then the database conformance run for every database with a Docker image for this CPU, then all of FerretDB's own tests (unit, vet, integration) - one stage at a time, all logs in log/<datetime>/")
run_everything
;;
"Test all databases that have a Docker image for this CPU, SEQUENTIALLY: build newest FerretDB v1 from source, then run every FerretDB v1 query type against each database and compare that they all answer the same (results in ../log/<datetime>/)")
"Test all databases that have a Docker image for this CPU, SEQUENTIALLY: build newest FerretDB v1 from source, then run every FerretDB v1 query type against each database and compare that they all answer the same (results in log/<datetime>/)")
# Everything this needs - the FerretDB source, the Go toolchain, the module
# dependencies - is fetched or built by the script itself; see the note at its
# top for why it is sequential and why SAP HANA is opt-in.
@@ -2225,5 +2604,21 @@ for _once in 1; do
break
;;
# No arm matched. $opt is the menu entry's FULL DESCRIPTION, written twice -
# once in the choose() list far above, once as the arm here - so an edit to
# the menu text that misses the arm leaves an option that matches nothing.
# Without this, the case simply falls through, the `for _once` loop ends and
# the script EXITS: the option looks like it worked, printed nothing and ran
# nothing. That is what renaming "EVERYTHING (sequential)" did to it.
# tests/buildScriptParity.test.cjs fails on the mismatch itself; this is what
# the person in front of the menu sees if one ever gets past it.
*)
echo "build.sh: no handler for this menu option - that is a bug in build.sh." >&2
echo " The menu entry's description and its case arm must be the SAME string:" >&2
echo " $opt" >&2
echo " Nothing was run. Please report it, or run the same thing from the command line:" >&2
echo " ./build.sh --list" >&2
;;
esac
done
+42 -21
View File
@@ -82,10 +82,20 @@
The SHARED variable (settingBody.css), so this menu and the Admin Panel's
are one menu at one width, and dragging either grip changes both. */
width: var(--wekan-left-menu-width);
/* ...but never wider than the track it is in: on a phone that track is capped
at 210px (#6523, the rules further down), and a fixed width would have run
the menu out past it. */
/* `min-width: 0` so the menu CAN be narrower than its contents: a grid item's
default `min-width: auto` is its content's intrinsic minimum, and a minimum
beats a maximum. The rows inside already ellipse (the workspace name carries
its own `min-width: 0`), so nothing needs the intrinsic width held open.
`max-width: 100%` does NOT cap the menu to its grid track, whatever it looks
like it should do - this comment used to claim it did. A percentage width on
a grid item does not resolve against the track, so on a phone the menu kept
all 260px and lay over the boards in Chromium, Firefox and WebKit alike.
What actually caps it is `width: auto` in the phone rules further down,
which makes the item stretch to its grid area. This stays because it is
still right for the non-grid places this menu is used. */
max-width: 100%;
min-width: 0;
box-sizing: border-box;
background-color: #f7f7f7;
/* Flush on three sides, like the Admin Panel's: it starts at the window's
@@ -1307,6 +1317,26 @@
grid-template-columns: minmax(140px, min(42%, 210px)) 1fr;
gap: 8px;
}
/* #6488. The TRACK above decides how wide the menu is on a phone, so the menu
must not carry a width of its own here: --wekan-left-menu-width is 260px,
which is the DESKTOP width the drag-grip sets, and on a 375px phone that is
100px wider than the 157.5px track it has to sit in.
leftMenu.js does not offer the grip below 800px for exactly this reason.
`width: auto`, NOT `width: 100%` or `max-width: 100%`: a grid item with an
auto width STRETCHES to its grid area (justify-self defaults to stretch),
which fills the track exactly with no percentage to resolve. The rule up at
.boards-left-menu already says `max-width: 100%` and claims in its comment
that this caps the menu to the track - it does not. All three engines
(Chromium, Firefox and WebKit) kept the menu at its full 260px and laid it
OVER the board column, which is what the screenshot of the failing test
shows: the menu's #f7f7f7 background running to x=255 with the board tiles
painted on top of it from x=170. A percentage here does not resolve against
the track, so the only reliable way to say "be the width of your column" is
to not name a width at all. */
.boards-left-menu {
width: auto;
}
.board-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1898,26 +1928,17 @@
padding-inline-end: 8px;
}
/* #6523: on a phone (either orientation) the count chip sits right after its
label, not pushed to the far edge. The menu item is `justify-content:
space-between` with a `flex: 1` label, which spreads the label and the count
apart across the whole column - fine on a narrow portrait menu, but a wide gap
on a landscape iPhone where the column is wider. Packing them left keeps the
number beside the text at any menu width. Scoped to the mobile layout so the
desktop 260px menu is untouched. */
/* The counts line up in ONE COLUMN, at every width.
#6523 had packed them next to their labels on a phone - the number beside the
text rather than across a gap - and that is what this reverses. Starred, Home,
Templates and Archive have labels of four different lengths, so a count that
follows its label lands at four different x positions and the column of
numbers reads as ragged rather than as a column at all. The row keeps the
`justify-content: space-between` and the `flex: 1` label the base rule gives
it, at every width, so every count sits at the end of its row and they align
with each other. */
@media screen and (max-width: 800px),
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
.boards-left-menu .menu-item a {
justify-content: flex-start;
gap: 8px;
}
.boards-left-menu .menu-item .menu-label {
flex: 0 1 auto;
}
/* No extra left margin now that a flex `gap` sets the spacing. */
.boards-left-menu .menu-item .menu-count {
margin-inline-start: 0;
}
/* The workspace row packs a drag handle, a folder icon, the NAME, a "bars" menu
button and a count chip. Told only to "give way to the fixed items", the name
+5
View File
@@ -249,6 +249,11 @@
text-align: center;
}
.minicard .minicard-title {
/* #6465: one step below the list heading above it (16px), and above the
"Add card" affordance below (13px). It used to inherit the 16px document
size, which made a card title exactly as loud as the heading of the column
holding it. See the note on .list-header-name in lists/list.css. */
font-size: 14px;
margin-inline-end: 21px;
/* #6444: isolate each title's bidirectional context. The title is a
dir="auto" .viewer; under a global dir="rtl" document, non-isolated
+51 -3
View File
@@ -119,6 +119,7 @@ body.list-resizing-active * {
}
/* More aggressive override for any container that might interfere */
.js-lists .list.list-resizing,
.js-swimlane .list.list-resizing,
.dragscroll .list.list-resizing,
[id^="swimlane-"] .list.list-resizing {
@@ -140,6 +141,18 @@ body.list-resizing-active * {
property, and without :not() this !important rule overrode the 30px collapsed
width, so collapsing a list that had a custom width left it full width.
See https://github.com/wekan/wekan/issues/5892 */
/* #6573: `.js-lists` is the selector that has to be here, and `.dragscroll` is
the one that cannot be relied on. In LISTS view the container is
`.swimlane.list-group.js-lists.dragscroll` - no `js-swimlane` class and no
`swimlane-<id>` id - so `.dragscroll` was the only one of the three that
matched, and the width of every list hung on it. Starting a card drag calls
suspendBoardDragscroll(), which REMOVES the dragscroll class from the board so
the same pointer cannot pan and drag at once (#6558); this rule stopped
matching with it, every list fell back to the flex rules below, and the whole
board re-flowed to fill the window the instant a card was picked up - then
snapped back on drop, when the class returned. Both views' containers carry
`js-lists`, and nothing removes it. */
.js-lists .list[style*="--list-width"]:not(.list-collapsed),
.js-swimlane .list[style*="--list-width"]:not(.list-collapsed),
.dragscroll .list[style*="--list-width"]:not(.list-collapsed),
[id^="swimlane-"] .list[style*="--list-width"]:not(.list-collapsed) {
@@ -163,6 +176,7 @@ body.list-resizing-active * {
in mobile mode on a desktop browser the inline width was there and the lists stayed
at their desktop width while everything else was in mobile layout. Same three
selectors, prefixed with body.mobile-mode so this one is the more specific. */
body.mobile-mode .js-lists .list[style*="--list-width"]:not(.list-collapsed),
body.mobile-mode .js-swimlane .list[style*="--list-width"]:not(.list-collapsed),
body.mobile-mode .dragscroll .list[style*="--list-width"]:not(.list-collapsed),
body.mobile-mode [id^="swimlane-"] .list[style*="--list-width"]:not(.list-collapsed) {
@@ -454,11 +468,20 @@ form.inlined-form .list-composer {
}
.list-header .list-header-name {
display: block;
font-size: 14px;
/* #6465: the type scale of a list, from the outside in. A list heading is to
its cards what an h2 is to its paragraphs, and it had ended up SMALLER than
them: the heading was 14px while a minicard title and the "Add card" link
both inherited the 16px document size, so the column looked like it was
labelled in fine print and the affordance to add a card shouted louder than
the cards. The scale is now heading 16 > card title 14 >= add-card link 13,
kept in that order by tests/listTypeScale.test.cjs. Mobile keeps its own
larger sizes (the !important rules further down) - a phone is read at arm's
length. */
font-size: 16px;
line-height: 1.2;
margin: 0;
font-weight: bold;
min-height: 14px;
min-height: 16px;
min-width: 56px;
overflow-wrap: break-word;
word-wrap: break-word;
@@ -732,6 +755,23 @@ form.inlined-form .list-composer {
.list-header .highlight {
color: #ce1414;
}
/* #6465: the WIP counter belongs on the title line, and the header of a list
that has one must be as tall as its neighbours - the report showed "(2/5)"
dropped onto a second line under the title, making that one column's header
taller than the lists either side of it.
The counter is already inside the h2, right after the title (listHeader.jade),
but the title is a `+viewer`, and `.viewer` is display: block with a 22px
min-height: a block in the middle of a heading pushes everything after it onto
the next line and reserves a band under it. Inline, the counter stays where the
markup already puts it. Same treatment the quick-access header and the table
view give their viewers. */
.list-header .list-header-name .viewer,
.list-header .list-header-name .viewer p {
display: inline;
min-height: 0;
margin: 0;
}
.list-header .cardCount {
color: #8c8c8c;
font-size: 12px;
@@ -784,12 +824,20 @@ form.inlined-form .list-composer {
margin-bottom: 9px;
}
.list-body .minicards .add-controls button {
min-height: 50px;
/* Was min-height: 50px - taller than a minicard is for its title, so the
"Add" button was the biggest thing on the board while typing. 34px is a
comfortable touch target that still reads as a control rather than a slab. */
min-height: 34px;
font-size: 14px;
}
.list-body .open-minicard-composer {
border-radius: 2px;
color: #8c8c8c;
display: block;
/* Smaller than a card title, because it is not a card: it is the affordance
for making one. At the inherited 16px it was the largest text in the list
body, level with the board's real content. */
font-size: 13px;
padding: 7px 10px;
position: relative;
text-decoration: none;
@@ -30,6 +30,8 @@ template(name="adminReports")
+eventStreamReport(stream="cpu")
else if showDatabase.get
+eventStreamReport(stream="database")
else if showIntegrity.get
+eventStreamReport(stream="integrity")
else if tablePageData
+tablePage(tablePageData)
+101 -3
View File
@@ -1,5 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
// The per-pane URLs of the Admin Panel. docs/Design/Page/Admin-Panel-URLs.md
import { adminPath } from '/models/lib/adminUrls';
@@ -109,6 +110,7 @@ Template.adminReports.onCreated(function () {
this.showTests = new ReactiveVar(false);
this.showCpu = new ReactiveVar(false);
this.showDatabase = new ReactiveVar(false);
this.showIntegrity = new ReactiveVar(false);
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
@@ -205,17 +207,79 @@ Template.adminReports.onCreated(function () {
});
};
// Which user the open report was subscribed as. See the autorun below.
this.subscribedAs = null;
// The pane the URL asks for. The route resolved it, so it is always a real
// pane id; a bare /admin-reports opens Summary. Reactive, so following a link
// to another report while this page is open switches to it - the route action
// runs again without re-creating the template.
// docs/Design/Page/Admin-Panel-URLs.md
//
// It depends on Meteor.userId() as well, and that is not decoration. Opening
// a report BY ITS ADDRESS - /admin/problems/files typed, bookmarked, or just
// refreshed - is a full page load, and Meteor resumes the login from
// localStorage ASYNCHRONOUSLY. The route sets problemsOpenPane before that
// lands, so the subscription was made with no user; the publication's admin
// check then answered `this.ready()` with no rows, and nothing ever
// re-subscribed because this autorun did not depend on the user. The pane drew
// its column headers, "No results" and a "1 / 1" pager over data that was
// plainly there - while the count METHOD, called later from the same page,
// happily reported five. Reached from the menu it worked, because by then the
// login had landed; only the address did not.
//
// openReportPane() returns early when the pane is already open, so re-running
// it after the login would do nothing at all - hence the second branch, which
// re-subscribes the report that is already open now that there is a user to
// subscribe as.
//
// The ONLY two things that may re-run this autorun are the pane id and the
// user - read reactively, above the nonreactive body. Everything the body
// touches (activeReport, and the count/page/search that loadReport reads
// through pageInfo) is read INSIDE Tracker.nonreactive, on purpose:
//
// 1. loadReport() calls its count method and then cfg.count.set(...). If the
// autorun depended on cfg.count, that set would re-run it - and a
// Meteor.subscribe made inside an autorun is AUTO-CANCELLED when the
// autorun re-runs. The re-run then took the `else if` (same user, same
// pane), did NOT re-subscribe, and left the report with no subscription
// at all: "attachments in minimongo: 0", the empty table over data that
// was plainly there. From the menu it worked only because switchMenu
// calls openReportPane from an EVENT, not a computation, so that
// subscribe was never auto-managed. By URL it was, which is why only the
// address failed.
// 2. The subscription's lifetime is ours to manage (loadReport stops the
// previous one; onDestroyed below stops the last), not the autorun's, so
// it must not be created inside this computation's reactive scope.
this.autorun(() => {
const paneId = Session.get('problemsOpenPane');
if (paneId) openReportPane(this, paneId);
const userId = Meteor.userId();
Tracker.nonreactive(() => {
if (!paneId) {
return;
}
if (paneId !== this.activeReport.get()) {
openReportPane(this, paneId);
this.subscribedAs = userId;
} else if (userId !== this.subscribedAs) {
this.subscribedAs = userId;
if (reportConfig(this)[paneId]) {
this.loadReport(paneId, { recount: true });
}
}
});
});
});
// The report subscription is created OUTSIDE a reactive computation now (see the
// autorun above), so the autorun no longer tears it down - this does, when the
// panel is left.
Template.adminReports.onDestroyed(function () {
if (this.subscription) {
this.subscription.stop();
}
});
// The Problems side menu, as data (docs/Design/Page/Left-Menu.md). Every entry
// used to be six lines of markup plus its own click handler; the twelve handlers
// are now the single .js-left-menu-item one below.
@@ -252,6 +316,10 @@ const PROBLEMS_MENU = [
// What the database itself said, classified: which database type, what it
// means and what to do (server/lib/databaseProblems.js).
{ id: 'report-database', icon: 'fa-database', labelKey: 'databaseReportTitle' },
// What the FILESYSTEM said: a stored file that is no longer the file WeKan
// stored, and whether this server stopped cleanly
// (docs/Security/Remediation/WeKan.md §13).
{ id: 'report-integrity', icon: 'fa-fingerprint', labelKey: 'integrityReportTitle' },
];
Template.adminReports.helpers({
@@ -434,6 +502,7 @@ function openReportPane(tmpl, targetID) {
tmpl.showTests.set(false);
tmpl.showCpu.set(false);
tmpl.showDatabase.set(false);
tmpl.showIntegrity.set(false);
if (tmpl.subscription) {
tmpl.subscription.stop();
}
@@ -470,6 +539,9 @@ function openReportPane(tmpl, targetID) {
} else if ('report-database' === targetID) {
tmpl.showDatabase.set(true);
tmpl.loading.set(false);
} else if ('report-integrity' === targetID) {
tmpl.showIntegrity.set(true);
tmpl.loading.set(false);
} else if ('report-broken' === targetID) {
// A report like the others now: same controls, same paging, same loader.
tmpl.brokenPage.set(1);
@@ -561,6 +633,12 @@ const REPORT_TABLES = {
try {
return reportPageResults(coll, 'report-files');
} catch (e) {
// Say what went wrong. This used to return [] in silence, and an empty
// catch on the one helper that fills the table is indistinguishable
// from "there is nothing to show": the pane draws its headers, says
// "No results", and nothing anywhere records that it threw. A report
// that cannot render its rows is worth one line in the console.
console.error('[report-files] could not read the page of rows:', e);
return [];
}
},
@@ -766,8 +844,28 @@ const EVENT_STREAM_COLUMNS = [
{ labelKey: 'event-severity', value: r => r.severity, data: r => r.severity },
{ labelKey: 'event-action', value: r => r.action || r.type },
{ labelKey: 'event-source', value: r => r.source },
// The user who triggered the event (e.g. who uploaded a sanitized file).
{ labelKey: 'username', value: r => userName(r.userId), userId: r => r.userId },
// WHO tried it. The stored `username` wins over looking the account up: it is
// what the account was CALLED when the event happened, so a later rename does
// not rewrite history and a deleted account does not erase it. The lookup
// stays as the fallback for the older events that predate the stored field.
{
labelKey: 'username',
value: r => r.username || userName(r.userId),
userId: r => r.userId,
},
// FROM WHERE. Resolved with the same spoofing-safe rule as the login throttle -
// X-Forwarded-For only as far as HTTP_FORWARDED_COUNT says to trust it - so this
// column cannot be written by sending a header.
{ labelKey: 'event-ip', nowrap: true, value: r => r.ip || '' },
// HOW MANY attempts this row stands for. A canary counts repeats inside its
// window rather than writing one row each, so "1" is an ordinary event and a
// larger number is a burst that was deliberately not written out in full
// (docs/Security/Remediation/WeKan.md §12).
{
labelKey: 'event-attempts',
nowrap: true,
value: r => (r.count && r.count > 1 ? String(r.count) : ''),
},
// The `database` stream's detail is WeKan's reading of the error - what it means
// and what to do - and it is worth little without the sentence the database
// itself produced, which is the one thing an admin can search for or paste into
@@ -238,3 +238,21 @@
line-height: 1.4;
margin-top: 2px;
}
/* #6465: "Please move the button down. This is too close" - Save sat directly
under the 1..28 day buttons of the monthly schedule, close enough to be hit
while aiming for a date. The gap is on the form-group that FOLLOWS the day
grid, so it applies wherever that grid is used and nowhere else. */
.attachments-content .schedule-day-buttons + .form-group,
.schedule-day-buttons + .form-group {
margin-top: 18px;
}
/* #6465: "The alignment is out of place" - the move button and the labelled
selects beside it are one row (align-items: flex-end), so a margin of the
button's own would offset it from the controls it acts on. Zero it, and let
the row do the aligning. */
.attachments-content .move-storage-form > button {
margin: 0;
align-self: flex-end;
}
+16
View File
@@ -264,3 +264,19 @@ table:not(.table-page-table) td:first-child {
(docs/Design/Page/Table.md), so its controls row, pagination and table are
styled there - and its pager is themed by paginationControls.css like every
other pager. Nothing pane-specific is needed here any more. */
/* #6465: the "all users" select-all in a table heading. One row, box after the
word, with a gap that cannot collapse - the two used to be loose inline nodes
in a centred heading cell, and at the column's width the box sat on top of the
text. inline-flex keeps the heading's own centring intact. */
.select-all-user {
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
white-space: nowrap;
}
.select-all-user input.allUserChkBox {
margin: 0;
flex: none;
}
+8 -2
View File
@@ -101,8 +101,14 @@ template(name="peopleGeneral")
+tablePage(this)
template(name="selectAllUser")
| {{_ 'dueCardsViewChange-choice-all'}}
input.allUserChkBox(type="checkbox", id="chkSelectAll")
//- #6465: label AND box in one <label>, so the box cannot land on top of the
//- word. They were two loose inline nodes in a table heading: the heading
//- centres its content, the checkbox is replaced-content with its own
//- baseline, and at the width the column happens to have, the box overlapped
//- the text. A label also makes the word itself click the box.
label.select-all-user(for="chkSelectAll")
span {{_ 'dueCardsViewChange-choice-all'}}
input.allUserChkBox(type="checkbox", id="chkSelectAll")
template(name="domainGeneral")
+tablePage(tablePageData)
+8 -2
View File
@@ -266,10 +266,16 @@ template(name="changeSettingsPopup")
option(value="#{day.value}") #{day.name}
label.bold.clear
| {{_ 'card-settings'}}
//- #6465: the checkbox and its label are ONE row, built the way every
//- other checkbox row in this file is (see the board list above):
//- a .flex anchor, the .materialCheckBox, then the text. It used to
//- carry a `b &nbsp;` spacer and `.left` (float: inline-start) as
//- well - a float inside a flex container is ignored, so the spacer
//- became a stray flex item and the box drifted away from the text it
//- belongs to, up beside the "Card settings" heading above.
ul#cards.card-description-rescued
a.flex.js-rescue-card-description(title="{{_ 'rescue-card-description'}}")
b &nbsp;
#rescue-card-description.materialCheckBox.left(class="{{#if rescueCardDescription}}is-checked{{/if}}", value=rescueCardDescription)
#rescue-card-description.materialCheckBox(class="{{#if rescueCardDescription}}is-checked{{/if}}", value=rescueCardDescription)
span {{_ 'rescue-card-description'}}
//- "Save", and themed. It said "Apply" while every other settings form in
//- WeKan says Save, and it carried no `.primary` class - so it fell to the
-1
View File
@@ -9,7 +9,6 @@ import './responsive.tests';
import './boardNumbering.tests';
import './subtaskStatus.tests';
import './subtaskViewGuard.tests';
import './boardTriggersClass.tests';
import './calendarFirstDay.tests';
import './mentionEnterGuard.tests';
import './subtaskViewTarget.tests';
+9 -1
View File
@@ -581,7 +581,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -725,6 +725,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+9 -1
View File
@@ -583,7 +583,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -727,6 +727,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+9 -1
View File
@@ -570,7 +570,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -714,6 +714,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+9 -1
View File
@@ -587,7 +587,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -731,6 +731,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+9 -1
View File
@@ -477,7 +477,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -621,6 +621,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+9 -1
View File
@@ -711,7 +711,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -855,6 +855,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+1 -1
View File
@@ -21,7 +21,7 @@
# opened exclusively. Give each service its own internal port via
# METEOR_SETTINGS and they coexist cleanly.
#
# Reference: see docs/Platforms/FOSS/Docker/Meteor3/multitenancy.md
# Reference: see docs/Platforms/FOSS/Container/Docker/Meteor3/multitenancy.md
# and https://docs.meteor.com/performance/ddp-transport#multitenancy
#
# Setup:
+9 -1
View File
@@ -541,7 +541,7 @@ services:
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/blob/main/docs/Login/LDAP.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Snap/Supported-settings-keys.md
# For Snap settings see https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/Container/Snap/Supported-settings-keys.md
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
@@ -685,6 +685,14 @@ services:
# must still be configured for any group search to work.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# Where the GROUPS are, when they are not under LDAP_BASEDN. Leave this
# unset when users and groups share one subtree: the group searches then
# use LDAP_BASEDN, exactly as they always have. Set it when the directory
# keeps them apart - ou=groups beside ou=people is a common layout - or
# every group search runs under the USER base, finds nothing, and
# LDAP_GROUP_FILTER_ENABLE=true refuses every login (wekan/wekan#5539).
#- LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
+6 -6
View File
@@ -5,7 +5,7 @@
# Upgrade Sandstorm WeKan
[Upgrade Sandstorm WeKan ](../Platforms/FOSS/Sandstorm/Sandstorm.md)
[Upgrade Sandstorm WeKan ](../Platforms/FOSS/Container/Sandstorm)
# Backup Docker
@@ -523,7 +523,7 @@ cat board.json | xclip -se c
```
Then paste to webbrowser Wekan Add Board / Import / From previous export.
You can [save all MongoDB database content as JSON files](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md). Files are base64 encoded in JSON files.
You can [save all MongoDB database content as JSON files](../Platforms/FOSS/Container/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md). Files are base64 encoded in JSON files.
Export board to Wekan JSON, and import as Wekan JSON can make some part of board to load, but you should check is some data missing.
@@ -765,7 +765,7 @@ makesRestore $1
## Docker Backup and Restore
[Docker Backup and Restore](../Platforms/FOSS/Docker/Export-Docker-Mongo-Data.md)
[Docker Backup and Restore](../Platforms/FOSS/Container/Docker/Export-Docker-Mongo-Data.md)
[Wekan Docker Upgrade](https://github.com/wekan/wekan-mongodb#backup-before-upgrading)
@@ -779,11 +779,11 @@ makesRestore $1
Download Wekan grain with arrow down download button to .zip file. You can restore it later.
[Export data from Wekan Sandstorm grain .zip file](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
[Export data from Wekan Sandstorm grain .zip file](../Platforms/FOSS/Container/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
## <a name="cloudron">Cloudron
If those [Backup](Backup.md) ways are not easily found at [Cloudron](../Platforms/Propietary/Cloud/Cloudron/Cloudron.md), one way is to install [Redash](https://redash.io/) and then backup this way:
If those [Backup](Backup.md) ways are not easily found at [Cloudron](../Platforms/Propietary/SaaS/Cloudron), one way is to install [Redash](https://redash.io/) and then backup this way:
Redash works with this kind of queries:
```json
@@ -844,7 +844,7 @@ but downloading with API script still works:
7) Repeat steps 1-4 and 6 for every collection/table like boards,cards, etc
8) Remove from downloaded .json files extra query related data, so that it is similar like [any other Wekan database backup JSON files](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
8) Remove from downloaded .json files extra query related data, so that it is similar like [any other Wekan database backup JSON files](../Platforms/FOSS/Container/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
9) Insert data to some other Wekan install with nosqlbooster like mentioned at page [Backup](Backup.md)
+1 -1
View File
@@ -93,7 +93,7 @@ Related, for creating apps to other appstores: [Browser compatibility matrix](Br
3. Clone WeKan repo, add favicons etc from step 2 to wekan/public/ , and build WeKan bundle from source like at [Emoji](../Features/Editor/Emoji.md) . Note: Currently WeKan does not have feature for custom favicons, it would require a lot of work for 100+ favicons etc customizations.
4. Run bundle at server like [Offline](../Platforms/Propietary/Windows/Offline.md) or [Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
4. Run bundle at server like [Offline](../Platforms/Propietary/OS/Windows/Offline.md) or [Raspberry Pi](../Platforms/FOSS/HW/RaspberryPi/Raspberry-Pi.md)
5. Install Caddy like [Caddy Webserver Config](../Webserver/Caddy.md) , with this config for PWA at https://boards.example.com , there add assetlinks.json details you got when you downloaded Android .zip file from https://pwabuilder.com , see assetlinks issue about where at Play Console those 2 SHA256 keys https://github.com/pwa-builder/PWABuilder/issues/3867#issuecomment-1450826565
+2 -2
View File
@@ -161,10 +161,10 @@ For SSL/TLS, I run Caddy at front of Node.js:
[Caddy](../../../Webserver/Caddy.md)
Related is docs about Raspberry Pi:
[Raspberry Pi](../../../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
[Raspberry Pi](../../../Platforms/FOSS/HW/RaspberryPi/Raspberry-Pi.md)
And also about Windows bundle:
[Offline](../../../Platforms/Propietary/Windows/Offline.md)
[Offline](../../../Platforms/Propietary/OS/Windows/Offline.md)
## Notes
+2 -2
View File
@@ -63,7 +63,7 @@ MONGO_OPLOG_URL=mongodb://<username>:<password>@<host>/local?authSource=admin&re
### ✅ Cloud Deployments
**Documentation Already Exists:**
- `docs/Platforms/Propietary/Cloud/AWS.md` - AWS MONGO_OPLOG_URL configuration
- `docs/Platforms/Propietary/Cloud/AWS` - AWS MONGO_OPLOG_URL configuration
- `docs/Databases/ToroDB/PostgreSQL/docker-compose.yml` - ToroDB oplog settings
### ✅ Documentation
@@ -162,7 +162,7 @@ top -p $(pgrep node)
## Related Documentation
- [Oplog-Configuration.md](Oplog-Configuration.md) - Full setup guide
- [AWS.md](../../Platforms/Propietary/Cloud/AWS.md) - AWS oplog configuration
- [AWS.md](../../Platforms/Propietary/Cloud/AWS) - AWS oplog configuration
- [LDAP.md](../../Login/LDAP.md) - LDAP with oplog setup
- [ToroDB-PostgreSQL](../ToroDB/PostgreSQL/docker-compose.yml) - ToroDB oplog config
+1 -1
View File
@@ -79,7 +79,7 @@ docker-compose up -d
9) Wekan and databases bind to address 0.0.0.0 so could be also available to other
computers in network. I have not tested this.
10) [Restore your MongoDB data](../../../Platforms/FOSS/Docker/Export-Docker-Mongo-Data.md).
10) [Restore your MongoDB data](../../../Platforms/FOSS/Container/Docker/Export-Docker-Mongo-Data.md).
## Feedback
+1 -1
View File
@@ -15,7 +15,7 @@ sudo apt -y install ntpdate
sudo ntpdate ntp.ubuntu.com
```
5. Now local time shows correctly at webbrowser, like for example at Friend Desktop [Friend](../Platforms/FOSS/Friend/Friend.md)
5. Now local time shows correctly at webbrowser, like for example at Friend Desktop [Friend](../Platforms/FOSS/Container/Friend)
For testing any other time related code, that it shows correct local time, compare time functions results to this:
+1 -1
View File
@@ -137,4 +137,4 @@ The macOS analogue of Windows Intune/SCCM/PDQ — the most Snap-like *fleet* aut
- Windows equivalent: [Windows.md](Windows.md)
- Linux Snap auto-refresh & multi-arch snap builds: [Snap-Core.md](../Forks/Snap-Core.md)
- macOS install docs: [Mac.md](../../../Platforms/Propietary/Mac/Mac.md)
- macOS install docs: [Mac.md](../../../Platforms/Propietary/OS/Mac)
+1 -1
View File
@@ -167,4 +167,4 @@ without the user re-running an installer.
- Linux Snap auto-refresh, and why the exotic-arch snap builds differ:
[Snap-Core.md](../Forks/Snap-Core.md)
- [Install WeKan on Windows](../../../Platforms/Propietary/Windows/Install-Windows.md) · [Windows.md](../../../Platforms/Propietary/Windows/Windows.md)
- [Install WeKan on Windows](../../../Platforms/Propietary/OS/Windows/Install-Windows.md) · [Windows.md](../../../Platforms/Propietary/OS/Windows)
+109
View File
@@ -0,0 +1,109 @@
# Design: which CPU platforms a release is built for
A WeKan release ships three kinds of artifact, and they are built in this order
because each one is made out of the one before it:
```
wekan-<version>-<arch>.zip the bundle: the app, its Node.js, its database
│
├──────────────► the snap (takes its Node.js from the bundle)
└──────────────► the Docker image (unpacks the bundle for that arch)
```
So a platform can only have a snap or an image if it has a **bundle**, and it can
only have a bundle if it has the two binaries a bundle carries: a **database**
and a **Node.js runtime**.
## The two binaries a platform needs
**FerretDB v1** — [wekan/FerretDB](https://github.com/wekan/FerretDB/releases)
builds `ferretdb-<arch>` for sixteen targets, and that is the widest of the two
lists. Every non-amd64, non-arm64 platform uses it: MongoDB ships no server for
any of them.
**Node.js 24** — three sources, tried **in this order** by the bundle job, which
prints which one served:
1. **Official** — <https://nodejs.org/dist/>
2. **Unofficial** — <https://unofficial-builds.nodejs.org/download/release/>
3. **This project's patches** — <https://github.com/wekan/node-patches/releases>,
upstream Node.js plus WeKan's patches, built for the platforms the first two
do not publish at all.
And when **none** of the three has a platform, that platform is **not built**
that release — no bundle, no snap, no image architecture, and no red job either.
Nothing has to be edited for it to come back: the next run resolves again and
builds it the moment a Node.js for it is published anywhere.
The first two ship a **tarball** (node, npm, the whole runtime); node-patches
ships the **bare binary**, because that is the only part missing — so when it
serves, npm is taken from the official amd64 tarball of the same version. npm is
JavaScript and runs on whatever node executes it, so an npm built for one CPU
drives a node built for another.
[`releases/resolve-node-source.sh`](../../../releases/resolve-node-source.sh) is
the one place that order and the platform-name mapping live: the bundle jobs, the
extra-arch preflight and the `Dockerfile` all ask it, so they cannot disagree
about where a platform's Node.js comes from.
## What is built, and where each part comes from
| Platform | Bundle | Snap | Docker | FerretDB asset | Node.js from |
| --- | --- | --- | --- | --- | --- |
| amd64 | ✅ | ✅ | ✅ | `ferretdb-amd64` | official |
| arm64 | ✅ | ✅ | ✅ | `ferretdb-arm64` | official |
| ppc64le | ✅ | ✅ `ppc64el` | ✅ | `ferretdb-ppc64le` | official |
| s390x | ✅ | ✅ | ✅ | `ferretdb-s390x` | official |
| riscv64 | ✅ | ✅ | ✅ | `ferretdb-riscv64` | unofficial |
| i386 | ✅ | ✅ | ✅ `linux/386` | `ferretdb-i386` | node-patches (`node-i386`) |
| armhf | ✅ | ✅ | ✅ `linux/arm/v7` | `ferretdb-armhf` | node-patches (`node-armhf`) |
| loong64 | ✅ | — | — | `ferretdb-loong64` | unofficial |
A "Node.js from" of node-patches is the one column that can be EMPTY for a
release: those are the platforms only node-patches builds, so until it has
published one, that row is not built at all.
Three vocabularies meet in that table and they disagree, which is why the build
matrix names all three separately (`arch`, `node_arch`, `ferretdb_arch`):
* Node says **x86** where Debian, snap and FerretDB say **i386**;
* Node says **armv7l** where they say **armhf**;
* snap says **ppc64el** where everyone else says **ppc64le**.
A row that named only one of them would download another CPU's binary and the
mistake would not show until somebody ran it.
## What is deliberately not built
**armel** (ARMv5, soft-float) — FerretDB builds it; Node.js cannot. V8 has not
supported ARMv5 for many years, nobody publishes a runtime for it, and
node-patches cannot make one either. A bundle with no runtime in it is not a
bundle. It is the permanent case of the general rule above: no Node.js, no
platform.
**loong64 snap and image** — the bundle exists, but loong64 is not a snap
architecture, and buildx and the three registries do not agree on it yet. It
ships as a `.zip`.
**Windows, macOS x64, FreeBSD** — out of scope for these three artifacts rather
than impossible: the snap and the image are Linux, and a Windows or FreeBSD
Node.js needs its own host to build on rather than a cross compiler on a Linux
runner. macOS arm64 has a bundle of its own already (`build-mac-arm64`).
## Where this is enforced
`tests/releaseSnapArches.test.cjs` reads the four lists — the bundle matrix, the
snap `platforms:` block and its arch mapping, the snap-launchpad matrix and the
Docker `--platform` list — and checks they agree: that every snap arch has a
bundle behind it, that every bundle row names all three vocabularies, and that
buildx builds exactly the platforms whose push is then verified. They were
edited one at a time before, which is how an arch ends up with a `.zip` and no
snap, or a snap that downloads a bundle nobody built.
## Related
- [Snap-Core.md](Forks/Snap-Core.md) — why the exotic arches build on Launchpad
- `.github/workflows/release-all.yml` — the bundle, snap and image jobs
- `releases/resolve-node-source.sh` — which of the three sources serves a platform
- `release-all.yml` in [wekan/node-patches](https://github.com/wekan/node-patches)
— how the third source is built (upstream Node.js plus WeKan's patches)
+2 -2
View File
@@ -1,6 +1,6 @@
# Dev: releasing WeKan to Univention Corporate Server (UCS) from release-all.yml
Based on [docs/Platforms/FOSS/UCS/UCS.md](../../Platforms/FOSS/UCS/UCS.md) and the
Based on [docs/Platforms/FOSS/Container/UCS](../../Platforms/FOSS/Container/UCS) and the
[wekan/univention](https://github.com/wekan/univention) app repo.
Goal: from `wekan/wekan`'s **release-all.yml**, also release the **Docker-based UCS App
@@ -244,7 +244,7 @@ is safe.
## See also
- [docs/Platforms/FOSS/UCS/UCS.md](../../Platforms/FOSS/UCS/UCS.md) — the user-facing UCS page.
- [docs/Platforms/FOSS/Container/UCS](../../Platforms/FOSS/Container/UCS) — the user-facing UCS page.
- [Snap-Core.md](Forks/Snap-Core.md) — the snap build that already runs the same MongoDB→FerretDB migration.
- [Snap-Ondra-Gantt.md](Snap-Ondra-Gantt.md) — the same "push to a downstream repo + publish"
pattern for variant snaps.
+3 -3
View File
@@ -33,9 +33,9 @@ https://www.youtube.com/watch?v=ke-mbnZM3zE
## Meteor WeKan Server
- Meteor.js 2.x, Node.js 14.x https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4 , MongoDB 6.x
- CPU: amd64, arm64, s390x, Source Bundle at [Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
- Windows On-Premise: [Offline](../Platforms/Propietary/Windows/Offline.md)
- Mac: Docker, or at [Mac](../Platforms/Propietary/Mac/Mac.md)
- CPU: amd64, arm64, s390x, Source Bundle at [Raspberry Pi](../Platforms/FOSS/HW/RaspberryPi/Raspberry-Pi.md)
- Windows On-Premise: [Offline](../Platforms/Propietary/OS/Windows/Offline.md)
- Mac: Docker, or at [Mac](../Platforms/Propietary/OS/Mac)
- Newest at platforms: Source Bundle, Snap Candidate, Docker, Kubernetes https://wekan.fi
- Platforms that will be updated to be up-to-date: Snap Stable, Sandstorm, UCS https://wekan.fi
+3 -3
View File
@@ -19,9 +19,9 @@
- On-Premise:
- Linux amd64/arm64/s390x
- https://wekan.fi/install/
- [Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
- Windows [Offline](../Platforms/Propietary/Windows/Offline.md)
- Mac [Mac](../Platforms/Propietary/Mac/Mac.md)
- [Raspberry Pi](../Platforms/FOSS/HW/RaspberryPi/Raspberry-Pi.md)
- Windows [Offline](../Platforms/Propietary/OS/Windows/Offline.md)
- Mac [Mac](../Platforms/Propietary/OS/Mac)
- Not like other software:
- But note that design of all software changes often, when they are in active development.
- Not bad parts of Trello:
+3 -3
View File
@@ -2,7 +2,7 @@
Today, hosting *n* customers means running *n* WeKan servers: *n* Node.js
processes, *n* `ROOT_URL`s, *n* databases, *n* upgrades. That is what
[`docs/Platforms/FOSS/Docker/Meteor3/`](../../Platforms/FOSS/Docker/Meteor3/README.md)
[`docs/Platforms/FOSS/Container/Docker/Meteor3/`](../../Platforms/FOSS/Container/Docker/Meteor3)
sets up and it works.
This page designs the **alternatives**: what it would take for **one** WeKan
@@ -23,8 +23,8 @@ Everything the question touches. Paths are from the repository root.
| File Path | File Type | Description |
| --- | --- | --- |
| `docs/Platforms/FOSS/Docker/Meteor3/README.md` | `.md` guide | The shipped topology: Caddy in front, one WeKan container per customer, one MongoDB, one database per customer. |
| `docs/Platforms/FOSS/Docker/Meteor3/multitenancy.md` | `.md` guide | The two supported transports for that topology, and why each instance in one network namespace needs its own internal `uws.port`. |
| `docs/Platforms/FOSS/Container/Docker/Meteor3` | `.md` guide | The shipped topology: Caddy in front, one WeKan container per customer, one MongoDB, one database per customer. |
| `docs/Platforms/FOSS/Container/Docker/Meteor3/multitenancy.md` | `.md` guide | The two supported transports for that topology, and why each instance in one network namespace needs its own internal `uws.port`. |
| `docker-compose-multitenancy.yml` | `.yml` compose file | The working example: per-tenant `PORT`, `ROOT_URL`, `MONGO_URL` database and `uws.port`. |
| `models/lib/universalUrlGenerator.js` | `.js` module | Builds attachment/avatar URLs that do **not** depend on `ROOT_URL`. Already tenant-safe, and the model the rest of the code should follow. |
| `server/routes/universalFileServer.js` | `.js` route | Serves `/cdn/storage/…` for any host. Two `Meteor.absoluteUrl()` calls are the only `ROOT_URL` dependency left in it. |
@@ -249,7 +249,7 @@ https://github.com/wekan/php/blob/main/page/allboardschar.php
- Redbean amd64 binary works at Win/Mac/Linux/BSD/BIOS without any modifications, because it is based on Cosmopolitan https://github.com/jart/cosmopolitan
- Uses HTMX https://htmx.org at UI
- Saves to SQLite database
- Petclinic fork, using Blink to run at s390x [s390x](../../Platforms/FOSS/s390x.md#petclinic-s390x)
- Petclinic fork, using Blink to run at s390x [s390x](../../Platforms/FOSS/HW/s390x.md#petclinic-s390x)
### PHP
@@ -290,7 +290,7 @@ https://github.com/wekan/php/blob/main/page/allboardschar.php
- Deno also includes Node.js as Deno's Node.js compatibility layer.
- Node.js supports many CPU/OS
- Node.js is used in production and has traceability. https://changelog.com/jsparty/294
- [s390x](../../Platforms/FOSS/s390x.md)
- [s390x](../../Platforms/FOSS/HW/s390x.md)
- https://nodejs.org/dist/latest-v20.x/
- https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4
+1
View File
@@ -77,6 +77,7 @@ The bare `/admin/settings` still resolves; it *redirects* to
| `/admin/problems/cards` | Cards Report | `report-cards` |
| `/admin/problems/recovery` | Recovery | `report-recovery` |
| `/admin/problems/database` | Database problems | `report-database` |
| `/admin/problems/integrity` | Filesystem integrity | `report-integrity` |
Two panes are called Security, and they are different things: the *settings*
pane and the *report*. `security` is the settings one, beside `notifications`
@@ -43,7 +43,7 @@ Price | Free and Open Source, [MIT license](../../LICENSE). Free for Commercial
Whitelabeling | Yes. Admin Panel/Layout: Hide Logo, Custom Product Name. | No | $ Yes
Theming | [Yes](../Theme/Custom-CSS-themes.md) | No | $ Yes
Redistributing | Yes | No | $ Yes
Hosting | [Self-host or SaaS provider](../Platforms/FOSS/Platforms.md) | SaaS | Self-host
Hosting | [Self-host or SaaS provider](../Platforms) | SaaS | Self-host
## Basic features: Board
@@ -280,7 +280,7 @@ Support Desk | No | No | $ Yes
Card Template App | Copy Checklist Template to Multiple Cards | Yes | $ Yes
Slack | [Yes](../Webhooks/Discord/Outgoing-Webhook-to-Discord.md) | Yes | $ Yes
Amazon Echo | No | No | $ Yes
Collaborate/TogetherJS | [Not yet](../Platforms/FOSS/Friend/Friend.md) | No | Yes
Collaborate/TogetherJS | [Not yet](../Platforms/FOSS/Container/Friend) | No | Yes
Gmail Add-on | No | Yes | Yes
Hangouts Chat bot | No | Yes | $ Yes
Print board | No | No | $ Yes
@@ -296,7 +296,7 @@ SEO Checklist | No | No | $ Yes
Features | Wekan | Trello | Restyaboard
------------ | ------------- | ------------- | -------------
iOS Mobile App | [Not yet](../Platforms/FOSS/Friend/Friend.md). Mobile Chrome browser works. | Yes | Yes
iOS Mobile App | [Not yet](../Platforms/FOSS/Container/Friend). Mobile Chrome browser works. | Yes | Yes
Android Mobile App | [Yes](../Browsers/Browser-compatibility-matrix.md) | Yes | Yes
Windows Microsoft Store App | [Yes](../Browsers/Browser-compatibility-matrix.md) | ? | ?
Ubuntu Touch OpenStore App | [Yes](../Browsers/Browser-compatibility-matrix.md) | ? | ?
+1 -1
View File
@@ -76,7 +76,7 @@ meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 --extra-pa
## Scaling to thousands of users
[Production setup at AWS](../Platforms/Propietary/Cloud/AWS.md)
[Production setup at AWS](../Platforms/Propietary/Cloud/AWS)
## Current versions of dependencies
@@ -43,7 +43,7 @@ When you run this, you see .jade template files where is search input box, and .
## Getting Started
- Currently Wekan development is done mainly at Ubuntu 20.10 64bit, but building Wekan does work on any Debian, Ubuntu, WSL Ubuntu 20.04, [Mac](../Platforms/Propietary/Mac/Mac.md). Sametime maybe on [Windows](../Platforms/Propietary/Windows/Windows.md) with `choco install -y meteor` and installing packages it then asks for with `meteor add ...` or `meteor npm install --save ...` and then running meteor again.
- Currently Wekan development is done mainly at Ubuntu 20.10 64bit, but building Wekan does work on any Debian, Ubuntu, WSL Ubuntu 20.04, [Mac](../Platforms/Propietary/OS/Mac). Sametime maybe on [Windows](../Platforms/Propietary/OS/Windows) with `choco install -y meteor` and installing packages it then asks for with `meteor add ...` or `meteor npm install --save ...` and then running meteor again.
- Look through old [pull requests](https://github.com/wekan/wekan/pulls)
- Read Wekan source code, you can use some git history viewer GUI like gitk
- Read [Meteor documentation](https://docs.meteor.com/) for [Meteor version](../../.meteor/release) in use, other versions mentioned at [Dockerfile](../../Dockerfile)
+9 -9
View File
@@ -93,22 +93,22 @@ Files in this directory are served by meteor as-is to the client. It hosts some
* **[authentication.js](../../server/authentication.js)** add the `Authentication`object to Meteor that provides methods for checking access rights.
* **[lib/utils.js](../../server/lib/utils.js)** defines some checks used by [checklists.js](../../models/checklists.js)** model. <span style="color:red">*XXX: these methods are defined in server-only code by are used in models, which are visible by the client (in Checklists.allow)?*</span>
* **[notifications](../../server/notifications)**
* **[notifications.js](../../server/notifications/notifications.js)**: defines the `Notifications` object that supports [Activities](models/activities.js) and holds a list of functions to call when its `notify()` method is called along with convenience methods to subscribe, unsubscribe and a way to filter recipients according to user settings for notification;
* **[notifications.js](../../server/notifications/notifications.js)**: defines the `Notifications` object that supports [Activities](../../models/activities.js) and holds a list of functions to call when its `notify()` method is called along with convenience methods to subscribe, unsubscribe and a way to filter recipients according to user settings for notification;
* **[email.js](../../server/notifications/email.js)**: makes use of the notification system to send an email to a user;
* **[profile.js](../../server/notifications/profile.js)**: *stub*; will allow associating notifications with user ids to be consumed by mobile apps;
* **[notifications.js](../../server/notifications/notifications.js)**: adds the `watch()` Meteor server-only [method](https://guide.meteor.com/methods.html) that may watch boards, lists or cards using [models/watchable.js](../../models/watchable.js);
* **[outgoing.js](../../server/notifications/outgoing.js)**: adds the `outgoingWebhooks()` Meteor server-only [method](https://guide.meteor.com/methods.html) that can call external API <span style="color:red">*XXX: I guess*</span>
* **[publications](../../server/publications)** defines sets of records that are [published](https://docs.meteor.com/api/pubsub.html#Meteor-publish) by the server and how clients can subscribe to those:
* **[accountSettings.js](../../server/publications/accountSettings.js)**: [AccountSettings](models/accountSettings.js) collection;
* **[activities.js](../../server/publications/activities.js)**: [Activities](models/activities.js) collection filtered and paginated;
* **[announcements.js](../../server/publications/announcements.js)**: [Announcements](models/announcements.js) collection;
* **[avatars.js](../../server/publications/avatars.js)**: [Avatars](models/avatars.js) collection for current user;
* **[boards.js](../../server/publications/boards.js)**: [Boards](models/boards.js) collection for current user, archived boards collection and individual board as a [relation](https://atmospherejs.com/cottz/publish-relations);
* **[accountSettings.js](../../server/publications/accountSettings.js)**: [AccountSettings](../../models/accountSettings.js) collection;
* **[activities.js](../../server/publications/activities.js)**: [Activities](../../models/activities.js) collection filtered and paginated;
* **[announcements.js](../../server/publications/announcements.js)**: [Announcements](../../models/announcements.js) collection;
* **[avatars.js](../../server/publications/avatars.js)**: [Avatars](../../models/avatars.js) collection for current user;
* **[boards.js](../../server/publications/boards.js)**: [Boards](../../models/boards.js) collection for current user, archived boards collection and individual board as a [relation](https://atmospherejs.com/cottz/publish-relations);
* **[cards.js](../../server/publications/cards.js)**: a [Card](../../models/cards.js) by its id;
* **[fast-render.js](https://github.com/wekan/wekan/tree/main/server/publications/fast-render.js)**: configures [FastRender](https://github.com/kadirahq/fast-render) to use the board data; <span style="color:red">*XXX: FastRender docs say "Make sure you're using Meteor.subscribe and not this.subscribe"*</span>
* **[people.js](../../server/publications/people.js)**: [Users](models/users.js) collection;
* **[settings.js](../../server/publications/settings.js)**: [Settings](models/settings.js) collection and, separately, the mail server;
* **[unsavedEdits.js](../../server/publications/unsavedEdits.js)**: [UnsavedEdits](models/unsavedEdits.js) collection;
* **[people.js](../../server/publications/people.js)**: [Users](../../models/users.js) collection;
* **[settings.js](../../server/publications/settings.js)**: [Settings](../../models/settings.js) collection and, separately, the mail server;
* **[unsavedEdits.js](../../server/publications/unsavedEdits.js)**: [UnsavedEdits](../../models/unsavedEdits.js) collection;
* **[users.js](../../server/publications/users.js)**: provides a "mini-profile" for individual users and a [way](https://docs.meteor.com/api/collections.html#fieldspecifiers) to check if current user is admin.
# Models
+2 -2
View File
@@ -1,5 +1,5 @@
- Read [Wekan new release ChangeLog](../../CHANGELOG.md)
- [Install Meteor.js](https://www.meteor.com/install). Note: Windows install is very slow, please use other option at [Windows wiki page](../Platforms/Propietary/Windows/Windows.md)
- [Install Meteor.js](https://www.meteor.com/install). Note: Windows install is very slow, please use other option at [Windows wiki page](../Platforms/Propietary/OS/Windows)
- Download newest bundle from https://releases.wekan.team
- Unarchive bundle .tar.gz file: `tar -zxvf wekan-VERSION.tar.gz`
- `cd bundle`
@@ -8,6 +8,6 @@
[Wekan for Meteor.js bundle bug reports and feature requests](https://github.com/wekan/wekan/issues)
[Build Meteor bundle from source](../Platforms/FOSS/Source/Source.md)
[Build Meteor bundle from source](../Platforms/FOSS/Source)
[Install from source without root](../Platforms/FOSS/Source/Install-from-source-without-root.md)
+1 -1
View File
@@ -137,7 +137,7 @@ docker-compose up -d --build
1) Install local development version of Sandstorm to your laptop from https://sandstorm.io/install
2) Download and install experimental version to your local Sandstorm:
[Sandstorm](../Platforms/FOSS/Sandstorm/Sandstorm.md)
[Sandstorm](../Platforms/FOSS/Container/Sandstorm)
Local sandstorm is at http://local.sandstorm.io:6080/ .
3) Download your production Wekan grain .zip file with down arrow button. Upload to your dev local Sandstorm.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 259 KiB

+2 -2
View File
@@ -526,7 +526,7 @@ They are moved here from https://github.com/wekan/wekan/issues/961
Sample:
### Wekan with TLS/SSL
To run Wekan secured with TLS/SSL do the following steps:
1. Setup a Web-Server/-Proxy with TLS/SSL support that maps requests to wekan. [Link](url)
1. Setup a Web-Server/-Proxy with TLS/SSL support that maps requests to wekan.
2. Set protocol to http**s**.
`export ROOT_URL='https://example.com/'`
3. Set Wekan to an internal port.
@@ -535,7 +535,7 @@ To run Wekan secured with TLS/SSL do the following steps:
### Wekan at subpath
To run Wekan to appear at a subpath of your domain:
1. Setup a Web-Server/-Proxy that maps requests to wekan. [Link](url)
1. Setup a Web-Server/-Proxy that maps requests to wekan.
2. Append subpath to domain, without trailing slash.
`export ROOT_URL='http://example.com/mywekan'`
3. Set Wekan to an internal port.
+4 -4
View File
@@ -19,7 +19,7 @@ that by providing one-click installation on various platforms.
- Wekan largest user has 30k users using Wekan in their company.
- Wekan has been [translated](https://app.transifex.com/wekan/) to about 70+ languages.
- [Features](../Features/Features.md): Wekan has real-time user interface.
- [Platforms](../Platforms/FOSS/Platforms.md): Wekan supports many platforms.
- [Platforms](../Platforms): Wekan supports many platforms.
Wekan is critical part of new platforms Wekan is currently being integrated to.
- [Integrations](../ImportExport/Integrations.md): Current possible integrations and future plans.
@@ -41,8 +41,8 @@ It's a very specific niche, with limited amount competitors, with all of this ap
## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
[Mac](../Platforms/Propietary/Mac/Mac.md) / [Windows](../Platforms/Propietary/Windows/Install-Wekan-from-source-on-Windows.md).
[More Platforms](../Platforms/FOSS/Platforms.md). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
[Mac](../Platforms/Propietary/OS/Mac) / [Windows](../Platforms/Propietary/OS/Windows/Install-Wekan-from-source-on-Windows.md).
[More Platforms](../Platforms). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
- 1 GB RAM minimum free for Wekan. Production server should have miminum total 4 GB RAM.
For thousands of users, for example with [Docker](../../docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
@@ -119,7 +119,7 @@ Usually:
* Slowest: Once a month.
* One release contains anything from one typo fix to many major features and bugfixes.
For [Wekan Platforms](../Platforms/FOSS/Platforms.md), it means these choices:
For [Wekan Platforms](../Platforms), it means these choices:
* Snap: You get updates automatically immediately, or updates automatically scheduled at midnight
* Source/Docker/VirtualBox: You need to test and install new Wekan version yourself
+1 -1
View File
@@ -38,7 +38,7 @@ Maxime Quandalle, original author of Wekan
* [EthKan](https://github.com/EthKan)
* [Friend](../Platforms/FOSS/Friend/Friend.md)
* [Friend](../Platforms/FOSS/Container/Friend)
# In Spanish
@@ -80,7 +80,7 @@ files. The pane, its menu entry, its helpers and its handler are commented out i
The grain migration itself is unchanged: it runs in `sandstorm-src/start.js` before
the app boots — see
[Sandstorm migration](../../../Platforms/FOSS/Sandstorm/Meteor3/Migration.md).
[Sandstorm migration](../../../Platforms/FOSS/Container/Sandstorm/Meteor3/Migration.md).
## Related
+1 -1
View File
@@ -11,5 +11,5 @@ Some Python etc code in WeKan, that is not yet added directly to WeKan:
- [Stats](https://github.com/wekan/wekan-stats)
- [Logstash and ElasticSearch](https://github.com/wekan/wekan-logstash)
- iCal server in [Python](https://github.com/wekan/wekan-ical-server)
- [Sandstorm CSV related Python code](../../Platforms/FOSS/Sandstorm/Wekan-Sandstorm-cards-to-CSV-using-Python.md)
- [Sandstorm CSV related Python code](../../Platforms/FOSS/Container/Sandstorm/Wekan-Sandstorm-cards-to-CSV-using-Python.md)
- [Trello API test code](../../ImportExport/trello)
+1 -1
View File
@@ -107,5 +107,5 @@ are now implemented (see the topic pages above).
## More
- [Platforms](../Platforms/FOSS/Platforms.md)
- [Platforms](../Platforms)
- [Integrations](../ImportExport/Integrations.md)
+1 -1
View File
@@ -18,4 +18,4 @@ Previous GPLv2 WeKan Gantt is deprecated https://github.com/wekan/wekan-gantt-gp
# UCS
[Gantt feature at UCS](../../Platforms/FOSS/UCS/UCS.md#gantt)
[Gantt feature at UCS](../../Platforms/FOSS/Container/UCS#gantt)
@@ -20,7 +20,7 @@
margin-top: 10px;
}
</style>
<script defer src="pretty-json-custom-element.js"></script>
<!-- <script defer src="pretty-json-custom-elements-js.txt"></script> -->
</head>
<body>
<h1>JSON Viewer</h1>
+3 -3
View File
@@ -1,6 +1,6 @@
### Install for example from:
- [Snap](../../Platforms/FOSS/Snap/Snap.md)
- [Docker](../../Platforms/FOSS/Docker/Docker.md)
- [Snap](../../Platforms/FOSS/Container/Snap)
- [Docker](../../Platforms/FOSS/Container/Docker)
*Make sure you are running at least **v2.21***
@@ -177,4 +177,4 @@ You may also find it useful to look at the following configuration information:
https://login.microsoftonline.com/**the-tenant-name-for-your-organization**/v2.0/.well-known/openid-configuration
Some Azure links also at wiki page about moving from Sandstorm to Docker/Snap , and using Docker Swarm:
- [Export from Wekan Sandstorm grain ](../../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md).zip-file#azure-links
- [Export from Wekan Sandstorm grain ](../../Platforms/FOSS/Container/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md).zip-file#azure-links
+2 -2
View File
@@ -17,7 +17,7 @@ sudo snap set wekan password-login-enabled='false'
## Windows On-Premise
[Offline](../Platforms/Propietary/Windows/Offline.md)
[Offline](../Platforms/Propietary/OS/Windows/Offline.md)
```
SET PASSWORD_LOGIN_ENABLED=false
```
@@ -25,7 +25,7 @@ SET PASSWORD_LOGIN_ENABLED=false
## Linux On-Premise
[Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
[Raspberry Pi](../Platforms/FOSS/HW/RaspberryPi/Raspberry-Pi.md)
```
export PASSWORD_LOGIN_ENABLED=false
```
+1 -1
View File
@@ -136,7 +136,7 @@ exit
More info:
- [Backup](../Backup/Backup.md)
- [Docker](../Platforms/FOSS/Docker/Docker.md)
- [Docker](../Platforms/FOSS/Container/Docker)
***
+8 -1
View File
@@ -224,7 +224,7 @@ services:
wekandb:
# All Wekan data is stored in MongoDB. For backup and restore, see:
# [Export Docker Mongo Data](../Platforms/FOSS/Docker/Export-Docker-Mongo-Data.md)
# [Export Docker Mongo Data](../Platforms/FOSS/Container/Docker/Export-Docker-Mongo-Data.md)
image: mongo:4.0.3
container_name: wekan-db
restart: always
@@ -341,6 +341,13 @@ services:
# LDAP_BASEDN : The base DN for the LDAP Tree
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
- LDAP_BASEDN=ou=prod,dc=mydomain,dc=com
# LDAP_GROUP_BASEDN : Where the GROUPS are, when they are not under
# LDAP_BASEDN. Leave unset when users and groups share one subtree - then
# the group searches use LDAP_BASEDN, exactly as before. Set it when the
# directory keeps them apart (ou=groups beside ou=people), or the group
# searches find nothing and LDAP_GROUP_FILTER_ENABLE refuses every login.
# example : LDAP_GROUP_BASEDN=ou=groups,dc=example,dc=org
#- LDAP_GROUP_BASEDN=ou=groups,dc=mydomain,dc=com
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
# example : LDAP_LOGIN_FALLBACK=true
- LDAP_LOGIN_FALLBACK=false
+1 -1
View File
@@ -1,6 +1,6 @@
Tested on Ubuntu 16.04 based distro.
Wekan installed with [Export Docker Mongo Data](../Platforms/FOSS/Docker/Export-Docker-Mongo-Data.md)
Wekan installed with [Export Docker Mongo Data](../Platforms/FOSS/Container/Docker/Export-Docker-Mongo-Data.md)
## A) Let's Encrypt support, without Google Auth:
+1 -1
View File
@@ -5,7 +5,7 @@ Related Meteor SAML code, not in WeKan yet:
Sandstorm has SAML login, and old WeKan that will be updated someday:
- [Sandstorm](../Platforms/FOSS/Sandstorm/Sandstorm.md)
- [Sandstorm](../Platforms/FOSS/Container/Sandstorm)
How SAML works:
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
# Different wiki page: [Production setup for thousands of users at AWS](../../Propietary/Cloud/AWS.md)
# Different wiki page: [Production setup for thousands of users at AWS](../../../Propietary/Cloud/AWS)
***
@@ -22,13 +22,13 @@ https://github.com/wekan/wekan-mongodb
## 3. Email
[Troubleshooting Email](../../../Email/Troubleshooting-Mail.md)
[Troubleshooting Email](../../../../Email/Troubleshooting-Mail.md)
## 4. Configure webserver as a front-end proxy
* [Caddy](../../../Webserver/Caddy.md)
* [Nginx](../../../Webserver/Nginx.md)
* [Apache](../../../Webserver/Apache.md)
* [Caddy](../../../../Webserver/Caddy.md)
* [Nginx](../../../../Webserver/Nginx.md)
* [Apache](../../../../Webserver/Apache.md)
## 5. Launch Wekan
@@ -1,6 +1,6 @@
More complete Docker info at:
* [Docker](Docker.md)
* [Docker](README.md)
Tested to work on AWS cloud:
@@ -29,7 +29,7 @@ This will speed up loading attachments a lot.
Note: Only `Oplog sockjs` works with multitenancy, see [multitenancy.md](multitenancy.md).
For the alternatives to running one Node.js server per tenant — one server for
many `ROOT_URL`s, and what each option costs — see
[Multitenancy design](../../../../Design/Multitenancy/Multitenancy.md).
[Multitenancy design](../../../../../Design/Multitenancy/Multitenancy.md).
Migrating from Parallel Snap setup, because `snap set wekan root-url=...`
etc commands are slow to run, and did not get Parallel Snap working properly yet.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Loaded 100 of 410 files, more files were not shown because too many files have changed in this diff. Show more