Commit b5c03a00 authored by Tinu Weber's avatar Tinu Weber

Initial commit: Add script, readme and license

parents
This diff is collapsed.
zr
==
**zr** is a small, dead-simple script to manage a personal, remote Arch Linux
package repository. It was originally written to manage the [*zuepfe*
repository][url:zuepferepo] (hence the name), but should now be usable for any
repository.
It works by mounting the remote storage location via sshfs and using pacman's
`repo-add`/`repo-remove` utilities to modify the package database.
Installation
------------
The script can be executed as-is. You may want to drop it somewhere within your
`PATH` (e.g. `~/.local/bin` or `/usr/local/bin`).
### Dependencies
* **sshfs** (for mounting the remote)
* **gnupg** (for signing packages and the database)
* **pacman** (for `repo-add`/`repo-remove`)
* **coreutils** (for all the rest)
Configuration
-------------
`zr` requires the configuration file `$XDG_CONFIG_HOME/zr/config` to be present.
An example configuration file could be the following:
REPOSERVER=example.org
REPOPATH=www
REPONAME=myrepo
REPOARCH=x86_64
This will cause sshfs to mount the remote directory `~/www/myrepo/os/x86_64` for
all interactions with the repository. Note that `REPOPATH` and `REPOARCH` are
optional (by default `~` and `x86_64`, respectively).
Usage
-----
### Add a new package to the repository
zr add /path/to/my_package-0.1-1-x86_64.pkg.tar.xz
### Remove a package from the repository
zr rm my_package
### Get help
zr -h
[url:zuepferepo]: http://archlinux.zuepfe.net/zuepfe
#!/usr/bin/env sh
set -e
# Environment:
HOME="${HOME:-$(getent passwd "$(id -un)" | cut -d: -f6)}"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
# Booleans and error codes:
FALSE=0
TRUE=1
E_SUCCESS=0
E_USER=1
E_FILESYSTEM=2
E_NETWORK=3
E_GPG=4
E_REPO=5
E_CONFIG=6
E_INTERNAL=127
# Default values:
runname="$(basename "$0")"
mountdir=zrmount
config="$XDG_CONFIG_HOME/zr/config"
force=$FALSE
# Default configuration:
REPOSERVER='' # mandatory
REPOPATH='~' # optional
REPONAME='' # mandatory
REPOARCH=x86_64 # optional
strerror()
{
case "$1" in
($E_SUCCESS) printf 'E_SUCCESS' ;;
($E_USER) printf 'E_USER' ;;
($E_FILESYSTEM) printf 'E_FILESYSTEM' ;;
($E_NETWORK) printf 'E_NETWORK' ;;
($E_GPG) printf 'E_GPG' ;;
($E_REPO) printf 'E_REPO' ;;
($E_CONFIG) printf 'E_CONFIG (%s)' "$config" ;;
(*) printf 'E_INTERNAL' ;;
esac
}
warn()
{
printf 'WARN: ' >&2
printf "$@" >&2
echo >&2
}
error()
{
printf 'ERROR: ' >&2
printf "$@" >&2
echo >&2
}
die()
{
retval=$(($1 + 0)); shift
if [ -n "$1" ]; then
if [ $retval -ne $E_USER ]; then
printf '%s: ' "$(strerror $retval)" >&2
fi
printf "$@" >&2
echo >&2
else
printf '%s\n' "$(strerror $retval)" >&2
fi
if [ $retval -eq $E_USER ]; then
printf "Run with \`-h\` for help.\n" >&2
fi
exit $retval
}
die_mounted()
{
zr_umount
die "$@"
}
zr_mount()
{
mkdir -v "$mountdir" || die $E_FILESYSTEM
if ! sshfs "$reporemote" "$mountdir"; then
rmdir -v "$mountdir" || die $E_FILESYSTEM
die $E_NETWORK \
'Failed to mount remote filesystem %s via sshfs' "$reporemote"
fi
if ! [ -w "$mountdir" ]; then
die_mounted $E_FILESYSTEM '%s: Write permission denied' "$mountdir"
fi
}
zr_umount()
{
if ! fusermount3 -u "$mountdir"; then
die $E_NETWORK 'Failed to unmount remote filesystem on %s' "$mountdir"
fi
rmdir -v "$mountdir" || die $E_FILESYSTEM
}
zr_add_pkgfile()
{
# Get files:
pkgfile="$1"
case "$pkgfile" in (/*) ;; (*) pkgfile="./$pkgfile" ;; esac
pkgfilesig="$pkgfile".sig
tgt_pkgfile="$mountdir/$(basename "$pkgfile")"
# Check file:
if ! [ -f "$pkgfile" ]; then
warn '%s: No such file (ignoring)' "$pkgfile"
return
fi
if ! [ -r "$pkgfile" ]; then
warn '%s: Read permission denied (ignoring)' "$pkgfile"
return
fi
if [ -e "$tgt_pkgfile" ] && [ $force -ne $TRUE ]; then
printf '%s: File already exists (ignoring)\n' "$tgt_pkgfile"
printf "Run with \`-f\` to force addition\n"
return
fi
# Sign file:
gpg -o "$pkgfilesig" -b "$pkgfile" || die_mounted $E_GPG
# Copy files:
cp -v "$pkgfile" "$pkgfilesig" "$mountdir/" || die_mounted $E_FILESYSTEM
# Add to repository:
repo-add -s -v -n "$mountdir/$REPONAME.db.tar.xz" "$pkgfile" || die_mounted $E_REPO
}
zr_add()
{
# Options:
while getopts :fh opt; do
case "$opt" in
(f) force=$TRUE ;;
(h) print_help; exit $E_SUCCESS ;;
('?') die $E_USER 'Unknown option -%s' "$OPTARG" ;;
(*) die $E_INTERNAL 'Unhandled option -%s' "$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
# Files:
test -n "$1" || die $E_USER 'Please specify at least one package file'
# Execute addition:
zr_mount
for pkgfile in "$@"; do
zr_add_pkgfile "$pkgfile"
done
rm -fv "$mountdir"/*.old "$mountdir"/*.old.sig || die_mounted $E_FILESYSTEM
zr_umount
}
zr_rm_pkgname()
{
# Get name:
pkgname="$1"
# Remove from repository:
repo-remove -s -v "$mountdir/$REPONAME.db.tar.xz" "$pkgname" || die_mounted $E_REPO
# Remove files:
for ext in '' .sig; do
ext=".pkg.tar.xz$ext"
rm -iv "$mountdir/$pkgname"-*-*-*"$ext" || true
done
}
zr_rm()
{
# Options:
while getopts :fh opt; do
case "$opt" in
(f) force=$TRUE ;;
(h) print_help; exit $E_SUCCESS ;;
('?') die $E_USER 'Unknown option -%s' "$OPTARG" ;;
(*) die $E_INTERNAL 'Unhandled option -%s' "$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
# Names:
test -n "$1" || die $E_USER 'Please specify a package name'
# Execute removal:
zr_mount
for pkgname in "$@"; do
zr_rm_pkgname "$pkgname"
done
rm -fv "$mountdir"/*.old "$mountdir"/*.old.sig || die_mounted $E_FILESYSTEM
zr_umount
}
print_help()
{
cat <<- EOF
$runname: Simple helper to manage a personal remote Arch Linux package repository
Usage: $runname [OPTIONS] ACTION
Options:
-c CONFIG Use configuration file CONFIG [default=$config]
-h Display this help message and exit
-m MOUNTDIR Use MOUNTDIR as local mountpoint [default=$mountdir]
Actions:
add [-f] PKGFILE Add package file PKGFILE to the repository
rm PKGNAME Remove package named PKGNAME from the repository
Configuration file variables ($config):
REPOSERVER Remote server (use ~/.ssh/config to set username/port/etc.)
REPOPATH (optional) Path on the remote server [default=$REPOPATH]
REPONAME Repository name
REPOARCH (optional) OS architecture [default=$REPOARCH]
The directory will be mounted with sshfs from:
\$REPOSERVER:\$REPOPATH/\$REPONAME/os/\$REPOARCH
EOF
}
# Read options:
while getopts :c:hm: opt; do
case "$opt" in
(c) config="$OPTARG" ;;
(h) print_help; exit $E_SUCCESS ;;
(m) mountdir="$OPTARG" ;;
(:) die $E_USER 'Missing argument for -%s' "$OPTARG" ;;
('?') die $E_USER 'Unknown option -%s' "$OPTARG" ;;
(*) die $E_INTERNAL 'Unhandled option -%s' "$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
# Check options:
test -n "$mountdir" || die $E_USER 'Please specify a non-empty mount directory'
test -n "$config" || die $E_USER 'Please specify a non-empty configuration filename'
test -f "$config" || die $E_FILESYSTEM '%s: File not found' "$config"
test -r "$config" || die $E_FILESYSTEM '%s: Read permission denied' "$config"
# Read configuration:
. "$config"
# Check configuration:
test -n "$REPOSERVER" || die $E_CONFIG 'empty or unset REPOSERVER'
test -n "$REPOPATH" || die $E_CONFIG 'empty REPOPATH'
test -n "$REPONAME" || die $E_CONFIG 'empty or unset REPONAME'
test -n "$REPOARCH" || die $E_CONFIG 'empty REPOARCH'
reporemote="$REPOSERVER:$REPOPATH/$REPONAME/os/$REPOARCH"
# Read action:
test -n "$1" || die $E_USER 'Please specify an action'
action="$1"; shift
case "$action" in
(add|rm) ;;
(*) die $E_USER 'Unknown action: %s' "$action" ;;
esac
runname="$runname $action"
# Execute action:
case "$action" in
(add) zr_add "$@" ;;
(rm) zr_rm "$@" ;;
(*) die $E_INTERNAL 'Unhandled action: %s' "$action"
esac
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment