{"id":49965895,"url":"https://github.com/russlank/backup-cleanup","last_synced_at":"2026-05-24T11:01:01.386Z","repository":{"id":358496309,"uuid":"1241605241","full_name":"russlank/backup-cleanup","owner":"russlank","description":"A lightweight SQL Server backup cleanup utility that safely removes expired FULL, DIFF, and LOG backup files according to configurable Grandfather-Father-Son (GFS) retention rules.","archived":false,"fork":false,"pushed_at":"2026-05-17T17:29:40.000Z","size":2694,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-19T06:46:04.988Z","etag":null,"topics":["backup","backup-cleanup","backup-retention","cli","devops","gfs","golang","linux","mssql","sql-server","sre","windows"],"latest_commit_sha":null,"homepage":"https://www.digixoil.se","language":"Go","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/russlank.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-05-17T15:43:13.000Z","updated_at":"2026-05-17T20:29:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/russlank/backup-cleanup","commit_stats":null,"previous_names":["russlank/backup-cleanup"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/russlank/backup-cleanup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russlank%2Fbackup-cleanup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russlank%2Fbackup-cleanup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russlank%2Fbackup-cleanup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russlank%2Fbackup-cleanup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/russlank","download_url":"https://codeload.github.com/russlank/backup-cleanup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russlank%2Fbackup-cleanup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33293430,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T02:57:32.698Z","status":"ssl_error","status_checked_at":"2026-05-21T02:57:31.990Z","response_time":62,"last_error":"SSL_read: 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","backup-cleanup","backup-retention","cli","devops","gfs","golang","linux","mssql","sql-server","sre","windows"],"created_at":"2026-05-18T05:54:25.835Z","updated_at":"2026-05-23T10:00:57.185Z","avatar_url":"https://github.com/russlank.png","language":"Go","funding_links":["https://buymeacoffee.com/russlank"],"categories":[],"sub_categories":[],"readme":"# backup-cleanup\n\n[![Latest Release](https://img.shields.io/github/v/release/russlank/backup-cleanup?display_name=tag\u0026sort=semver)](https://github.com/russlank/backup-cleanup/releases/latest)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-%23FFDD00.svg?\u0026style=flat\u0026logo=buy-me-a-coffee\u0026logoColor=black)](https://buymeacoffee.com/russlank)\n\n\nA Go replacement for the [`backup-cleanup`](doc/original-backup-cleanup.sh) Bash script that removes old SQL Server backup files according to Grandfather-Father-Son (GFS) retention rules.\n\n`backup-cleanup` is designed to be used alongside [Ola Hallengren's SQL Server Maintenance Solution](https://ola.hallengren.com/) — a widely-used set of SQL Server Agent jobs and stored procedures that produce `FULL`, `DIFF`, and `LOG` backup files.  `backup-cleanup` enforces a GFS retention policy on those files and removes outdated copies from the backup storage host.\n\nThe goal is operational compatibility: the binary keeps the same command name, CLI flags, environment/config variable names, retention decisions, log messages, dry-run behavior, and optional `send-pulse` telemetry behavior as the original Bash script.\n\n## Quick navigation\n\n| Topic | File |\n|---|---|\n| Configuration reference | [this file, §Configuration](#configuration) |\n| JSON configuration format | [this file, §JSON configuration](#json-configuration) |\n| Go application architecture | [doc/architecture.md](doc/architecture.md) |\n| Testing guide (how to add tests) | [doc/testing-guide.md](doc/testing-guide.md) |\n| CI pipelines (Woodpecker + GitHub Actions) | [doc/ci-pipeline.md](doc/ci-pipeline.md) |\n| Version control and release workflow | [doc/version-control.md](doc/version-control.md) |\n| Compatibility with original Bash script | [doc/compatibility-notes.md](doc/compatibility-notes.md) |\n| Original Bash script (reference) | [doc/original-backup-cleanup.sh](doc/original-backup-cleanup.sh) |\n\n## Project layout\n\n```text\nbackup-cleanup/\n├── .github/workflows/ci.yml      # GitHub Actions pipeline\n├── .woodpecker.yml              # Woodpecker CI pipeline\n├── Makefile                     # Build, test, and dist targets\n├── go.mod                       # Go module definition\n├── cmd/backup-cleanup/\n│   ├── main.go                  # Entire command implementation (single package)\n│   └── main_test.go             # All unit and integration tests (same package)\n├── configs/\n│   ├── backup-cleanup.conf.example\n│   └── backup-cleanup.json.example\n├── dist/                        # Compiled binaries and SHA256SUMS (git-ignored)\n├── doc/\n│   ├── architecture.md          # Go architecture and code walkthrough\n│   ├── testing-guide.md         # How tests are organised and how to add more\n│   ├── ci-pipeline.md           # Woodpecker CI pipeline details\n│   ├── version-control.md       # Git workflow and release process\n│   ├── compatibility-notes.md   # Differences from the original Bash script\n│   ├── csharp-review-guide.md   # Notes for C# reviewers\n│   └── original-backup-cleanup.sh\n└── scripts/\n    └── build-dist.sh            # Standalone cross-compile script\n```\n\n## Build prerequisites\n\nBuilding from source requires Go 1.23 or newer.  The resulting binary has **no runtime dependency** on Go or any shared library.\n\n```bash\ngo version   # must be 1.23+\n```\n\n## Build\n\n```bash\nmake dist\n```\n\nThis produces:\n\n```text\ndist/backup-cleanup-linux-amd64\ndist/backup-cleanup-linux-arm64\ndist/backup-cleanup-windows-amd64.exe\ndist/SHA256SUMS\n```\n\nThe binaries embed the version, commit SHA, and build date — visible via `--version`.\n\n## Install\n\n```bash\n# x86-64 server\nsudo install -m 0755 dist/backup-cleanup-linux-amd64 /usr/local/bin/backup-cleanup\n\n# ARM64 server\nsudo install -m 0755 dist/backup-cleanup-linux-arm64 /usr/local/bin/backup-cleanup\n\n# Windows (PowerShell — copy to any directory in %PATH%)\nCopy-Item dist\\backup-cleanup-windows-amd64.exe C:\\Tools\\backup-cleanup.exe\n```\n\n## Test\n\n```bash\nmake test\n# or\ngo test -count=1 ./...\n\n# verbose (shows each test case name)\ngo test -v -count=1 ./...\n```\n\nSee [doc/testing-guide.md](doc/testing-guide.md) for the full description of how the test suite is structured and how to add new tests.\n\n## Usage\n\n```text\nbackup-cleanup [options]\n\nOPTIONS:\n  --backup-path \u003cpath\u003e     Path to backup directory (default: /mnt/backup01/remote)\n  --dry-run                Show what would be deleted without actually deleting\n  --debug                  Enable debug output\n  --version                Print version and exit\n  -h, --help               Show this help\n```\n\nAlways run with `--dry-run` before enabling real deletion in a new environment:\n\n```bash\nbackup-cleanup --dry-run --debug\n```\n\n## Configuration\n\nThe default configuration file is:\n\n```text\n/etc/backup-utils/backup-cleanup.conf\n```\n\nThe file must be a shell-compatible list of `KEY=value` assignments (it is sourced by Bash at startup).  Variable references and comments are supported:\n\n```bash\nPULSE_BACKUP_HOST_ID=\"pulse.monitor.local\"\nCLEANUP_PULSE_SUBJECT=\"backup/${PULSE_BACKUP_HOST_ID}/cleanup\"\n```\n\nSee [`configs/backup-cleanup.conf.example`](configs/backup-cleanup.conf.example) for a complete annotated template.\n\n### Configuration reference\n\n| Variable | Default | Description |\n|---|---|---|\n| `BACKUP_PATH` | `/mnt/backup01/remote` | Root directory containing per-database backup folders |\n| `LOG_TAG` | `backup-cleanup` | Syslog tag |\n| `PULSE_BACKUP_HOST_ID` | `pulse.monitor.local` | Host identifier used in the default pulse subject |\n| `CLEANUP_PULSE_SUBJECT` | `backup/{host}/cleanup` | Metric subject passed to `send-pulse` |\n| `CLEANUP_ENABLED` | `1` | Set to `0` to skip cleanup without disabling the scheduled job |\n| `FULL_DAILY_RETENTION_DAYS` | `7` | Keep every FULL backup this many days old or newer |\n| `FULL_WEEKLY_RETENTION_WEEKS` | `4` | Keep one FULL backup per GNU `%W` week bucket (on `FULL_WEEKLY_DAY`) within this window |\n| `FULL_WEEKLY_DAY` | `Sunday` | Weekday name for the weekly FULL backup anchor |\n| `FULL_MONTHLY_RETENTION_MONTHS` | `12` | Keep the oldest FULL backup per calendar month within this window |\n| `DIFF_RETENTION_DAYS` | `14` | Delete DIFF backups older than this many days (by file mtime) |\n| `LOG_RETENTION_DAYS` | `7` | Delete LOG backups older than this many days (by file mtime) |\n| `EXCLUDE_PATTERNS` | _(empty)_ | Space-separated path substrings; matching files are never deleted |\n\nAll integer values must be non-negative.  Setting a value to `0` disables that retention tier.\n\n## JSON configuration\n\nAs an alternative to the shell-compatible `.conf` format, `backup-cleanup` also accepts a JSON configuration file.  This format is required on Windows (where `bash` is not available to source `.conf` files) and is also convenient when config management tooling generates JSON.\n\nPoint the binary at a JSON file by setting the `CONFIG_FILE` environment variable:\n\n```bash\nCONFIG_FILE=/etc/backup-utils/backup-cleanup.json backup-cleanup\n```\n\nOn Windows:\n\n```powershell\n$env:CONFIG_FILE = 'C:\\ProgramData\\backup-utils\\backup-cleanup.json'\n.\\backup-cleanup.exe --dry-run\n```\n\nThe JSON keys are identical to the shell variable names.  Both string and integer JSON values are accepted:\n\n```json\n{\n  \"BACKUP_PATH\": \"D:\\\\Backups\\\\remote\",\n  \"CLEANUP_ENABLED\": 1,\n  \"FULL_DAILY_RETENTION_DAYS\": 7,\n  \"FULL_WEEKLY_RETENTION_WEEKS\": 4,\n  \"FULL_WEEKLY_DAY\": \"Sunday\",\n  \"FULL_MONTHLY_RETENTION_MONTHS\": 12,\n  \"DIFF_RETENTION_DAYS\": 14,\n  \"LOG_RETENTION_DAYS\": 7\n}\n```\n\n\u003e **Note:** Variable expansion (`${VAR}`) is not supported in JSON files.  Use literal values for all settings.\n\nSee [`configs/backup-cleanup.json.example`](configs/backup-cleanup.json.example) for a complete annotated template.\n\n## CI and releases\n\nReleases are published automatically by the Woodpecker CI pipeline when a semver tag is pushed:\n\n```bash\ngit tag v1.2.3\ngit push origin v1.2.3\n```\n\nWoodpecker lints, vets, tests, cross-compiles, and uploads three binaries (`linux-amd64`, `linux-arm64`, `windows-amd64.exe`) plus `SHA256SUMS` to Gitea releases.\n\nFor GitHub-hosted mirrors, `.github/workflows/ci.yml` provides the equivalent flow (lint, vet, test, build dist, release on tag). See [doc/ci-pipeline.md](doc/ci-pipeline.md) for full details.\n\n## Compatibility notes\n\nSee [doc/compatibility-notes.md](doc/compatibility-notes.md).\n\nKey improvements over the original Bash script:\n\n- Invalid or negative integer config values fail early with a clear error.\n- Stale `.cleanup_processed` markers are always cleaned up, even after a partial failure.\n- `time.Now()` is captured once per cleanup function, not per file.\n- A summary log line is emitted at the end of every run.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\nCopyright (c) 2026 Russlan Kafri (\u003chttps://github.com/russlank\u003e).\n\n## Documentation style note\n\nThis codebase targets both Go developers and C# reviewers.  `main.go` therefore uses standard Go documentation comments combined with XML-style `\u003csummary\u003e` / `\u003cremarks\u003e` blocks inside comments to make the intent clear to a C# reader.  See [doc/csharp-review-guide.md](doc/csharp-review-guide.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frusslank%2Fbackup-cleanup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frusslank%2Fbackup-cleanup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frusslank%2Fbackup-cleanup/lists"}