{"id":51526206,"url":"https://github.com/kithuto/sftpwarden","last_synced_at":"2026-07-08T22:00:13.541Z","repository":{"id":366248319,"uuid":"1274465309","full_name":"kithuto/SFTPWarden","owner":"kithuto","description":"Container-native SFTP management with OpenSSH, declarative users, Docker deployment, and remote synchronization.","archived":false,"fork":false,"pushed_at":"2026-06-30T17:18:42.000Z","size":931,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T18:04:55.452Z","etag":null,"topics":["automation","container","devops","docker","docker-compose","file-transfer","gitops","infraestructure","openssh","platform-engineering","python","self-hosted","sftp","ssh"],"latest_commit_sha":null,"homepage":"http://sftpwarden.espacionacho.com/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kithuto.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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-06-19T14:40:52.000Z","updated_at":"2026-06-30T17:11:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kithuto/SFTPWarden","commit_stats":null,"previous_names":["kithuto/sftpwarden"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/kithuto/SFTPWarden","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kithuto%2FSFTPWarden","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kithuto%2FSFTPWarden/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kithuto%2FSFTPWarden/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kithuto%2FSFTPWarden/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kithuto","download_url":"https://codeload.github.com/kithuto/SFTPWarden/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kithuto%2FSFTPWarden/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35279442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["automation","container","devops","docker","docker-compose","file-transfer","gitops","infraestructure","openssh","platform-engineering","python","self-hosted","sftp","ssh"],"created_at":"2026-07-08T22:00:12.523Z","updated_at":"2026-07-08T22:00:13.478Z","avatar_url":"https://github.com/kithuto.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg\n    src=\"https://raw.githubusercontent.com/kithuto/SFTPWarden/main/docs/_static/logo-sftpwarden.png\"\n    alt=\"SFTPWarden - Container-native SFTP management\"\n    width=\"760\"\n  \u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Container-native SFTP for teams that want a small, auditable OpenSSH runtime with\n  declarative users, predictable Compose/Kubernetes deployment, and a CLI that\n  works the same locally, on remote hosts, and in clusters.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#key-features\"\u003eKey Features\u003c/a\u003e\n  \u0026nbsp;·\u0026nbsp;\n  \u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e\n  \u0026nbsp;·\u0026nbsp;\n  \u003ca href=\"#5-minute-quick-start\"\u003eQuick Start\u003c/a\u003e\n  \u0026nbsp;·\u0026nbsp;\n  \u003ca href=\"#deployment-choices\"\u003eDeployment\u003c/a\u003e\n  \u0026nbsp;·\u0026nbsp;\n  \u003ca href=\"#providers\"\u003eProviders\u003c/a\u003e\n  \u0026nbsp;·\u0026nbsp;\n  \u003ca href=\"#documentation\"\u003eDocs\u003c/a\u003e\n  \u0026nbsp;·\u0026nbsp;\n  \u003ca href=\"#contributing\"\u003eContributing\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nSFTPWarden runs OpenSSH in a container and keeps users, host keys, data, and runtime\nstate outside the image. You manage environments with `sftpwarden`, and the runtime\nkeeps Linux users synchronized from YAML, CSV, SQLite, MySQL, MariaDB,\nPostgreSQL, or MongoDB.\n\n## Table of Contents\n\n- [Key Features](#key-features)\n- [Installation](#installation)\n- [Shell Autocomplete](#shell-autocomplete)\n- [5-Minute Quick Start](#5-minute-quick-start)\n- [Deployment Choices](#deployment-choices)\n- [Project Files](#project-files)\n- [User Management](#user-management)\n- [Providers](#providers)\n- [Operations](#operations)\n- [Backup and Restore](#backup-and-restore)\n- [Security](#security)\n- [Documentation](#documentation)\n- [Roadmap](#roadmap)\n- [Contributing](#contributing)\n\n---\n\n## Key Features\n\n- **Fast adoption for real SFTP needs:** create a local, remote, or Kubernetes\n  SFTP environment with `sftpwarden init`, add users, and deploy without\n  hand-writing OpenSSH container plumbing.\n- **Declarative user sources:** manage accounts from YAML, CSV, SQLite, MySQL,\n  MariaDB, PostgreSQL, or MongoDB, so small teams can start with files and larger\n  systems can use databases.\n- **Safe user isolation:** every SFTP user is forced into OpenSSH `internal-sftp`\n  and isolated under `/data/\u003cusername\u003e` with chroot-oriented defaults.\n- **Container-native operations:** generated Compose files, Kubernetes manifests,\n  Helm values, `sftpwarden deploy`, `plan`, `refresh`, `watch`, `--dry-run`, and\n  `--json` make it practical for local development, CI, and production runbooks.\n- **First-class Kubernetes and Helm support:** generate manifests or Helm values,\n  manage namespaces, PVCs, runtime probes, and declarative YAML/CSV provider\n  syncs while keeping database providers recommended for production clusters.\n- **Context-based workflow:** use Docker-style active contexts for `dev`, `prod`,\n  remote local-sync, and remote-only deployments instead of repeating long flags\n  on every command.\n- **Remote deployment built in:** deploy through SSH, rsync/scp, and Docker\n  Compose, with auto-detected watcher backends for syncing user-provider changes.\n- **Portable operations:** copy users between providers, export/import user\n  snapshots, create backups, restore safely, and run project/runtime healthchecks.\n- **Operationally conservative defaults:** secrets are not baked into images,\n  plaintext provider passwords are rejected, host keys and state are persisted,\n  and user data is never deleted unless explicitly requested.\n\nSFTPWarden is intentionally lightweight. It is not a full identity platform, a file\nsharing suite, or VM-grade isolation. It gives you a conservative OpenSSH-based SFTP\nruntime that is easy to understand, deploy, and operate.\n\n## Installation\n\nInstall the CLI:\n\n```bash\npip install sftpwarden\nsftpwarden --version\n```\n\nFor source checkout usage:\n\n```bash\ngit clone https://github.com/kithuto/sftpwarden.git\ncd sftpwarden\npython -m venv .venv\nsource .venv/bin/activate\npython -m pip install -e \".[mysql,postgres,mongodb]\"\nsftpwarden --version\n```\n\nOptional extras:\n\n| Need | Install |\n| --- | --- |\n| SQLite provider | Included, no extra |\n| MySQL provider | `pip install \"sftpwarden[mysql]\"` |\n| MariaDB provider | `pip install \"sftpwarden[mariadb]\"` |\n| PostgreSQL provider | `pip install \"sftpwarden[postgres]\"` |\n| MongoDB provider | `pip install \"sftpwarden[mongodb]\"` |\n| Documentation/development | `pip install -e \".[dev,docs,mysql,postgres,mongodb]\"` |\n\n`mariadb` is an alias of the MySQL extra. Installing either\n`sftpwarden[mysql]` or `sftpwarden[mariadb]` enables both MySQL and MariaDB\nproviders because they share PyMySQL.\n\nFor runtime and watcher image development, build the images locally:\n\n```bash\ndocker build -t sftpwarden:local -f docker/runtime/Dockerfile .\ndocker build -t sftpwarden-watcher:local -f docker/watcher/Dockerfile .\n```\n\n## Shell Autocomplete\n\nSFTPWarden can install shell autocomplete through the Typer/Click helpers included\nin the CLI:\n\n```bash\nsftpwarden --install-completion\n```\n\nOpen a new terminal, then use `\u003cTAB\u003e` to complete commands and options:\n\n```bash\nsftpwarden con\u003cTAB\u003e\nsftpwarden user create --\u003cTAB\u003e\n```\n\nTo inspect the generated completion script without installing it:\n\n```bash\nsftpwarden --show-completion\n```\n\n## 5-Minute Quick Start\n\nCreate a local SFTP project:\n\n```bash\nsftpwarden config default-provider yaml\nmkdir -p ~/sftpwarden-dev\ncd ~/sftpwarden-dev\nsftpwarden init dev --user-schema 1 --yes\nsftpwarden validate\nsftpwarden deploy\n```\n\nThe quick start uses **user schema v1**, the simplest provider format:\nusers store anonymous `public_keys` directly on each user. New projects default\nto schema v2 when `--user-schema` is omitted; v2 adds named keys, metadata,\nexpiry, disable/enable, rotation, and migrations.\n\nAdd a user:\n\n```bash\nsftpwarden user create alice \\\n  --password \"correct horse battery staple\" \\\n  --comment \"Main upload account\"\n```\n\nConnect with any SFTP client:\n\n```bash\nsftp -P 2222 alice@localhost\n```\n\nPreview and apply runtime changes:\n\n```bash\nsftpwarden plan\nsftpwarden refresh\n```\n\n`sftpwarden init` makes the created context active, so day-to-day commands do not\nneed `--context`. This follows the same friendly idea people know from Docker:\nwork in a project directory, keep an active context, and pass an explicit context\nonly when you need to override it. To switch later, run `sftpwarden context use dev`.\n\nRead or update project settings without opening YAML:\n\n```bash\nsftpwarden config project.name dev2\nsftpwarden config server.port 2200\nsftpwarden context root ~/sftpwarden-dev2 --yes\n```\n\n## Deployment Choices\n\nPick the model that matches how your team works.\n\n| Model | Best for | Source of truth | Watcher |\n| --- | --- | --- | --- |\n| Local | Development, demos, single-host testing | Local project folder | No |\n| Remote local-sync | Production managed from a workstation or CI runner | Local project folder synced to remote host | Yes |\n| Remote-only | Existing remote deployments managed in-place | Remote project folder | No |\n| Kubernetes | Platform/SRE teams using `kubectl` or Helm | Project config plus generated manifests or Helm values | No |\n\nCompose is the default when `--deploy` is omitted. Choose the deployment target\nexplicitly during `init` when you already know where the project will run:\n\n```bash\nsftpwarden init dev --deploy compose --yes\nsftpwarden init prod --deploy kube --yes\nsftpwarden init prod --deploy helm --yes\n```\n\nIf a project was initialized with the default and you want to change it before\ndeploying, update the project config and preview the generated deployment:\n\n```bash\nsftpwarden config deploy.target kubernetes\nsftpwarden config kubernetes.mode helm\nsftpwarden deploy --dry-run\n```\n\nLocal:\n\n```bash\nmkdir -p ~/sftpwarden-dev\ncd ~/sftpwarden-dev\nsftpwarden init dev --yes\nsftpwarden deploy\n```\n\nRemote local-sync:\n\n```bash\nmkdir -p ~/sftpwarden-prod\ncd ~/sftpwarden-prod\nsftpwarden init prod --remote deploy@sftp-prod.example.com:/opt/sftpwarden \\\n  --critical\n\nsftpwarden deploy --dry-run\nsftpwarden deploy --yes\n```\n\nRemote-only:\n\n```bash\nsftpwarden init archive --remote deploy@sftp-archive.example.com:/opt/sftpwarden \\\n  --remote-only \\\n  --critical\n\nsftpwarden refresh --dry-run\n```\n\nKubernetes manifests:\n\n```bash\nsftpwarden init prod --deploy kube --yes\nsftpwarden kube render\nsftpwarden deploy --dry-run\n```\n\nHelm:\n\n```bash\nsftpwarden init prod --deploy helm --yes\nsftpwarden helm values --write\nsftpwarden helm template\nsftpwarden deploy --dry-run\n```\n\nDuring Kubernetes or Helm init, SFTPWarden checks the namespace. If it does not\nexist, interactive init asks whether to create it; `--yes` creates it\nautomatically. The default namespace is `sftpwarden`; use `--namespace \u003cname\u003e`\nfor an existing or custom namespace, or `--no-create-namespace` to require it to\nexist already.\n\nFor Kubernetes projects that use YAML or CSV providers, the local provider file\nis declarative: `sftpwarden deploy`, `sftpwarden kube apply`, and\n`sftpwarden helm upgrade` render its current contents and copy them into the\nprovider PVC during the runtime rollout. `refresh` reloads users already visible\ninside the runtime; it does not copy local YAML/CSV files into a cluster by\nitself. Treat Kubernetes YAML/CSV provider files as deployment material because\nthe rendered manifests or Helm values include their user entries. For production\nKubernetes, prefer database providers when user state must change outside deploy\ncycles or when provider data should not be carried in manifests.\n\nKubernetes and Helm projects reserve `10Gi` for SFTP user uploads by default.\nIncrease that PVC before deploying with:\n\n```bash\nsftpwarden config kubernetes.data_storage_size 50Gi\nsftpwarden deploy --dry-run\nsftpwarden deploy --yes\n```\n\nCompose healthcheck timing and Kubernetes probe timing are configurable too:\nuse `healthcheck.*` for Compose and `kubernetes.*_probe.*` for generated\nmanifests or Helm values.\n\nSource checkouts use the local chart. Python package installations use the\npublished GHCR OCI chart with the same version as the installed CLI.\n\nUse `sftpwarden context add` when a SFTPWarden project already exists and you only\nwant to register it on this machine:\n\n```bash\nsftpwarden context add prod deploy@sftp-prod.example.com:/opt/sftpwarden --critical\nsftpwarden context use prod\n```\n\nProduction-like names such as `prod`, `production`, `prd`, `live`, and `main`\nrequire confirmation unless marked with `--critical` or accepted with `--yes`.\n\n## Project Files\n\nBy default, `sftpwarden init` creates a Compose-backed project directory:\n\n```text\nsftpwarden.yaml\nusers.yaml              # or users.csv / users.sqlite\ndocker-compose.yml\ndata/\nstate/\nhost_keys/\n```\n\nKubernetes-targeted projects use the same `sftpwarden.yaml`, plus generated\n`kubernetes.yml` or `values.yaml` when you render/apply manifests or Helm values;\nthey do not require `docker-compose.yml`.\n\nThe container always listens on port `22` internally. Configure the host-facing\nport with `server.port` in `sftpwarden.yaml`.\n\nRuntime state is stored in `/var/lib/sftpwarden/state.json` inside the container\nand should be backed by the `state/` volume. Host keys are stored in `host_keys/`\nto keep SSH fingerprints stable across restarts.\n\n## User Management\n\nList and inspect users:\n\n```bash\nsftpwarden users\nsftpwarden user show alice\n```\n\nAdd users:\n\n```bash\nsftpwarden user create alice --password \"correct horse battery staple\"\nsftpwarden user create bob --password-hash '$6$rounds=500000$...'\nsftpwarden user create carol --public-key \"ssh-ed25519 AAAA...\"\n```\n\nUpdate users:\n\n```bash\nsftpwarden user update alice --upload-dir inbound\nsftpwarden user update alice --uid 12001 --gid 12001\nsftpwarden user update alice --comment \"Finance inbox\"\nsftpwarden user disable alice\nsftpwarden user enable alice\n```\n\nRemoving a user disables access but does not delete user data:\n\n```bash\nsftpwarden user remove alice --yes\n```\n\nTo permanently remove the user's data directory too:\n\n```bash\nsftpwarden user remove alice --delete-files --yes\n```\n\nUpdating only `comment` does not refresh the runtime because comments are metadata.\n\n### User Schemas and Named Keys\n\nSFTPWarden supports two user schemas:\n\n- **Schema v1** keeps simple `public_keys` on each user. It is supported for\n  small setups and quick starts.\n- **Schema v2** stores named `keys` with fingerprint, comment, disabled state,\n  timestamps, expiry, source, and metadata. It is the default for new projects.\n\nChoose explicitly during init:\n\n```bash\nsftpwarden init demo --user-schema 1 --yes\nsftpwarden init prod --user-schema 2 --yes\n```\n\nNamed key lifecycle commands:\n\n```bash\nsftpwarden user key list alice\nsftpwarden user key show alice prod-ci\nsftpwarden user key add alice prod-ci --public-key ./prod-ci.pub\nsftpwarden user key rotate alice prod-ci --public-key ./prod-ci-new.pub\nsftpwarden user key expire alice prod-ci --at 2027-01-01\nsftpwarden user key disable alice prod-ci\nsftpwarden user key enable alice prod-ci\nsftpwarden user key rename alice prod-ci ci-prod\nsftpwarden user key remove alice ci-prod --yes\nsftpwarden user key import alice --from-dir ./keys\n```\n\nSchema v1 can still list, show, add, and remove anonymous keys using\ndeterministic names/fingerprints. Advanced key operations prompt to migrate that\nprovider to schema v2; non-interactive use requires `--yes`.\n\nInspect or migrate provider data explicitly:\n\n```bash\nsftpwarden provider schema show\nsftpwarden provider keys migrate --dry-run\nsftpwarden provider schema migrate --to 2 --backup --yes\n```\n\n## Providers\n\n| Provider | Runtime reads | CLI mutations | Good fit |\n| --- | ---: | ---: | --- |\n| YAML | Yes | Yes | Quick start, GitOps-style small deployments |\n| CSV | Yes | Yes | Spreadsheet-friendly user handoff |\n| SQLite | Yes | Yes | Single-host/self-hosted deployments without an external database |\n| MySQL | Yes | Yes | Existing application databases |\n| MariaDB | Yes | Yes | MySQL-compatible MariaDB deployments |\n| PostgreSQL | Yes | Yes | Existing platform or product databases |\n| MongoDB | Yes | Yes | Existing document databases |\n\nFor production Kubernetes environments, prefer PostgreSQL, MariaDB/MySQL, or\nMongoDB. The runtime reads those providers directly, so external provider changes\ncan be picked up by the runtime sync loop or an explicit `sftpwarden refresh`.\nYAML/CSV remain useful for GitOps-style Kubernetes deployments where deploy is\nthe synchronization point. If you use YAML/CSV in Kubernetes, keep the provider\nfile in the same review and secret-handling process as the generated manifests.\n\nSQL providers read from `sftp_users` by default. Schema v1 uses only the users\ntable:\n\n```text\nusername, public_keys, password_hash, uid, gid, upload_dir, comment, disabled\n```\n\nSchema v2 keeps the users table and adds `sftp_user_keys` for named key rows.\nCSV v2 uses a `keys` JSON column; MongoDB v2 embeds `keys` in each document.\n\nSee `examples/mysql/schema.sql`, `examples/mariadb/schema.sql`, and\n`examples/postgres/schema.sql` for schema v2 SQL tables.\n\nSQLite is built in:\n\n```bash\nsftpwarden init dev --provider sqlite --yes\n```\n\nSQLite is a good lightweight option for one host and one writer. Avoid it for NFS,\nhigh-concurrency, or multi-writer deployments.\n\nDuring `init`, SFTPWarden checks whether external provider storage exists. For\nMySQL, MariaDB, and PostgreSQL that means the configured SQL table. For MongoDB\nthat means the configured collection and username index. If storage is missing,\ninteractive init asks whether to create it or abort so you can create it manually:\n\n```bash\nsftpwarden init prod \\\n  --provider postgresql \\\n  --dsn 'postgresql://sftpwarden:change-me@db.example.com:5432/sftpwarden' \\\n  --create-table\n```\n\nMariaDB uses the same compatible implementation as MySQL:\n\n```bash\nsftpwarden init prod \\\n  --provider mariadb \\\n  --dsn 'mariadb://sftpwarden:change-me@db.example.com:3306/sftpwarden' \\\n  --create-table\n```\n\nMongoDB stores one document per user with `_id = username`:\n\n```bash\nsftpwarden init prod \\\n  --provider mongodb \\\n  --dsn 'mongodb://mongo.example.com:27017/sftpwarden' \\\n  --collection sftp_users\n```\n\n`--dsn` uses the standard database URL/DSN convention:\n\n```text\npostgresql://user:password@host:5432/database\nmysql://user:password@host:3306/database\nmariadb://user:password@host:3306/database\nmongodb://user:password@host:27017/database\n```\n\nFor real environments, prefer an environment variable so the secret is not typed\ndirectly in shell history or committed in project files:\n\n```bash\nexport SFTPWARDEN_POSTGRES_DSN='postgresql://sftpwarden:change-me@db.example.com:5432/sftpwarden'\n\nsftpwarden init prod \\\n  --provider postgresql \\\n  --dsn '${SFTPWARDEN_POSTGRES_DSN}' \\\n  --create-table\n```\n\nIf you run interactive `init` with MySQL, MariaDB, or PostgreSQL and omit\n`--dsn`, SFTPWarden asks for host, port, database, username, and password, then\nwrites the equivalent DSN for you. For MongoDB, interactive init asks for the\nMongoDB DSN directly.\n\n## Operations\n\nCommon operational commands:\n\n```bash\nsftpwarden doctor\nsftpwarden validate --config sftpwarden.yaml\nsftpwarden compose --write\nsftpwarden deploy --dry-run\nsftpwarden deploy --json --dry-run\nsftpwarden kube status --json\nsftpwarden helm lint\nsftpwarden plan --json\nsftpwarden refresh --dry-run\nsftpwarden watcher status --json\nsftpwarden health --json\nsftpwarden backup --output sftpwarden-dev.tar.gz --yes\nsftpwarden provider export --format json \u003e users.json\n```\n\n`watch` and `refresh` are different on purpose:\n\n- `sftpwarden watch` syncs YAML/CSV/SQLite user provider files for remote\n  `local-sync` contexts.\n- `sftpwarden refresh` tells a running runtime to reload users immediately.\n- Configuration, Docker Compose, Kubernetes, and Helm changes require\n  `sftpwarden deploy`; Kubernetes YAML/CSV provider files are also copied to the\n  provider PVC by deploy/apply/upgrade.\n- `sftpwarden.yaml` is desired state. Changes made with `sftpwarden config` and\n  manual edits are applied by the next deploy/apply/upgrade step.\n- `sftpwarden health` validates config, provider readability, Compose drift, and\n  runtime health where available.\n- `sftpwarden backup` stores config, a provider user snapshot, host keys, and\n  runtime state. It excludes `data/` unless `--include-data` is explicitly used.\n- `sftpwarden provider copy` moves users between contexts/providers with explicit\n  `--merge` or `--replace` semantics.\n\n`sftpwarden deploy` uses the configured deployment target. Compose remains the\ndefault. Kubernetes manifest mode uses `kubectl`, and Helm mode uses `helm`.\nMissing tools are reported with actionable messages.\n\nWatcher installs default to `auto`. SFTPWarden detects the host scheduler and\nuses Windows Task Scheduler, macOS launchd, or Linux systemd, OpenRC, runit, or\nsupervisord when available. You can force a backend with `--watcher systemd`,\n`--watcher openrc`, `--watcher runit`, `--watcher supervisord`,\n`--watcher launchd`, `--watcher windows-task`, or `--watcher docker`.\n\nNative watcher modes use the host's normal SSH identity, agent, `~/.ssh/config`,\nknown hosts, bastions, and `ProxyJump` settings. Docker watcher mode is stricter\nand requires explicit dedicated deployment keys. It mounts watched project\nfolders read-only and copies mounted keys inside the container with private\npermissions before syncing. Source checkouts use `sftpwarden-watcher:local`;\nPython package installations use\n`ghcr.io/kithuto/sftpwarden-watcher:\u003cinstalled-version\u003e` unless `--image` is set.\n\n## Backup and Restore\n\nBack up the active context before upgrades, provider migrations, or risky\nconfiguration changes:\n\n```bash\nsftpwarden backup --output sftpwarden-prod.tar.gz --yes\n```\n\nThe default backup includes `sftpwarden.yaml`, generated deployment files,\navailable file-backed provider data, `provider/users.json` with the current\nusers read from the provider, host keys, and runtime state. That user snapshot is\nalso created for SQL and MongoDB providers when the CLI can reach the configured\ndatabase. The backup does not include uploaded SFTP user files under `data/`\nunless you ask for that explicitly:\n\n```bash\nsftpwarden backup --include-data --output sftpwarden-prod-full.tar.gz\n```\n\nPreview backup contents in automation with:\n\n```bash\nsftpwarden backup --dry-run --json\n```\n\nRestore into the active context with:\n\n```bash\nsftpwarden restore sftpwarden-prod.tar.gz --yes\n```\n\nUse `--include-data` only when the archive was created with user data and you\nintend to overwrite the current `data/` tree:\n\n```bash\nsftpwarden restore sftpwarden-prod-full.tar.gz --include-data --yes\n```\n\nRestore creates a safety backup before overwriting files. After restoring, review\nand apply the deployment so the running runtime matches the restored project:\n\n```bash\nsftpwarden deploy --dry-run\nsftpwarden deploy --yes\n```\n\nBackup archives can contain DSNs, provider snapshots, host keys, and uploaded\nuser files when `--include-data` is used. Store them like infrastructure secrets.\n\n## Security\n\nSFTPWarden follows conservative defaults:\n\n- users and secrets are not baked into images;\n- plaintext passwords are rejected in provider data;\n- `sftpwarden user create --password` stores only a system password hash;\n- SFTP users are forced into `internal-sftp`;\n- root login, empty passwords, forwarding, tunneling, X11, and user environments\n  are disabled;\n- user data is not deleted automatically;\n- `sftpwarden user remove --delete-files` is explicit and irreversible;\n- `.env`, `data/`, `state/`, `host_keys/`, Git metadata, and Python caches are not\n  watched or synced.\n\nKey-only deployment:\n\n```yaml\nauth:\n  allow_public_key: true\n  allow_password: false\n  recommended: public_key\n```\n\nRead the [security guide](docs/security.md) before exposing a deployment to a\npublic or customer-facing network.\n\n## Documentation\n\nThe README is the adoption path. Detailed guides live in:\n\n- [Getting Started](docs/getting-started.md)\n- [Configuration](docs/configuration.md)\n- [Providers](docs/providers.md)\n- [Named Keys](docs/named-keys.md)\n- [Operations](docs/operations.md)\n- [Security](docs/security.md)\n- [CLI Reference](docs/cli-reference.md)\n- [Contributing, development, and testing](docs/contributing.md)\n\nThe Sphinx documentation is built from `docs/` and published to GitHub Pages.\n\nBuild it locally:\n\n```bash\npython -m pip install -e \".[docs]\"\nsphinx-build -b html docs docs/_build/html\n```\n\n## Roadmap\n\nSee the [changelog](https://github.com/kithuto/sftpwarden/blob/main/CHANGELOG.md)\nfor released versions and the longer future roadmap.\n\n### v1.4 - Audit and Transfer Visibility\n\n- Add audit logging for CLI and runtime operations.\n- Add commands for listing, tailing, filtering, and exporting audit events.\n- Add transfer visibility and richer runtime status.\n\n### v1.5 - Security Hardening\n\n- Add production-oriented security check profiles and strict mode.\n- Add secret-file support for DSNs and sensitive provider settings.\n- Add assisted host key rotation workflows.\n\n## Contributing\n\nContributions are welcome: bug reports, docs fixes, examples, tests, provider work,\nand operational feedback are all useful.\n\nContribution workflow:\n\n1. Fork the repository.\n2. Create your own branch from `dev`.\n3. Develop and validate your change in that branch.\n4. Open a Pull Request from your branch to `dev`.\n\nNormal contribution PRs should target `dev`, not `main`. The maintainer promotes\naccepted changes from `dev` to `main` for production and release work.\n\nStart here:\n\n- [CONTRIBUTING.md](https://github.com/kithuto/sftpwarden/blob/dev/CONTRIBUTING.md)\n  for the GitHub workflow.\n- [docs/contributing.md](docs/contributing.md) for install, development, testing,\n  docs, and release checks.\n- [SECURITY.md](https://github.com/kithuto/sftpwarden/blob/dev/SECURITY.md) for\n  responsible vulnerability reporting.\n- [CODE_OF_CONDUCT.md](https://github.com/kithuto/sftpwarden/blob/dev/CODE_OF_CONDUCT.md)\n  for participation expectations.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkithuto%2Fsftpwarden","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkithuto%2Fsftpwarden","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkithuto%2Fsftpwarden/lists"}