{"id":20425300,"url":"https://github.com/catseye/ellsync","last_synced_at":"2026-05-10T15:46:56.582Z","repository":{"id":51062556,"uuid":"152069037","full_name":"catseye/ellsync","owner":"catseye","description":"MIRROR of https://codeberg.org/catseye/ellsync : An opinionated poka-yoke for rsync","archived":false,"fork":false,"pushed_at":"2023-10-27T08:33:46.000Z","size":78,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-15T15:11:57.742Z","etag":null,"topics":["poka-yoke","rsync-wrapper"],"latest_commit_sha":null,"homepage":"http://catseye.tc/node/ellsync","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/catseye.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2018-10-08T11:30:33.000Z","updated_at":"2023-10-26T20:44:25.000Z","dependencies_parsed_at":"2025-01-15T15:08:52.729Z","dependency_job_id":"b57f042c-1165-41d9-a17c-0f0cd32e0c14","html_url":"https://github.com/catseye/ellsync","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2Fellsync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2Fellsync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2Fellsync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/catseye%2Fellsync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/catseye","download_url":"https://codeload.github.com/catseye/ellsync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241966977,"owners_count":20050324,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["poka-yoke","rsync-wrapper"],"created_at":"2024-11-15T07:12:49.179Z","updated_at":"2026-05-10T15:46:51.545Z","avatar_url":"https://github.com/catseye.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"`ellsync`\n=========\n\n_Version 0.5_\n| _Entry_ [@ catseye.tc](https://catseye.tc/node/ellsync)\n| _See also:_ [yastasoti](https://codeberg.org/catseye/yastasoti#yastasoti)\n∘ [tagfarm](https://codeberg.org/catseye/tagfarm#tagfarm)\n∘ [shelf](https://codeberg.org/catseye/shelf#shelf)\n\n- - - -\n\n\u003cimg align=\"right\" src=\"images/ellsync-logo.png?raw=true\" /\u003e\n\n**`ellsync`** is an opinionated poka-yoke for [`rsync`][].\n\n*   [opinionated][]: it was designed for a particular use case for `rsync`\n    (offline backups).\n*   [poka-yoke][]: it exposes a restricted interface to `rsync`, which\n    prevents using it in dangerous ways.\n\nBecause the restricted interface that `ellsync` presents can be accessed\nby shorthand form, it also happens to provide some convenience over\nusing `rsync` directly — but its real purpose is to increase safety.\n(I've been burned more than once when I've made a mistake using `rsync`.)\n\nQuick start\n-----------\n\nMake sure you have Python (2.7 or 3.x) installed, clone this repository,\nand put its `bin` directory on your executable search path.  You will\nthen be able to run `ellsync` from your terminal.\n\nUsage guide\n-----------\n\n### Backup router\n\n`ellsync`'s operation is based on a *backup router* which is a JSON file\nthat looks like this:\n\n    {\n        \"art\": {\n            \"from\": \"/media/user/External1/art/\",\n            \"to\": \"/home/user/art/\"\n        }\n    }\n\nIn this, `art` is the name of a _backup stream_, in which files in\n`/media/user/External1/art/` (called the *canonical*) are periodically\nsynced to `/home/user/art/` (called the *cache*).\n\nThe idea is that all changes to the contents of the canonical directory\nare bona fide changes, but any change to the contents of the cache can be\ndiscarded.\n\n### `sync` command\n\nWith the above router saved as `router.json` we can then say\n\n    ellsync router.json sync art\n\nand this will in effect run\n\n    rsync --archive --verbose --delete --dry-run /home/user/art/ /media/user/External1/art/\n\nNote that by default it only runs a `--dry-run`.  It's a good practice to\ndo a dry run first, to see what will be changed.  As a bonus, the files\ninvolved will often remain in the filesystem cache, meaning a subsequent\nactual run will go quite quickly.  To do that actual run, use `--apply`:\n\n    ellsync router.json sync art --apply\n\nNote that, since the contents of the canonical and the cache normally\nhave the same directory structure, `ellsync` allows specifying that\nonly a subdirectory of a stream is to be synced:\n\n    ellsync router.json sync art:painting/oil/ --apply\n\nWhile `rsync` is sensitive about whether a directory name ends in a slash or\nnot,  `ellsync` detects when a trailing slash is missing and adds it.  Thus\n\n    ellsync router.json sync art:painting/oil --apply\n\nwill work as well as the above.  (But note that the directories specified\nin the router *do* need to have the trailing slashes.)\n\n#### `--thorough` option\n\nBy default, `rsync` does not attempt to sync the contents of an existing file\nif the destination file has a same-or-newer timestamp as the source file.\n\nHowever, this means that if the destination file has become corrupted (a not-\nuncommon occurrence on inexpensive removable media), `rsync` will not attempt\nto repair the corruption, as the timestamp of the corrupted file did not change.\n\nTo compensate for this, `ellsync` provides the `--thorough` option:\n\n    ellsync router.json sync art:painting/oil --thorough\n\nThis invokes `rsync` with the `--checksum` flag, to force it to do a thorough\ncheck of the files.  See `man rsync` for more details.\n\n### `list` command\n\nEither the canonical or the cache (or both) may be offline storage (removable\nmedia), therefore neither directory is assumed to exist (it might not exist\nif the volume is not mounted.)  If either of the directories does not exist,\n`ellsync` will refuse to use this backup stream.  Based on this, there is a\nsubcommand to list which streams are, at the moment, backupable:\n\n    ellsync router.json list\n\n### `rename` command\n\nSometimes you want to rename a subdirectory somewhere under the canonical of\none of the streams.  It's completely fine to do this, but the next time it is synced,\n`rsync` will treat it, in the cache, as the old subdirectory being deleted and\na new subdirectory being created.  If there are a large number of files in the\nsubdirectory, this delete-and-create sync can take a long time.  It's also not\nobvious from `rsync`'s logging output that everything being deleted is also being\ncreated somewhere else.\n\nTo ease this situation, `ellsync` has a `rename` command that works like so:\n\n    ellsync router.json rename art: sclupture sculpture\n\nThis renames the `/media/user/External1/art/sclupture` directory to\n`/media/user/External1/art/sculpture` and *also* renames the `/home/user/art/sclupture`\ndirectory to `/home/user/art/sculpture`.  If the contents of the source and\ndestination directories were in sync before this rename occurred, they will\ncontinue to be in sync after the rename happens.\n\nHints and Tips\n--------------\n\nYou might have a router you use almost always, in which case you might\nwant to establish an alias like\n\n    alias myellsync ellsync $HOME/my-standard-router.json\n\n(or whatever.)\n\nTODO\n----\n\n*   If `rsync` encounters an error, it will abort, having only partially completed.\n    In particular, if it encounters a directory which it cannot read, because it\n    is for example owned by another user and not world-readable, it will abort.\n    `ellsync` does not currently detect this properly.  It should be made to handle\n    it gracefully, if possible.\n*   (Aspirational) Ability to convert the backup router to a `dot` file (`graphviz`)\n    so that the relationships between the streams can be easily visualized.\n\nHistory\n-------\n\n### 0.5\n\nThe output of the `list` subcommand is now sorted by stream name.\n\nThe `sync` subcommand now supports multiple streams.  Each stream will be synced\nin the order they are given on the command line.  OS-level `sync` will only be\nperformed once, at the very end.\n\nA bash tab-completion script is included in the `script` directory.  It enables\ntab-completion of both subcommand names, and stream names in the `sync` subcommand.\n\nInternally, shell expansion is no longer used when executing system commands, and\nseveral new tests have been added to the test suite.\n\n### 0.4\n\nThe `:` in a backup stream identifier is optional, when no subdirectory is being\nspecified.\n\n### 0.3\n\nArgument parser was refactored to use subparsers, improving usage info and usage\nerror output.\n\nRemoved `syncdirs` as it introduces some redundancy and I never use it.\n\nAfter `sync` is performed, the system `sync` command is run, to ensure all buffers\nare flushed to devices before the `ellsync` tool actually exits.\n\nThe `--thorough` options now invokes `rsync` with `--checksum` flag, to cause it\nto thoroughly check if files differ, even if their datestamps have not changed.\n\nAdded `--stream-name-only` option to `list` command.\n\n### 0.2\n\nEvery `ellsync` functionality has an explicit subcommand (`list` and `sync` to\nstart.)\n\n`sync` was split into `sync` (takes a stream) and `syncdirs` (takes to and\nfrom dirs).\n\nAdded `rename` command.\n\n### 0.1\n\nInitial release.\n\n[`rsync`]: https://rsync.samba.org/\n[opinionated]: https://softwareengineering.stackexchange.com/questions/12182/what-does-opinionated-software-really-mean\n[poka-yoke]: https://en.wikipedia.org/wiki/Poka-yoke\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fellsync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcatseye%2Fellsync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcatseye%2Fellsync/lists"}