The os packages previously stored the sqlite database and all other
runtime data in /opt/vikunja, next to the binary. Fresh installs now
default to /var/lib/vikunja, which is the correct FHS location for
variable state data. The package creates the directory and the service
units use it as their working directory.
BREAKING CHANGE: New installations store their data in /var/lib/vikunja
instead of /opt/vikunja. Existing installations keep their current
config and are unaffected, but setups relying on the old default path
for new machines need to move their data or adjust the config.
The unconditional type=raw,value=latest applied the latest tag to every
pushed git tag, including prereleases like v1.0.0-rc1. Removing it lets
metadata-action's default latest=auto flavor handle it, which only tags
latest for non-prerelease semver versions.
Fixes#2161
Todoist returns opaque identifiers instead of urls in file_url for
attachments it does not host itself (mail attachments for example).
Passing those to the http client failed with "unsupported protocol
scheme" and aborted the entire migration.
Skip attachments without an http(s) url and log-and-continue when a
single download fails instead of failing the whole migration.
The auto-label workflow's LLM call succeeds, but the docker action ran
as a non-root user (`USER appuser` in the Dockerfile) and could not
write the runner's `GITHUB_OUTPUT` file:
```
Error: failed to set output: ... open /github/file_commands/set_output_...: permission denied
```
GitHub docker actions must run as root — the runner's file-command files
are owned by the runner user. Fixed in the fork
(tink-bot/LLM-action@8526bab removes the non-root user) and bumped the
pin here.
Failing run:
https://github.com/go-vikunja/vikunja/actions/runs/30904804523/job/91977288580
Co-authored-by: kolaente <k@knt.li>
The config was shipped as plain `%config`, so rpm installs the packaged
file on every upgrade and moves the user's version aside as `.rpmsave`.
The postinstall script then re-ran its seds against the fresh sample and
generated a new JWT secret, logging everyone out — with the real config
no longer in place.
Mark it `config|noreplace` so rpm keeps the existing file and writes
`.rpmnew` instead. deb and apk treat the type exactly like `config`, so
nothing changes there.
nfpm only maps `scripts.postinstall` to apk's `.post-install` and
archlinux's `post_install`, both of which run on a fresh install. Their
upgrade hooks come from the packager-specific `apk.scripts.postupgrade`
and `archlinux.scripts.postupgrade` keys, which we never set — so
upgrading on Alpine or Arch executed no maintainer script whatsoever, and
the restart added in the previous commit would never have fired there.
Point both at the matching install script.
The postinstall script only ran `systemctl enable`, which creates the
wants-symlink and nothing else. On an upgrade the package manager
replaced the binary while the old process kept running the old inode, so
users had to restart vikunja by hand to get the new version. Changes to
vikunja.service were never picked up either, for lack of a
`daemon-reload`.
Add a daemon-reload plus `try-restart` (`rc-service restart` on OpenRC).
try-restart is a no-op while the unit is stopped, so fresh installs keep
their current behaviour of being enabled but not started.
The config fixups move above the service handling so a restart never
races a half-patched config.
GetUserFromClaims read the id claim straight into User.ID without checking the
token type. A link share JWT carries the raw, positive share id in that claim
and reaches every authenticated route, so the only thing preventing
impersonation was the incidental absence of a username claim in link share
tokens. Adding one would have reintroduced the confusion with a positive id,
bypassing the GetID negation entirely.
AuthTypeUser moves to pkg/user, which parses the claims and cannot import
pkg/modules/auth, so the value exists once.
files.Create stores GetID() straight into files.created_by_id, so a share
uploading an attachment used to persist a row indistinguishable from one
created by the user with the same id.
The guard on Webhook.CanRead is unreachable: no route exposes a read-one
webhook, and DoReadAll never calls CanRead. Two paths were left open:
- the v2 user-webhook list passes a.GetID() into Webhook.UserID, which is
negative for a link share, so the w.UserID > 0 branch and its link share
check were skipped and the request fell through to the project branch with
project id 0, returning 404 instead of 403.
- the project branch never rejected link shares at all, so any holder of a
public share link could list the project's webhooks. target_url is a bearer
secret for Slack, Discord, Teams and Zapier.
Guard both by rejecting link shares at the top of ReadAll.
web.Auth is satisfied by both *user.User and *LinkSharing, so returning the
raw positive share.ID made a share with id N indistinguishable from the user
with id N at every permission check comparing against a users.id column.
The rest of the codebase already keys shares negatively
(getUsersOrLinkSharesFromIDs, toUser), so this makes GetID consistent with
that contract instead of an exception to it.
Neither bucket mode radio matches the none mode a view has before it
becomes a kanban view, so both stayed unselected. A single watcher
normalizes the mode for kanban views, on load as well as when the kind
changes.
Views broken by the bug above keep their state until something writes
them again, so repair them on startup: set the manual mode, seed the
default buckets when the view has none and place the project's tasks in
the default bucket.
Views whose project or saved filter is gone are skipped rather than
repaired with a dangling creator, soft-deleted tasks stay out of the
backfill, and the mode flip is the last write per view so an
interrupted run picks the view up again.
Switching an existing view to kanban left bucket_configuration_mode at
none, so the tasks endpoint returned a flat task list which the
frontend rendered as empty bucket columns.
The mode is now normalized on create and update: a kanban view without
a mode becomes manual, a non-kanban view loses its mode, and an update
which omits the mode keeps the stored one together with its bucket
configuration. Becoming a manual kanban view seeds the default buckets
and backfills task_buckets rows for tasks which have none in that view,
so tasks created while the view was of a different kind stay visible.
Bucket ids from the request are validated against the view: an id of a
bucket which is gone resets to zero instead of locking the view, an id
belonging to another view is rejected, and non-kanban views no longer
write those columns at all so a round trip can restore them.
The backfill selects only task ids, scopes saved filter views to the
projects their owner can see, batches its inserts and ignores conflicts
with concurrently placed tasks.
Fixes https://github.com/go-vikunja/vikunja/issues/3386
Upstream rejects max_tokens/temperature for gpt-5 series models,
breaking auto-label with gpt-5.6-luna. Pin to the fork carrying
appleboy/LLM-action#24 until it is merged and released.
Multiline quick add previously created each line with its own request
plus client-side index guessing — parallel creates raced and persisted
scrambled task order. All input now goes through the atomic bulk
endpoint (single lines too, per review), so the server assigns indexes
and positions in input order. This drops the defaultPosition prop and
ProjectList's firstNewPosition.
Created tasks are emitted once as a batch: per-task emits made
non-position-sorted lists reload per task and forced consumers'
insert order into AddTask. Relations run serialized through runWrites,
and a relation failure toasts instead of restoring input for tasks that
already exist. Creation failure restores the original input string.
E2E waits pinned to the old v1 create request follow the flow to the
bulk endpoint.
Fixes#3288
createNewTasksBulk builds full task payloads from quick add magic
titles (extracted from createNewTask so both paths share it) and hands
them to the service in one call. Label application failures toast and
continue — the tasks already exist server-side, failing the whole
action would invite duplicate resubmits.
runWrites moves to helpers/ so components can use the write-serializing
util without importing the store module.
bulkCreate posts to /api/v2/projects/{id}/tasks/bulk: tasks grouped per
project, chunked at the endpoint's 100-task limit, requests strictly
sequential (server assigns indexes at insert time and concurrent bulk
writes fail under write contention), batches of one project posted last
chunk first because the server places each batch on top of every view.
Returns slots aligned 1:1 with the input plus the first error, so
partial progress survives a failed batch.
The payload is an explicit allowlist — the v2 schema rejects unknown
properties, so sending the full processModel output 422s. Response
shape is guarded and the error is translated via the message module
(direct i18n.global.t in the service trips vue-i18n's type
instantiation limit).