DeepSeek Harness Remote
DeepSeek Harness Remote: DeepSeek Harness 실행 PC와 모바일·PC를 안전하게 연결해 어디서나 작업 이어받기
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Fast, self-hostable scheduling platform. Like Cal.com, but written in Rust.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Fast, self-hostable scheduling. Like Cal.com, but written in Rust.
"Your time, your stack."
calrs is an open-source scheduling platform built in Rust. Connect your CalDAV calendar (Nextcloud, Fastmail, BlueMind, iCloud...), define bookable meeting types, and share a link. No Node.js, no PostgreSQL, no subscription.
/u/alice+bob/intro) for instant collective meetings without creating a team. All participants' calendars are intersected. Autocomplete user picker in the event type editor, opt-out toggle in settings.ics invitescalrs supports seven distinct booking scenarios. Each serves a different use case — there is no overlap:
| Type | Who books? | How do they find it? | Assigned to | Example |
|---|---|---|---|---|
| Personal (public) | Anyone | Listed on your profile | You | Freelancer "30min intro call" |
| Personal (internal) | Invited guests only | Any colleague generates a link | You | Senior engineer: any teammate can share a "Code Review" link with an external contributor |
| Personal (private) | Invited guests only | You send an invite link | You | Executive coaching for selected clients |
| Team (public) | Anyone | Listed on team page | Round-robin (least busy) | Public "Support Call" page |
| Team (internal) | Invited guests only | Any employee generates a link | Round-robin (least busy) | Cross-team: Sales shares Support booking links with customers |
| Team (private) | Invited guests only | Owner sends an invite link | Round-robin (least busy) | Demo team: sales manager sends links to qualified leads |
| Dynamic group | Anyone with the URL | Ad-hoc link: /u/alice+bob/slug | Event type owner | One-off sales call needing engineering support |
/team/{slug}/{event-slug}Create a team — go to Teams in the sidebar, click New team. Name it, pick public or private visibility, and add members (individual users and/or OIDC groups) from the unified search bar.
Create a team event type — go to Event Types, click New event type, and select your team as the owner. Choose a scheduling mode:
Share the booking link — public teams are bookable at /team/{slug}/{event-slug}. Private teams use an invite token link from the team settings page.
Each team member's CalDAV calendars are checked for conflicts. The availability rules and overrides on the event type apply to all members.
Private team vs internal event type: A private team gates access at the team level — the team admin shares one invite link covering all the team's event types. Internal visibility gates access at the event type level — any authenticated employee can generate per-event-type links on the fly from the Organization dashboard. Use private teams when you want controlled distribution by a team admin. Use internal when you want self-serve link generation across the org (e.g., any Sales rep can generate a Support Call link for a customer).
.ics calendar invites on booking, cancellation, and approval.ics invites, and reschedule/cancel action buttonsCALRS_SECRET_KEY<cap-widget> is shown on all booking pages and the token is verified server-to-server before any booking is processed. Disabled by default — no widget, no network call if unconfiguredAccept-Language with q-weights). Authenticated users can override the choice in Profile & Settingscargo fmt, cargo clippy, cargo test, and template validation via GitHub Actionsamd64 + arm64) published to GHCR on every releasetracing + tower-http for request-level observability, configurable via RUST_LOGPre-built images are available on GitHub Container Registry for amd64 and arm64:
docker run -d --name calrs \
-p 3000:3000 \
-v calrs-data:/var/lib/calrs \
-e CALRS_BASE_URL=https://cal.example.com \
ghcr.io/olivierlambert/calrs:latest
Podman works as a drop-in replacement — just use
podmaninstead ofdockerin all commands.
Then visit http://localhost:3000, register an account, and add your calendars from the dashboard.
services:
calrs:
image: ghcr.io/olivierlambert/calrs:latest
ports:
- "3000:3000"
volumes:
- calrs-data:/var/lib/calrs
environment:
- CALRS_BASE_URL=https://cal.example.com
restart: unless-stopped
volumes:
calrs-data:
Works with both docker compose and podman-compose.
To build from source instead, replace
image: ghcr.io/olivierlambert/calrs:latestwithbuild: .(or usedocker build -t calrs .).
# Build from source
cargo build --release
# Install
sudo cp target/release/calrs /usr/local/bin/
sudo cp -r templates /var/lib/calrs/templates
# Create a system user
sudo useradd -r -s /bin/false -m -d /var/lib/calrs calrs
# Install and configure the service
sudo cp calrs.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now calrs
Edit /etc/systemd/system/calrs.service to set CALRS_BASE_URL to your public URL. The service runs on port 3000 by default — put a reverse proxy in front for TLS (see Reverse proxy below).
cargo build --release
calrs serve --port 3000
Then register at http://localhost:3000 — the first user becomes admin.
# Install mdBook (one time)
cargo install mdbook
# Build and serve the docs
cd docs
mdbook serve --open
This builds the user documentation from docs/src/ and opens it in your browser. The docs are also available as static HTML in docs/book/.
Once installed, you can manage everything from the web UI or use the CLI:
# Connect your CalDAV calendar
calrs source add --url https://nextcloud.example.com/remote.php/dav \
--username alice --name "My Calendar"
# Pull events
calrs sync
# Create a bookable meeting type
calrs event-type create --title "30min intro call" --slug intro --duration 30
# Check your availability
calrs event-type slots intro
# Book a slot
calrs booking create intro --date 2026-03-20 --time 14:00 \
--name "Jane Doe" --email [email protected]
calrs connects to any CalDAV server. You need the DAV root URL for your provider — not a calendar-specific or public link. When adding a source from the web dashboard, selecting a provider auto-fills the URL pattern.
https://mail.yourcompany.com/dav/https://cloud.example.com/remote.php/davhttps://caldav.fastmail.com/dav/calendars/user/[email protected]/ (use an app-specific password)https://caldav.icloud.com/ (use an app-specific password from appleid.apple.com)https://mail.example.com/dav/https://mail.example.com/SOGo/dav/https://cal.example.com/calrs auto-discovers your principal URL and calendar-home-set via PROPFIND (RFC 4791). If the connection test hangs or fails, use the "skip connection test" option and try syncing directly.
Google Calendar is not currently supported. Google dropped Basic Auth for CalDAV in 2020 and now requires OAuth2, and Google "app passwords" only work for IMAP/SMTP. To use Google Calendar availability in calrs, bridge it through a CalDAV server that can subscribe to a Google calendar (for example, Nextcloud's calendar app).
In your Keycloak realm, create a new OpenID Connect client:
calrshttps://your-calrs-host/auth/oidc/callbackhttps://your-calrs-hostCopy the Client secret from the Credentials tab.
Configure calrs:
calrs config oidc \
--issuer-url https://keycloak.example.com/realms/your-realm \
--client-id calrs \
--client-secret YOUR_CLIENT_SECRET \
--enabled true \
--auto-register true
export CALRS_BASE_URL=https://your-calrs-host
calrs serve --port 3000
The login page will show a "Sign in with SSO" button. With --auto-register true, users are created automatically on first OIDC login. Existing local users are linked by email.
calrs integrates Cap, a self-hosted proof-of-work CAPTCHA that requires no third-party service and collects no user data. The widget appears only on booking pages — never on the dashboard or registration form.
The feature is opt-in. If you do not configure it, the booking flow works exactly as before: no widget, no network call.
Cap is distributed as a Docker image. Minimal compose example:
services:
cap:
image: ghcr.io/trycap/cap:latest
ports:
- "3001:3001"
environment:
CAP_SECRET: your-secret-key
Create a site in the Cap dashboard and note the site key and secret.
Go to Admin panel → Captcha and fill in:
| Field | Example | Notes |
|---|---|---|
| Instance URL | https://captcha.example.com | Base URL of your Cap server |
| Site key | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | From the Cap dashboard |
| Secret | your-secret-key | Encrypted at rest with AES-256-GCM |
| Widget script URL | (leave empty) | Defaults to https://cdn.jsdelivr.net/npm/cap-widget. Override for air-gapped deployments. |
| Widget WASM URL | (leave empty) | Where the widget loads its WASM solver from. By default the widget fetches it from cdn.jsdelivr.net even when everything else is self-hosted; point it at the WASM file your Cap server serves (see Cap's asset server docs) to keep booking pages fully self-hosted. |
Click Save captcha settings. All changes — including the widget script URL — take effect immediately, no restart required.
To disable, clear the Instance URL, Site key, and Secret fields and save.
cap-token form fieldPOST <instance>/<site-key>/siteverify with the token and the secret (server-to-server, never exposed to the browser)calrs sets a strict CSP on all responses. When Cap is configured, the following directives are added and updated automatically on every save:
| Directive | Added value | Why |
|---|---|---|
script-src | 'wasm-unsafe-eval' <widget-origin> | The widget JS loads from an external origin; wasm-unsafe-eval allows the WASM proof-of-work solver |
worker-src | blob: | The widget spawns a Web Worker via a blob URL to run the solver off the main thread |
connect-src | <instance-origin> <widget-origin> [<wasm-origin>] | The browser fetches the WASM binary (from the widget CDN, or from the Widget WASM URL's origin when one is set) and sends the solved token to the Cap server |
When captcha is disabled, none of these directives are present — the CSP reverts to its strict baseline immediately after saving.
calrs listens on HTTP (port 3000 by default). Use a reverse proxy for TLS termination.
The simplest option — automatic HTTPS with Let's Encrypt:
cal.example.com {
reverse_proxy localhost:3000
}
Save as /etc/caddy/Caddyfile and reload: sudo systemctl reload caddy.
server {
listen 443 ssl http2;
server_name cal.example.com;
ssl_certificate /etc/letsencrypt/live/cal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cal.example.com/privkey.pem;
# Room for the 2 MB logo and avatar uploads (nginx's default is 1 MB).
client_max_body_size 3m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name cal.example.com;
return 301 https://$host$request_uri;
}
Get certificates with certbot: sudo certbot --nginx -d cal.example.com.
Important: Set
CALRS_BASE_URLto your public URL (e.g.https://cal.example.com) so that OIDC redirect URIs and email links point to the right host.
calrs uses structured logging via the tracing crate. All log output goes to stderr and is captured by systemd journal, Docker logs, or any log aggregator.
Set the log level via the RUST_LOG environment variable:
# Default (recommended for production)
RUST_LOG=calrs=info,tower_http=info
# Verbose (debug HTTP requests + internal events)
RUST_LOG=calrs=debug,tower_http=debug
# Quiet (errors only)
RUST_LOG=calrs=error
| Category | Level | Events |
|---|---|---|
| Auth | info/warn | Login success/failure, registration, logout, OIDC login |
| Bookings | info | Created, cancelled, approved, declined, guest self-cancel, reminder sent |
| CalDAV | info/error | Sync started/completed, write-back success/failure, source added/removed |
| Admin | info/warn | Role changes, user enable/disable, auth/OIDC config updates, impersonation |
| debug/error | Delivery success, send failures | |
| HTTP | info | Every request via tower-http TraceLayer (method, path, status, latency) |
| Database | info | Migration applied on startup |
| Server | info | Startup, shutdown |
2026-03-12T14:30:00Z INFO calrs: calrs server listening on 127.0.0.1:3000
2026-03-12T14:30:05Z INFO calrs::auth: user login [email protected] ip=192.168.1.1
2026-03-12T14:31:00Z INFO calrs::web::booking: booking created booking_id=a1b2c3 event_type=intro [email protected]
2026-03-12T14:31:01Z INFO tower_http::trace: response{method=POST path="/u/alice/intro/book" status=200 latency="45ms"}
2026-03-12T14:31:02Z ERROR calrs::web::calendar_push: CalDAV write-back failed uid=a1b2c3@calrs error="connection refused"
2026-03-12T14:32:00Z WARN calrs::auth: login failed [email protected] ip=10.0.0.5
2026-03-12T15:00:00Z WARN calrs::web::booking: rate limited ip=10.0.0.5
calrs source add [--no-test] Connect a CalDAV calendar
calrs source list List connected sources
calrs source remove <id> Remove a source
calrs source test <id> Test a connection
calrs sync [--full] Pull latest events from CalDAV
calrs event-type create Define a new bookable meeting
calrs event-type list List your event types
calrs event-type slots <slug> Show available slots
calrs calendar show [--from] [--to] View your calendar
calrs booking create <slug> Book a slot
calrs booking list [--upcoming] View bookings
calrs booking cancel <id> Cancel a booking
calrs config smtp Configure SMTP for email notifications
calrs config show Show current configuration
calrs config smtp-test <email> Send a test email
calrs config auth Configure registration/domain restrictions
calrs config oidc Configure OIDC (SSO via Keycloak, etc.)
calrs user list List users
calrs user create Create a user
calrs user set-password <email> Set a user's password
calrs user promote <email> Promote user to admin
calrs serve [--host 127.0.0.1] [--port 3000] Start the web booking server
calrs/
├── Cargo.toml
├── migrations/ SQLite schema (incremental)
├── templates/ Minijinja HTML templates
│ ├── base.html Base layout + CSS (dark mode)
│ ├── auth/ Login + registration
│ ├── dashboard_base.html Sidebar layout for all dashboard pages
│ ├── dashboard_overview.html Overview with stats
│ ├── dashboard_event_types.html Event types listing
│ ├── dashboard_bookings.html Bookings listing
│ ├── dashboard_sources.html Calendar sources
│ ├── dashboard_teams.html Teams listing
│ ├── dashboard_internal.html Internal/organization event types
│ ├── admin.html Admin panel
│ ├── settings.html Profile & settings
│ ├── event_type_form.html Create/edit event types
│ ├── invite_form.html Invite management (send + list)
│ ├── overrides.html Date overrides per event type
│ ├── source_form.html Add CalDAV source (provider presets)
│ ├── source_test.html Connection test / sync results
│ ├── source_write_setup.html Write-back calendar selection
│ ├── team_form.html Create/manage teams
│ ├── team_settings.html Team settings (members, groups)
│ ├── team_profile.html Public team page
│ ├── profile.html Public user profile
│ ├── troubleshoot.html Availability troubleshoot timeline
│ ├── slots.html Slot picker (timezone aware)
│ ├── book.html Booking form
│ ├── confirmed.html Confirmation / pending page
│ ├── booking_approved.html Email approve success
│ ├── booking_decline_form.html Email decline form
│ ├── booking_declined.html Email decline success
│ ├── booking_cancel_form.html Guest self-cancel form
│ ├── booking_cancelled_guest.html Guest cancel success
│ ├── booking_host_reschedule.html Host reschedule page
│ ├── booking_reschedule_confirm.html Reschedule confirmation
│ └── booking_action_error.html Invalid/expired token error
└── src/
├── main.rs CLI entry point (clap)
├── db.rs SQLite connection + migrations
├── models.rs Domain types
├── auth.rs Authentication (local + OIDC)
├── email/ SMTP email with .ics invites + HTML templates
├── rrule.rs Recurring event expansion (RRULE)
├── utils.rs Shared utilities (iCal splitting/parsing)
├── caldav/mod.rs CalDAV client (RFC 4791) + write-back
├── web/ Axum web server, one file per area
└── commands/ CLI subcommands
Contributing code? Start with ARCHITECTURE.md: which file owns what, the main concepts, and where to add a route, an email, a translatable string or a migration.
Storage: SQLite (WAL mode). Single file, zero ops.
CalDAV: Pull-based sync for free/busy, write-back for confirmed bookings.
.ics invitescalrs ships with nine complete translations: English, French, Spanish, Polish, German, Italian, Estonian, Brazilian Portuguese and Dutch. Strings live in Fluent .ftl files under i18n/ and are embedded in the binary at compile time, so there are no runtime translation files to deploy.
German comes in two registers: Deutsch addresses the reader as "du", Deutsch (Sie) as "Sie", as most businesses in Germany, Austria and Switzerland address their customers. Browsers only ask for "German", so visitors get "du" unless the instance chooses otherwise. An admin can set German form of address to Formal (Sie) under System settings, after which every visitor whose browser asks for German is addressed as "Sie": on all public pages, in the bookings they make, and in every email they receive. A single user can also pick Deutsch (Sie) as their language in Profile & Settings and set their public booking pages to Always my language.
Each of these nine locales carries all 1325 message ids. A test holds them there, so a release cannot ship a half-translated language. Formal German is the exception by design: i18n/de-formal/ is an overlay on de that only carries the messages addressing the reader and reads every other message from de. Do not copy unchanged German messages into it; a test rejects an override identical to de.
| Locale | Status |
|---|---|
| English | Source language |
| French | Human-translated and reviewed |
| Brazilian Portuguese | Contributed by a native speaker |
| Spanish, Polish, German, Italian, Estonian, Dutch | Complete, but machine-seeded. Native-speaker review very welcome |
Complete is not the same as good. The machine-seeded locales are grammatical and internally consistent, and they have been checked for the things automated translation usually gets wrong: the register stays informal throughout, Polish carries the plural categories its grammar needs rather than English's two, and sentences that interpolate a value avoid agreeing with it. What they have not had is a native speaker reading them in context, on the actual pages. If one of those is your language, the section below is for you.
| Visitor | Source of truth |
|---|---|
| Guest (not logged in) | Browser Accept-Language header, with q-weights honoured |
| Logged-in user with no preference set | Same as above |
| Logged-in user with a preference set | The user's choice in Profile & Settings |
Untranslated keys fall back to English at runtime, so a partial translation never breaks a page. Shipped locales are never partial, but the fallback keeps a work-in-progress branch usable.
Everything happens in this repository through a pull request. There is no separate platform to sign up for, and the six people who have contributed translations so far all did it this way.
Base your work on the i18n branch, not main. It is permanent, it normally sits exactly at main, and it exists so new strings can settle before a release picks them up.
i18ni18n/{code}/main.ftl. The message id is on the left of the = and never changes; only the text to its right gets translatedcargo test i18n::i18nYou do not need to touch Rust, and you do not need to translate everything. A pull request fixing one awkward sentence is worth sending.
Four steps, and the order matters. Doing them out of order fails the build in a way that looks like the project is broken:
i18n/en/main.ftl to i18n/{code}/main.ftl and translate every value, keeping the ids exactly as they are.SUPPORTED_LANGS in src/i18n.rs: the code, the language's own name for itself (Eesti, not Estonian), and an include_str! of the new file.required table in plural_messages_carry_the_locale_categories in src/i18n.rs. Most languages need one, other; Polish needs one, few, many. The CLDR plural rules chart is the reference.cargo test i18n::Step 1 comes before step 2 for a reason: the coverage test demands every key the moment a locale appears in SUPPORTED_LANGS, so registering an empty file fails with 1325 errors at once.
That is the whole change on the calrs side. The language then appears in the Profile & Settings dropdown and is matched against Accept-Language automatically.
This is fine, and it is how five of the current locales started. It is also why native review matters more than volume. Three things a model reliably gets wrong here:
{ $count } must survive verbatim, including the spaces. A literal brace has to be written {"{"}, because a bare { starts a placeable. There are no backslash escapes: a newline is {"\u000A"}, not \n.one/other shape into Polish produces text that reads wrong at 2 and at 5. Write the categories your language actually uses.i18n/de-formal/ (Sie). A German string that addresses the reader also needs its "Sie" form in i18n/de-formal/ (an overlay on de; the tests flag a missing one). Models default to the formal register in German and Spanish unless told otherwise, and the result is a page that switches politeness level halfway down.The test suite checks structure, not meaning. It will catch a missing key, a broken placeable and a missing plural category. It cannot tell you that a sentence is stilted, that a term is wrong for your industry, or that a word is right in Portugal and wrong in Brazil. Say in the pull request description if a translation is machine-seeded, so reviewers know what they are reading.
i18n/{lang}/main.ftl, one file per language, kebab-case message idssrc/i18n.rs. Templates use {{ t("message-id", arg=value) }}; the active language reaches the template as langi18n branch first, then merge into main. The flow is one-directional: never merge main into i18ncargo test i18n:: before pushing. It checks key coverage across all nine locales, that every template still loads, and that plural messages carry the categories each language needsAGPL-3.0 — free to use, modify, and self-host. Contributions welcome.
sync-token / ctag