Alpine lacks shared-mime-info, so file extensions with no built-in Go
mime type (ODF, OOXML, epub, ics, ...) fall back to content sniffing
and are served as "application/zip", breaking Collabora/DAVx5 on
Android. Switch to debian:bookworm-slim and install shared-mime-info.
Also install wget for the /healthz healthcheck instead of curl.
Complements the existing "build: ." example (for a full checkout) with
a minimal compose file that just pulls git.arnef.de/arnef/nidus:latest,
for users who only want to run the container without cloning the repo.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both were low-value docs (TLS config is already in the Configuration
reference table; Go/JS dependencies are visible in go.mod and
web/package.json) that just added noise.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch from a dynamically-derived registry/GITHUB_TOKEN login to the
same pattern already proven in the ebooks project: a fixed
git.arnef.de/arnef/nidus registry/image, docker/login-action with
REGISTRY_USERNAME/REGISTRY_PASSWORD repo secrets, docker/metadata-action
for tag/label generation (version, major.minor, latest, short SHA), and
a final buildx/system prune cleanup step. Also add a manual
workflow_dispatch trigger.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bump the builder image to golang:1.25-alpine to match go.mod's "go
1.25.0" requirement — the previous 1.22 base failed to build at all.
- Stop COPYing the git-ignored config.yaml (which doesn't exist in a
fresh checkout, breaking CI/release builds); ship config.example.yaml
as the image's default instead, same as it's already overridden via
a volume mount in docker-compose.yaml.
- Build and include the nidusctl binary in the runtime image so users
can create their first user/calendars/address books straight from a
running container via `docker compose exec`, without installing Go.
- README: document that Docker workflow, mention the pre-built
multi-arch images published by the new release CI, and drop the
"Client configuration" section — how to configure a given DAV client
is out of scope for this project's docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Builds linux/amd64 + linux/arm64 images via Buildx/QEMU whenever a "v*"
tag is pushed or a release is published, and pushes them (tagged with
the version and "latest") to this repository's own container registry.
Written in plain GitHub Actions syntax, which Gitea Actions runs as-is.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Explain the "nidus" name (Latin for "nest").
- Note that AI coding assistants were used during development and that
not everything has been fully reviewed yet.
- Bring the Web UI section up to date with the Files, Contacts,
Calendar, and Account areas that already existed but weren't
documented.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Layout nav wraps gracefully on narrow screens instead of overflowing
(username truncates, links/logout reflow).
- Dashboard resource cards and share rows wrap instead of clipping
when names/usernames are long.
- Contacts list hides secondary columns (organization/email below
md, phone below sm) so the name and actions stay usable on phones.
- Calendar month/week grids get a horizontally scrollable wrapper
with a sane minimum width and smaller cell padding on mobile so the
7-day grid stays legible instead of being squeezed unreadably thin.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- DELETE /files/{path} recursively removes a file/folder (root itself
can't be deleted, 404 on missing paths); wired to a new "Delete"
action in the web UI with a confirmation prompt.
- Files now open inline (Content-Disposition: inline) so browsers can
play/preview natively-supported types (video, audio, images, PDF)
directly instead of always forcing a download. A separate
"Download" link (?download=1) still forces a save-as.
- Narrow screens get a stacked card list (name, size, modified date,
download/delete actions) instead of a squeezed table, so nothing is
hidden or requires horizontal scrolling; the table layout is kept
unchanged for sm+ screens.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add db.DisplayName(username), returning the user's display name if
set, otherwise the raw username. Use it everywhere a shared calendar's
owner is shown: the web calendar legend ("shared by ..."), the "new
event"/edit calendar select label, the dashboard's "Shared with you"
list, and the CalDAV-side description/name-collision disambiguation
("Name (Owner)") shown to CalDAV clients like DAVx5.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a new /web/account page, reachable by clicking the username in
the nav, with two forms: updating display name/email, and changing
the password (requires the current password, min 8 chars, confirm
match). Add db.SetProfile to persist display name/email.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract the shared per-calendar event-collection logic out of
buildMonthView into collectCalendarEvents, keyed by day so it can feed
either the existing 42-cell month grid or a new 7-cell week grid.
Add templates.WeekDay/WeekViewData, a WeekView templ, and a
viewSwitcher component shown in both views' headers to switch between
Month and Week. Add buildWeekView, handleCalendarWeek, parseWeekStart,
and mondayOf in internal/web/calendar.go, and register the new
/calendar/week route.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The month view rendered each event's text (and its little leading dot)
in the calendar's own color, on a very lightly tinted (~13% opacity)
background of that same color. For a light color like white or pale
yellow, the text ended up effectively invisible against its own
near-white background.
internal/web/templates/calendar.templ: add eventTextColor(hex), which
darkens colors above a perceived-luminance threshold (keeping the hue,
e.g. white -> a mid gray, pale yellow -> olive) while leaving already-
legible colors untouched; used for both the event text and its leading
dot. Also add a subtle ring to the event dot and to the calendar legend's
color swatch so a white/near-white swatch stays visible against the
page's white background, independent of the text-color fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
If a calendar shared with a user has the same name as one of the user's
own calendars (or as another share from a different owner), DAVx5 would
show two calendars with an identical, indistinguishable title. The
shared calendar's CalDAV display name (Calendar.Name, exposed via
displayname/DAVx5's calendar list) now gets the owning user's username
appended in parentheses in that case, e.g. "personal (alice)" — the
underlying calendar path/URL is unaffected, and a user's own calendars
are never renamed (only shared ones can collide with something else).
internal/caldav/backend.go: calendarMeta split into calendarMeta (own
calendars, unchanged name) and calendarMetaNamed (explicit display name);
new sharedDisplayName(requester, owner, realName) checks requester's own
calendars and other shares for a collision. Wired into both
ListCalendars and GetCalendar (the latter used by direct
PROPFIND/REPORT against a shared calendar's own URL).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DAVx5 reported a 500 Internal Server Error on a time-range REPORT
against a calendar containing a recurring event whose DTSTART/DTEND used
a Windows-style TZID (e.g. "W. Europe Standard Time", as commonly written
by Outlook/Exchange and some Thunderbird/Lightning setups) instead of an
IANA zone name.
go-ical resolves TZID via a plain time.LoadLocation call, which only
understands IANA names. A simple decode of such an event succeeds (RRULE
dates aren't parsed eagerly), but expanding its recurrence - which
go-webdav's caldav.Filter does for every time-range REPORT - calls
Component.RecurrenceSet, which does call time.LoadLocation(tzid) and
fails with "ical: error parsing start time: unknown time zone ...".
Add internal/icalfix, a small shared helper that rewrites recognized
Windows timezone identifiers (TZID parameters and VTIMEZONE TZID: lines)
to their IANA equivalent in raw ICS bytes before decoding. Wire it into
every ical.NewDecoder call site: internal/caldav/backend.go's
decodeObject (fixes the reported bug), internal/web/calendar.go's event
rendering/ICS import, and internal/icssub's remote feed fetching, so a
subscribed feed with the same issue doesn't hit it either.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Users can now subscribe to a remote ICS/webcal feed from the dashboard
(name + color), the same way they set up a real calendar or the virtual
Birthdays calendar. Subscriptions are read-only, per-user, and share the
"/cal/home/<name>/" namespace with real calendars and "birthdays" (name
collisions are rejected in both directions).
- internal/db: new ics_subscriptions table + CRUD (internal/db/ics.go);
CreateCalendarWithColor checks for a colliding subscription name.
- internal/icssub: shared HTTP-fetch + TTL cache (15 min) for remote ICS
calendars, with webcal:// -> https:// rewriting and stale-on-error
fallback, used by both the web UI and the CalDAV backend.
- internal/web: dashboard "Subscribe to an ICS/webcal calendar" form,
color picker, delete button (internal/web/ics.go,
templates/dashboard.templ); month view renders subscription events in
their chosen color, read-only (internal/web/calendar.go).
- internal/caldav: subscriptions are exposed as read-only calendars
(internal/caldav/ics.go) - listed in PROPFIND, events served via
GET/REPORT, PUT/DELETE on individual events rejected with 403, but
DELETE on the calendar itself unsubscribes; calendar-color is injected
the same way as for real calendars and Birthdays.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add a per-user birthday_calendars table (color, cascade-deletes with
the user) and GetBirthdayCalendarColor/SetBirthdayCalendarColor in
internal/db, plus a reservedCalendarNames guard ("birthdays") in
CreateCalendarWithColor so no real calendar can collide with the
synthetic one, whether created via the web UI, nidusctl, or CalDAV
MKCALENDAR.
- Add a "Birthdays" virtual resource card to the dashboard (color
picker only, no delete/share controls) backed by a new
ResourceCard.Virtual flag and POST /web/resources/birthdays/color
handler.
- Extract the birthday-parsing/generation logic shared by the web
calendar view and CalDAV into internal/birthdays (ParseBirthday,
Collect, OccurrenceDate, Summary) instead of duplicating it.
- Expose the Birthdays calendar over real CalDAV in
internal/caldav/backend.go + birthdays.go: it's always listed for
every user, generates one VEVENT per (contact, year) for a rolling
window (current year -2..+8) with "🎂 Name (Age)" titles, is
read-only (Put/Delete/DeleteCalendar all return 403), and its
Apple/DAVx5 calendar-color is injected from the same per-user
setting used by the dashboard/web view.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The combined month view now always includes a read-only "Birthdays"
calendar (ref "@birthdays"), generated on the fly from every contact's
BDAY field across the user's address books — no calendar objects are
stored for it. Each birthday appears as an all-day event titled
"🎂 Name (Age)" (age omitted if BDAY has no year, e.g. "--MM-DD"), colored
pink, and links to the contact's edit page instead of an event editor.
Handles both "YYYY-MM-DD"/"YYYYMMDD" and year-less vCard BDAY formats,
and skips Feb 29 birthdays in non-leap years.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Combine own and shared calendars into a single /web/calendar month
view, each event colored per its source calendar.
- New event creation now uses a calendar <select> (only writable
calendars offered); editing keeps the event's original calendar fixed.
- Shared calendars use an "owner~name" reference in URLs, resolved via
resolveCalRef which also enforces read/write permissions from
calendar_shares (read-only shares can view/export but not edit/delete).
- Add per-calendar ICS export (single event and export-all) and import,
splitting multi-VEVENT uploads into individual stored objects while
preserving VTIMEZONE definitions and source VERSION/PRODID.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- List of the user's own calendars (web/calendar)
- Month view per calendar with a Monday-first 6-week grid, showing
all-day and timed events, colored dot matching the calendar's color
- Navigation between months (prev/next/today) via query params
- Create/edit/delete events: title, description, location, all-day
toggle, start/end date+time
- Clicking a day cell prefills a new all-day event on that date; the
"New event" button defaults to a one-hour slot starting next full hour
- Deleting an event or invalid time ranges (end before/equal start) are
validated with inline error messages
- Verified round-trip with the real CalDAV protocol handler (events
created via the web UI are correctly visible to a REPORT query)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Files browser (web/files):
- Alphabetical listing grouped folders-then-files
- Upload via button or drag-and-drop, including whole folders
- Folder creation
- Fixed layout for long filenames without spaces (table-fixed + break-all)
Contacts (web/contacts):
- Full CRUD for CardDAV contacts (create/edit/delete)
- VCF import (multi-card files) and export (single/all)
- Photo upload with preview, birthday field
- TYPE labels (private/business) for phone, email, address
- Repeatable multi-input rows for phones/emails/addresses instead of textareas
- Sanitizes a known malformed TYPE parameter pattern from some vCard
exporters (e.g. Nextcloud Contacts) that otherwise caused phone numbers
to be silently dropped on import
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DAVx5/CalDAV clients displayed a shared calendar's DAV:displayname as
"alice~work" (the synthetic owner~name local identifier nidus uses
internally to keep a shared calendar's URL unique per subscriber).
That internal name is still needed for routing (it's still what appears
in the calendar's URL path), but there's no reason to show it to the
end user as the calendar's label.
calendarMeta() now always sets caldav.Calendar.Name to the plain
calendar name (e.g. "work"), while Path still uses the "owner~name"
local name. The web UI already showed the plain name via a separate
"shared by <owner>" entry, so it's unaffected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DAVx5 explicitly requests the Apple calendar-color property by name.
go-webdav's stock property map doesn't know it, so it always emitted a
404 Not Found propstat for it. Our injection was adding a *second*,
200 OK propstat with the color into the same <response>, producing a
response with two propstats for the same property name — invalid
multistatus that real clients (dav4jvm/DAVx5) resolved by preferring the
404, so the color was silently ignored.
Now the bogus 404-only propstat for calendar-color is stripped before
injecting the 200 OK one, leaving a single, valid propstat per response.
Verified against Nextcloud's documented behavior (single propstat with
the color) and against a live PROPFIND matching DAVx5's actual request
shape.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Calendars can now have a color (hex, e.g. #3b82f6) that DAVx5 and other
CalDAV clients pick up via the Apple/dav4jvm calendar-color property.
- db: add calendars.color column with migration for existing DBs;
CreateCalendarWithColor, SetCalendarColor, GetCalendarColor;
ListCalendars now returns []Calendar{Name, Color} instead of []string
- caldav: since go-webdav's caldav.Backend interface has no extension
point for vendor properties, wrap the handler with a response-rewriting
middleware that injects <calendar-color xmlns="http://apple.com/ns/ical/">
into PROPFIND responses for calendars that have a color set
- web: color picker on the "New calendar" form and an inline color swatch/
picker on each calendar card (calendars only, not address books)
- nidusctl: `calendar create --color` flag and a new `calendar color`
subcommand; `calendar list` now also prints the color if set
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>