feat: add systemd user services and timer units

- Create systemd user service files for both Go and Rust implementations
- Add comprehensive timer configurations: 30-minute, hourly, and daily schedules
- Include security settings, resource limits, and proper service dependencies
- Add justfile recipes for service management (install, enable, disable, status)
- Remove deprecated Makefile in favor of just-based build system
- Fix Rust compilation warnings in imap.rs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-03 23:59:34 +02:00
commit e6ab28bc9e
12 changed files with 267 additions and 128 deletions

119
Makefile
View file

@ -1,119 +0,0 @@
# Makefile for mail2couch project
# Builds both Go and Rust implementations with distinct binary names
.PHONY: all build build-go build-rust clean test help install
# Default target
all: build
# Build both implementations
build: build-go build-rust
# Build Go implementation as mail2couch-go
build-go:
@echo "Building Go implementation..."
cd go && go build -o mail2couch-go .
@echo "✅ Built: go/mail2couch-go"
# Build Rust implementation as mail2couch-rs
build-rust:
@echo "Building Rust implementation..."
cd rust && cargo build --release
@echo "✅ Built: rust/target/release/mail2couch-rs"
# Build optimized release versions
build-release: build-go-release build-rust-release
# Build optimized Go release
build-go-release:
@echo "Building optimized Go release..."
cd go && go build -ldflags="-s -w" -o mail2couch-go .
@echo "✅ Built optimized: go/mail2couch-go"
# Build optimized Rust release (already built with --release above)
build-rust-release: build-rust
# Install binaries to /usr/local/bin (requires sudo)
install: build-release
@echo "Installing binaries to /usr/local/bin..."
sudo cp go/mail2couch-go /usr/local/bin/
sudo cp rust/target/release/mail2couch-rs /usr/local/bin/
sudo chmod +x /usr/local/bin/mail2couch-go
sudo chmod +x /usr/local/bin/mail2couch-rs
@echo "✅ Installed mail2couch-go and mail2couch-rs"
# Run tests for both implementations
test: test-go test-rust
# Test Go implementation
test-go:
@echo "Running Go tests..."
cd go && go test ./...
# Test Rust implementation
test-rust:
@echo "Running Rust tests..."
cd rust && cargo test
# Clean build artifacts
clean:
@echo "Cleaning build artifacts..."
cd go && rm -f mail2couch-go mail2couch
cd rust && cargo clean
@echo "✅ Cleaned build artifacts"
# Check code formatting and linting
check: check-go check-rust
# Check Go code
check-go:
@echo "Checking Go code..."
cd go && go fmt ./...
cd go && go vet ./...
# Check Rust code
check-rust:
@echo "Checking Rust code..."
cd rust && cargo fmt --check
cd rust && cargo clippy -- -D warnings
# Fix code formatting
fmt: fmt-go fmt-rust
# Format Go code
fmt-go:
@echo "Formatting Go code..."
cd go && go fmt ./...
# Format Rust code
fmt-rust:
@echo "Formatting Rust code..."
cd rust && cargo fmt
# Show help
help:
@echo "Available targets:"
@echo " all - Build both implementations (default)"
@echo " build - Build both implementations"
@echo " build-go - Build Go implementation (mail2couch-go)"
@echo " build-rust - Build Rust implementation (mail2couch-rs)"
@echo " build-release - Build optimized release versions"
@echo " install - Install binaries to /usr/local/bin (requires sudo)"
@echo " test - Run tests for both implementations"
@echo " test-go - Run Go tests"
@echo " test-rust - Run Rust tests"
@echo " clean - Clean build artifacts"
@echo " check - Check code formatting and linting"
@echo " fmt - Format code"
@echo " help - Show this help message"
@echo ""
@echo "Output binaries:"
@echo " go/mail2couch-go - Go implementation"
@echo " rust/target/release/mail2couch-rs - Rust implementation"
# Development convenience targets
dev-go: build-go
./go/mail2couch-go --help
dev-rust: build-rust
./rust/target/release/mail2couch-rs --help

Binary file not shown.

View file

