{"id":45764780,"url":"https://github.com/c-mueller/ts-restic-server","last_synced_at":"2026-04-11T15:05:47.462Z","repository":{"id":340140239,"uuid":"1164334517","full_name":"c-mueller/ts-restic-server","owner":"c-mueller","description":"Restic REST server (v1 + v2 API) in Go with pluggable storage backends (filesystem, S3, WebDAV, rclone) and optional Tailscale listener","archived":false,"fork":false,"pushed_at":"2026-04-06T20:46:22.000Z","size":202,"stargazers_count":1,"open_issues_count":10,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-06T22:22:24.512Z","etag":null,"topics":["backup","restic","restic-backups","s3","tailscale","webdav"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c-mueller.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-23T00:39:08.000Z","updated_at":"2026-04-06T20:43:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/c-mueller/ts-restic-server","commit_stats":null,"previous_names":["c-mueller/ts-restic-server"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/c-mueller/ts-restic-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-mueller%2Fts-restic-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-mueller%2Fts-restic-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-mueller%2Fts-restic-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-mueller%2Fts-restic-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c-mueller","download_url":"https://codeload.github.com/c-mueller/ts-restic-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-mueller%2Fts-restic-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31684544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T13:07:20.380Z","status":"ssl_error","status_checked_at":"2026-04-11T13:06:47.903Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backup","restic","restic-backups","s3","tailscale","webdav"],"created_at":"2026-02-26T00:26:41.286Z","updated_at":"2026-04-11T15:05:47.456Z","avatar_url":"https://github.com/c-mueller.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ts-restic-server\n\nA [restic](https://restic.net/) REST server written in Go, implementing the official REST backend API (v1 + v2). It features pluggable storage backends and an optional [Tailscale](https://tailscale.com/) listener for zero-config, encrypted connectivity via [tsnet](https://pkg.go.dev/tailscale.com/tsnet).\n\n## Features\n\n- Full restic REST API (v1 and v2) compatibility\n- Multiple storage backends: Filesystem, S3-compatible, WebDAV, Rclone, SMB/CIFS, NFS, In-Memory\n- Multi-repository support via URL path prefixes (e.g. `/host-a/backups`, `/host-b/docs`)\n- Optional Tailscale integration for TLS without certificates or port forwarding\n- ACL engine with per-identity, per-repo-path access control (Tailscale tags, users, hostnames, IPs)\n- Web UI with dashboard, repository list, and lock management\n- Per-repository traffic statistics (SQLite-backed)\n- Prometheus metrics for HTTP, ACL, storage, and per-host observability\n- Append-only mode (deletes blocked except for lock removal)\n- Data sharding (`data/00-ff`) for restic-server compatible directory layout with unsharded fallback\n- Structured JSON logging with per-request IDs (zap)\n- HTTP security response headers\n- Graceful shutdown with configurable timeout\n- Configuration via CLI flags, config file, or environment variables (with `${VAR}` substitution)\n\n## Early Stage Notice\n\nThis project is in early development and was largely vibe-coded with AI assistance. It may contain bugs or missing edge cases. **Pull requests and bug reports are welcome!**\n\n## Installation\n\n### Pre-built Binaries\n\nDownload pre-built binaries and Debian packages from the [GitHub Releases](https://github.com/c-mueller/ts-restic-server/releases) page. Available for:\n\n- Linux (amd64, arm64, armv7)\n- macOS (amd64, arm64)\n- Windows (amd64)\n\nDebian/Ubuntu:\n\n```bash\nsudo dpkg -i ts-restic-server_*_linux_amd64.deb\n```\n\n### Docker\n\n```bash\ndocker pull ghcr.io/c-mueller/ts-restic-server:latest\n```\n\n```bash\ndocker run -d \\\n  -p 8880:8880 \\\n  -v ./config.yaml:/etc/ts-restic-server/config.yaml:ro \\\n  -v restic-data:/data \\\n  ghcr.io/c-mueller/ts-restic-server:latest \\\n  serve --config /etc/ts-restic-server/config.yaml\n```\n\nOr with Docker Compose — create a directory with `compose.yaml` and `config.yaml`:\n\n```yaml\n# compose.yaml\nservices:\n  ts-restic-server:\n    image: ghcr.io/c-mueller/ts-restic-server:latest\n    ports:\n      - \"8880:8880\"\n    volumes:\n      - ./config.yaml:/etc/ts-restic-server/config.yaml:ro\n      - data:/data\n    command: [\"serve\", \"--config\", \"/etc/ts-restic-server/config.yaml\"]\n    restart: unless-stopped\n\nvolumes:\n  data:\n```\n\n```yaml\n# config.yaml\nlisten: \":8880\"\nlisten_mode: plain\nstorage:\n  backend: filesystem\n  path: /data\n```\n\n```bash\ndocker compose up -d\n```\n\nMulti-arch images (amd64 + arm64) are published to `ghcr.io/c-mueller/ts-restic-server` on every push to master. Tagged releases are available under the corresponding tag name.\n\nSee [docs/docker.md](docs/docker.md) for more details.\n\n### Building from Source\n\nRequires Go 1.26+.\n\n```bash\ngo build -o ts-restic-server .\n```\n\nVersion information can be embedded at build time:\n\n```bash\ngo build -ldflags \"-X github.com/c-mueller/ts-restic-server/internal/buildinfo.Version=1.0.0\" -o ts-restic-server .\n```\n\n## Usage\n\n```bash\n# Start with filesystem backend (default)\n./ts-restic-server serve\n\n# Start with in-memory backend\n./ts-restic-server serve --storage-backend memory\n\n# Start with a config file\n./ts-restic-server serve --config config.yaml\n\n# Start with Tailscale listener\n./ts-restic-server serve --listen-mode tailscale\n\n# Print version and build information\n./ts-restic-server version\n```\n\n### Using with restic\n\n```bash\n# Initialize a repository\nrestic -r rest:http://localhost:8880/ init\n\n# Initialize a repository under a sub-path (multi-repo)\nrestic -r rest:http://localhost:8880/my-host/backups init\n\n# Backup\nrestic -r rest:http://localhost:8880/my-host/backups backup ~/Documents\n\n# With Tailscale\nrestic -r rest:https://my-restic-server.my-tailnet.ts.net/my-host/backups init\n```\n\n## Configuration\n\nConfiguration is loaded with the following priority: **CLI flags \u003e config file \u003e environment variables**.\n\nEnvironment variables use the prefix `RESTIC_` with underscores replacing dots (e.g. `RESTIC_STORAGE_BACKEND=s3`).\n\nSee [`config.example.yaml`](config.example.yaml) for all available options:\n\n```yaml\nlisten: \":8880\"\nlisten_mode: plain       # \"plain\" or \"tailscale\"\nappend_only: false\nlog_level: info\nshutdown_timeout: 30     # graceful shutdown timeout in seconds\n\ntailscale:\n  hostname: restic-server\n  state_dir: ./ts-state\n  auth_key: \"\"\n\nmetrics:\n  enabled: true\n  password: \"\"            # if set, /-/metrics requires Basic Auth (user: prometheus)\n  per_host_enabled: true  # per-identity/repo-path metrics (disable to reduce cardinality)\n  acl_enabled: false      # route /-/metrics through ACL instead of Basic Auth\n\nstats:\n  enabled: false           # per-repo traffic stats (SQLite)\n  db_path: ./stats.db\n\nui:\n  enabled: false           # web UI at /-/ui/\n  auth:\n    username: \"\"\n    password: \"\"\n\nstorage:\n  backend: filesystem     # \"filesystem\", \"s3\", \"webdav\", \"rclone\", \"smb\", \"nfs\", \"memory\"\n  path: ./restic_data\n  max_memory_bytes: 104857600  # 100MB for memory backend\n  s3:\n    bucket: my-bucket\n    prefix: \"\"\n    region: eu-central-1\n    endpoint: \"\"\n    access_key: \"\"\n    secret_key: \"\"\n  webdav:\n    endpoint: \"\"\n    username: \"\"\n    password: \"\"\n    prefix: \"\"\n  rclone:\n    endpoint: \"\"\n    username: \"\"\n    password: \"\"\n  smb:\n    server: \"\"\n    share: \"\"\n    username: \"\"\n    password: \"\"\n    domain: WORKGROUP\n    port: 445\n    base_path: \"\"\n  nfs:\n    server: \"\"\n    export: \"\"\n    base_path: \"\"\n    uid: 65534\n    gid: 65534\n```\n\n### CLI Flags\n\n| Flag | Description |\n|------|-------------|\n| `--config` | Path to config file (default: `./config.yaml`) |\n| `--listen` | Listen address (default: `:8880`) |\n| `--listen-mode` | `plain` or `tailscale` |\n| `--append-only` | Enable append-only mode |\n| `--log-level` | `debug`, `info`, `warn`, `error` |\n| `--storage-backend` | `filesystem`, `s3`, `webdav`, `rclone`, `smb`, `nfs`, `memory` |\n| `--storage-path` | Path for filesystem backend |\n| `--shutdown-timeout` | Graceful shutdown timeout in seconds (default `30`) |\n| `--metrics-password` | Password for `/-/metrics` endpoint (user: `prometheus`) |\n| `--env-lenient` | Allow unresolved `${VAR}` placeholders in config values |\n\n## Storage Backends\n\n### Filesystem\n\nThe default backend. Stores data in the local filesystem with restic's standard directory layout (data/00-ff subdirectories, atomic writes with fsync).\n\n```yaml\nstorage:\n  backend: filesystem\n  path: ./restic_data\n```\n\n### S3-Compatible\n\nWorks with AWS S3, MinIO, Hetzner Object Storage, and other S3-compatible providers. Supports custom endpoints and static credentials. If `access_key` and `secret_key` are left empty, the standard AWS credential chain is used (environment, shared credentials file, IAM role, etc.).\n\n```yaml\nstorage:\n  backend: s3\n  s3:\n    bucket: my-backup-bucket\n    prefix: \"\"                                    # optional key prefix\n    region: eu-central-1\n    endpoint: https://fsn1.your-objectstorage.com # leave empty for AWS\n    access_key: AKIA...\n    secret_key: wJal...\n```\n\n### WebDAV\n\nWorks with any WebDAV-compatible cloud storage: Nextcloud, ownCloud, HiDrive, Box, and others. No rclone intermediary needed.\n\n```yaml\nstorage:\n  backend: webdav\n  webdav:\n    endpoint: https://cloud.example.com/remote.php/dav/files/user\n    username: myuser\n    password: mypassword\n    prefix: backups            # optional subdirectory within the WebDAV server\n```\n\n### SMB/CIFS\n\nPure-Go SMB2/3 client — no OS-level mounting required. Supports NTLM authentication, atomic writes via temp+rename, and automatic reconnection on connection loss.\n\n```yaml\nstorage:\n  backend: smb\n  smb:\n    server: nas.local\n    share: backups\n    username: backup-user\n    password: ${SMB_PASSWORD}  # env var substitution\n    domain: WORKGROUP\n    port: 445\n    base_path: restic          # optional subdirectory within the share\n```\n\n### NFS\n\nPure-Go NFSv3 client using `AUTH_SYS` authentication — no OS-level mounting required. Supports automatic reconnection on connection loss.\n\n```yaml\nstorage:\n  backend: nfs\n  nfs:\n    server: nas.local\n    export: /volume1/backups\n    base_path: restic          # optional subdirectory within the export\n    uid: 1000                  # UID for NFS AUTH_SYS (default: 65534/nobody)\n    gid: 1000                  # GID for NFS AUTH_SYS (default: 65534/nogroup)\n```\n\n### Rclone\n\nProxies all storage operations to a remote restic REST server, such as [`rclone serve restic`](https://rclone.org/commands/rclone_serve_restic/). This enables using any of rclone's 70+ supported cloud providers as storage.\n\n```yaml\nstorage:\n  backend: rclone\n  rclone:\n    endpoint: http://localhost:8080\n    username: \"\"       # optional basic auth\n    password: \"\"\n```\n\n### In-Memory\n\nUseful for testing. All data is lost when the server stops. Enforces a configurable memory cap (default 100MB).\n\n```yaml\nstorage:\n  backend: memory\n  max_memory_bytes: 104857600\n```\n\n## Web UI\n\nThe built-in web UI provides a dashboard with repository overview, per-repo traffic statistics, and lock management. It is served at `/-/ui/` and uses a dark theme (Bootswatch darkly) with all assets embedded in the binary.\n\n```yaml\nui:\n  enabled: true\n  auth:\n    username: admin        # optional Basic Auth\n    password: secret\n```\n\nRequires `stats.enabled: true` for traffic statistics display.\n\n## Tailscale Integration\n\nWhen `listen_mode` is set to `tailscale`, the server uses [tsnet](https://pkg.go.dev/tailscale.com/tsnet) to join your Tailnet and serve over HTTPS with automatic TLS certificates. No port forwarding or manual certificate management required.\n\n```yaml\nlisten_mode: tailscale\ntailscale:\n  hostname: restic-server        # appears as restic-server.my-tailnet.ts.net\n  state_dir: ./ts-state          # persistent Tailscale state\n  auth_key: tskey-auth-...       # optional, for headless auth\n```\n\nThe Tailscale listener always binds to port 443, so restic clients can connect without specifying a port.\n\n## Multi-Repository Support\n\nThe server supports hosting multiple independent repositories under different URL paths. The path prefix is transparently passed to the storage backend:\n\n- **S3**: path prefix becomes part of the S3 key (e.g. `{prefix}/host-a/backups/data/...`)\n- **WebDAV**: path prefix becomes a subdirectory on the WebDAV server\n- **Filesystem/SMB/NFS**: path prefix becomes a subdirectory\n- **Memory**: each path prefix gets its own isolated in-memory store\n\n```bash\nrestic -r rest:http://localhost:8880/host-a/daily init\nrestic -r rest:http://localhost:8880/host-b/daily init\n# These are completely independent repositories\n```\n\n## FAQ\n\n### Can I run this without Tailscale?\n\nYes. Set `listen_mode: plain` (the default) and the server listens on a regular TCP port. However, plain mode serves unencrypted HTTP. For production use without Tailscale, you should place the server behind a reverse proxy (e.g. nginx, Caddy, Traefik) that handles TLS termination. The server itself does not support standalone TLS certificates — it's either Tailscale-managed TLS or plain HTTP.\n\nExample with Caddy:\n\n```text\nrestic.example.com {\n    reverse_proxy localhost:8880\n}\n```\n\n### What storage backends are available?\n\n- **Filesystem** — local disk storage with restic's standard directory layout. Default and simplest option.\n- **S3-compatible** — any S3-compatible object storage. Supports custom endpoints for non-AWS providers.\n- **WebDAV** — any WebDAV-compatible cloud storage (Nextcloud, ownCloud, HiDrive, Box, etc.).\n- **SMB/CIFS** — any SMB2/3 share. Pure-Go client, no OS mount required.\n- **NFS** — any NFSv3 export. Pure-Go client, no OS mount required.\n- **Rclone** — proxies to `rclone serve restic` or any restic REST server. Access 70+ cloud providers via rclone.\n- **In-Memory** — ephemeral storage for testing. Data is lost on restart. Configurable memory cap.\n\n### Which S3 providers have been tested?\n\nSo far only **Hetzner Object Storage** has been tested. AWS S3 and MinIO should work but have not been verified yet. If you successfully use another provider, please open an issue or PR to help expand this list.\n\n### Does it support authentication / access control?\n\nYes. The built-in ACL engine provides fine-grained access control per identity and repository path. In Tailscale mode, identities are resolved via the WhoIs API — giving you access to Tailscale tags (`tag:backup`), user logins (`alice@example.com`), hostnames, and IPs. In plain mode, identities are resolved via rDNS.\n\n```yaml\nacl:\n  default_role: deny\n  verbose_denials: true   # include identity details in 403 responses (default: true)\n  rules:\n    - paths: [\"/\"]\n      identities: [\"tag:backup\"]\n      permission: full-access\n    - paths: [\"/alice\"]\n      identities: [\"alice@example.com\"]\n      permission: full-access\n```\n\nSee [docs/acl.md](docs/acl.md) for full documentation including cascading rules, permissions, trusted proxies, and examples.\n\n### Can I host multiple repositories on one server?\n\nYes. Every URL path prefix creates an independent repository. For example, `/server-a/daily` and `/server-b/weekly` are completely isolated from each other, even though they share the same storage backend (same S3 bucket, same filesystem root, etc.). This works identically to the official restic REST server.\n\n### How does append-only mode work?\n\nWith `append_only: true`, the server rejects all DELETE requests on blobs with HTTP 403, preventing data from being removed. Lock deletion remains allowed so that stale locks can still be cleaned up. This is useful as a safeguard against accidental or malicious data deletion.\n\n## Disclaimer\n\nThis project is not affiliated with or endorsed by the [restic](https://restic.net/) project or [Tailscale Inc.](https://tailscale.com/) Tailscale is a registered trademark of Tailscale Inc.\n\n## License\n\nApache License 2.0 - see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-mueller%2Fts-restic-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc-mueller%2Fts-restic-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-mueller%2Fts-restic-server/lists"}