9 Commits
0.1.0 ... 0.2.1

Author SHA1 Message Date
b0395a432f Merge pull request 'Patch dependecies' (#2) from fix_dependencies_notation into main
Reviewed-on: #2
2025-12-21 10:25:30 +00:00
603e2ac0c6 Patch dependecies
All checks were successful
Lint & Security / precommit-and-security (pull_request) Successful in 1m0s
2025-12-21 10:23:59 +00:00
9c915576e9 Merge pull request 'Add tab completion, update README, version bump 0.2.0' (#1) from relax_dependencies into main
Reviewed-on: #1
2025-12-21 10:03:33 +00:00
96970b6963 Rework lint-and-security workflow to add Poetry and the export plugin to work with pip-audit
All checks were successful
Lint & Security / precommit-and-security (pull_request) Successful in 59s
2025-12-21 10:01:05 +00:00
5353310e15 Edit workflow to run pip-audit against a poetry export file
Some checks failed
Lint & Security / precommit-and-security (pull_request) Failing after 1m11s
2025-12-21 09:54:08 +00:00
e8f63386bb Add filelock ^3.20.1 in order to get away from CVE-2025-68146
Some checks failed
Lint & Security / precommit-and-security (pull_request) Failing after 47s
2025-12-21 09:40:56 +00:00
e0ec2ce60a Add tab completion, update README, version bump 0.2.0
Some checks failed
Lint & Security / precommit-and-security (pull_request) Failing after 1m37s
2025-12-21 09:29:21 +00:00
a5551e7047 Add gitea workflow and CODEOWNERS 2025-12-21 08:46:30 +00:00
4b67e721e7 Add logo file, update README 2025-12-21 07:36:36 +00:00
7 changed files with 153 additions and 24 deletions

1
.gitea/CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
* @mdaleo404

View File

@@ -0,0 +1,36 @@
name: Lint & Security
on:
pull_request:
jobs:
precommit-and-security:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install pre-commit
run: pip install pre-commit
- name: Run pre-commit hooks
run: pre-commit run --all-files --color always
- name: Install Poetry
run: |
pip install poetry
poetry self add poetry-plugin-export
- name: Install pip-audit
run: pip install pip-audit
- name: Audit dependencies (Poetry lockfile)
run: |
poetry export -f requirements.txt --without-hashes \
| pip-audit -r /dev/stdin

View File

@@ -4,6 +4,11 @@
# chguard # chguard
<div align="center">
<img src="chguard.png" alt="chguard logo" width="256" />
</div>
**chguard** is a safety-first command-line tool that snapshots and restores **chguard** is a safety-first command-line tool that snapshots and restores
filesystem ownership and permissions. filesystem ownership and permissions.
@@ -193,6 +198,16 @@ Snapshots are stored in a local SQLite database containing:
Usernames and permission strings are resolved only for display. Usernames and permission strings are resolved only for display.
### TAB completion
Add this to your `.bashrc`
```
eval "$(register-python-argcomplete chguard)"
```
And then
```
source ~/.bashrc
```
## pre-commit ## pre-commit
This project uses [**pre-commit**](https://pre-commit.com/) to run automatic formatting and security checks before each commit (Black, Bandit, and various safety checks). This project uses [**pre-commit**](https://pre-commit.com/) to run automatic formatting and security checks before each commit (Black, Bandit, and various safety checks).

BIN
chguard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,6 +1,8 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import argcomplete
import importlib.metadata
import os import os
import sys import sys
import stat import stat
@@ -27,6 +29,13 @@ from chguard.restore import plan_restore, apply_restore
from chguard.util import normalize_root from chguard.util import normalize_root
def get_version():
try:
return importlib.metadata.version("chguard")
except importlib.metadata.PackageNotFoundError:
return "unknown"
def _uid_to_name(uid: int) -> str: def _uid_to_name(uid: int) -> str:
"""Return username for uid, or uid as string if unknown.""" """Return username for uid, or uid as string if unknown."""
try: try:
@@ -83,6 +92,19 @@ def _is_root() -> bool:
return os.geteuid() == 0 return os.geteuid() == 0
def complete_state_names(prefix, parsed_args, **kwargs):
try:
conn = connect(
Path(parsed_args.db).expanduser().resolve()
if parsed_args.db
else None
)
rows = conn.execute("SELECT name FROM states").fetchall()
return [name for (name,) in rows if name.startswith(prefix)]
except Exception:
return []
def main() -> None: def main() -> None:
""" """
Entry point for the CLI. Entry point for the CLI.
@@ -100,45 +122,99 @@ def main() -> None:
) )
actions = parser.add_mutually_exclusive_group(required=True) actions = parser.add_mutually_exclusive_group(required=True)
actions.add_argument("--save", metavar="PATH", help="Save state for PATH")
parser.add_argument(
"--version",
action="version",
version=f"chguard {get_version()}",
)
actions.add_argument( actions.add_argument(
"--restore", action="store_true", help="Restore a saved state" "--save",
) metavar="PATH",
help="Save state for PATH",
).completer = argcomplete.FilesCompleter()
actions.add_argument( actions.add_argument(
"--list", action="store_true", help="List saved states" "--restore",
action="store_true",
help="Restore a saved state",
) )
actions.add_argument( actions.add_argument(
"--delete", metavar="STATE", help="Delete a saved state" "--list",
action="store_true",
help="List saved states",
)
actions.add_argument(
"--delete",
metavar="STATE",
help="Delete a saved state",
).completer = complete_state_names
# positional STATE
parser.add_argument(
"state",
nargs="?",
help="State name (required with --restore)",
).completer = complete_state_names
parser.add_argument(
"--name",
help="State name (required with --save)",
) )
parser.add_argument( parser.add_argument(
"state", nargs="?", help="State name (required with --restore)" "--overwrite",
) action="store_true",
parser.add_argument("--name", help="State name (required with --save)") help="Overwrite existing state",
parser.add_argument(
"--overwrite", action="store_true", help="Overwrite existing state"
) )
parser.add_argument( parser.add_argument(
"--permissions", action="store_true", help="Restore MODE only" "--permissions",
) action="store_true",
parser.add_argument( help="Restore MODE only",
"--owner", action="store_true", help="Restore OWNER only"
) )
parser.add_argument( parser.add_argument(
"--dry-run", action="store_true", help="Preview only; do not apply" "--owner",
) action="store_true",
parser.add_argument( help="Restore OWNER only",
"--yes", action="store_true", help="Apply without confirmation"
) )
parser.add_argument("--root", metavar="PATH", help="Override restore root")
parser.add_argument( parser.add_argument(
"--exclude", action="append", default=[], help="Exclude path prefix" "--dry-run",
action="store_true",
help="Preview only; do not apply",
) )
parser.add_argument("--db", metavar="PATH", help="Override database path")
parser.add_argument(
"--yes",
action="store_true",
help="Apply without confirmation",
)
parser.add_argument(
"--root",
metavar="PATH",
help="Override restore root",
).completer = argcomplete.FilesCompleter()
parser.add_argument(
"--exclude",
action="append",
default=[],
help="Exclude path prefix",
).completer = argcomplete.FilesCompleter()
parser.add_argument(
"--db",
metavar="PATH",
help="Override database path",
).completer = argcomplete.FilesCompleter()
argcomplete.autocomplete(parser)
args = parser.parse_args() args = parser.parse_args()
console = Console() console = Console()

2
poetry.lock generated
View File

@@ -289,4 +289,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.10,<4.0" python-versions = ">=3.10,<4.0"
content-hash = "49f77d614e46109e49e997fa270cb7093d6f7e7d258e370c4eddd4354c20437f" content-hash = "4a5c993fcc16fe3739c43eb00bed750ce0803d45e37c7a786aa0b83bb4930267"

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "chguard" name = "chguard"
version = "0.1.0" version = "0.2.1"
description = "Safety-first tool to snapshot and restore filesystem ownership and permissions." description = "Safety-first tool to snapshot and restore filesystem ownership and permissions."
authors = ["Marco D'Aleo <marco@marcodaleo.com>"] authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
@@ -12,7 +12,8 @@ repository = "https://git.sysmd.uk/guardutils/chguard"
python = ">=3.10,<4.0" python = ">=3.10,<4.0"
rich = ">=12" rich = ">=12"
argcomplete = ">=2" argcomplete = ">=2"
platformdirs = "^4.5.1" platformdirs = ">=4.5.1"
filelock = ">=3.20.1"
[tool.poetry.scripts] [tool.poetry.scripts]
chguard = "chguard.cli:main" chguard = "chguard.cli:main"