@ -135,4 +135,95 @@ versions: build
@./rust/target/release/mail2couch-rs --version @./rust/target/release/mail2couch-rs --version
# Clean and rebuild everything # Clean and rebuild everything
rebuild: clean build rebuild: clean build
# Systemd service management
# Install systemd user service files
install-services:
@echo "Installing systemd user service files..."
mkdir -p ~/.config/systemd/user
cp systemd/*.service ~/.config/systemd/user/
cp systemd/*.timer ~/.config/systemd/user/
systemctl --user daemon-reload
@echo "✅ Installed systemd user services and timers"
@echo "💡 Enable with: systemctl --user enable mail2couch-{go,rs}.service"
@echo "💡 Enable timers with: systemctl --user enable mail2couch-{go,rs}.timer"
# Uninstall systemd user service files
uninstall-services:
@echo "Uninstalling systemd user service files..."
systemctl --user stop mail2couch-go.service mail2couch-rs.service || true
systemctl --user stop mail2couch-go.timer mail2couch-rs.timer || true
systemctl --user stop mail2couch-go-hourly.timer mail2couch-rs-hourly.timer || true
systemctl --user stop mail2couch-go-daily.timer mail2couch-rs-daily.timer || true
systemctl --user disable mail2couch-go.service mail2couch-rs.service || true
systemctl --user disable mail2couch-go.timer mail2couch-rs.timer || true
systemctl --user disable mail2couch-go-hourly.timer mail2couch-rs-hourly.timer || true
systemctl --user disable mail2couch-go-daily.timer mail2couch-rs-daily.timer || true
rm -f ~/.config/systemd/user/mail2couch-*.service
rm -f ~/.config/systemd/user/mail2couch-*.timer
systemctl --user daemon-reload
@echo "✅ Uninstalled systemd user services and timers"
# Show systemd service status
service-status:
@echo "Service status:"
systemctl --user status mail2couch-go.service || true
systemctl --user status mail2couch-rs.service || true
@echo ""
@echo "Timer status:"
systemctl --user list-timers mail2couch-*
# Enable Go implementation with default 30-minute timer
enable-go:
systemctl --user enable mail2couch-go.service
systemctl --user enable mail2couch-go.timer
systemctl --user start mail2couch-go.timer
@echo "✅ Enabled Go implementation with 30-minute timer"
# Enable Rust implementation with default 30-minute timer
enable-rust:
systemctl --user enable mail2couch-rs.service
systemctl --user enable mail2couch-rs.timer
systemctl --user start mail2couch-rs.timer
@echo "✅ Enabled Rust implementation with 30-minute timer"
# Enable Go implementation with hourly timer
enable-go-hourly:
systemctl --user enable mail2couch-go.service
systemctl --user enable mail2couch-go-hourly.timer
systemctl --user start mail2couch-go-hourly.timer
@echo "✅ Enabled Go implementation with hourly timer"
# Enable Rust implementation with hourly timer
enable-rust-hourly:
systemctl --user enable mail2couch-rs.service
systemctl --user enable mail2couch-rs-hourly.timer
systemctl --user start mail2couch-rs-hourly.timer
@echo "✅ Enabled Rust implementation with hourly timer"
# Enable Go implementation with daily timer
enable-go-daily:
systemctl --user enable mail2couch-go.service
systemctl --user enable mail2couch-go-daily.timer
systemctl --user start mail2couch-go-daily.timer
@echo "✅ Enabled Go implementation with daily timer"
# Enable Rust implementation with daily timer
enable-rust-daily:
systemctl --user enable mail2couch-rs.service
systemctl --user enable mail2couch-rs-daily.timer
systemctl --user start mail2couch-rs-daily.timer
@echo "✅ Enabled Rust implementation with daily timer"
# Disable all timers and services
disable-all:
systemctl --user stop mail2couch-go.timer mail2couch-rs.timer || true
systemctl --user stop mail2couch-go-hourly.timer mail2couch-rs-hourly.timer || true
systemctl --user stop mail2couch-go-daily.timer mail2couch-rs-daily.timer || true
systemctl --user disable mail2couch-go.timer mail2couch-rs.timer || true
systemctl --user disable mail2couch-go-hourly.timer mail2couch-rs-hourly.timer || true
systemctl --user disable mail2couch-go-daily.timer mail2couch-rs-daily.timer || true
systemctl --user disable mail2couch-go.service mail2couch-rs.service || true
@echo "✅ Disabled all timers and services"

View file

@ -578,9 +578,9 @@ impl ImapClient {
let headers = self.extract_headers(&parsed_message); let headers = self.extract_headers(&parsed_message);
// Extract attachments and their data // Extract attachments and their data
let (has_attachments, attachment_stubs, attachment_data) = self.extract_attachments_with_data(&parsed_message); let (has_attachments, _attachment_stubs, attachment_data) = self.extract_attachments_with_data(&parsed_message);
let mut mail_doc = MailDocument::new( let mail_doc = MailDocument::new(
uid.to_string(), uid.to_string(),
mailbox.to_string(), mailbox.to_string(),
from, from,
@ -738,12 +738,6 @@ impl ImapClient {
(has_attachments, attachment_stubs, attachment_data) (has_attachments, attachment_stubs, attachment_data)
} }
/// Extract attachments from a parsed message (deprecated - use extract_attachments_with_data)
/// Returns (has_attachments, attachment_stubs)
fn extract_attachments(&self, message: &Message) -> (bool, HashMap<String, AttachmentStub>) {
let (has_attachments, attachment_stubs, _) = self.extract_attachments_with_data(message);
(has_attachments, attachment_stubs)
}
/// Determine if a message part is an attachment /// Determine if a message part is an attachment
fn is_attachment_part(&self, part: &mail_parser::MessagePart, content_type: &mail_parser::ContentType) -> bool { fn is_attachment_part(&self, part: &mail_parser::MessagePart, content_type: &mail_parser::ContentType) -> bool {

View file

@ -0,0 +1,17 @@
[Unit]
Description=Run mail2couch Go Implementation daily
Documentation=https://kode.naiv.no/olemd/mail2couch
Requires=mail2couch-go.service
[Timer]
# Run daily at 2:30 AM with some randomization
OnCalendar=*-*-* 02:30:00
# Run 10 minutes after boot if we missed a scheduled run
OnBootSec=10min
# If the system was off, run shortly after startup
Persistent=true
# Add up to 30 minutes randomization to spread load
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,17 @@
[Unit]
Description=Run mail2couch Go Implementation every hour
Documentation=https://kode.naiv.no/olemd/mail2couch
Requires=mail2couch-go.service
[Timer]
# Run every hour at a random minute to spread load
OnCalendar=hourly
# Run 5 minutes after boot if we missed a scheduled run
OnBootSec=5min
# If the system was off, run shortly after startup
Persistent=true
# Add randomization to avoid all users hitting servers simultaneously
RandomizedDelaySec=600
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,35 @@
[Unit]
Description=mail2couch Go Implementation - Email backup to CouchDB
Documentation=https://kode.naiv.no/olemd/mail2couch
After=network.target
Wants=network.target
[Service]
Type=oneshot
User=%i
ExecStart=%h/bin/mail2couch-go
WorkingDirectory=%h/.config/mail2couch
# Environment
Environment=PATH=%h/bin:/usr/local/bin:/usr/bin:/bin
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mail2couch-go
# Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=read-only
ReadOnlyPaths=/
ReadWritePaths=%h/.config/mail2couch %h/.local/share/mail2couch
# Resource limits
MemoryHigh=512M
MemoryMax=1G
CPUQuota=50%
[Install]
WantedBy=default.target

View file

@ -0,0 +1,17 @@
[Unit]
Description=Run mail2couch Go Implementation every 30 minutes
Documentation=https://kode.naiv.no/olemd/mail2couch
Requires=mail2couch-go.service
[Timer]
# Run every 30 minutes
OnCalendar=*:0/30
# Run 2 minutes after boot if we missed a scheduled run
OnBootSec=2min
# If the system was off, run shortly after startup
Persistent=true
# Add some randomization to avoid all users hitting servers simultaneously
RandomizedDelaySec=300
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,17 @@
[Unit]
Description=Run mail2couch Rust Implementation daily
Documentation=https://kode.naiv.no/olemd/mail2couch
Requires=mail2couch-rs.service
[Timer]
# Run daily at 2:30 AM with some randomization
OnCalendar=*-*-* 02:30:00
# Run 10 minutes after boot if we missed a scheduled run
OnBootSec=10min
# If the system was off, run shortly after startup
Persistent=true
# Add up to 30 minutes randomization to spread load
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,17 @@
[Unit]
Description=Run mail2couch Rust Implementation every hour
Documentation=https://kode.naiv.no/olemd/mail2couch
Requires=mail2couch-rs.service
[Timer]
# Run every hour at a random minute to spread load
OnCalendar=hourly
# Run 5 minutes after boot if we missed a scheduled run
OnBootSec=5min
# If the system was off, run shortly after startup
Persistent=true
# Add randomization to avoid all users hitting servers simultaneously
RandomizedDelaySec=600
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,36 @@
[Unit]
Description=mail2couch Rust Implementation - Email backup to CouchDB
Documentation=https://kode.naiv.no/olemd/mail2couch
After=network.target
Wants=network.target
[Service]
Type=oneshot
User=%i
ExecStart=%h/bin/mail2couch-rs
WorkingDirectory=%h/.config/mail2couch
# Environment
Environment=PATH=%h/bin:/usr/local/bin:/usr/bin:/bin
Environment=RUST_LOG=info
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mail2couch-rs
# Security settings
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=read-only
ReadOnlyPaths=/
ReadWritePaths=%h/.config/mail2couch %h/.local/share/mail2couch
# Resource limits
MemoryHigh=512M
MemoryMax=1G
CPUQuota=50%
[Install]
WantedBy=default.target

View file

@ -0,0 +1,17 @@
[Unit]
Description=Run mail2couch Rust Implementation every 30 minutes
Documentation=https://kode.naiv.no/olemd/mail2couch
Requires=mail2couch-rs.service
[Timer]
# Run every 30 minutes
OnCalendar=*:0/30
# Run 2 minutes after boot if we missed a scheduled run
OnBootSec=2min
# If the system was off, run shortly after startup
Persistent=true
# Add some randomization to avoid all users hitting servers simultaneously
RandomizedDelaySec=300
[Install]
WantedBy=timers.target