6 Commits

Author SHA1 Message Date
Lennart J. Kurzweg (Nx2)
65aeeda263 Merge branch 'master' of ssh://ssh.nx2.site:50022/nx2/nxcaldav 2026-04-23 17:32:33 +02:00
Lennart J. Kurzweg (Nx2)
f61e014d2a smtp pw 2026-04-23 17:32:30 +02:00
ce6a5c7477 Delete shell.nix 2026-04-23 17:21:10 +02:00
Lennart J. Kurzweg (Nx2)
f66f58f67f no shebang 2026-04-23 17:17:52 +02:00
Lennart J. Kurzweg (Nx2)
5f036dbc89 fixed frfr 2026-04-22 20:32:48 +02:00
Lennart J. Kurzweg (Nx2)
6394c8496d remove log 2026-04-22 17:46:23 +02:00
9 changed files with 104 additions and 135 deletions

View File

@@ -3,74 +3,45 @@ database:
server:
bind_address: 0.0.0.0:14243
default_class: CONFIDENTIAL
public_url: http://nxc.nx2.site/
email_domain: nx2.site
public_url: http://example.com/
email_domain: example.com
redaction_text: '[-]'
smtp:
host: localhost
port: 587
user: nxcaldav@nx2.site
password: Vastly-Wrinkle9-Corsage
password_cmd: echo "Vastly-Wrinkle9-Corsage"
users:
- name: daniel
password: ll
- name: alice
password: 123
groups:
- family
- name: lennart
password: ll
- name: bob
password: abc
groups:
- family
- name: shared
password: Oxidant-Ageless3-Dispersed
calendars:
- id: preservation
owner: lennart
- id: test
owner: alice
color: '#F6F5F4'
- id: effort
owner: lennart
color: '#FF0000'
- id: experience
owner: lennart
color: '#2C33FF'
- id: leisure
owner: lennart
color: '#10B400'
- id: daniel
owner: daniel
color: '#ff2222'
- access:
- group: family
mode: read-write
id: family
- id: family
owner: shared
color: '#999999'
access:
- group: family
mode: read-write
address_books:
- id: contacts
owner: lennart
- id: contacts
owner: daniel
owner: alice
- id: family
owner: shared
owner: bob
access:
- group: family
mode: read-write
aggregates:
- access:
- group: family
mode: read-only
- ics: future-only
id: lennart-aggregat
owner: lennart
color: '#dd9999'
sources:
- preservation
- effort
- experience
- leisure
- family

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
import os
import argparse
import psycopg2

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
import os
import argparse
import psycopg2

View File

@@ -8,7 +8,6 @@ import (
"log"
"net/http"
"net/url"
"os/exec"
"path"
"slices"
"strings"
@@ -140,16 +139,9 @@ func (b *DBBackend) initSchema(ctx context.Context) error {
}
func (b *DBBackend) resolvePassword(u config.User) (string, error) {
var raw string
if u.PasswordCmd != "" {
cmd := exec.Command("sh", "-c", u.PasswordCmd)
out, err := cmd.Output()
raw, err := config.ResolvePassword(u.Password, u.PasswordCmd)
if err != nil {
return "", fmt.Errorf("failed to run password command for %s: %v", u.Name, err)
}
raw = strings.TrimSpace(string(out))
} else {
raw = u.Password
return "", fmt.Errorf("failed to resolve password for %s: %v", u.Name, err)
}
// If it already looks like a bcrypt hash, return as is.

View File

@@ -10,6 +10,7 @@ import (
"strings"
"github.com/emersion/go-ical"
"nxcaldav/internal/config"
)
// sendInvitation sends an iMIP (RFC 6047) invitation email.
@@ -99,8 +100,14 @@ func (b *DBBackend) sendInvitation(senderName, recipientEmail, summary, descript
if err = c.StartTLS(tlsConfig); err != nil { return err }
}
}
if b.smtp.User != "" && b.smtp.Password != "" {
auth := smtp.PlainAuth("", b.smtp.User, b.smtp.Password, b.smtp.Host)
smtpPassword, err := config.ResolvePassword(b.smtp.Password, b.smtp.PasswordCmd)
if err != nil {
return fmt.Errorf("failed to resolve SMTP password: %v", err)
}
if b.smtp.User != "" && smtpPassword != "" {
auth := smtp.PlainAuth("", b.smtp.User, smtpPassword, b.smtp.Host)
if err = c.Auth(auth); err != nil { return err }
}

View File

@@ -1,8 +1,10 @@
package config
import (
"fmt"
"net/url"
"os"
"os/exec"
"slices"
"strings"
@@ -68,6 +70,7 @@ type SMTPConfig struct {
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
PasswordCmd string `yaml:"password_cmd"`
}
type Config struct {
@@ -79,6 +82,18 @@ type Config struct {
AddressBooks []AddressBook `yaml:"address_books"`
Aggregates []Aggregate `yaml:"aggregates"`
}
func ResolvePassword(password, passwordCmd string) (string, error) {
if passwordCmd != "" {
cmd := exec.Command("sh", "-c", passwordCmd)
out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to run password command: %v", err)
}
return strings.TrimSpace(string(out)), nil
}
return password, nil
}
func (c *Config) setDefaults() {
if c.Server.BindAddress == "" { c.Server.BindAddress = ":8080" }
if c.Server.Redaction == "" { c.Server.Redaction = "Busy" }

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"context"
"io"
"log"
"net/http"
"nxcaldav/internal/backend"

13
main.go
View File

@@ -19,11 +19,9 @@ import (
"nxcaldav/internal/extra"
)
func main() {
// -- GET CONFIG
path := "config.yaml";
path := "config.yaml"
if len(os.Args) == 3 {
if os.Args[1] == "-c" {
path = os.Args[2]
@@ -41,18 +39,18 @@ func main() {
log.Fatalf("failed to initialize database backend: %v", err)
}
// -- GET CONTEXT AND DB
caldavHandler := &caldav.Handler{Backend: be}
carddavHandler := &carddav.Handler{Backend: be}
publicURL, _ := url.Parse(cfg.Server.PublicURL)
// -- DISCOVERIES
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
scheme := r.URL.Scheme
if scheme == "" { scheme = "http" }
if scheme == "" {
scheme = "http"
}
// set host (because reverse proxies exist)
if publicURL != nil && publicURL.Host != "" {
@@ -60,7 +58,7 @@ func main() {
r.URL.Host = publicURL.Host
// prioritize X-Forwarded-Proto, then PublicURL (e.g. Cloudfalre proxy)
scheme := publicURL.Scheme
scheme = publicURL.Scheme
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
scheme = proto
}
@@ -162,6 +160,7 @@ func main() {
http.NotFound(w, r)
}
}
})
fmt.Printf("Starting CalDAV/CardDAV server on %s...\n", cfg.Server.BindAddress)
server := &http.Server{

View File

@@ -1,12 +0,0 @@
{ pkgs ? import <nixpkgs> { } }: let
my-python = pkgs.python312;
python-with-my-packages = my-python.withPackages (p: with p; [
ical
ics
caldav
pyyaml
psycopg2
]);
in pkgs.mkShell {
buildInputs = [ python-with-my-packages ];
}