Compare commits

..

No commits in common. "main" and "0.3" have entirely different histories.
main ... 0.3

28 changed files with 151 additions and 1063 deletions

View file

@ -1,24 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,3 +0,0 @@
- [ ] Closes #xxxx
- [ ] Tests added / passed
- [ ] All linting tests pass

View file

@ -1,28 +0,0 @@
name: Docs
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install dependencies
run: python -m pip install --upgrade pip sphinx
- name: Build docs
run: sphinx-build -a docs/source docs/build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/build
publish_branch: gh-pages

View file

@ -1,24 +0,0 @@
name: Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install dependencies
run: python -m pip install --upgrade pip pytest flake8
- name: Run linter
run: flake8 --max-line-length=120 scripts/
- name: Run tests
run: python -m pytest -v

4
.gitignore vendored
View file

@ -1,4 +0,0 @@
__pycache__
venv
docs/build
man8

12
10_snapper-post.hook Normal file
View file

@ -0,0 +1,12 @@
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
Description = snapper post snapshot
Depends = snapper
When = PostTransaction
Exec = /usr/bin/snapper-pac-post

12
10_snapper-pre.hook Normal file
View file

@ -0,0 +1,12 @@
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
Description = snapper pre snapshot
Depends = snapper
When = PreTransaction
Exec = /usr/bin/snapper-pac-pre

12
99_grub-config.hook Executable file
View file

@ -0,0 +1,12 @@
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
Description = generate GRUB configuration file
Depends = grub-btrfs-git
When = PostTransaction
Exec = /usr/bin/snapper-pac-grub

View file

