{"id":50820744,"url":"https://github.com/laserattack/chains","last_synced_at":"2026-06-13T13:34:53.287Z","repository":{"id":342864896,"uuid":"1175207024","full_name":"laserattack/chains","owner":"laserattack","description":"incremental backups without any bullshit","archived":false,"fork":false,"pushed_at":"2026-04-10T16:09:54.000Z","size":445,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-13T13:34:45.082Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"wtfpl","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/laserattack.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-03-07T11:38:09.000Z","updated_at":"2026-04-10T16:10:00.000Z","dependencies_parsed_at":"2026-03-08T00:01:43.502Z","dependency_job_id":"bdd589df-2626-4caa-8184-78e32a6c4cf0","html_url":"https://github.com/laserattack/chains","commit_stats":null,"previous_names":["laserattack/ark","laserattack/chains"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/laserattack/chains","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laserattack%2Fchains","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laserattack%2Fchains/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laserattack%2Fchains/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laserattack%2Fchains/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/laserattack","download_url":"https://codeload.github.com/laserattack/chains/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/laserattack%2Fchains/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34286975,"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-06-13T02:00:06.617Z","response_time":62,"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":[],"created_at":"2026-06-13T13:34:52.675Z","updated_at":"2026-06-13T13:34:53.282Z","avatar_url":"https://github.com/laserattack.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](pic.jpg)\n\n# Wtf??\n\nSimple script for backups\n\nWhy another backup tool? Because most existing solutions are way too complicated. They offer a ton of shit like support for dozens of protocols, encryption, rotation policies, and worst of all - they store your data in their own format. Good luck restoring anything if you don't have the binary on hand\n\nI wanted something **dumb and simple**. A tool that just does incremental backups and nothing else. No dependencies, no magic. Just plain `tar.gz` files that you can access on any Linux system with nothing but `tar`\n\nThis entire script is essentially a convenient interface to `tar`\n\n# Usage\n\n**!!!! IF YOU'RE BRAVE ENOUGH TO USE THIS, READ THE NOTES FIRST !!!!**\n\n```\nchains - incremental backups without any bullshit\n\nUsage: chains [OPTION]...\n\nOptions:\n  -h, --help                 This help\n\n  -m, --main-directory DIR   Main program directory (default: ~/.chains)\n\n  -i, --incremental          Make incremental backup (to the last chain)\n\n  -f, --full                 Make full backup (new chain)\n\n  -r, --recover [TIMESTAMP]  Restore the chain to the specified timestamp\n                             TIMESTAMP: YYMMDDTHHMMSS | latest\n                             If TIMESTAMP omitted, recovers latest state\n\n  -w, --verify [RANGE]       Verify integrity of chain\n                             TIMESTAMP: YYMMDDTHHMMSS | latest\n                             RANGE: TIMESTAMP | TIMESTAMP:TIMESTAMP\n                             Examples: 250309T143045\n                                       latest\n                                       250309T143045:250310T102030\n                                       250309T143045:latest\n                                       latest:latest\n                             If RANGE omitted, verifies the chain\n                             up to the latest state\n\n  -C, --directory DIR        Change to DIR before any operation\n\n  -p, --print                Print all backup chains structure\n\n  -v, --verbose              Verbosely list files processed\n\n  -y, --yes                  Automatically answer yes to all prompts\n\n  -n, --no                   Automatically answer no to all prompts\n                             Overrides --yes\n\n      --no-check-device      Ignore device number changes when\n                             determining changed files\n\nNotes (!!!!!!! PLS READ THIS !!!!!!!):\n  1. GNU TAR IS REQUIRED\n\n  2. NEVER RUN MULTIPLE chains PROCESSES WITH THE SAME MAIN DIRECTORY\n\n  3. Interrupting backup creation may leave incomplete archives\n\n  4. Always verify your backups can be restored\n\n  5. EXCLUDE BACKUP DIR FROM BACKUP\n\n  6. CHECK AVAILABLE SPACE\n\n  7. Run with sudo if backing up system files\n\n  8. IMPORTANT: After modifying include_paths.txt, create a new full backup (-f)\n     The snar metadata file only tracks files that existed when it was created\n     New paths won't be included in incremental backups without a fresh chain\n     THIS IS HOW TAR WORKS. I DON'T LIKE IT EITHER!\n```\n\n# Examples\n\nThis script was written for backing up moderately important data to an external drive. Here's how I typically use it:\n\n**Weekly full backup:**\n\n```\nchains -fm $KINGSTON/chains/\n```\n\n**Daily incremental backups (run as often as needed):**\n\n```\nchains -im $KINGSTON/chains/\n```\n\n**Verify integrity of backup chain (check if all archives in chain/subchain can be unpacked):**\n\n```\nchains -wm $KINGSTON/chains/\n```\n\n**Restore latest backup:**\n\n```\nchains -rm $KINGSTON/chains/ -C ~/projects/temp/\n```\n\n**Restore concrete backup using timestamp:**\n\n```\nchains -r 260309T205505 -m $KINGSTON/chains/ -C ~/projects/temp/\n```\n\n**Display all existing backup chains:**\n\n```\nchains -pm $KINGSTON/chains/\n```\n\n\u003e Note: $KINGSTON is a variable defined in my `.bashrc` pointing to the mount point of my external drive\n\n# Installation\n\nJust download somewhere\n\n```\nwget https://raw.githubusercontent.com/laserattack/chains/refs/heads/master/chains\n```\n\nMake it executable\n\n```\nchmod +x chains\n```\n\nAnd use\n\n```\n./chains\n```\n\nOptionally, you can add the script to your PATH\n\n# How it works\n\nWhen you run **chains**, it automatically checks and creates (if missing) the required directory structure in your main directory (default: `~/.chains` or custom path with `-m` flag):\n\n```\nMAIN_DIR/\n├── include_paths.txt\n├── exclude_patterns.txt\n└── chains/\n\n* include_paths.txt - absolute paths to backup (one per line)\n* exclude_patterns.txt - tar exclusion patterns (one per line)\n* chains/ - directory containing all backup chains\n```\n\nA **chain** is a sequence of backups (`tar.gz` archives), where the first backup is full and all subsequent backups are incremental (containing only changes since the previous backup).\n\n## Starting a new chain\n\nTo start a new chain, create a full backup with:\n\n```\nchains -fm /path/to/main/dir\n```\n\n**chains** will create a folder for the chain and place the first backup there:\n\n```\nMAIN_DIR/\n├── chains\n│   └── since_260309T200625\n│       ├── [2.0G] 260309T200625.tar.gz\n│       └── incremental.snar\n├── exclude_patterns.txt\n└── include_paths.txt\n\n* chains/ - directory containing all backup chains\n* since_260309T200625/ - specific chain directory (named after start time)\n* .tar.gz files - backup archives (first is full, rest are incremental)\n* incremental.snar - GNU tar metadata for tracking changes\n* exclude_patterns.txt - tar exclusion patterns (one per line)\n* include_paths.txt - absolute paths to backup (one per line)\n```\n\n## Adding incremental backups\n\nOnce a chain exists, add incremental backups with:\n\n```\nchains -im /path/to/main/dir\n```\n\nAfter the first incremental backup, the chain folder will look like this:\n\n```\nMAIN_DIR/\n├── chains\n│   └── since_260309T200625\n│       ├── [2.0G] 260309T200625.tar.gz\n│       ├── [1.2M] 260309T202739.tar.gz\n│       └── incremental.snar\n├── exclude_patterns.txt\n└── include_paths.txt\n\n* chains/ - directory containing all backup chains\n* since_260309T200625/ - specific chain directory (named after start time)\n* .tar.gz files - backup archives (first is full, rest are incremental)\n* incremental.snar - GNU tar metadata for tracking changes\n* exclude_patterns.txt - tar exclusion patterns (one per line)\n* include_paths.txt - absolute paths to backup (one per line)\n```\n\nAs you continue taking incremental backups, they are added to the same chain:\n\n```\nMAIN_DIR/\n├── chains\n│   └── since_260309T200625\n│       ├── [2.0G] 260309T200625.tar.gz\n│       ├── [1.2M] 260309T202739.tar.gz\n│       ├── [1.2M] 260309T202747.tar.gz\n│       ├── [1.5M] 260309T205505.tar.gz\n│       ├── [1.4M] 260309T211003.tar.gz\n│       ├── [1.7M] 260309T220534.tar.gz\n│       ├── [1.2M] 260309T221151.tar.gz\n│       ├── [1.5M] 260310T131826.tar.gz\n│       ├── [1.5M] 260310T133139.tar.gz\n│       ├── [221M] 260310T133328.tar.gz\n│       ├── [1.3M] 260310T133457.tar.gz\n│       ├── [1.4M] 260310T135812.tar.gz\n│       ├── [1.6M] 260310T153300.tar.gz\n│       ├── [1.3M] 260310T153454.tar.gz\n│       ├── [1.3M] 260310T153504.tar.gz\n│       ├── [1.3M] 260310T153509.tar.gz\n│       ├── [1.6M] 260310T154758.tar.gz\n│       ├── [1.9M] 260310T164949.tar.gz\n│       └── incremental.snar\n├── exclude_patterns.txt\n└── include_paths.txt\n\n* chains/ - directory containing all backup chains\n* since_260309T200625/ - specific chain directory (named after start time)\n* .tar.gz files - backup archives (first is full, rest are incremental)\n* incremental.snar - GNU tar metadata for tracking changes\n* exclude_patterns.txt - tar exclusion patterns (one per line)\n* include_paths.txt - absolute paths to backup (one per line)\n```\n\nWhen you want to start a fresh chain (for example, to create a new full backup baseline), simply run:\n\n```\nchains -fm /path/to/main/dir\n```\n\nThis creates a new timestamped folder (e.g., `since_260310T170000`) and places a new full backup there. All subsequent incremental backups with `-i` will be added to this newest chain.\n\n## Restoring data\n\nTo restore your data to a state corresponding to a specific backup in a chain, you need to extract all backups from the first (full) backup up to and including the target backup, extracting them all into the same directory. This applies the changes sequentially, recreating the exact state at that point in time.\n\nYou can do this manually, or use **chains**:\n\n```\n# Restore to the latest backup\nchains -rm /path/to/main/dir -C /path/to/output/dir\n\n# Restore to a specific timestamp\nchains -r 260309T205505 -m /path/to/main/dir -C /path/to/output/dir\n```\n\n# Requirements\n\n- Linux system\n- Perl 5.10 or newer\n- GNU tar\n\n# References\n\n- [GNU tar manual](https://www.gnu.org/software/tar/manual/html_node/tar_toc.html)\n- [stackoverflow.com/questions/2001709/how-to-check-if-a-unix-tar-gz-file-is-a-valid-file-without-uncompressing](https://stackoverflow.com/questions/2001709/how-to-check-if-a-unix-tar-gz-file-is-a-valid-file-without-uncompressing)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaserattack%2Fchains","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaserattack%2Fchains","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaserattack%2Fchains/lists"}