@ -1,42 +0,0 @@
# snap-pac
# https://github.com/wesbarnett/snap-pac
# Copyright (C) 2016-2021 James W. Barnett
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
PKGNAME = snap-pac
PREFIX ?= /usr
SHARE_DIR = $(DESTDIR)$(PREFIX)/share
.PHONY: install test docs
install: man
@install -Dm755 scripts/snap_pac.py $(SHARE_DIR)/libalpm/scripts/snap-pac
@install -Dm644 hooks/* -t $(SHARE_DIR)/libalpm/hooks/
@install -Dm644 LICENSE -t $(SHARE_DIR)/licenses/$(PKGNAME)/
@install -Dm644 man8/* -t $(SHARE_DIR)/man/man8/
@install -Dm644 README.md -t $(SHARE_DIR)/doc/$(PKGNAME)/
@install -Dm644 extra/snap-pac.ini $(DESTDIR)/etc/snap-pac.ini
test:
@python -m pytest -v .
man:
@cd docs && make man
@mkdir -p man8
@awk 'NR==33{print ".SH DESCRIPTION"}7' docs/build/man/snap-pac.8 > man8/snap-pac.8
docs: man
@sphinx-build -a docs/source docs/build

110
README.md
View file

@ -1,25 +1,111 @@
# snap-pac # snap-pac
## Synopsis This makes Arch Linux's pacman use
[snapper](https://wiki.archlinux.org/index.php/Snapper) to automatically take a
pre and post snapshot before and after pacman transactions, similar to how YaST
does with OpenSuse.
This is a set of [pacman](https://wiki.archlinux.org/index.php/Pacman) hooks and scripts that automatically cause [snapper](http://snapper.io/) to perform a snapshot before pacman transactions. *Note:* The scripts only take snapshots of the subvolume mounted at `/`; other
subvolumes are not included. You must modify the scripts to include other
subvolumes. It's recommended that you create subvolumes of directories you do
*not* want included (*e.g.* `/var/cache/pacman/pkg`).
Forked from Wes Barnett's solution for my own personal needs. Takes a single screenshot vs. the original's pre/post pair. The scripts are set up to use the `number` algorithm. That is, snapper will
periodically clean up snapshots tagged with `number` after reaching a set
threshold in the snapper configuration file.
## [Installation](https://wesbarnett.github.io/snap-pac/installation.html) Additionally the package provides a hook to regenerate your GRUB configuration
file after every pacman transaction. This is useful when using
[grub-btrfs](https://aur.archlinux.org/packages/grub-btrfs-git/).
## Configuration ## Installation
Most likely, configuration is not needed. By default, the snapper configuration named `root` will have snapshots taken before every pacman transaction. Install [the package from the
AUR](https://aur.archlinux.org/packages/snap-pac/).
For more information on configuring snap-pac, see [the documentation](https://wesbarnett.github.io/snap-pac/configuration.html). Optionally install
[grub-btrfs](https://aur.archlinux.org/packages/grub-btrfs-git/) to populate
your GRUB menu with the ability to boot into snapshots.
## Documentation ## Usage
See the [documentation here](https://wesbarnett.github.io/snap-pac/) or `man 8 snap-pac` after installation. **Use pacman (and AUR helpers) as normal and watch snapper do its thing.** No
bash scripts for you to call. No bash aliases to setup.
## Troubleshooting Because these are pacman hooks, it doesn't matter how you call pacman (whether
directly, through an AUR helper, or an alias)---snapper will create the
snapshots whenever pacman is asked to install, upgrade, or remove a package. The
description for the snapshot is the pacman command that called the hook in the
first place.
After reviewing the documentation, [check the issues page] and file a new issue if your problem is not covered. ### Example
[check the issues page]: https://codeberg.org/ak95/snap-pac/issues Installing the `nano` package as normal:
# pacman -S nano
resolving dependencies...
looking for conflicting packages...
Packages (1) nano-2.5.3-1
Total Installed Size: 2.14 MiB
:: Proceed with installation? [Y/n] Y
(1/1) checking keys in keyring [######################################] 100%
(1/1) checking package integrity [######################################] 100%
(1/1) loading package files [######################################] 100%
(1/1) checking for file conflicts [######################################] 100%
(1/1) checking available disk space [######################################] 100%
:: Running pre-transaction hooks...
(1/1) snapper pre snapshot
:: Processing package changes...
(1/1) installing nano [######################################] 100%
:: Running post-transaction hooks...
(1/1) snapper post snapshot
And here are the snapshots:
# snapper -c root list -t pre-post | tail -n 1
1033 | 1034 | Fri 22 Apr 2016 01:54:13 PM CDT | Fri 22 Apr 2016 01:54:14 PM CDT | pacman -S nano |
What changed (see the man page for what each symbol means)?
# snapper -c root status 1033..1034
+..... /etc/nanorc
c..... /etc/snapper/.snap-pac-pre
+..... /usr/bin/nano
+..... /usr/bin/rnano
+..... /usr/share/doc/nano
+..... /usr/share/doc/nano/faq.html
+..... /usr/share/doc/nano/fr
+..... /usr/share/doc/nano/fr/nano.1.html
+..... /usr/share/doc/nano/fr/nanorc.5.html
+..... /usr/share/doc/nano/fr/rnano.1.html
+..... /usr/share/doc/nano/nano.1.html
+..... /usr/share/doc/nano/nanorc.5.html
+..... /usr/share/doc/nano/rnano.1.html
c..... /usr/share/info/dir
+..... /usr/share/info/nano.info.gz
(I truncated the above output, but it continues...) You can also do `snapper
diff` in the same way---I'll spare you that one.
To undo the upgrade:
# snapper -c root undochange 1033..1034
create:0 modify:3 delete:100
And `nano` is now gone:
$ pacman -Qi nano
error: package 'nano' was not found
## References
* [snapper homepage](http://snapper.io/)
* [Btrfs homepage](https://wiki.archlinux.org/index.php/Btrfs)
* [ArchWiki Snapper article](https://wiki.archlinux.org/index.php/Snapper)
* [ArchWiki Btrfs article](https://wiki.archlinux.org/index.php/Btrfs)
* `man alpm-hooks`
* `man snapper`
* `man btrfs`

View file

@ -1,20 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View file

@ -1,35 +0,0 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View file

@ -1,65 +0,0 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'snap-pac'
copyright = '2021, Wes Barnett, PhD'
author = 'Wes Barnett, PhD'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_theme_options = {
'badge_branch': 'main',
'description': 'pacman hooks that use snapper to create pre/post btrfs snapshots',
'fixed_sidebar': True,
'github_banner': True,
'github_user': 'wesbarnett',
'github_repo': 'snap-pac',
'github_type': 'star'
}
man_pages = [("index", "snap-pac", "Pacman hooks that use snapper to create pre/post btrfs snapshots like openSUSE's YaST", "Wes Barnett", "8")]
manpages_url = 'https://man.archlinux.org/man/{page}.{section}'

View file

@ -1,99 +0,0 @@
Configuration
=============
.. toctree::
:maxdepth: 2
Configuration is done via Python ini configuration files. The defaults
should be suitable for most users, so you may not need to do any configuration at all.
By default only the ``root`` snapper configuration is snapshotted.
A commented example configuration files is located at ``/etc/snap-pac.ini``.
Edit with your favorite editor. The file is commented and should be
self-explanatory.
Each section corresponds with a snapper configuration. Add additional sections to add
other snapper configurations to be snapshotted. By default, only the root configuration
is snapshotted. Additionally you can add a section named ``DEFAULT`` with options that
apply to all snapper configurations unless overridden in a later section.
Each section can have the following entries:
* ``desc_limit`` - integer; maximum length of description string before being truncated.
Default: 72
* ``important_packages`` - list of strings; names of packages that if involved in a pacman
transaction will add ``important=yes`` to the snapper userdata for the pair of
snapshots. Default: []
* ``important_commands`` - list of strings; parent commands that will add
``important=yes`` to the snapper userdata for the pair of snapshots. Default: []
* ``pre_description`` - string; description for the pre snapshot. Default: the parent
command that called the pacman hook.
* ``post_description`` - string; description for the post snapshot. Default: space
separated list of packages that were installed, upgraded, or removed.
* ``snapshot`` - boolean; whether or not to snapshot the configuration. Default: True for
``root`` configuration; False otherwise.
* ``userdata`` - list of strings; key-value pairs that will be added to the userdata for
the pair of snapshots. Default: []
Examples
--------
Turn off snapshots for ``root`` configuration and turn on for ``home`` configuration:
.. code-block:: ini
[root]
snapshot = False
[home]
snapshot = True
Set the snapper to add the userdata ``important=yes`` for every snapshot in the ``root``
configuration when a system upgrade is performed:
.. code-block:: ini
[root]
important_commands = ["pacman -Syu"]
Set the snapper to add the userdata ``important=yes`` for every snapshot in the ``root``
configuration when a pacman transaction handles the packages ``linux`` and ``linux-lts``:
.. code-block:: ini
[root]
important_packages = ["linux", "linux-lts"]
Here's a fuller example, with several options set for different configurations. In this
case the ``root`` configuration snapshot will have ``important=yes`` when ``linux`` and
``linux-lts`` packages are part of the transaction. Additionally when full system
upgrades are performed ``root`` snapshots will be marked ``important=yes``. Note that
you don't have to add ``snapshot = True`` for the ``root`` configuration since that is
the default.
This file also turns one snapshots for the ``home`` snapper configuration and adds the
userdata ``requestid=42,user=arthur`` to all snapshots for that configuration.
Additionally the post snapshot description is overridden.
.. code-block:: ini
[root]
important_packages = ["linux", "linux-lts"]
important_commands = ["pacman -Syu"]
[home]
snapshot = True
userdata = ["requestid=42", "user=arthur"]
post_description = "pacman transaction post snapshot"
Environment Variables
=====================
To temporarily prevent snapshots from being performed for a single pacman
command, set the environment variable ``SNAP_PAC_SKIP``. For example:
.. code-block:: bash
sudo SNAP_PAC_SKIP=y pacman -Syu

View file

@ -1,91 +0,0 @@
Example
=======
.. toctree::
:maxdepth: 2
Here is an example of how the snapshots are created and how to rollback and pacman
transaction. Here the nano package is installed:
.. code-block:: bash
pacman -S nano
.. code-block:: none
resolving dependencies...
looking for conflicting packages...
Packages (1) nano-2.5.3-1
Total Installed Size: 2.14 MiB
:: Proceed with installation? [Y/n] Y
(1/1) checking keys in keyring [######################################] 100%
(1/1) checking package integrity [######################################] 100%
(1/1) loading package files [######################################] 100%
(1/1) checking for file conflicts [######################################] 100%
(1/1) checking available disk space [######################################] 100%
:: Running pre-transaction hooks...
(1/1) Performing snapper pre snapshots for the following configurations...
=> root: 1033
:: Processing package changes...
(1/1) installing nano [######################################] 100%
:: Running post-transaction hooks...
(1/1) Performing snapper post snapshots for the following configurations...
=> root: 1034
The snapper snapshot number is given for each snapper configuration that is used. This
is also logged in pacman's log.
Here are the snapshots created before and after the pacman transaction:
.. code-block:: bash
snapper -c root list -t pre-post | tail -n 1
.. code-block:: none
1033 | 1034 | Fri 22 Apr 2016 01:54:13 PM CDT | Fri 22 Apr 2016 01:54:14 PM CDT | pacman -S nano |
Here is what changed during the transaction:
.. code-block:: bash
snapper -c root status 1033..1034
.. code-block:: none
+..... /etc/nanorc
c..... /etc/snapper/.snap-pac-pre
+..... /usr/bin/nano
+..... /usr/bin/rnano
+..... /usr/share/doc/nano
+..... /usr/share/doc/nano/faq.html
+..... /usr/share/doc/nano/fr
+..... /usr/share/doc/nano/fr/nano.1.html
+..... /usr/share/doc/nano/fr/nanorc.5.html
+..... /usr/share/doc/nano/fr/rnano.1.html
The above output is truncated, but it continues. See :manpage:`snapper(8)` to for what each
symbol means. You can also do ``snapper diff`` in the same way.
Then, to undo the pacman transaction:
.. code-block:: bash
snapper -c root undochange 1033..1034
.. code-block:: none
create:0 modify:3 delete:100
Now nano is no longer installed, along with all the files it changed:
.. code-block:: bash
pacman -Qi nano
.. code-block:: none
error: package 'nano' was not found

View file

@ -1,47 +0,0 @@
FAQ
===
.. toctree::
:maxdepth: 2
**Does snap-pac backup non-btrfs /boot partitions?**
No, but you can add a hook that does it for you. It would be something like the following:
.. code-block:: none
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = linux
[Action]
Description = Backing up /boot...
When = PreTransaction
Exec = /usr/bin/rsync -avzq --delete /boot /.bootbackup
Note that you will probably want to name the file with a numbered prefix less than
``05`` so that it is run before the snap-pac pre snapshot takes place. That will ensure
that the snapshot taken will have the boot partition back-up corresponding with the
state of the system. For example, you could name it ``04-backupboot.hook``.
**How do I link old kernel modules automatically when the kernel is upgraded?**
This behavior is no longer a part of this package. Use a pacman hook like the following:
.. code-block:: none
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = linux
[Action]
Description = Symlinking old kernel modules...
When = PostTransaction
Exec = /usr/bin/bash -c "find /usr/lib/modules -xtype l -delete; ln -sv /.snapshots/$(snapper -c root list | awk 'END{print $1}')/snapshot/usr/lib/modules/$(uname -r) /usr/lib/modules/"

View file

@ -1,79 +0,0 @@
.. snap-pac documentation master file, created by
sphinx-quickstart on Thu Mar 11 19:49:12 2021.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
snap-pac
========
This is a set of `pacman <https://archlinux.org/pacman/>`_ hooks and script that causes
`snapper <http://snapper.io/>`_ to automatically take a pre and post snapshot before and
after pacman transactions, similar to how `YaST <https://yast.opensuse.org/>`_ does with
OpenSuse. This provides a simple way to undo changes to a system after a pacman
transaction.
Because these are pacman hooks, it doesn't matter how you call pacman—whether
directly, through an AUR helper, or using an alias—snapper will create the snapshots
when pacman installs, upgrades, or removes a package. The pacman command used is
logged in the snapper description for the snapshots. Additionally the snapshot numbers
are output to the screen and to the pacman log for each snapper configuration during the
pacman transaction, so that the user can easily find which changes he or she may want to
revert.
.. code-block:: none
:caption: When you run pacman, the snapper pre/post snapshots are created automatically. For a fuller example see :doc:`examples`.
:emphasize-lines: 17,18,25,26
$ sudo pacman -S vim
resolving dependencies...
looking for conflicting packages...
Packages (1) vim-8.2.2489-1
Total Installed Size: 3.79 MiB
Net Upgrade Size: 0.00 MiB
:: Proceed with installation? [Y/n]
(1/1) checking keys in keyring [############] 100%
(1/1) checking package integrity [############] 100%
(1/1) loading package files [############] 100%
(1/1) checking for file conflicts [############] 100%
(1/1) checking available disk space [############] 100%
:: Running pre-transaction hooks...
(1/1) Performing snapper pre snapshots for the following configurations...
==> root: 7394
:: Processing package changes...
(1/1) installing vim [############] 100%
:: Running post-transaction hooks...
(1/4) Arming ConditionNeedsUpdate...
(2/4) Updating icon theme caches...
(3/4) Updating the desktop file MIME type cache...
(4/4) Performing snapper post snapshots for the following configurations...
==> root: 7395
To undo changes from a pacman transaction, use ``snapper undochange``. See the :manpage:`snapper(8)`
for more details as well as examples.
If you have severe breakage—like snapper is gone for some reason and you can't get it
back—you'll have to resort to more extreme methods, such as taking a snapshot of the pre
snapshot and making it the default subvolume or mounting it as /. Most likely you'll
need to use a live USB to get into a chroot environment to do any of these things.
Snapper has a ``snapper rollback`` feature, but your setup has to be properly configured to
use it. The exact procedure depends on your specific setup. Be careful.
Note that the pre transaction hooks occur before the pacman transaction but after the
pacman database is synced, if applicable. In other words, if you run `pacman -Syu` and
roll back the upgrade according to the above instructions, you will not have rolled back
the pacman database to the previous state. Thus, if, after upgrading and rolling back
the upgrade, you then install a package, it will possibly be a partial upgrade, which is
unsupported.
.. toctree::
:maxdepth: 1
installation
configuration
examples
troubleshooting
faq

View file

@ -1,66 +0,0 @@
Installation
============
Install the ``snap-pac`` package using pacman:
.. code-block:: bash
pacman -S snap-pac
Alternatively download the `latest release and signature
<https://github.com/wesbarnett/snap-pac/releases>`_. Then, verify the download:
.. code-block:: bash
gpg --verify snap-pac-<version>.tar.gz.sig
where ``<version>`` is the version number you downloaded.
Finally, run:
.. code-block:: bash
make install
I have signed the release tarball and commits with my PGP key. Starting with release
2.2, the tarballs are signed with my key with fingerprint
``F7B28C61944FE30DABEEB0B01070BCC98C18BD66``.
For previous releases, the key's fingerprint was
``8535CEF3F3C38EE69555BF67E4B5E45AA3B8C5C3``.
Dependencies
------------
``python``, ``pacman``, and ``snapper`` are all required.
Testing
-------
For testing, ``pytest`` is required. To run the tests do:
.. code-block:: bash
make test
Documentation
-------------
Typically, you will not need to build the documentation on your own and can simply
access it by visiting the `online documentation
<https://wesbarnett.github.io/snap-pac/>`_ or by accessing the manpage:
.. code-block:: bash
man 8 snap-pac
To build the documentation, ``sphinx`` is required. To build the documentation you can
do:
.. code-block:: bash
make docs
The resulting html documentation will then be located at ``docs/build/index.html``.
Additionally, this generates the manpage which will be located under ``man8``.

View file

@ -1,23 +0,0 @@
Troubleshooting
===============
.. toctree::
:maxdepth: 2
**snap-pac is only taking snapshots of the root configuration.**
That's the default behavior. See :doc:`configuration`.
**No snapshots are being taken when I run pacman.**
No snapper configurations are set up for snap-pac's pacman hooks. By default snap-pac
will take snapshots for the root configuration and any other configuration which has
SNAPSHOT set to yes in its configuration file. See :doc:`configuration`.
**After restoring snapshot from snap-pac, the pacman database is locked.**
The pre/post snaphots are taken while pacman is running, so this is expected. Follow
the instructions pacman gives you (*e.g.*, removing the lock file). You can add the
database lock file to a snapper filter so that snapper won't consider it when
performing snapper diff, snapper status, snapper undochange, etc. See the Filters
section in :manpage:`snapper(8)` for more information.

View file

@ -1,38 +0,0 @@
# snap-pac example configuration file
# see snap-pac(8) for more details
# Each section corresponds with a snapper configuration. Add additional sections to add
# other configurations to be snapshotted. By default, only the root configuration is snapshotted.
# Create a section named [DEFAULT] to have a setting apply for all snapper configurations
## Uncomment to set parameters for snapper configuration named root
#[root]
## How many characters to limit the description for snapper.
## Default is 72
#desc_limit = 72
## Whether or not to take snapshots of this snapper configuration
## Default is True for root configuration and False for all other configurations
#snapshot = True
## What snapper cleanup algorithm to use
## Default is number
#cleanup_algorithm = number
## Uncomment to add "important=yes" to userdata for snapshots referring to these packages
## Default is []
#important_packages = ["linux", "linux-lts"]
## Uncomment to add "important=yes" to userdata for snapshots that were created with the following commands
## Default is []
#important_commands = ["pacman -Syu"]
## Add custom userdata. Each key-value pair should be an item in the list
## Default is []
#userdata = ["key=value","foo=bar"]
## Example for another snapper configuration named "home"
# [home]
## Default is False
# snapshot = True

View file

@ -1,32 +0,0 @@
# snap-pac
# https://github.com/wesbarnett/snap-pac
# Copyright (C) 2016, 2017, 2018 James W. Barnett
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
Description = Taking snapper snapshot before installing...
Depends = snap-pac
When = PreTransaction
Exec = /usr/share/libalpm/scripts/snap-pac
NeedsTargets
AbortOnFail

View file

@ -1,28 +0,0 @@
# snap-pac
# https://github.com/wesbarnett/snap-pac
# Copyright (C) 2016, 2017, 2018 James W. Barnett
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
[Trigger]
Operation = Remove
Type = Package
Target = snap-pac
[Action]
Description = You are removing snap-pac. No post transaction snapshots will be taken.
Depends = snap-pac
When = PreTransaction
Exec = /usr/bin/bash -c "rm -f /tmp/snap-pac-pre_*"

View file

@ -1,161 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2021 Wes Barnett
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Script for taking snapshots; run from pacman hooks."""
from argparse import ArgumentParser
from configparser import ConfigParser
import json
import logging
from pathlib import Path
import os
import sys
import tempfile
logging.basicConfig(format="%(message)s", level=logging.INFO)
class SnapperCmd:
def __init__(self, config, cleanup_algorithm, description="", nodbus=False, userdata=""):
self.cmd = ["snapper"]
if nodbus:
self.cmd.append("--no-dbus")
self.cmd.extend([
f"--config {config} create",
f"--cleanup-algorithm {cleanup_algorithm}",
"--print-number"
])
self.cmd.append("--read-write")
if description:
self.cmd.append(f"--description \"{description}\"")
if userdata:
self.cmd.append(f"--userdata \"{userdata}\"")
def __call__(self):
return os.popen(self.__str__()).read().rstrip("\n")
def __str__(self):
return " ".join(self.cmd)
class ConfigProcessor:
def __init__(self, ini_file, parent_cmd=None, packages=None):
"""Set up defaults for snap-pac configuration."""
if parent_cmd is None:
self.parent_cmd = os.popen(f"ps -p {os.getppid()} -o args=").read().strip()
else:
self.parent_cmd = parent_cmd
if packages is None:
self.packages = [line.rstrip("\n") for line in sys.stdin]
else:
self.packages = packages
self.config = ConfigParser()
self.config["DEFAULT"] = {
"snapshot": False,
"cleanup_algorithm": "number",
"description": self.parent_cmd,
"desc_limit": 72,
"important_packages": [],
"important_commands": [],
"userdata": []
}
self.config["root"] = {
"snapshot": True
}
self.config.read(ini_file)
def get_cleanup_algorithm(self, section):
return self.config.get(section, "cleanup_algorithm")
def get_description(self, section):
desc_limit = self.config.getint(section, "desc_limit")
return self.config.get(section, "description")[:desc_limit]
def check_important_commands(self, section):
return self.parent_cmd in json.loads(self.config.get(section, "important_commands"))
def check_important_packages(self, section):
important_packages = json.loads(self.config.get(section, "important_packages"))
return any(x in important_packages for x in self.packages)
def check_important(self, section):
return (self.check_important_commands(section) or
self.check_important_packages(section))
def get_userdata(self, section):
userdata = set(json.loads(self.config.get(section, "userdata")))
if self.check_important(section):
userdata.add("important=yes")
return ",".join(sorted(list(userdata)))
def __call__(self, section):
if section not in self.config:
self.config.add_section(section)
return {
"description": self.get_description(section),
"cleanup_algorithm": self.get_cleanup_algorithm(section),
"userdata": self.get_userdata(section),
"snapshot": self.config.getboolean(section, "snapshot")
}
def get_snapper_configs(conf_file):
"""Get the snapper configurations."""
for line in conf_file.read_text().split("\n"):
if line.startswith("SNAPPER_CONFIGS"):
line = line.rstrip("\n").rstrip("\"").split("=")
return line[1].lstrip("\"").split()
def check_skip():
return os.getenv("SNAP_PAC_SKIP", "n").lower() in ["y", "yes", "true", "1"]
def parse_args():
parser = ArgumentParser(description="Script for taking snapper snapshots. Used with pacman hooks.")
parser.add_argument(
"--ini", dest="snap_pac_ini", type=Path,
default=Path("/etc/snap-pac.ini"), help="snap-pac ini file path"
)
parser.add_argument(
"--conf", dest="snapper_conf_file", type=Path,
default=Path("/etc/conf.d/snapper"), help="snapper configuration file path"
)
return parser.parse_args()
if __name__ == "__main__":
if check_skip():
logging.warning("snapper snapshots skipped")
quit()
args = parse_args()
config_processor = ConfigProcessor(args.snap_pac_ini)
chroot = os.stat("/") != os.stat("/proc/1/root/.")
for snapper_config in get_snapper_configs(args.snapper_conf_file):
data = config_processor(snapper_config)
if data["snapshot"]:
num = SnapperCmd(snapper_config, data["cleanup_algorithm"], data["description"], chroot, data["userdata"])()
logging.info(f"==> {snapper_config}: {num}")

2
snapper-pac-grub Executable file
View file

@ -0,0 +1,2 @@
#!/bin/bash
/usr/bin/grub-mkconfig -o /boot/grub/grub.cfg 2> /dev/null

11
snapper-pac-post Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
PACMAN_CMD=$(ps -C pacman -o args=)
PREFILE=/etc/snapper/.snap-pac-pre
if [ -f $PREFILE ]; then
SNAPPERPACPRE=$(cat $PREFILE)
snapper --config root create --cleanup-algorithm number --type post --pre-number $SNAPPERPACPRE --description "$PACMAN_CMD"
rm $PREFILE
else
echo "WARNING: $PREFILE does not exist, so not performing post snapshot. If you are initially installing snap-pac, this is normal."
fi

4
snapper-pac-pre Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
PACMAN_CMD=$(ps -C pacman -o args=)
snapper --config root create --type pre --cleanup-algorithm number --print-number --description "$PACMAN_CMD" > /etc/snapper/.snap-pac-pre

View file

@ -1,122 +0,0 @@
import tempfile
from pathlib import Path
import os
import pytest
from scripts.snap_pac import check_skip, ConfigProcessor, get_snapper_configs, Prefile, SnapperCmd
@pytest.mark.parametrize("snapper_cmd, actual_cmd", [
(
SnapperCmd("root", "pre", "number", "foo"),
"snapper --config root create --cleanup-algorithm number --print-number --description \"foo\" --type pre"
),
(
SnapperCmd("root", "post", "number", "bar", False, 1234),
"snapper --config root create --cleanup-algorithm number --print-number"
" --description \"bar\" --pre-number 1234 --type post"
),
(
SnapperCmd("root", "post", "number", "bar", True, 1234),
"snapper --no-dbus --config root create --cleanup-algorithm number --print-number"
" --description \"bar\" --pre-number 1234 --type post"
),
(
SnapperCmd("root", "post", "number", "bar", False, 1234, "important=yes"),
"snapper --config root create --cleanup-algorithm number --print-number"
" --description \"bar\" --userdata \"important=yes\" --pre-number 1234 --type post"
),
(
SnapperCmd("root", "post", "number", "bar", False, 1234, "foo=bar,important=yes"),
"snapper --config root create --cleanup-algorithm number --print-number"
" --description \"bar\" --userdata \"foo=bar,important=yes\" --pre-number 1234 --type post"
),
(
SnapperCmd("root", "post", "number", "bar", False, None, "foo=bar,important=yes"),
"snapper --config root create --cleanup-algorithm number --print-number"
" --description \"bar\" --userdata \"foo=bar,important=yes\" --type single"
)
])
def test_snapper_cmd(snapper_cmd, actual_cmd):
assert str(snapper_cmd) == actual_cmd
def test_get_snapper_configs():
with tempfile.NamedTemporaryFile("w", delete=False) as f:
f.write("## Path: System/Snapper\n")
f.write("\n")
f.write("## Type: string\n")
f.write("## Default: \"\"\n")
f.write("# List of snapper configurations.\n")
f.write("SNAPPER_CONFIGS=\"home root foo bar\"\n")
name = f.name
assert get_snapper_configs(Path(name)) == ["home", "root", "foo", "bar"]
def test_skip_snap_pac():
os.environ["SNAP_PAC_SKIP"] = "y"
assert check_skip() is True
@pytest.mark.parametrize("section, command, packages, snapshot_type, result", [
(
"root", "foo", ["bar"], "pre",
{"description": "foo", "cleanup_algorithm": "number", "userdata": "", "snapshot": True}
),
(
"root", "pacman -Syu", [], "pre",
{"description": "pacman -Syu", "cleanup_algorithm": "number", "userdata": "important=yes", "snapshot": True}
),
(
"mail", "pacman -Syu", [], "pre",
{"description": "pacman -Syu", "cleanup_algorithm": "number", "userdata": "", "snapshot": False}
),
(
"home", "pacman -Syu", [], "pre",
{"description": "pac", "cleanup_algorithm": "number", "userdata": "foo=bar,requestid=42", "snapshot": True}
),
(
"home", "pacman -Syu", [], "post",
{"description": "a r", "cleanup_algorithm": "number", "userdata": "foo=bar,requestid=42", "snapshot": True}
),
(
"myconfig", "pacman -S linux", ["linux"], "post",
{"description": "linux", "cleanup_algorithm": "timeline",
"userdata": "foo=bar,important=yes,requestid=42", "snapshot": True}
),
])
def test_config_processor(section, command, packages, snapshot_type, result):
with tempfile.NamedTemporaryFile("w", delete=False) as f:
f.write("[root]\n")
f.write("important_commands = [\"pacman -Syu\"]\n\n")
f.write("[home]\n")
f.write("snapshot = True\n")
f.write("desc_limit = 3\n")
f.write("post_description = a really long description\n")
f.write("userdata = [\"foo=bar\", \"requestid=42\"]\n\n")
f.write("[myconfig]\n")
f.write("snapshot = True\n")
f.write("cleanup_algorithm = timeline\n")
f.write("important_packages = [\"linux\", \"linux-lts\"]\n")
f.write("userdata = [\"foo=bar\", \"requestid=42\"]\n")
name = f.name
config_processor = ConfigProcessor(name, snapshot_type, command, packages)
assert config_processor(section) == result
def test_prefile_read_none():
prefile = Prefile("root", "pre")
assert prefile.read() is None
def test_prefile_read():
prefile = Prefile("root", "pre")
prefile.write("1234")
prefile = Prefile("root", "post")
assert prefile.read() == "1234"
def test_no_prefile():
prefile = Prefile("foo-pre-file-not-found", "post")
assert prefile.read() is None