initial commit
This commit is contained in:
commit
d73ec91cf2
99 changed files with 8117 additions and 0 deletions
0
.builddeb
Normal file
0
.builddeb
Normal file
0
.publish-git
Normal file
0
.publish-git
Normal file
0
.update
Normal file
0
.update
Normal file
29
LICENSE
Normal file
29
LICENSE
Normal file
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Jakobus Schürz
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
59
Makefile
Normal file
59
Makefile
Normal file
|
@ -0,0 +1,59 @@
|
|||
DIR=$(shell basename $(CURDIR))
|
||||
|
||||
VERSION=`gawk '$$1 == "Version:"{print $$2}' $(DIR)/DEBIAN/control`
|
||||
ARCH=`gawk '$$1 == "Architecture:"{print $$2}' $(DIR)/DEBIAN/control`
|
||||
COMMIT = $(shell date "+xe%Y%m%d_%H%M%S")
|
||||
|
||||
SUBDIRS := $(shell find $(DIR) -type d -print)
|
||||
FILTER := $(abspath .git% %.deb .publish-git .builddeb %.swp Makefile)
|
||||
FILTERORIG := $(abspath .git% %.deb .publish-git .builddeb %.swp Makefile) $(shell test -e noupdate.files && cat noupdate.files) /DEBIAN%
|
||||
FILES := $(filter-out $(FILTER), $(abspath $(shell find . -mindepth 1 -type f -print) ))
|
||||
ORIGS := $(filter-out $(FILTERORIG), $(realpath $(subst ./$(DIR),,$(shell sudo find . -mindepth 2 -type f -print))))
|
||||
#FILES := $(filter-out $(FILTER), $(abspath $(shell find . -mindepth 1 -type f -exec echo {} \;) ))
|
||||
#ORIGS := $(filter-out $(FILTERORIG), $(realpath $(subst ./$(DIR),,$(shell sudo find . -mindepth 2 -type f -exec echo {] \;))))
|
||||
FILESGIT := $(filter-out $(abspath .git%), $(abspath $(shell find . -mindepth 1 -type f -print)))
|
||||
|
||||
#all: $(DIR)/DEBIAN/control
|
||||
|
||||
#$(DIR)/DEBIAN/control: $(FILES)
|
||||
|
||||
-include Makefile.repo
|
||||
|
||||
all: .builddeb
|
||||
@#echo FILE $(FILESGIT)
|
||||
|
||||
.builddeb: $(FILES)
|
||||
@#echo FILT $(FILTER)
|
||||
@#echo FILE $(FILES)
|
||||
@echo `gawk -f ../increment.awk $(DIR)/DEBIAN/control`
|
||||
sed -e "s/^Version:.*/`gawk -f ../increment.awk $(DIR)/DEBIAN/control`/" $(DIR)/DEBIAN/control > $(DIR)/DEBIAN/control.tmp
|
||||
mv $(DIR)/DEBIAN/control.tmp $(DIR)/DEBIAN/control
|
||||
fakeroot dpkg-deb --build $(DIR) "$(DIR)_$(VERSION)_$(ARCH).deb"
|
||||
ln -sf "$(DIR)_$(VERSION)_$(ARCH).deb" "$(DIR)_current_$(ARCH).deb"
|
||||
aptly repo add xundeenergie "$(DIR)_$(VERSION)_$(ARCH).deb"
|
||||
touch .builddeb
|
||||
|
||||
buildonlydeb: $(FILES)
|
||||
@#echo FILT $(FILTER)
|
||||
@#echo FILE $(FILES)
|
||||
@echo `gawk -f ../increment.awk $(DIR)/DEBIAN/control`
|
||||
sed -e "s/^Version:.*/`gawk -f ../increment.awk $(DIR)/DEBIAN/control`/" $(DIR)/DEBIAN/control > $(DIR)/DEBIAN/control.tmp
|
||||
mv $(DIR)/DEBIAN/control.tmp $(DIR)/DEBIAN/control
|
||||
fakeroot dpkg-deb --build $(DIR) "$(DIR)_$(VERSION)_$(ARCH).deb"
|
||||
ln -sf "$(DIR)_$(VERSION)_$(ARCH).deb" "$(DIR)_current_$(ARCH).deb"
|
||||
|
||||
.update: $(ORIGS)
|
||||
@#for i in $(ORIGS); do $$i;done
|
||||
@echo "$(ORIGS)"
|
||||
@echo "Copy originals to $(DIR)"
|
||||
@for i in $(ORIGS); do sudo cp -uv $$i $(DIR)$$i;done
|
||||
touch .update
|
||||
|
||||
.publish-git: $(FILESGIT)
|
||||
fakeroot git add .
|
||||
fakeroot git commit -m $(COMMIT) && git push origin master || exit 0
|
||||
touch .publish-git
|
||||
|
||||
|
||||
pull-git:
|
||||
git pull origin || exit 0
|
30
Makefile.old
Normal file
30
Makefile.old
Normal file
|
@ -0,0 +1,30 @@
|
|||
DIR=$(shell basename $(CURDIR))
|
||||
|
||||
VERSION=`gawk '$$1 == "Version:"{print $$2}' $(DIR)/DEBIAN/control`
|
||||
ARCH=`gawk '$$1 == "Architecture:"{print $$2}' $(DIR)/DEBIAN/control`
|
||||
COMMIT = $(shell date "+xe%Y%m%d_%H%M%S")
|
||||
|
||||
SUBDIRS := $(shell find $(DIR) -type d -print)
|
||||
FILTER := $(abspath .git% %.deb .%)
|
||||
FILTERORIG := $(abspath .git% %.deb) /DEBIAN%
|
||||
FILES := $(filter-out $(FILTER), $(abspath $(shell find . -mindepth 1 -type f -print)))
|
||||
ORIGS := $(filter-out $(FILTERORIG), $(realpath $(subst ./$(DIR),,$(shell find . -mindepth 2 -type f -print))))
|
||||
FILESGIT := $(filter-out $(abspath .git%), $(abspath $(shell find . -mindepth 1 -type f -print)))
|
||||
|
||||
all: $(DIR)/DEBIAN/control
|
||||
|
||||
$(DIR)/DEBIAN/control: $(FILES)
|
||||
echo DIR $(DIR)
|
||||
sed -e "s/^Version:.*/`gawk -f ../increment.awk $(DIR)/DEBIAN/control`/" $(DIR)/DEBIAN/control > $(DIR)/DEBIAN/control.tmp
|
||||
mv $(DIR)/DEBIAN/control.tmp $(DIR)/DEBIAN/control
|
||||
sudo dpkg-deb --build $(DIR) "$(DIR)_$(VERSION)_$(ARCH).deb"
|
||||
aptly repo add xundeenergie "$(DIR)_$(VERSION)_$(ARCH).deb"
|
||||
|
||||
update:
|
||||
for i in $(ORIGS); do sudo cp -u $$i $(DIR)$$i;done
|
||||
|
||||
publish-git: $(FILESGIT)
|
||||
sudo git add .
|
||||
git commit -m $(COMMIT)
|
||||
git push origin master
|
||||
|
161
README.md
Executable file
161
README.md
Executable file
|
@ -0,0 +1,161 @@
|
|||
# mkbackup-btrfs
|
||||
Make snapshots recursively from btrfs-subvolumes
|
||||
This scripts are written in python3 and replace https://github.com/xundeenergie/mkbtrbackup
|
||||
|
||||
|
||||
## Automatic installation with script
|
||||
|
||||
Start a debian-live session. Download a netboot.iso or any other stretch-live.iso
|
||||
Take care, that you've installed btrfs-progs and kernel in the highest possible version.
|
||||
Download and install the package mkbackup-btrfs.deb (always a symlink to the latest package) from https://github.com/xundeenergie/mkbackup-btrfs
|
||||
Or just download and make it executeable
|
||||
wget https://raw.githubusercontent.com/xundeenergie/mkbackup-btrfs/master/mkbackup-btrfs/usr/local/bin/create-btrfs-subs.sh
|
||||
chmod a+x create-btrfs-subs-sh
|
||||
|
||||
Then create a new as big as possible partition and format it with btrfs.
|
||||
|
||||
If you want to use UEFI, your partition-table must be GPT. For UEFI you need an extra partition for ESP (Efi System Partition). Just lool on other places how to format a Disk for the usage with (U)EFI.
|
||||
|
||||
If you've created a big btrfs-partition, mount it in your live-system:
|
||||
mount /dev/sdXY /mnt -osubvolume=/,compress
|
||||
|
||||
X is your drive, Y is the number oft the btrfs-partition on drive X.
|
||||
|
||||
then change to /mnt
|
||||
cd /mnt
|
||||
|
||||
and run the script
|
||||
sudo /path/to/download/create-btrfs-subs.sh
|
||||
|
||||
/path/to/download is where you've downloaded the script before.
|
||||
|
||||
You will end up in a chroot of your new installed ground-system. It is not bootable now.
|
||||
Install a kernel, tzdata, initramfs, grub or refind, install other packages you need (firmware, network-manager, desktops...)
|
||||
|
||||
|
||||
## Manually creation of the subvolume-structure, no installation!
|
||||
You have to prepare your installation with some subvolumes.
|
||||
|
||||
First create a directory
|
||||
|
||||
mkdir -p /var/cache/btrfs_pool_SYSTEM
|
||||
|
||||
and
|
||||
|
||||
mkdir -p /var/cache/backup
|
||||
|
||||
The first is for the local HD, to mount the whole btrfs-partition, the btrfs-pool.
|
||||
The second one is for the external HD to save the backup.
|
||||
This two directories are hardcoded for the default-configuration in the python-skript.
|
||||
|
||||
Mount your btrfs-partition to /var/cache/btrfs_pool_SYSTEM
|
||||
|
||||
mount -t btrfs -ocompress=lzo /dev/sdXY /var/cache/btrfs_pool_SYSTEM
|
||||
|
||||
You neet two major subvolumes.
|
||||
|
||||
One for your system, which is snapshotted on every upgrade/update, on successfull boots and so on.
|
||||
The other one for data you will need accurat even if you boot from an older snapshot (recover your system, you need /home, /var/spool accurat - it's user-data!).
|
||||
|
||||
The first is called for example: "@debian"
|
||||
The second one is hardcoded with "`__ALWAYSCURRENT__`"
|
||||
|
||||
```
|
||||
btrfs subvol create /var/cache/btrfs_pool_SYSTEM/@debian
|
||||
btrfs subvol create /var/cache/btrfs_pool_SYSTEM/__ALWAYSCURRENT__
|
||||
```
|
||||
|
||||
The system mounts the default-subvolume on bootup. So be sure, that @debian is your default-subvolume.
|
||||
prepare your /etc/fstab to mount the always current subvolumes from `__ALWAYSCURRENT__`
|
||||
Create the following subvolumes there:
|
||||
home
|
||||
opt
|
||||
srv
|
||||
usr-local
|
||||
var-cache
|
||||
var-lib-mpd
|
||||
var-lib-named
|
||||
var-log
|
||||
var-opt
|
||||
var-spool
|
||||
var-spool-dovecot
|
||||
var-tmp
|
||||
var-www
|
||||
|
||||
Look at the fstab-example for mounting all this subvolumes.
|
||||
|
||||
Copy your data to this subvolumes
|
||||
|
||||
cp --reflink=always -ar source destination
|
||||
|
||||
Reboot an check if all this subvolumes are mounted correctly. You can clean the original directories in @debian while they are overmounted with the new ones, if you go to /var/cache/btrfs_pool_SYSTEM/@debian/sub/vol/ume and delete it there.
|
||||
|
||||
Be carefull. If you copy the systemd-units to your system, the timers and units are enabled!! Disable all the snapshotting with:
|
||||
|
||||
systemctl stop mkbackup.target
|
||||
|
||||
and disable it
|
||||
|
||||
systemctl disable mkbackup.target
|
||||
|
||||
|
||||
Enable and start it if all the data and subvolume-structure is correct and working.
|
||||
|
||||
You can make your first snapshot with:
|
||||
|
||||
systemctl start mkbackup@manually.service
|
||||
|
||||
##mlocate:
|
||||
To avoid, that mlocate searches the backups, edit its configuration
|
||||
|
||||
/etc/updatedb.conf
|
||||
PRUNE_BIND_MOUNTS="yes"
|
||||
# PRUNENAMES=".git .bzr .hg .svn"
|
||||
PRUNEPATHS="/tmp /var/spool /media /backup /backup-local /var/cache/backup /var/cache/btrfs_pool_SYSTEM"
|
||||
PRUNEFS="NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre tmpfs usbfs udf fuse.glusterfs fuse.sshfs curlftpfs fuse.MksnapshotFS.py"
|
||||
|
||||
==Ignore Subvolumes on creating a System-Snapshot
|
||||
If you want to ignore a subvolume from making a backup-Snapshot, you can handle this on several ways.
|
||||
The easiest way is to drop a Drop-In-File for example like this for a guest-session-home:
|
||||
|
||||
editor /etc/mkbackup-btrfs.conf.d/guestsession.conf
|
||||
|
||||
[DEFAULT]
|
||||
ignore = +/home/gast
|
||||
|
||||
The filename doesn't matter. But it must end in ".conf"
|
||||
This can be done by placing such a Drop-In with a debian-package, or manually.
|
||||
You can choose, if it should be ignored generally or only on specific interval-snapshots.
|
||||
The example above appends /home/gast to an existing list of ignored snapshots.
|
||||
|
||||
[DEFAULT]
|
||||
ignore = /home/gast
|
||||
|
||||
this replaces every ignore-list with only this subvolume "/home/gast"
|
||||
the "+" before the subvolume means, that the subvolume(s) given are being appended to a existing list. Without a "+", the list is replaced by the given.
|
||||
|
||||
If you want to ignore a specific subvolume additionally on a specific interval (f.e. /var/www should not be backed up on hourly snapshots), place this in a file:
|
||||
|
||||
[hourly]
|
||||
ignore = +/var/www
|
||||
|
||||
You can set the ignore-Option in every section also in /etc/mkbackup-btrfs.conf
|
||||
It works the same way. DEFAULT is valid to every interval, and default-Values get overwritten or extended (missing or given "+") by the interval-sections
|
||||
|
||||
To ignore several subvolumes when using mkbackup-btrfs from commandline, just use the -i option (more than once)
|
||||
|
||||
mkbackup -v -t manually -i /home/guest -i /var/www create SNP @debian
|
||||
|
||||
This overrides settings from config-files.
|
||||
|
||||
|
||||
TODO:
|
||||
- Regular-Expressions for ignoring subvolumes. Test and describe it in Todo
|
||||
|
||||
=Changelog
|
||||
|
||||
18.9.2016:
|
||||
-added experimental new code btrfssubvols.py - not working yet!!
|
||||
-new library in /usr/lib/python3/dist-packages for config-parsing
|
||||
-new Fuse-Filesystem for usermounting the backups and snapshots in $HOME/backup
|
||||
|
5
files/DEBIAN/conffiles
Normal file
5
files/DEBIAN/conffiles
Normal file
|
@ -0,0 +1,5 @@
|
|||
/etc/apt/apt.conf.d/00mksnapshot_btrfs
|
||||
lib/systemd/system/timer-aptupgrade.target
|
||||
lib/systemd/system/timer-aptupgrade.timer
|
||||
lib/systemd/system/timer-plugin.target
|
||||
lib/systemd/system/timer-plugin.timer
|
14
files/DEBIAN/control
Normal file
14
files/DEBIAN/control
Normal file
|
@ -0,0 +1,14 @@
|
|||
Package: mkbackup-btrfs
|
||||
Version: 0.8.48
|
||||
Section: backup
|
||||
Priority: extra
|
||||
Architecture: all
|
||||
Maintainer: Jakobus Schürz <xundeenergie@gmail.com>
|
||||
Homepage: http://github.com/xundeenergie/mksnapshot
|
||||
Provides: mkbackup-btrfs
|
||||
Depends: python3, python3-progressbar, python3-psutil, btrfs-progs, systemd-alt-cron, python3-paramiko, python-paramiko, python-fuse, deb-systemd-helper-new, system-notification
|
||||
Recommends: python3-pip, xe-base-config
|
||||
Suggests: install-on-btrfs
|
||||
Description: Backup-Suite for btrfs written in python3
|
||||
This backup-suite handles all around making backups on btrfs-filesystems, including a gnome-shell-extension
|
||||
This version includes also handling for remote (via ssh) locations for source and/or backup.
|
93
files/DEBIAN/postinst
Executable file
93
files/DEBIAN/postinst
Executable file
|
@ -0,0 +1,93 @@
|
|||
#!/bin/sh
|
||||
|
||||
|
||||
|
||||
# postinst script for webpy-example
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
#_DEB_SYSTEMD_HELPER_DEBUG=1
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
# source debconf library
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
# For user-services only, for earch target an extra section! for system-services user deb-systemd*
|
||||
# put in full unit-name. for example "mkbackup@hourly.service"
|
||||
|
||||
|
||||
case "$1" in
|
||||
|
||||
configure)
|
||||
# normal systemd-units
|
||||
#SERVICES="mkbackup.target backup.automount var-cache-backup.automount btrfs-scrub@var-cache-btrfs_pool_SYSTEM.service"
|
||||
SERVICES="mkbackup.target backup.automount var-cache-backup.automount"
|
||||
for s in $SERVICES; do
|
||||
deb-systemd-helper-new unmask $s >/dev/null || true
|
||||
if deb-systemd-helper-new --quiet was-enabled $s; then
|
||||
# Enables the unit on first installation, creates new
|
||||
# symlinks on upgrades if the unit file has changed.
|
||||
deb-systemd-helper-new enable $s >/dev/null || true
|
||||
else
|
||||
# Update the statefile to add new symlinks (if any), which need to be
|
||||
# cleaned up on purge. Also remove old symlinks.
|
||||
deb-systemd-helper-new update-state $s >/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# instantiated systemd-units
|
||||
|
||||
DEVICE=$(awk '$9 == "btrfs" && $5 == "/" {gsub("/dev/","",$10);print $10}' /proc/self/mountinfo)
|
||||
ROT=$(cat /sys/block/$(printf '%s' "$DEVICE" | sed 's/[0-9]//g')/queue/rotational)
|
||||
if test $ROT -eq 0; then
|
||||
INTERVALS="hourly daily weekly aptupgrade plugin afterboot manually"
|
||||
else
|
||||
INTERVALS="aptupgrade plugin manually"
|
||||
fi
|
||||
CINT=$(for i in $INTERVALS;do echo "mkbackup@${i}.service" ; done)
|
||||
SERVICES="btrfs-scrub@var-cache-btrfs_pool_SYSTEM.service mkbackup-conf@mkbackup\x2dbtrfs.path mkbackup-conf@mkbackup\x2dbtrfs.service $CINT"
|
||||
|
||||
for s in $SERVICES; do
|
||||
#deb-systemd-helper-new-xe unmask $s >/dev/null || true
|
||||
deb-systemd-helper-new unmask $s || true
|
||||
# was-enabled defaults to true, so new installations run enable.
|
||||
echo "enable $s"
|
||||
if deb-systemd-helper-new --quiet was-enabled $s; then
|
||||
# Enables the unit on first installation, creates new
|
||||
# symlinks on upgrades if the unit file has changed.
|
||||
deb-systemd-helper-new enable $s >/dev/null || true
|
||||
else
|
||||
# Update the statefile to add new symlinks (if any), which need to be
|
||||
# cleaned up on purge. Also remove old symlinks.
|
||||
deb-systemd-helper-new update-state $s >/dev/null || true
|
||||
fi
|
||||
done
|
||||
;;
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
|
||||
exit 0
|
||||
|
||||
|
53
files/DEBIAN/postrm
Executable file
53
files/DEBIAN/postrm
Executable file
|
@ -0,0 +1,53 @@
|
|||
#! /bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# In case this system is running systemd, we make systemd reload the unit files
|
||||
# to pick up changes.
|
||||
if [ -d /run/systemd/system ] ; then
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# System-Services:
|
||||
# put in full unit-name. for example "mkbackup@hourly.service"
|
||||
#SERVICES="mkbackup.target backup.automount var-cache-backup.automount btrfs-scrub@var-cache-btrfs_pool_SYSTEM.service mkbackup@manually.service mkbackup@aptupgrade.service mkbackup@daily.service mkbackup@weekly.service mkbackup@monthly.service mkbackup@plugin.service mkbackup@manually.service"
|
||||
SERVICES="mkbackup.target backup.automount var-cache-btrfs_pool_SYSTEM.automount"
|
||||
INTERVALS="hourly daily weekly aptupgrade plugin afterboot manually"
|
||||
CINT="$(for i in $INTERVALS;do echo "mkbackup@${i}.service" ; done)"
|
||||
INSTSERVICES="btrfs-scrub@var-cache-btrfs_pool_SYSTEM.service mkbackup-conf@mkbackup\x2dbtrfs.path mkbackup-conf@mkbackup\x2dbtrfs.service"
|
||||
|
||||
|
||||
case "$1" in
|
||||
purge)
|
||||
# systemctl disable $SERVICES $INSTSERVICES
|
||||
# for s in $INSTSERVICES;do
|
||||
# rm -rf /lib/systemd/system/${s}
|
||||
# done
|
||||
|
||||
if [ -x "/usr/bin/deb-systemd-helper-new" ]; then
|
||||
deb-systemd-helper-new purge $SERVICES $INSTSERVICES $CINT >/dev/null
|
||||
deb-systemd-helper-new unmask $SERVICES $INSTSERVICES $CINT >/dev/null
|
||||
fi
|
||||
;;
|
||||
abort-upgrade)
|
||||
;;
|
||||
remove)
|
||||
# systemctl mask $SERVICES
|
||||
# for s in $INSTSERVICES;do
|
||||
# ln -s /dev/null /lib/systemd/system/${s}
|
||||
# done
|
||||
if [ -x "/usr/bin/deb-systemd-helper-new" ]; then
|
||||
deb-systemd-helper-new mask $SERVICES $INSTSERVICES $CINT >/dev/null
|
||||
fi
|
||||
;;
|
||||
upgrade|failed-upgrade|abort-install|disappear)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postrm called with unknown argument \`$1'" >&2
|
||||
exit 0
|
||||
;;
|
||||
|
||||
esac
|
2
files/etc/apt/apt.conf.d/00mksnapshot_btrfs
Executable file
2
files/etc/apt/apt.conf.d/00mksnapshot_btrfs
Executable file
|
@ -0,0 +1,2 @@
|
|||
//Take a snapshot befor upgrading the system
|
||||
DPkg::Pre-Invoke {"/bin/systemctl start timer-aptupgrade.timer";};
|
2
files/etc/mkbackup-btrfs.conf.d/docker.conf
Normal file
2
files/etc/mkbackup-btrfs.conf.d/docker.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
[DEFAULT]
|
||||
ignore=+var-lib-docker
|
34
files/etc/mkbackup-btrfs.conf.d/l10n-de.conf
Normal file
34
files/etc/mkbackup-btrfs.conf.d/l10n-de.conf
Normal file
|
@ -0,0 +1,34 @@
|
|||
[DEFAULT]
|
||||
Description = Erstellt ein Backup
|
||||
|
||||
[dmin]
|
||||
Description = + alle 10 Minuten
|
||||
|
||||
[hourly]
|
||||
Description = + jede Stunde
|
||||
|
||||
[daily]
|
||||
Description = + jeden Tag
|
||||
|
||||
[weekly]
|
||||
Description = + jede Woche
|
||||
|
||||
[monthly]
|
||||
Description = + jedes Monat
|
||||
|
||||
[yearly]
|
||||
Description = + einmal jedes Jahr
|
||||
|
||||
[afterboot]
|
||||
Description = + nach jedem erfolgreichen Boot
|
||||
|
||||
[plugin]
|
||||
Description = + nach jedem Einstecken des externen Backup-Mediums
|
||||
|
||||
[manually]
|
||||
Description = + bei manuellem Aufruf
|
||||
|
||||
[aptupgrade]
|
||||
Description = + vor jedem Systemupgrade oder jeder Paketinstallation
|
||||
|
||||
|
11
files/etc/mkbackup-btrfs.conf.d/live-builds.conf
Normal file
11
files/etc/mkbackup-btrfs.conf.d/live-builds.conf
Normal file
|
@ -0,0 +1,11 @@
|
|||
[daily]
|
||||
ignore=+/home/jakob/src/live-images
|
||||
|
||||
[weekly]
|
||||
ignore=+/home/jakob/src/live-images
|
||||
|
||||
[monthly]
|
||||
ignore=+/home/jakob/src/live-images
|
||||
|
||||
[yearly]
|
||||
ignore=+/home/jakob/src/live-images
|
2
files/etc/mkbackup-btrfs.conf.d/thunderbird-imap.conf
Normal file
2
files/etc/mkbackup-btrfs.conf.d/thunderbird-imap.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
[DEFAULT]
|
||||
ignore=+\.icedove|thunderbird/.*/ImapMail
|
8
files/etc/systemd/system/scripts/btrfs-action.sh
Executable file
8
files/etc/systemd/system/scripts/btrfs-action.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
DEVICE="$1"; shift
|
||||
ACTION="$1"; shift
|
||||
MAINPID="$1"
|
||||
|
||||
[ x"$MAINPID" = "x" ] && exit 0
|
||||
/bin/ps h -o command -p "$MAINPID" && /sbin/btrfs $ACTION cancel "$DEVICE" || exit 0
|
135
files/etc/systemd/system/scripts/mksnapshot-create-volume.sh
Executable file
135
files/etc/systemd/system/scripts/mksnapshot-create-volume.sh
Executable file
|
@ -0,0 +1,135 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Create udev-rule and mount-entry for new backup-volume
|
||||
|
||||
ACTION=$1
|
||||
case $2 in
|
||||
-u)
|
||||
UUID=$3
|
||||
DEV=$(readlink -f /dev/disk/by-uuid/$UUID)
|
||||
PARTUUID="$(blkid /dev/disk/by-uuid/$UUID -o value -s PARTUUID)"
|
||||
;;
|
||||
u-*)
|
||||
UUID=${2#u-}
|
||||
DEV=$(readlink -f /dev/disk/by-uuid/$UUID)
|
||||
PARTUUID="$(blkid /dev/disk/by-uuid/$UUID -o value -s PARTUUID)"
|
||||
;;
|
||||
-p)
|
||||
PARTUUID=$3
|
||||
DEV=$(readlink -f /dev/disk/by-partuuid/$PARTUUID)
|
||||
UUID="$(blkid /dev/disk/by-partuuid/$PARTUUID -o value -s UUID)"
|
||||
;;
|
||||
p-*)
|
||||
PARTUUID=${2#p-}
|
||||
DEV=$(readlink -f /dev/disk/by-partuuid/$PARTUUID)
|
||||
UUID="$(blkid /dev/disk/by-partuuid/$PARTUUID -o value -s UUID)"
|
||||
;;
|
||||
d-*)
|
||||
DEV=${2#d-}
|
||||
UUID="$(blkid $DEV -o value -s UUID)"
|
||||
PARTUUID="$(blkid $DEV -o value -s PARTUUID)"
|
||||
;;
|
||||
*)
|
||||
DEV="$(/bin/systemd-escape -p -u $2)"
|
||||
UUID="$(blkid $DEV -o value -s UUID)"
|
||||
#PARTUUID="$(blkid $DEV -o value -s PARTUUID)"
|
||||
PRE="d-"
|
||||
;;
|
||||
esac
|
||||
|
||||
#DESTUDEV="/tmp/"
|
||||
#DESTSYSTEMD="/tmp/"
|
||||
DESTUDEV="/etc/udev/rules.d/"
|
||||
DESTSYSTEMD="/etc/systemd/system/"
|
||||
|
||||
SYSTEMCTL="/bin/systemctl"
|
||||
|
||||
echo "$ACTION ${DEV} | ${UUID} | ${PARTUUID}"
|
||||
|
||||
sleep 1
|
||||
|
||||
if [ "$DEV"x = "x" ]
|
||||
then
|
||||
TYPE="btrfs"
|
||||
else
|
||||
TYPE="$(blkid $DEV -o value -s TYPE)"
|
||||
echo "T $TYPE | $DEV"
|
||||
fi
|
||||
|
||||
if [ "$PARTUUID"x = "x" ]; then
|
||||
if [ "$UUID"x = "x" ]; then
|
||||
echo "$PARTUUID | $UUID | $DEV is no valid device"
|
||||
exit 3
|
||||
else
|
||||
DUUID="$UUID" #DUUID is uuid which is taken to use
|
||||
SUUID="ID_FS_UUID" #SUUID is the string for the udev-rule it's UUID or PARTUUID
|
||||
ID="uuid" #ID is also for the udev-rule. To look in /dev/disk/by-uuid or /dev/disk/by-partuuid
|
||||
PRE="u-"
|
||||
fi
|
||||
else
|
||||
DUUID="$PARTUUID"
|
||||
SUUID="ID_PART_ENTRY_UUID"
|
||||
ID="partuuid"
|
||||
PRE="p-"
|
||||
fi
|
||||
|
||||
#echo "$DUUID | $SUUID | $ID | $PRE"
|
||||
# Start by udev
|
||||
start () {
|
||||
|
||||
mkdir -p "${DESTSYSTEMD}var-cache-backup.mount.d/"
|
||||
|
||||
echo "[Mount]
|
||||
What=/dev/disk/by-${ID}/${DUUID}
|
||||
" > "${DESTSYSTEMD}var-cache-backup.mount.d/source.conf"
|
||||
|
||||
$SYSTEMCTL daemon-reload
|
||||
|
||||
}
|
||||
|
||||
# Create udev-Rule for new external drive
|
||||
register () {
|
||||
#echo "$UUID"
|
||||
#echo "AAA $(/bin/systemd-escape /dev/disk/by-${ID}/${DUUID}|sed -e 's@\\@\\\\@g')"
|
||||
#echo "ACTION==\"add\", KERNEL==\"sd*\", SUBSYSTEMS==\"usb\", ENV{${SUUID}}==\"$DUUID\", SYMLINK+=\"disk/mars\", TAG+=\"systemd\", ENV{SYSTEMD_WANTS}+=\"mkbackup-external@${PRE}${DUUID}.service\", ENV{SYSTEMD_WANTS}+=\"mkbackup@BKP.target\", ENV{SYSTEMD_WANTS}+=\"smartctl-fast@$(/bin/systemd-escape /dev/disk/by-${ID}/${DUUID}|sed -e 's@\\@\\\\@g').service\"
|
||||
echo "ACTION==\"add\", KERNEL==\"sd*\", SUBSYSTEMS==\"usb\", ENV{${SUUID}}==\"$DUUID\", SYMLINK+=\"disk/mars\", TAG+=\"systemd\", ENV{SYSTEMD_WANTS}+=\"mkbackup-external@${PRE}${DUUID}.service\", ENV{SYSTEMD_WANTS}+=\"mkbackup@BKP.target\", ENV{SYSTEMD_WANTS}+=\"smartctl-fast@$(/bin/systemd-escape /dev/disk/by-${ID}/${DUUID}).service\"
|
||||
|
||||
ACTION==\"remove\", KERNEL==\"sd*\", SUBSYSTEMS==\"usb\", ENV{${SUUID}}=\"$DUUID\", \
|
||||
RUN+=\"${SYSTEMCTL} --no-block stop mkbackup@BKP.target\"" > "${DESTUDEV}99-ext-bkp-volume-${PRE}${DUUID}.rules"
|
||||
}
|
||||
|
||||
|
||||
# delete udev-rule, if external drive is not longer in use for backups.
|
||||
unregister () {
|
||||
[ -e "${DESTUDEV}99-ext-bkp-volume-${PRE}${DUUID}.rules" ] && rm "${DESTUDEV}99-ext-bkp-volume-${PRE}${DUUID}.rules"
|
||||
}
|
||||
|
||||
case $TYPE in
|
||||
btrfs)
|
||||
;;
|
||||
*)
|
||||
echo "$DEV isn't a btrfs-filesystem. Exiting"; exit 1;;
|
||||
esac
|
||||
|
||||
case $ACTION in
|
||||
register)
|
||||
#setup udev-rule for device
|
||||
register
|
||||
;;
|
||||
unregister)
|
||||
#delete udev-rule for device
|
||||
unregister ;;
|
||||
start)
|
||||
#activate device
|
||||
start;;
|
||||
stop)
|
||||
#deactivate device
|
||||
stop ;;
|
||||
*)
|
||||
echo "$ACTION not recognized";
|
||||
exit 2;;
|
||||
esac
|
||||
|
||||
$SYSTEMCTL daemon-reload
|
||||
#/bin/systemctl
|
||||
exit 0
|
9
files/etc/systemd/system/set-environ.service
Normal file
9
files/etc/systemd/system/set-environ.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Set Environment syssubvol for systemd
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/sh -c '/bin/systemctl set-environment SYSSUBVOL=`/usr/bin/syssubvol`'
|
||||
|
||||
[Install]
|
||||
WantedBy=basic.target
|
|
@ -0,0 +1,2 @@
|
|||
[Unit]
|
||||
RequiresMountsFor=/var/log
|
13
files/lib/systemd/system/backup.automount
Normal file
13
files/lib/systemd/system/backup.automount
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Automounting of /backup - activated by backup.path
|
||||
#Before=local-fs.target remote-fs.target
|
||||
#DefaultDependencies = yes
|
||||
After=backup.path
|
||||
|
||||
[Automount]
|
||||
TimeoutIdleSec=15s
|
||||
Where=/backup
|
||||
|
||||
|
||||
#[Install]
|
||||
#WantedBy=mkbackup.target
|
11
files/lib/systemd/system/backup.mount
Normal file
11
files/lib/systemd/system/backup.mount
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=Mounts Backup-Snapshots from System to /backup
|
||||
After=local-fs.target
|
||||
#BindsTo=backup.automount
|
||||
|
||||
[Mount]
|
||||
What=MksnapshotFS.py
|
||||
Where=/backup
|
||||
Type=fuse
|
||||
TimeoutSec=10s
|
||||
Options=noauto,ro,nofail
|
12
files/lib/systemd/system/backup.path
Normal file
12
files/lib/systemd/system/backup.path
Normal file
|
@ -0,0 +1,12 @@
|
|||
[Unit]
|
||||
Description=Start automounter for /backup, if this directory exists
|
||||
PartOf=mkbackup.target
|
||||
#Before=local-fs.target
|
||||
#DefaultDependencies = no
|
||||
|
||||
[Path]
|
||||
PathExists=/backup
|
||||
Unit=backup.automount
|
||||
|
||||
[Install]
|
||||
WantedBy=mkbackup.target
|
41
files/lib/systemd/system/btrfs-balance@.service
Normal file
41
files/lib/systemd/system/btrfs-balance@.service
Normal file
|
@ -0,0 +1,41 @@
|
|||
[Unit]
|
||||
Description=Balance btrfs-filesystem %f
|
||||
|
||||
#ConditionPathExists=!/run/btrfs-actions
|
||||
#ConditionDirectoryNotEmpty=!/run/btrfs-actions
|
||||
#ConditionPathExists=!/run/mkbackup
|
||||
#ConditionDirectoryNotEmpty=!/run/mkbackup
|
||||
ConditionACPower=true
|
||||
|
||||
Conflicts=sleep.target suspend.target shutdown.target
|
||||
Wants=btrfs-scrub@%i.service
|
||||
OnFailure=status-email-root@%n.service
|
||||
Wants=status-email-root@%n.service
|
||||
Before=status-email-root@%n.service
|
||||
|
||||
RefuseManualStart=true
|
||||
|
||||
[Service]
|
||||
RuntimeDirectory=btrfs-actions
|
||||
Type=oneshot
|
||||
#Restart=always
|
||||
|
||||
Nice=12
|
||||
IOSchedulingClass=3
|
||||
#IOSchedulingPriority=
|
||||
CPUSchedulingPolicy=idle
|
||||
|
||||
ExecStartPre=/bin/touch /run/btrfs-actions/balance-systemd.lock
|
||||
#ExecStart=/bin/btrfs balance start -musage=0 -dusage=0 -v %f
|
||||
ExecStart=/bin/btrfs balance start -musage=5 -dusage=5 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -musage=10 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -musage=40 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -musage=60 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -dusage=0 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -dusage=5 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -dusage=10 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -dusage=40 -v %f
|
||||
#ExecStart=/bin/btrfs balance start -dusage=65 -v %f
|
||||
ExecStop=/usr/lib/systemd/scripts/btrfs-action.sh %f balance $MAINPID
|
||||
ExecStopPost=/bin/rm /run/btrfs-actions/balance-systemd.lock
|
||||
|
35
files/lib/systemd/system/btrfs-scrub@.service
Normal file
35
files/lib/systemd/system/btrfs-scrub@.service
Normal file
|
@ -0,0 +1,35 @@
|
|||
[Unit]
|
||||
#Description=Scrub btrfs-filesystem %f %i
|
||||
Description=Scrubs filesystem on %f weekly
|
||||
Wants=btrfs-balance@%i.service
|
||||
After=btrfs-balance@%i.service
|
||||
|
||||
#ConditionPathExists=!/run/btrfs-actions
|
||||
#ConditionDirectoryNotEmpty=!/run/btrfs-actions
|
||||
#ConditionPathExists=!/run/mkbackup
|
||||
#ConditionDirectoryNotEmpty=!/run/mkbackup
|
||||
#ConditionACPower=true
|
||||
|
||||
|
||||
#Wants=btrfs-status-email-root@%i.service
|
||||
#Before=btrfs-status-email-root@%i.service
|
||||
Conflicts=sleep.target suspend.target shutdown.target
|
||||
OnFailure=status-email-root@%n.service
|
||||
|
||||
[Service]
|
||||
RuntimeDirectory=btrfs-actions
|
||||
#Type=oneshot
|
||||
|
||||
Nice=11
|
||||
IOSchedulingClass=3
|
||||
#IOSchedulingPriority=
|
||||
CPUSchedulingPolicy=idle
|
||||
|
||||
ExecStartPre=/bin/touch /run/btrfs-actions/scrub-systemd-%i.lock
|
||||
ExecStart=/bin/btrfs scrub start -Bd %f
|
||||
ExecStop=/usr/lib/systemd/scripts/btrfs-action.sh %f scrub $MAINPID
|
||||
ExecStopPost=/bin/rm /run/btrfs-actions/scrub-systemd-%i.lock
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=timer-weekly.target
|
|
@ -0,0 +1,8 @@
|
|||
[Unit]
|
||||
Description=btrfs-status email for %f to root
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/systemd-btrfs-email root@localhost %i %f
|
||||
#User=nobody
|
||||
Group=systemd-journal
|
11
files/lib/systemd/system/mkbackup-conf@.path
Normal file
11
files/lib/systemd/system/mkbackup-conf@.path
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=Upate /tmp/%I.conf.tmp if /etc/%I* changes
|
||||
|
||||
[Path]
|
||||
PathModified=/etc/%I.conf
|
||||
PathModified=/etc/%I.conf.d/
|
||||
PathChanged=/tmp/%I.conf.tmp
|
||||
Unit=mkbackup-conf@%i.service
|
||||
|
||||
[Install]
|
||||
WantedBy=paths.target
|
9
files/lib/systemd/system/mkbackup-conf@.service
Normal file
9
files/lib/systemd/system/mkbackup-conf@.service
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Create or update temporary conf-file /tmp/%I.conf.tmp for shell-extension
|
||||
|
||||
[Service]
|
||||
#ExecStart=/bin/sh -c "(/usr/local/bin/mkbackup list -i --print-config > /tmp/%I.conf.tmp)"
|
||||
ExecStart=/usr/local/bin/mkbackup list -i --print-config -o /tmp/%I.conf.tmp
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
8
files/lib/systemd/system/mkbackup-external@.service
Normal file
8
files/lib/systemd/system/mkbackup-external@.service
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Unit]
|
||||
Description=Activate external Drive/Partition for mkbackup for device %i
|
||||
Wants=mkbackup@BKP.target
|
||||
Before=mkbackup@BKP.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/lib/systemd/scripts/mksnapshot-create-volume.sh start %i
|
6
files/lib/systemd/system/mkbackup-register@.service
Normal file
6
files/lib/systemd/system/mkbackup-register@.service
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Unit]
|
||||
Description=Activate external Drive/Partition for mkbackup/mksnapshot for device %I.
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/lib/systemd/scripts/mksnapshot-create-volume.sh register %i
|
6
files/lib/systemd/system/mkbackup-unregister@.service
Normal file
6
files/lib/systemd/system/mkbackup-unregister@.service
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Unit]
|
||||
Description=Deactivate external Drive/Partition for mkbackup/mksnapshot for device %I.
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/lib/systemd/scripts/mksnapshot-create-volume.sh unregister %i
|
6
files/lib/systemd/system/mkbackup.target
Normal file
6
files/lib/systemd/system/mkbackup.target
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Unit]
|
||||
Description=Enables all backup.services
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=basic.target
|
13
files/lib/systemd/system/mkbackup@.path
Normal file
13
files/lib/systemd/system/mkbackup@.path
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Activate backup on Device %i
|
||||
|
||||
[Path]
|
||||
PathExists=/dev/disk/by-partuuid/%i
|
||||
#PathExists=/dev/disk/by-uuid/%i
|
||||
#Unit=systemd-cryptsetup@mars.service
|
||||
#Unit=status-email-jakob@%n.service
|
||||
Unit=var-cache-backup@%i.mount
|
||||
#Unit=dev-disk-by\x2dpartuuid-%i.mount
|
||||
|
||||
[Install]
|
||||
WantedBy=paths.target
|
44
files/lib/systemd/system/mkbackup@.service
Normal file
44
files/lib/systemd/system/mkbackup@.service
Normal file
|
@ -0,0 +1,44 @@
|
|||
[Unit]
|
||||
Description=Make %i-backup from system-subvolume
|
||||
|
||||
#ConditionPathExists=!/run/btrfs-actions
|
||||
#ConditionDirectoryNotEmpty=!/run/btrfs-actions
|
||||
#ConditionPathExists=!/run/mkbackup
|
||||
ConditionPathExistsGlob=!/run/mkbackup/*
|
||||
#ConditionPathExists=!/run/btrfs-actions
|
||||
#ConditionDirectoryNotEmpty=!/run/mkbackup
|
||||
|
||||
Requires=set-environ.service
|
||||
Wants=dpkg-get-selection.service
|
||||
#Wants=mkbackup-transfer.service
|
||||
After=set-environ.service dpkg-get-selection.service mkbackup.target
|
||||
#After=set-environ.service dpkg-get-selection.service
|
||||
#Before=mkbackup-transfer.service
|
||||
|
||||
Conflicts=suspend.target shutdown.target sleep.target
|
||||
OnFailure=status-email-root@%n.service
|
||||
|
||||
[Service]
|
||||
#Type=oneshot
|
||||
#Type=simple
|
||||
Type=idle
|
||||
#EnvironmentFile=/etc/mkbtrbackup.conf.d/%i.conf
|
||||
|
||||
#BusName=at.xundeenergie.mkbackup
|
||||
RuntimeDirectory=mkbackup
|
||||
|
||||
Nice=10
|
||||
IOSchedulingClass=3
|
||||
#IOSchedulingPriority=
|
||||
CPUSchedulingPolicy=idle
|
||||
|
||||
ExecStartPre=/bin/sh -c "(/bin/systemctl is-active -q mkbackup.target)"
|
||||
#ExecStartPre=/bin/touch /run/mkbackup-%i/systemd.lock
|
||||
ExecStart=/usr/local/bin/mkbackup -V -v -t %i create SNP
|
||||
#ExecStopPost=-/bin/rm /run/mkbackup-%i/systemd.lock
|
||||
|
||||
KillMode=mixed
|
||||
|
||||
[Install]
|
||||
WantedBy=timer-%i.target
|
||||
DefaultInstance=mkbackup@manually.service
|
5
files/lib/systemd/system/mkbackup@BKP.target
Normal file
5
files/lib/systemd/system/mkbackup@BKP.target
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=Target for services for %i in mkbackup
|
||||
|
||||
Documentation=man:systemd.special(7)
|
||||
StopWhenUnneeded=no
|
8
files/lib/systemd/system/mkbackup@SNP.target
Normal file
8
files/lib/systemd/system/mkbackup@SNP.target
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Unit]
|
||||
Description=Target for services for %i in mkbackup
|
||||
|
||||
Documentation=man:systemd.special(7)
|
||||
StopWhenUnneeded=no
|
||||
|
||||
[Install]
|
||||
WantedBy=local-fs.target
|
5
files/lib/systemd/system/smartctl-fast@.service
Normal file
5
files/lib/systemd/system/smartctl-fast@.service
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=Smartctl Health-Test for %f
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/sbin/smartctl -H %f
|
3
files/lib/systemd/system/timer-aptupgrade.target
Normal file
3
files/lib/systemd/system/timer-aptupgrade.target
Normal file
|
@ -0,0 +1,3 @@
|
|||
[Unit]
|
||||
Description=Triggered by apt
|
||||
StopWhenUnneeded=yes
|
15
files/lib/systemd/system/timer-aptupgrade.timer
Normal file
15
files/lib/systemd/system/timer-aptupgrade.timer
Normal file
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=Runs backup %I before system-update/upgrade with apt
|
||||
#BindsTo=mkbackup@BKP.target
|
||||
PartOf=mkbackup@SNP.target
|
||||
OnFailure=status-email-root@%n.service
|
||||
BindsTo=timer-xe.target
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=1s
|
||||
AccuracySec=2s
|
||||
Unit=timer-aptupgrade.target
|
||||
RemainAfterElapse=false
|
||||
|
||||
[Install]
|
||||
WantedBy=mkbackup@SNP.target
|
3
files/lib/systemd/system/timer-plugin.target
Normal file
3
files/lib/systemd/system/timer-plugin.target
Normal file
|
@ -0,0 +1,3 @@
|
|||
[Unit]
|
||||
Description=Target after successfully plugging in external backup-drive
|
||||
StopWhenUnneeded=yes
|
16
files/lib/systemd/system/timer-plugin.timer
Normal file
16
files/lib/systemd/system/timer-plugin.timer
Normal file
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=Runs backup %I after pluggin in external HD
|
||||
BindsTo=mkbackup@BKP.target
|
||||
#PartOf=mkbackup@BKP.target
|
||||
OnFailure=status-email-root@%n.service
|
||||
RefuseManualStart=yes
|
||||
RefuseManualStop=yes
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=30s
|
||||
AccuracySec=10min
|
||||
Unit=timer-plugin.target
|
||||
RemainAfterElapse=false
|
||||
|
||||
[Install]
|
||||
WantedBy=mkbackup@BKP.target
|
5
files/lib/systemd/system/umount-notify@.service
Normal file
5
files/lib/systemd/system/umount-notify@.service
Normal file
|
@ -0,0 +1,5 @@
|
|||
[Unit]
|
||||
Description=dbus-notification after umount from %I
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/dbus-send --system /at/xundeenergie/notifications at.xundeenergie.notifications.Notification string:"External Disk" string:"%I unmounted" string:"Platte kann ausgesteckt werden"
|
17
files/lib/systemd/system/var-cache-backup.automount
Normal file
17
files/lib/systemd/system/var-cache-backup.automount
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
# Automatically generated by systemd-fstab-generator
|
||||
|
||||
[Unit]
|
||||
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
|
||||
DefaultDependencies=no
|
||||
Conflicts=umount.target
|
||||
Before=umount.target
|
||||
#BindsTo=mkbackup@BKP.target
|
||||
PartOf=mkbackup@BKP.target
|
||||
|
||||
[Automount]
|
||||
Where=/var/cache/backup
|
||||
TimeoutIdleSec=10s
|
||||
|
||||
[Install]
|
||||
WantedBy=mkbackup@BKP.target
|
18
files/lib/systemd/system/var-cache-backup.mount
Normal file
18
files/lib/systemd/system/var-cache-backup.mount
Normal file
|
@ -0,0 +1,18 @@
|
|||
[Unit]
|
||||
SourcePath=/etc/fstab
|
||||
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
|
||||
#Requires=var-cache-backup.automount
|
||||
#After=var-cache-backup.automount
|
||||
PartOf=var-cache-backup.automount
|
||||
#Wants=backup.mount backup.automount
|
||||
#Before=backup.mount
|
||||
#Before=umount-notification@%p.service
|
||||
#Wants=umount-notification@%P.service
|
||||
|
||||
[Mount]
|
||||
#What - drop-in in var-cache-backup.mount.d by mkbackup-start@...service which is started by udev-rule
|
||||
#What=/dev/disk/by-uuid/e75ad421-9cda-4031-a61c-4e99bc882e1c
|
||||
Where=/var/cache/backup
|
||||
Type=btrfs
|
||||
TimeoutSec=10s
|
||||
Options=noauto,user,noatime,nofail,compress=lzo,space_cache,noinode_cache,relatime,subvol=/
|
459
files/usr/bin/MksnapshotFS.py
Executable file
459
files/usr/bin/MksnapshotFS.py
Executable file
|
@ -0,0 +1,459 @@
|
|||
#!/usr/bin/python -u
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2001 Jeff Epler <jepler@unpythonic.dhs.org>
|
||||
# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
|
||||
#
|
||||
# This program can be distributed under the terms of the GNU LGPL.
|
||||
# See the file COPYING.
|
||||
#
|
||||
|
||||
import os, sys
|
||||
from errno import *
|
||||
from stat import *
|
||||
import fcntl
|
||||
# pull in some spaghetti to make this stuff work without fuse-py being installed
|
||||
try:
|
||||
import _find_fuse_parts
|
||||
except ImportError:
|
||||
pass
|
||||
import fuse
|
||||
from fuse import Fuse
|
||||
|
||||
# JS - Snapshotnamehandling, get the current user and so on
|
||||
import getpass
|
||||
import socket
|
||||
|
||||
from time import strptime, strftime
|
||||
from datetime import datetime,date,time,timedelta
|
||||
|
||||
#from mksnapshotconfig import *
|
||||
from mkbackup.mkbackup_btrfs_config import *
|
||||
|
||||
DEBUG = False
|
||||
|
||||
if not hasattr(fuse, '__version__'):
|
||||
raise RuntimeError, \
|
||||
"your fuse-py doesn't know of fuse.__version__, probably it's too old."
|
||||
|
||||
fuse.fuse_python_api = (0, 2)
|
||||
|
||||
fuse.feature_assert('stateful_files', 'has_init')
|
||||
|
||||
def flag2mode(flags):
|
||||
md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
|
||||
m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
|
||||
|
||||
if flags | os.O_APPEND:
|
||||
m = m.replace('w', 'a', 1)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class Xmp(Fuse):
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
|
||||
Fuse.__init__(self, *args, **kw)
|
||||
|
||||
if DEBUG:
|
||||
for i in args: print "ARG: "+i
|
||||
for i in kw: print "KW: "+i
|
||||
|
||||
#get hostname
|
||||
# die auskommentierte if-Funktion liefert hostname.localdomain.
|
||||
# if socket.gethostname().find('.')>=0:
|
||||
# hostname=socket.gethostname()
|
||||
# else:
|
||||
# hostname=socket.gethostbyaddr(socket.gethostname())[0]
|
||||
#socket.gethostname() liefert nur "hostname" ohne .localdomain
|
||||
hostname=socket.gethostname()
|
||||
|
||||
# do stuff to set up your filesystem here, if you want
|
||||
#import thread
|
||||
#thread.start_new_thread(self.mythread, ())
|
||||
|
||||
CONFIG=Config()
|
||||
|
||||
self.root=CONFIG.getStorePath('SNP').encode()
|
||||
self.local=CONFIG.getStorePath('SNP').encode()
|
||||
self.extern=CONFIG.getStorePath('BKP').encode()
|
||||
self.snapshots={}
|
||||
self.direntries=[]
|
||||
Xmp.realpath=""
|
||||
self.USER=getpass.getuser()
|
||||
if os.getuid() == 0:
|
||||
self.uroot = True
|
||||
else:
|
||||
self.uroot = False
|
||||
|
||||
try:
|
||||
self.HOME=os.environ['HOME']
|
||||
except:
|
||||
self.HOME = None
|
||||
self.uroot = True
|
||||
|
||||
# JS - some helpers
|
||||
def translate(self,subvolname,location='loc'):
|
||||
n = len(subvolname.split('.'))
|
||||
ac = ""
|
||||
sn = subvolname.split('.')[0]+'.'
|
||||
if n == 1:
|
||||
#return subvolname+'--'+location
|
||||
return 'CURRENT--'+location
|
||||
elif n == 2:
|
||||
return subvolname+'--'+location
|
||||
elif n == 3:
|
||||
try:
|
||||
sndt = datetime.strptime(subvolname.split('.')[1],"%Y-%m-%d_%H:%M:%S") #snapshot-timestamp
|
||||
except:
|
||||
return subvolname+'--'+location
|
||||
action = subvolname.split('.')[2]
|
||||
if self.uroot:
|
||||
ac='-'+action
|
||||
else:
|
||||
sn = ''
|
||||
if action == 'manually':
|
||||
ac="-"+action
|
||||
elif action == 'aptupgrade':
|
||||
ac="-"+action
|
||||
else:
|
||||
ac=""
|
||||
|
||||
snd = datetime.date(sndt) #snapshot-date
|
||||
if snd == datetime.date(datetime.now()):
|
||||
#today
|
||||
dts = sn+'heute_%H-%M-%S'
|
||||
elif snd == datetime.date(datetime.now()-timedelta(days=1)):
|
||||
#yesterday
|
||||
dts = sn+'gestern_%H-%M-%S'
|
||||
# elif snd == datetime.date(datetime.now()-timedelta(days=2)):
|
||||
# #day before yesterday
|
||||
# dts = 'vorgestern %H-%M-%S'
|
||||
else:
|
||||
#any day else
|
||||
dts = sn+'%Y.%m.%d_%H-%M-%S'
|
||||
if self.uroot:
|
||||
return datetime.strftime(sndt,dts)+ac+'--'+location
|
||||
else:
|
||||
return datetime.strftime(sndt,dts)+'--'+location+ac
|
||||
else:
|
||||
return subvolname+'--'+location
|
||||
|
||||
def __lsnapshots(self):
|
||||
if DEBUG: print "ls snapshots"
|
||||
self.snapshots.clear()
|
||||
for location in self.BDIRS.iterkeys():
|
||||
if DEBUG == True: print 'Scan location '+location+': '+self.BDIRS[location]
|
||||
try:
|
||||
dirents = os.listdir(self.BDIRS[location])
|
||||
except:
|
||||
continue
|
||||
if self.uroot:
|
||||
for entry in dirents:
|
||||
if os.path.exists(self.BDIRS[location]+'/'+entry) \
|
||||
and not os.path.islink(self.BDIRS[location]+'/'+entry):
|
||||
self.snapshots[self.translate(entry,location=location)] \
|
||||
= [self.BDIRS[location], '/'+entry+'/', location]
|
||||
else:
|
||||
for entry in dirents:
|
||||
if os.path.exists(self.BDIRS[location]+'/'+entry+self.HOME) \
|
||||
and not os.path.islink(self.BDIRS[location]+'/'+entry):
|
||||
self.snapshots[self.translate(entry,location=location)] \
|
||||
= [self.BDIRS[location], '/'+entry+self.HOME+'/', location]
|
||||
|
||||
|
||||
def __realpath(self,path):
|
||||
ss = path.split('/')[1]
|
||||
#sv = './'+'/'.join(path.split('/')[2:])
|
||||
sv = '/'.join(path.split('/')[2:])
|
||||
if path == "/" and (self.root in self.BDIRS.values()):
|
||||
return self.root
|
||||
elif ss in self.snapshots:
|
||||
self.root = self.snapshots[ss][0]
|
||||
Xmp.realpath = self.snapshots[ss][0]+self.snapshots[ss][1]
|
||||
return self.snapshots[ss][0]+self.snapshots[ss][1]+sv
|
||||
else:
|
||||
self.root = self.BDIRS['loc']
|
||||
Xmp.realpath = self.root + path
|
||||
return Xmp.realpath
|
||||
|
||||
def __lsdir(self,path):#
|
||||
if DEBUG: print "ls dir"
|
||||
ss = path.split('/')[1]
|
||||
subdir = '/'+'/'.join(path.split('/')[2:])
|
||||
dirents = ['.', '..']
|
||||
#if path == "/" and (self.root.strip('/') == self.BDIRS['loc'].strip('/') or self.root == self.BDIRS['ext'].strip('/')):
|
||||
if path == "/" and (self.root in self.BDIRS.values()):
|
||||
self.root = self.BDIRS['loc']
|
||||
dirents.extend(self.snapshots.keys())
|
||||
elif ss in self.snapshots or path == "./":
|
||||
dirents.extend(os.listdir(self.__realpath(path)))
|
||||
else:
|
||||
self.root = self.BDIRS['loc']
|
||||
dirents.extend(os.listdir(path))
|
||||
return dirents
|
||||
|
||||
|
||||
# def mythread(self):
|
||||
#
|
||||
# """
|
||||
# The beauty of the FUSE python implementation is that with the python interp
|
||||
# running in foreground, you can have threads
|
||||
# """
|
||||
# print "mythread: started"
|
||||
# while 1:
|
||||
# time.sleep(120)
|
||||
# print "mythread: ticking"
|
||||
|
||||
def getattr(self, path):
|
||||
return os.lstat(self.__realpath(path))
|
||||
if path == "/" and (self.root in self.BDIRS.values()):
|
||||
return os.lstat(self.root)
|
||||
else:
|
||||
return os.lstat(self.__realpath(path))
|
||||
|
||||
def readlink(self, path):
|
||||
return os.readlink(self.__realpath(path))
|
||||
|
||||
def readdir(self, path, offset):
|
||||
if path == "/" and (self.root in self.BDIRS.values()):
|
||||
if DEBUG: print path
|
||||
pass
|
||||
if path == "/":
|
||||
self.__lsnapshots()
|
||||
for e in self.__lsdir(path):
|
||||
yield fuse.Direntry(e)
|
||||
|
||||
def unlink(self, path):
|
||||
return -EROFS
|
||||
#os.unlink("." + path)
|
||||
|
||||
def rmdir(self, path):
|
||||
return -EROFS
|
||||
#os.rmdir("." + path)
|
||||
|
||||
def symlink(self, path, path1):
|
||||
return -EROFS
|
||||
#os.symlink(path, "." + path1)
|
||||
|
||||
def rename(self, path, path1):
|
||||
return -EROFS
|
||||
#os.rename("." + path, "." + path1)
|
||||
|
||||
def link(self, path, path1):
|
||||
return -EROFS
|
||||
#os.link("." + path, "." + path1)
|
||||
|
||||
def chmod(self, path, mode):
|
||||
return -EROFS
|
||||
#os.chmod("." + path, mode)
|
||||
|
||||
def chown(self, path, user, group):
|
||||
return -EROFS
|
||||
#os.chown("." + path, user, group)
|
||||
|
||||
def truncate(self, path, len):
|
||||
return -EROFS
|
||||
#f = open("." + path, "a")
|
||||
#f.truncate(len)
|
||||
#f.close()
|
||||
|
||||
def mknod(self, path, mode, dev):
|
||||
return -EROFS
|
||||
#os.mknod("." + path, mode, dev)
|
||||
|
||||
def mkdir(self, path, mode):
|
||||
return -EROFS
|
||||
#os.mkdir("." + path, mode)
|
||||
|
||||
def utime(self, path, times):
|
||||
os.utime(self.__realpath(path), times)
|
||||
|
||||
# The following utimens method would do the same as the above utime method.
|
||||
# We can't make it better though as the Python stdlib doesn't know of
|
||||
# subsecond preciseness in acces/modify times.
|
||||
#
|
||||
# def utimens(self, path, ts_acc, ts_mod):
|
||||
# os.utime("." + path, (ts_acc.tv_sec, ts_mod.tv_sec))
|
||||
|
||||
def access(self, path, mode):
|
||||
if DEBUG: print "access " + path + '||'+str(mode)
|
||||
if not os.access(self.__realpath(path), mode):
|
||||
return -EACCES
|
||||
|
||||
# This is how we could add stub extended attribute handlers...
|
||||
# (We can't have ones which aptly delegate requests to the underlying fs
|
||||
# because Python lacks a standard xattr interface.)
|
||||
#
|
||||
# def getxattr(self, path, name, size):
|
||||
# val = name.swapcase() + '@' + path
|
||||
# if size == 0:
|
||||
# # We are asked for size of the value.
|
||||
# return len(val)
|
||||
# return val
|
||||
#
|
||||
# def listxattr(self, path, size):
|
||||
# # We use the "user" namespace to please XFS utils
|
||||
# aa = ["user." + a for a in ("foo", "bar")]
|
||||
# if size == 0:
|
||||
# # We are asked for size of the attr list, ie. joint size of attrs
|
||||
# # plus null separators.
|
||||
# return len("".join(aa)) + len(aa)
|
||||
# return aa
|
||||
|
||||
def statfs(self):
|
||||
"""
|
||||
Should return an object with statvfs attributes (f_bsize, f_frsize...).
|
||||
Eg., the return value of os.statvfs() is such a thing (since py 2.2).
|
||||
If you are not reusing an existing statvfs object, start with
|
||||
fuse.StatVFS(), and define the attributes.
|
||||
|
||||
To provide usable information (ie., you want sensible df(1)
|
||||
output, you are suggested to specify the following attributes:
|
||||
|
||||
- f_bsize - preferred size of file blocks, in bytes
|
||||
- f_frsize - fundamental size of file blcoks, in bytes
|
||||
[if you have no idea, use the same as blocksize]
|
||||
- f_blocks - total number of blocks in the filesystem
|
||||
- f_bfree - number of free blocks
|
||||
- f_files - total number of file inodes
|
||||
- f_ffree - nunber of free file inodes
|
||||
"""
|
||||
|
||||
return os.statvfs(".")
|
||||
|
||||
def fsinit(self):
|
||||
if DEBUG: print "fsinit"
|
||||
self.BDIRS={'loc':'/'+self.local.strip('/'),'ext':'/'+self.extern.strip('/')}
|
||||
os.chdir('/'+self.local.strip('/'))
|
||||
self.__lsnapshots()
|
||||
|
||||
|
||||
class XmpFile(object):
|
||||
|
||||
def __init__(self, path, flags, *mode):
|
||||
path = Xmp.realpath+'/'.join(path.split('/')[2:])
|
||||
self.fd = os.open(path, flags, *mode)
|
||||
self.file = os.fdopen(self.fd, flag2mode(flags))
|
||||
|
||||
def read(self, length, offset):
|
||||
self.file.seek(offset)
|
||||
return self.file.read(length)
|
||||
|
||||
def write(self, buf, offset):
|
||||
return -EROFS
|
||||
self.file.seek(offset)
|
||||
self.file.write(buf)
|
||||
return len(buf)
|
||||
|
||||
def release(self, flags):
|
||||
self.file.close()
|
||||
|
||||
def _fflush(self):
|
||||
if 'w' in self.file.mode or 'a' in self.file.mode:
|
||||
self.file.flush()
|
||||
|
||||
def fsync(self, isfsyncfile):
|
||||
self._fflush()
|
||||
if isfsyncfile and hasattr(os, 'fdatasync'):
|
||||
os.fdatasync(self.fd)
|
||||
else:
|
||||
os.fsync(self.fd)
|
||||
|
||||
def flush(self):
|
||||
self._fflush()
|
||||
# cf. xmp_flush() in fusexmp_fh.c
|
||||
os.close(os.dup(self.fd))
|
||||
|
||||
def fgetattr(self):
|
||||
return os.fstat(self.fd)
|
||||
|
||||
def ftruncate(self, len):
|
||||
return -EROFS
|
||||
#self.file.truncate(len)
|
||||
|
||||
def lock(self, cmd, owner, **kw):
|
||||
#return -EROFS
|
||||
# The code here is much rather just a demonstration of the locking
|
||||
# API than something which actually was seen to be useful.
|
||||
|
||||
# Advisory file locking is pretty messy in Unix, and the Python
|
||||
# interface to this doesn't make it better.
|
||||
# We can't do fcntl(2)/F_GETLK from Python in a platfrom independent
|
||||
# way. The following implementation *might* work under Linux.
|
||||
#
|
||||
# if cmd == fcntl.F_GETLK:
|
||||
# import struct
|
||||
#
|
||||
# lockdata = struct.pack('hhQQi', kw['l_type'], os.SEEK_SET,
|
||||
# kw['l_start'], kw['l_len'], kw['l_pid'])
|
||||
# ld2 = fcntl.fcntl(self.fd, fcntl.F_GETLK, lockdata)
|
||||
# flockfields = ('l_type', 'l_whence', 'l_start', 'l_len', 'l_pid')
|
||||
# uld2 = struct.unpack('hhQQi', ld2)
|
||||
# res = {}
|
||||
# for i in xrange(len(uld2)):
|
||||
# res[flockfields[i]] = uld2[i]
|
||||
#
|
||||
# return fuse.Flock(**res)
|
||||
|
||||
# Convert fcntl-ish lock parameters to Python's weird
|
||||
# lockf(3)/flock(2) medley locking API...
|
||||
op = { fcntl.F_UNLCK : fcntl.LOCK_UN,
|
||||
fcntl.F_RDLCK : fcntl.LOCK_SH,
|
||||
fcntl.F_WRLCK : fcntl.LOCK_EX }[kw['l_type']]
|
||||
if cmd == fcntl.F_GETLK:
|
||||
return -EOPNOTSUPP
|
||||
elif cmd == fcntl.F_SETLK:
|
||||
if op != fcntl.LOCK_UN:
|
||||
op |= fcntl.LOCK_NB
|
||||
elif cmd == fcntl.F_SETLKW:
|
||||
pass
|
||||
else:
|
||||
return -EINVAL
|
||||
|
||||
fcntl.lockf(self.fd, op, kw['l_start'], kw['l_len'])
|
||||
|
||||
|
||||
def main(self, *a, **kw):
|
||||
|
||||
self.file_class = self.XmpFile
|
||||
|
||||
return Fuse.main(self, *a, **kw)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
usage = """
|
||||
Userspace nullfs-alike: mirror the filesystem tree from some point on.
|
||||
|
||||
""" + Fuse.fusage
|
||||
|
||||
server = Xmp(version="%prog " + fuse.__version__,
|
||||
usage=usage,
|
||||
dash_s_do='setsingle')
|
||||
|
||||
server.parser.add_option(mountopt="root", metavar="PATH", default=server.local,
|
||||
help="mirror filesystem from under PATH [default: %default]")
|
||||
server.parser.add_option(mountopt="local", metavar="PATH", default=server.local,
|
||||
help="set path to filesystem from internal HDD/SSD under PATH [default: %default]")
|
||||
server.parser.add_option(mountopt="extern", metavar="PATH", default=server.extern,
|
||||
help="set path to filesystem from external HDD/SSD under PATH [default: %default]")
|
||||
server.parser.add_option(mountopt="uroot", metavar="BOOL", default=server.uroot,
|
||||
help="""use ist for mounting on /backup for root -
|
||||
whole snapshots! BOOL [default: %default]""")
|
||||
|
||||
server.parse(values=server, errex=1)
|
||||
|
||||
try:
|
||||
if server.fuse_args.mount_expected():
|
||||
os.chdir(server.local)
|
||||
except OSError:
|
||||
print >> sys.stderr, "can't enter root of underlying filesystem"
|
||||
sys.exit(1)
|
||||
|
||||
server.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
59
files/usr/bin/mkbackup-gui
Executable file
59
files/usr/bin/mkbackup-gui
Executable file
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
|
||||
|
||||
# This file is part of mkbackup-gui.
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
#import os
|
||||
#gi_typelib_path = ["/usr/lib/x86_64-linux-gnu/mkbackup-gui/girepository-1.0",]
|
||||
#if 'GI_TYPELIB_PATH' in os.environ:
|
||||
# gi_typelib_path.append(os.environ['GI_TYPELIB_PATH'])
|
||||
#os.environ['GI_TYPELIB_PATH'] = ":".join(gi_typelib_path)
|
||||
|
||||
#ld_library_path = ["/usr/lib/x86_64-linux-gnu/mkbackup-gui",]
|
||||
#if 'LD_LIBRARY_PATH' in os.environ:
|
||||
# ld_library_path.append(os.environ['LD_LIBRARY_PATH'])
|
||||
#os.environ['LD_LIBRARY_PATH'] = ":".join(ld_library_path)
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(1, '/usr/lib/python3.6/site-packages')
|
||||
|
||||
import gettext, locale
|
||||
from gettext import gettext as _
|
||||
gettext.textdomain("mkbackup-gui")
|
||||
locale.textdomain("mkbackup-gui")
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, GObject
|
||||
from dfeet.application import DFeetApp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
data_dir = "/usr/share/mkbackup-gui"
|
||||
#use local paths when debugging
|
||||
if os.getenv("DFEET_DEBUG") is not None:
|
||||
data_dir = os.path.join(os.path.dirname(__file__), "..", "data")
|
||||
Gtk.IconTheme.get_default().prepend_search_path(
|
||||
os.path.join(os.path.dirname(__file__), "..", "data", "icons"))
|
||||
#start the application
|
||||
print(data_dir)
|
||||
app = MkBackup(package="mkbackup-gui", version="0.1.0", data_dir=data_dir)
|
||||
sys.exit(app.run(sys.argv))
|
11
files/usr/bin/syssubvol
Executable file
11
files/usr/bin/syssubvol
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
from mkbackup.mkbackup_btrfs_config import MountInfo
|
||||
|
||||
def relpath(mp):
|
||||
print(MountInfo().relpath(mp))
|
||||
|
||||
if __name__ == "__main__":
|
||||
for i in sys.argv[1:]:
|
||||
relpath(i)
|
|
@ -0,0 +1 @@
|
|||
../../../python3/dist-packages/mkbackup/mkbackup_btrfs_config.py
|
|
@ -0,0 +1 @@
|
|||
../../../python3/dist-packages/mkbackup/mkbackup_emitter.py
|
|
@ -0,0 +1 @@
|
|||
../../../python3/dist-packages/mkbackup/system_notification_emitter.py
|
|
@ -0,0 +1,38 @@
|
|||
"""Emmitter functionality."""
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
|
||||
|
||||
class Emitter(dbus.service.Object):
|
||||
"""Emitter DBUS service object."""
|
||||
|
||||
def __init__(self, conn=None, object_path=None, bus_name=None):
|
||||
"""Initialize the emitter DBUS service object."""
|
||||
dbus.service.Object.__init__(self, conn=conn, object_path=object_path)
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.Notification')
|
||||
def low(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a low test signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.Notification')
|
||||
def normal(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a normal test signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.Notification')
|
||||
def critical(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a critical test signal')
|
||||
|
||||
""" Example to use
|
||||
Simple_Notification = Emitter(dbus.SystemBus(),
|
||||
'/at/xundeenergie/notifications/simple/Notification')
|
||||
Advanced_Notification = Emitter(dbus.SystemBus(),
|
||||
'/at/xundeenergie/notifications/advanced/Notification')
|
||||
|
||||
#Simple_Notification.low('M')
|
||||
Advanced_Notification.normal(
|
||||
{'sender': 'emitter1.py', 'header': 'Testmessage', 'body': 'Test Body'})
|
||||
"""
|
35
files/usr/lib/python3/dist-packages/mkbackup-dbus/service
Executable file
35
files/usr/lib/python3/dist-packages/mkbackup-dbus/service
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/python3 -d
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import dbus, dbus.service, dbus.exceptions
|
||||
import sys
|
||||
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from gi.repository import GLib
|
||||
|
||||
# Initialize a main loop
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
loop = GLib.MainLoop()
|
||||
|
||||
# Declare a name where our service can be reached
|
||||
try:
|
||||
sysbus_name = dbus.service.BusName("at.xundeenergie",
|
||||
bus=dbus.SystemBus(),
|
||||
do_not_queue=True)
|
||||
except dbus.exceptions.NameExistsException:
|
||||
print("service is already running")
|
||||
sys.exit(1)
|
||||
|
||||
# Run the loop
|
||||
try:
|
||||
# Create our initial objects
|
||||
from services.mkbackup import MkBackupDBus
|
||||
MkBackupDBus(sysbus_name, "/at/xundeenergie/mkbackup/Intervals")
|
||||
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
print("keyboard interrupt received")
|
||||
except Exception as e:
|
||||
print("Unexpected exception occurred: '{}'".format(str(e)))
|
||||
finally:
|
||||
loop.quit()
|
|
@ -0,0 +1,117 @@
|
|||
import dbus
|
||||
import dbus.service
|
||||
import random
|
||||
import time
|
||||
import os
|
||||
|
||||
from mkbackup_btrfs_config import Config, MountInfo, connect, Myos, __version__
|
||||
|
||||
config = Config()
|
||||
|
||||
class MkBackup:
|
||||
def __init__(self, bus_name, base_path):
|
||||
# self._bus = dbus.SystemBus()
|
||||
# self.notification = Notification()
|
||||
Intervals(bus_name, base_path)
|
||||
|
||||
for intv in config.ListIntervals():
|
||||
Properties(bus_name, os.path.join(base_path, intv), intv)
|
||||
|
||||
class Intervals(dbus.service.Object):
|
||||
def __init__(self, bus_name, bus_path):
|
||||
super().__init__(bus_name, bus_path)
|
||||
|
||||
@dbus.service.method(dbus_interface='at.xundeenergie.mkbackup.Intervals',
|
||||
in_signature='', out_signature='v')
|
||||
def Names(self):
|
||||
return config.ListIntervals()
|
||||
|
||||
class Properties(dbus.service.Object):
|
||||
def __init__(self, bus_name, bus_path, interval):
|
||||
super().__init__(bus_name, bus_path)
|
||||
self.interface = "at.xundeenergie.mkbackup.Status"
|
||||
self.interval = interval
|
||||
self.STATI = ['reset', 'stop', 'running', 'finished']
|
||||
self.properties = dict()
|
||||
self.properties[self.interface] = dict()
|
||||
self.properties[self.interface]['progress'] = 0 # 0-100
|
||||
self.properties[self.interface]['status'] = 'stop' # stop, running, finished, reset
|
||||
self.properties[self.interface]['transfer'] = config.getTransfer(interval)
|
||||
self.properties[self.interface]['lastrun'] = 0 # datetime
|
||||
self.properties[self.interface]['finished'] = True # Boolean
|
||||
self.properties[self.interface]['name'] = interval # Boolean
|
||||
self.properties['function'] = dict()
|
||||
self.properties['function']['progress'] = self.update_progress
|
||||
self.properties['function']['status'] = self.update_status
|
||||
# self.properties['function']['lastrun'] = self.update_lastrun
|
||||
from dbus import Interface
|
||||
|
||||
def update_progress(self, interface, incr):
|
||||
print("Update progress: %i / %i, %s" % (float(incr),
|
||||
self.properties[interface]['progress'],self.properties[interface]['status']))
|
||||
if self.properties[interface]['status'] == 'running':
|
||||
if 0 < self.properties[interface]['progress'] + float(incr) < 100:
|
||||
#self.properties[interface]['progress'] = self.properties[interface]['progress'] + float(incr)
|
||||
self.properties[interface]['progress'] += float(incr)
|
||||
elif self.properties[interface]['progress'] + float(incr) >= 100:
|
||||
self.properties[interface]['progress'] = 99
|
||||
else:
|
||||
print('B', incr, type(incr))
|
||||
return self.properties[interface]['progress']
|
||||
|
||||
def update_status(self, interface, status):
|
||||
if status in self.STATI:
|
||||
print("Update status: %s" % status)
|
||||
print("Status: ", self.properties[interface]['status'])
|
||||
if status == 'finished':
|
||||
self.properties[interface]['status'] = status
|
||||
self.properties[interface]['progress'] = 100
|
||||
elif status == 'stop':
|
||||
self.properties[interface]['status'] = status
|
||||
self.properties[interface]['progres'] = 0
|
||||
elif status == 'reset':
|
||||
self.properties[interface]['status'] = 'running'
|
||||
self.properties[interface]['progress'] = 0
|
||||
print("status: ", self.properties[interface]['status'])
|
||||
return self.properties[interface]['status']
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='ss', out_signature='v')
|
||||
def Get(self, interface_name, property_name):
|
||||
return self.GetAll(interface_name)[property_name]
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='s', out_signature='a{sv}')
|
||||
def GetAll(self, interface_name):
|
||||
if interface_name == self.interface:
|
||||
return self.properties[interface_name]
|
||||
else:
|
||||
raise dbus.exceptions.DBusException(
|
||||
'at.xundeenergie.mkbackup.UnknownInterface',
|
||||
'The Foo object does not implement the %s interface'
|
||||
% interface_name)
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='ssv')
|
||||
def Set(self, interface_name, property_name, new_value):
|
||||
# validate the property name and value, update internal state…
|
||||
"""https://recalll.co/ask/v/topic/D-Bus-D-Feet-Send-Dictionary-of-String%2CVariants-in-Python-Syntax/5565e1372bd273d7108b7b82
|
||||
__import__('gi.repository.GLib', globals(), locals(), ['Variant']).Variant("s", "value")"""
|
||||
if interface_name in self.properties:
|
||||
if property_name in self.properties[interface_name]:
|
||||
func = self.properties['function'].get(property_name)
|
||||
new_value = func(interface_name, new_value)
|
||||
#self.properties[str(interface_name)][str(property_name)] = new_value
|
||||
self.PropertiesChanged(interface_name,
|
||||
{ property_name: new_value, 'interval': self.interval}, [])
|
||||
else:
|
||||
raise dbus.exceptions.DBusException(
|
||||
'at.xundeenergie.mkbackup.UnknownInterface',
|
||||
'The Foo object does not implement the %s interface'
|
||||
% interface_name)
|
||||
|
||||
@dbus.service.signal(dbus.PROPERTIES_IFACE,
|
||||
signature='sa{sv}as')
|
||||
def PropertiesChanged(self, interface_name, changed_properties,
|
||||
invalidated_properties):
|
||||
pass
|
0
files/usr/lib/python3/dist-packages/mkbackup/__init__.py
Normal file
0
files/usr/lib/python3/dist-packages/mkbackup/__init__.py
Normal file
Binary file not shown.
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
from gi.repository import GObject, Gtk, Gio
|
||||
from mkbackup.uiloader import UILoader
|
||||
|
||||
import os
|
||||
|
||||
from dfeet.wnck_utils import IconTable
|
||||
|
||||
class IntervalBox(Gtk.Box):
|
||||
"""class to represent a snapshot-interval"""
|
||||
def __init__(self, interval):
|
||||
super(IntervalBox, self).__init__(spacing=5, expand=True)
|
||||
self.__interval_name = interval
|
||||
self.__enabled = False
|
||||
self.__icon_table = IconTable.get_instance()
|
||||
self.__icon_image = Gtk.Image.new_from_pixbuf(self.__icon_table.default_icon)
|
||||
|
||||
self.__hbox = Gtk.HBox(spacing=5, halign=Gtk.Align.START)
|
||||
self.pack_start(self.__hbox, True, True, 0)
|
||||
# icon
|
||||
self.__hbox.pack_start(self.__icon_image, True, True, 0)
|
||||
# other information
|
||||
self.__vbox_right = Gtk.VBox(spacing=5, expand=True)
|
||||
self.__hbox.pack_start(self.__vbox_right, True, True, 0)
|
||||
|
||||
# first element
|
||||
self.__label_interval_name = Gtk.Label()
|
||||
self.__label_interval_name.set_halign(Gtk.Align.START)
|
||||
self.__vbox_right.pack_start(self.__label_interval_name, True, True, 0)
|
||||
# second element
|
||||
self.__label_info = Gtk.Label()
|
||||
self.__label_info.set_halign(Gtk.Align.START)
|
||||
self.__vbox_right.pack_start(self.__label_info, True, True, 0)
|
||||
# switch to enable/disable it
|
||||
self.__switch_enabled = Gtk.Switch()
|
||||
self.__switch_enabled.set_halign(Gtk.Align.START)
|
||||
self.__switch_enabled.connect('notify::active', self.on_switch_activated)
|
||||
self.__vbox_right.pack_start(self.__switch_enabled, True, True, 0)
|
||||
# transfer snapshot to backup
|
||||
self.__check_transfer = GtkCheckButton()
|
||||
self.__check_transfer.set_active(props['transfer'])
|
||||
self.__vbox_right.pack_start(self.__check_transfer, True, True, 0)
|
||||
# progressbar
|
||||
self.__progress = Gtk.ProgressBar()
|
||||
self.__progress.set_fraction(props['progress']/100)
|
||||
self.__vbox_right.pack_start(self.__progress, True, True, 0)
|
||||
# separator for the boxes
|
||||
self.pack_end(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), True, True, 0)
|
||||
# update widget information
|
||||
self.__update_widget()
|
||||
self.show_all()
|
||||
|
||||
|
||||
class IntervalWatch(object):
|
||||
"""watch a given snapshot-interval"""
|
||||
def __init__(self, interval):
|
||||
self.__interval_name = interval
|
||||
|
||||
# Setup ui
|
||||
ui = Gtk.Builder()
|
||||
ui.add_from_file("test.glade")
|
||||
|
56
files/usr/lib/python3/dist-packages/mkbackup/mkbackup.client
Executable file
56
files/usr/lib/python3/dist-packages/mkbackup/mkbackup.client
Executable file
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Take in a single optional integral argument
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
|
||||
arg_parser = argparse.ArgumentParser(description='Get random numbers')
|
||||
arg_parser.add_argument('bits', nargs='?', default=16)
|
||||
arg_parser.add_argument('-s', '--slow', action='store_true',
|
||||
default=False, required=False,
|
||||
help='Use the slow method')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
# Encapsulate calling the Status object on the session bus with a main loop
|
||||
import dbus, dbus.exceptions, dbus.mainloop.glib
|
||||
import dbus.service
|
||||
import threading
|
||||
from gi.repository import GLib
|
||||
|
||||
from time import sleep
|
||||
|
||||
from mkbackup_emitter import Emitter as EM
|
||||
class EmDBUS(EM):
|
||||
""" Example to use
|
||||
progress = Emitter(dbus.SystemBus(),
|
||||
'/at/xundeenergie/mkbackup/Status')
|
||||
|
||||
progress.start(
|
||||
{'intv': 'hourly'})
|
||||
|
||||
progress.update(
|
||||
{'intv': 'hourly', 'progr': 5})
|
||||
|
||||
progress.finished(
|
||||
{'intv': 'hourly'})
|
||||
|
||||
progress.reset(
|
||||
{'intv': 'hourly'})
|
||||
"""
|
||||
def __init__(self, interval):
|
||||
super().__init__(conn=dbus.SystemBus(), bus_name='at.xundeenergie', object_path=os.path.join('/at/xundeenergie/mkbackup/Intervals', interval))
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
args = parser.parse_args()
|
||||
args.action='daily'
|
||||
args.mdb = EmDBUS(args.action)
|
||||
args.mdb.Reset()
|
||||
args.mdb.Start()
|
||||
print("RUN PROGRAMM")
|
||||
steps = 20
|
||||
for i in range(0,steps):
|
||||
sleep(0.5)
|
||||
args.mdb.Update(100/steps)
|
||||
args.mdb.Finished()
|
|
@ -0,0 +1,751 @@
|
|||
from __future__ import (absolute_import, division, print_function,
|
||||
unicode_literals)
|
||||
#from builtins import *
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import socket
|
||||
import os
|
||||
import errno
|
||||
import re
|
||||
import paramiko
|
||||
|
||||
__author__ = "Jakobus Schuerz <jakobus.schuerz@gmail.com>"
|
||||
__version__ = "0.04.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY34 = sys.version_info[0:2] >= (3, 4)
|
||||
|
||||
|
||||
if PY3:
|
||||
from configparser import ConfigParser
|
||||
from configparser import RawConfigParser, NoOptionError, NoSectionError
|
||||
else:
|
||||
from ConfigParser import ConfigParser
|
||||
from ConfigParser import RawConfigParser, NoOptionError, NoSectionError
|
||||
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
class NoSubvolumeError(Error):
|
||||
def __init__(self):
|
||||
print("ERROR - Snapshot not found" )
|
||||
pass
|
||||
|
||||
class SSHConnectionError(Error):
|
||||
def __init__(self):
|
||||
print("ERROR - ssh-connection not available" )
|
||||
pass
|
||||
|
||||
def s2bool(s):
|
||||
return s.lower() in ['true','yes','y','1'] if s else False
|
||||
|
||||
# quote awk-argument in ssh-command
|
||||
def quote_argument(argument):
|
||||
return '"%s"' % (
|
||||
argument
|
||||
.replace('\\', '\\\\')
|
||||
.replace('"', '\\"')
|
||||
.replace('$', '\\$')
|
||||
.replace('`', '\\`')
|
||||
)
|
||||
|
||||
def connect(conn=None):
|
||||
if conn == None:
|
||||
pass
|
||||
else:
|
||||
if not conn['active']:
|
||||
try:
|
||||
#if conn['conn'].is_active(): print("Session alive")
|
||||
#conn['conn'].close()
|
||||
conn['conn'].connect(conn['host'],conn['port'],conn['user'],auth_timeout=10)
|
||||
conn['active'] = True
|
||||
#print("open connection for %s@%s" % (conn['user'], conn['host']))
|
||||
# except (paramiko.BadHostKeyException,
|
||||
# paramiko.AuthenticationException, paramiko.SSHException,
|
||||
# socket.gaierror, socket.error) as e:
|
||||
# #print("C",e)
|
||||
# #raise e
|
||||
# print("No connection to host %s" % (conn['host']))
|
||||
# return(False)
|
||||
# except (paramiko.BadHostKeyException, paramiko.AuthenticationException, paramiko.SSHException, socket.error) as e:
|
||||
# raise e
|
||||
except:
|
||||
return(False)
|
||||
|
||||
return(True)
|
||||
raise SSHConnectionError
|
||||
else:
|
||||
return(True)
|
||||
# try:
|
||||
# #if conn['conn'].is_active(): print("Session alive")
|
||||
# conn['conn'].close()
|
||||
# conn['conn'].connect(conn['host'],conn['port'],conn['user'])
|
||||
# print("open connection for %s@%s" % (conn['user'], conn['host']))
|
||||
# except (paramiko.BadHostKeyException, paramiko.AuthenticationException, paramiko.SSHException, socket.error) as e:
|
||||
# print("C",e)
|
||||
# raise e
|
||||
|
||||
|
||||
class MountInfo():
|
||||
def __init__(self,mountinfo='/proc/self/mountinfo',conn=None):
|
||||
self.mi = dict()
|
||||
if conn == None:
|
||||
mif = open(mountinfo)
|
||||
else:
|
||||
if connect(conn):
|
||||
#conn['conn'].connect(conn['host'],conn['port'],conn['user'])
|
||||
sftp_client = conn['conn'].open_sftp()
|
||||
mif = sftp_client.open(mountinfo)
|
||||
else:
|
||||
print("Host not reachable (MountInfo): %s" % (conn['host']))
|
||||
mif = open(mountinfo)
|
||||
try:
|
||||
for line in mif:
|
||||
if len(line.split()) == 10:
|
||||
a,b,c,relpath,mntp,d,typ,fstype,dev,opts = line.split()
|
||||
else:
|
||||
a,b,c,relpath,mntp,d,e,typ,fstype,dev,opts = line.split()
|
||||
mntp = mntp.replace('\\040',' ')
|
||||
self.mi[mntp] = dict()
|
||||
self.mi[mntp]['relpath'] = relpath
|
||||
self.mi[mntp]['typ'] = typ
|
||||
self.mi[mntp]['fstype'] = fstype
|
||||
self.mi[mntp]['dev'] = dev
|
||||
self.mi[mntp]['opts'] = opts
|
||||
finally:
|
||||
mif.close()
|
||||
|
||||
def __check(self,mountpoint,attribute):
|
||||
if not mountpoint[0] == '/':
|
||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), mountpoint)
|
||||
mp = mountpoint.rstrip('/')if len(mountpoint) > 1 else mountpoint
|
||||
rec = False
|
||||
rep = ''
|
||||
if os.path.exists(mp):
|
||||
try:
|
||||
rp = self.mi[mp][attribute]
|
||||
rep = mp
|
||||
except:
|
||||
rec = True
|
||||
a,rep,rp = self.__check(os.path.dirname(mp),attribute)
|
||||
return [rec,rep,rp]
|
||||
else:
|
||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), mp)
|
||||
|
||||
def relpath(self,mountpoint):
|
||||
rec,rep,mp = self.__check(mountpoint,'relpath')
|
||||
#print(mountpoint,rec,rep,mp)
|
||||
if rec: return mp.replace(rep,'') + mountpoint
|
||||
return mp
|
||||
|
||||
def fstype(self,mountpoint):
|
||||
return self.__check(mountpoint,'fstype')[2]
|
||||
|
||||
def typ(self,mountpoint):
|
||||
return self.__check(mountpoint,'typ')[2]
|
||||
|
||||
def device(self,mountpoint):
|
||||
return self.__check(mountpoint,'dev')[2]
|
||||
|
||||
class MyConfigParser(ConfigParser):
|
||||
comment = """replace get in Configparser to give the default-option, if a
|
||||
section doesn't exist, and option exists in default"""
|
||||
def get(self, section, option, **kw):
|
||||
try:
|
||||
return ConfigParser.get(self, section, option, raw=True)
|
||||
except:
|
||||
return ConfigParser.get(self, 'DEFAULT', option, raw=True)
|
||||
|
||||
class Myos():
|
||||
def __init__(self,dry=False):
|
||||
self.dry = dry
|
||||
pass
|
||||
|
||||
def __run__(self,command,conn=None):
|
||||
if not conn == None:
|
||||
if connect(conn):
|
||||
out=''
|
||||
stdin, stdout, stderr = conn['conn'].exec_command(command)
|
||||
for line in stdout:
|
||||
out += line
|
||||
return(out)
|
||||
else:
|
||||
print("Host not reachable (Myos): %s" % (conn['host']))
|
||||
|
||||
def stat(self,path,conn=None):
|
||||
if not conn == None:
|
||||
command='/usr/bin/stat'
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
return os.stat(path)
|
||||
|
||||
|
||||
def path_isdir(self,path,conn=None):
|
||||
#print("myos.path",os.path.exists(path))
|
||||
if not conn == None:
|
||||
command='/bin/test -d %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
# print("is local dir %s" % (path))
|
||||
return os.path.isdir(path)
|
||||
|
||||
def path_isfile(self,path,conn=None):
|
||||
if not conn == None:
|
||||
command='/bin/test -f %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
# print("is local file %s" % (path))
|
||||
return os.path.isfile(path)
|
||||
|
||||
def path_realpath(self,path,conn=None):
|
||||
#print("myos.path",os.path.exists(path))
|
||||
if not conn == None:
|
||||
command='/usr/bin/realpath %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
# print("local realpath for %s" % (path))
|
||||
return os.path.realpath(path)
|
||||
|
||||
def path_exists(self,path,conn=None):
|
||||
#print("myos.path",os.path.exists(path))
|
||||
if not conn == None:
|
||||
command='/bin/test -e %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
# print("exists-local %s" % (path))
|
||||
return os.path.exists(path)
|
||||
|
||||
def remove(self,path,conn=None):
|
||||
if self.dry == True:
|
||||
print('Remove %s (dry run)' % (path))
|
||||
return
|
||||
else:
|
||||
if not conn == None:
|
||||
command='/bin/rm %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
# print("remove-local %s" % (path))
|
||||
return os.remove(path)
|
||||
|
||||
def rename(self,From,To,conn=None):
|
||||
if self.dry == True:
|
||||
print('Rename %s to %s (dry run)' % (From,To))
|
||||
return
|
||||
else:
|
||||
if not conn == None:
|
||||
# print("RENAME",From,To,conn['host'])
|
||||
command='/bin/mv %s %s' % (From,To)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
# print("rename-local %s %s" % (From,To))
|
||||
return os.rename(From,To)
|
||||
|
||||
|
||||
def path_islink(self,path,conn=None):
|
||||
if not conn == None:
|
||||
command='/bin/test -h %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
return os.path.islink(path)
|
||||
|
||||
def listdir(self,path,conn=None):
|
||||
if not conn == None:
|
||||
command='/bin/ls %s' % (path)
|
||||
return(self.__run__(command,conn))
|
||||
else:
|
||||
return os.listdir(path)
|
||||
|
||||
|
||||
class Config():
|
||||
def __init__(self,cfile='/etc/mkbackup-btrfs.conf'):
|
||||
self.cfile = cfile
|
||||
#self.config = ConfigParser()
|
||||
self.config = MyConfigParser()
|
||||
#self.hostname = subprocess.check_output("/bin/hostname",shell=True).decode('utf8').split('\n')[0]
|
||||
self.hostname=socket.gethostname()
|
||||
self.mountinfo = MountInfo()
|
||||
self.syssubvol = self.mountinfo.relpath('/')[1:]
|
||||
#self.syssubvol=subprocess.check_output(['/usr/bin/grub-mkrelpath','/'], shell=False).decode('utf8').split("\n")[0].strip("/")
|
||||
self.ssh = dict()
|
||||
self.ssh_cons = dict()
|
||||
|
||||
#if os.path.exists(self.cfile):
|
||||
if Myos().path_exists(self.cfile):
|
||||
pass #print('OK')
|
||||
else:
|
||||
print('Default-Config created at %s' % (self.cfile))
|
||||
self.CreateConfig()
|
||||
self._read()
|
||||
|
||||
for i in self.ListIntervals() +['DEFAULT']:
|
||||
self.ssh[i] = dict()
|
||||
for s in ['SRC', 'SNP', 'BKP']:
|
||||
#print('X',self.getSSHLogin(s,i))
|
||||
self.ssh[i][s] = dict()
|
||||
if self.getSSHLogin(s,i) != None:
|
||||
c,x,p,uh = self.getSSHLogin(s,i).strip().split(' ')
|
||||
u,h = uh.split('@')
|
||||
if not uh in self.ssh_cons:
|
||||
self.ssh_cons[uh] = paramiko.SSHClient()
|
||||
self.ssh_cons[uh].set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
#self.ssh_cons[uh].connect(h, int(p), u)
|
||||
|
||||
#self.ssh[i][s]['conn'] = uh
|
||||
self.ssh[i][s]['conn'] = self.ssh_cons[uh]
|
||||
self.ssh[i][s]['host'] = h
|
||||
self.ssh[i][s]['port'] = int(p)
|
||||
self.ssh[i][s]['user'] = u
|
||||
self.ssh[i][s]['creds'] = (h, int(p), u)
|
||||
self.ssh[i][s]['active'] = False
|
||||
|
||||
else:
|
||||
self.ssh[i][s] = None
|
||||
|
||||
def _read(self):
|
||||
|
||||
csup = dict() #for each dropin-file a csup = config-superseed-dict-entry
|
||||
#self.config = ConfigParser()
|
||||
self.config.read(self.cfile)
|
||||
self.csupdir = self.cfile+'.d'
|
||||
if os.path.exists(str(self.csupdir)) and os.path.isdir(str(self.csupdir)):
|
||||
for csuplst in os.listdir(self.csupdir):
|
||||
if csuplst.endswith('.conf'):
|
||||
csup[csuplst] = ConfigParser()
|
||||
csup[csuplst].read(self.csupdir+'/'+csuplst)
|
||||
|
||||
# first superseed defaults
|
||||
for i in sorted(csup.keys()):
|
||||
# Set Options
|
||||
for j in ['DEFAULT']:
|
||||
for k in csup[i].defaults() if j == 'DEFAULT' else csup[i].options(j):
|
||||
if self.config.has_section(j) or j == 'DEFAULT':
|
||||
if k == 'ignore':
|
||||
# only attend pattern on option 'ignore'
|
||||
orig = self.config.get(j,k) + ',' if self.config.has_option(j,k) else ''
|
||||
elif k == 'description':
|
||||
# only attend pattern on option 'description'
|
||||
#orig = self.config.get(j,k) if self.config.has_option(j,k) else ''
|
||||
orig = ''
|
||||
else:
|
||||
# if option is not ignore, do the same as without
|
||||
# +, but remove + as first character
|
||||
orig = ''
|
||||
self.config.set(j,k,orig + re.sub('^\+','',csup[i].get(j,k)))
|
||||
#self.config.set(j,k,re.sub('^\+*','',csup[i].get(j,k)))
|
||||
else:
|
||||
# add section
|
||||
self.config.add_section(j)
|
||||
for k in csup[i].options(j):
|
||||
# add option to new section
|
||||
self.config.set(j,k,re.sub('^\+','',csup[i].get(j,k)))
|
||||
|
||||
# second superseed normal options
|
||||
for i in sorted(csup.keys()):
|
||||
for j in csup[i].sections():
|
||||
for k in csup[i].defaults() if j == 'DEFAULT' else csup[i].options(j):
|
||||
if self.config.has_section(j) or j == 'DEFAULT':
|
||||
if k == 'ignore':
|
||||
# only attend pattern on option 'ignore'
|
||||
orig = self.config.get(j,k) + ',' if self.config.has_option(j,k) else ''
|
||||
elif k == 'description':
|
||||
# only attend pattern on option 'description'
|
||||
#print(self.config.get(j,k))
|
||||
orig = self.config.get(j,k) if self.config.has_option(j,k) else ''
|
||||
else:
|
||||
# if option is not ignore, do the same as without
|
||||
# +, but remove + as first character
|
||||
orig = ''
|
||||
self.config.set(j,k,orig + re.sub('^\+','',csup[i].get(j,k)))
|
||||
else:
|
||||
# add section
|
||||
self.config.add_section(j)
|
||||
for k in csup[i].options(j):
|
||||
# add option to new section
|
||||
self.config.set(j,k,re.sub('^\+','',csup[i].get(j,k)))
|
||||
|
||||
|
||||
# If directory, where mkbackup-btrfs is started from, is one of SNP or
|
||||
# BKP, set SRC to the configured path and store
|
||||
psrc = os.getcwd()
|
||||
if '/'+psrc.strip('/') == self.getMountPath('SNP')[1]:
|
||||
#print("A",self.getMountPath('SNP'))
|
||||
self.config.set('DEFAULT','SRC', ' '.join(self.getMountPath('SNP')))
|
||||
self.config.set('DEFAULT','srcstore', self.getStoreName('SNP'))
|
||||
elif '/'+psrc.strip('/') == self.getMountPath('SNP')[1]+'/'+self.getStoreName('SNP'):
|
||||
#print("B")
|
||||
self.config.set('DEFAULT','SRC', ' '.join(self.getMountPath('SNP')))
|
||||
self.config.set('DEFAULT','srcstore', self.getStoreName('SNP'))
|
||||
elif '/'+psrc.strip('/') == self.getMountPath('BKP')[1]+'/'+self.getStoreName('BKP'):
|
||||
#print("C")
|
||||
self.config.set('DEFAULT','SRC', ' '.join(self.getMountPath('BKP')))
|
||||
self.config.set('DEFAULT','srcstore', self.getStoreName('BKP'))
|
||||
elif '/'+psrc.strip('/') == self.getMountPath('BKP')[1]:
|
||||
#print("D")
|
||||
self.config.set('DEFAULT','SRC', ' '.join(self.getMountPath('BKP')))
|
||||
self.config.set('DEFAULT','srcstore', self.getStoreName('BKP'))
|
||||
else:
|
||||
#print("E")
|
||||
self.config.set('DEFAULT','SRC', psrc)
|
||||
self.config.set('DEFAULT','srcstore', '')
|
||||
|
||||
# for i in self.config.sections():
|
||||
# print('XX[%s]' %(i))
|
||||
# for j in self.config.options(i):
|
||||
# print("XX"+j+' = ',self.__trnName(self.config.get(i,j)))
|
||||
# print('')
|
||||
|
||||
def getssh(self,tag,store):
|
||||
tg = tag if tag in self.ssh else 'DEFAULT'
|
||||
return(self.ssh[tg][store])
|
||||
|
||||
def getSsh(self,tag):
|
||||
tg = tag if tag in self.ssh else 'DEFAULT'
|
||||
return(self.ssh[tg])
|
||||
|
||||
def CreateConfig(self):
|
||||
self.config['DEFAULT'] = {
|
||||
'Description': "Erstellt ein Backup",
|
||||
'SNPMNT': '/var/cache/btrfs_pool_SYSTEM',
|
||||
'BKPMNT': '/var/cache/backup',
|
||||
'snpstore': '',
|
||||
'bkpstore': '$h',
|
||||
'volumes': '$S,__ALWAYSCURRENT__',
|
||||
'interval': 5,
|
||||
'symlink': 'LAST',
|
||||
'transfer': False,
|
||||
'notification': None,
|
||||
'notification_urgency': None}
|
||||
self.config['hourly'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '24','transfer': True}
|
||||
self.config['daily'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '7','transfer': True}
|
||||
self.config['weekly'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '5','transfer': True}
|
||||
self.config['monthly'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '12','transfer': True}
|
||||
self.config['yearly'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '7','transfer': True}
|
||||
self.config['afterboot'] = {'volumes': '$S','interval': '4','symlink': 'LASTBOOT'}
|
||||
self.config['aptupgrade'] = {'volumes': '$S','interval': '6','symlink': 'BEFOREUPDATE'}
|
||||
self.config['dmin'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '6'}
|
||||
self.config['plugin'] = {'volumes': '$S,__ALWAYSCURRENT__','interval':
|
||||
'5','transfer': True, 'notification': 'desktop', 'notification_urgency': 1}
|
||||
self.config['manually'] = {'volumes': '$S,__ALWAYSCURRENT__','interval': '5','symlink': 'MANUALLY',
|
||||
'transfer': True, 'notification': 'desktop', 'notification_urgency': 2}
|
||||
|
||||
with open(self.cfile, 'w') as configfile:
|
||||
try:
|
||||
self.config.write(configfile)
|
||||
except:
|
||||
exit("Failure during creation of config-file")
|
||||
return(self.config)
|
||||
|
||||
def PrintConfig(self,tag=None,of=None):
|
||||
if tag == None:
|
||||
seclist = self.config.sections()
|
||||
else:
|
||||
seclist = [tag]
|
||||
|
||||
out = list()
|
||||
if tag == None:
|
||||
out.append('[DEFAULT]')
|
||||
for j in self.config.defaults():
|
||||
out.append("%s = %s" % (j,self.__trnName(self.config.get('DEFAULT',j))))
|
||||
out.append('')
|
||||
|
||||
#for i in self.config.sections() if tag == None else [tag]:
|
||||
for i in seclist:
|
||||
out.append('[%s]' %(i))
|
||||
for j in self.config.options(i):
|
||||
out.append("%s = %s" % (j,self.__trnName(self.config.get(i,j))))
|
||||
out.append('')
|
||||
|
||||
if of != None:
|
||||
with open(of, 'w') as f:
|
||||
try:
|
||||
f.write('\n'.join(out))
|
||||
except:
|
||||
raise
|
||||
exit("Failure during creation of tmp-config-file")
|
||||
else:
|
||||
print('\n'.join(out))
|
||||
|
||||
|
||||
def ListIntervals(self):
|
||||
LST = []
|
||||
for i in self.config.sections():
|
||||
LST.append(i)
|
||||
LST.append('misc')
|
||||
return(LST)
|
||||
|
||||
def ListIntervalsFull(self):
|
||||
#self._read()
|
||||
LST = []
|
||||
for i in self.config.sections():
|
||||
LST.append(i+': '+str(self.config.get(i,'interval')))
|
||||
LST.append('misc: '+str(self.config.get(i,'interval')))
|
||||
return(LST)
|
||||
|
||||
def ListSymlinkNames(self):
|
||||
#self._read()
|
||||
LST = []
|
||||
for i in self.config.sections():
|
||||
LST.append(self.config.get(i,'symlink'))
|
||||
return(list(set(LST)))
|
||||
|
||||
|
||||
def getMountPath(self, store='SRC', tag='DEFAULT', shlogin=False, original=True):
|
||||
if store == 'SRC':
|
||||
path = self.config.get(tag,'SRC')
|
||||
elif store == 'SNP':
|
||||
path = self.config.get(tag,'SNPMNT')
|
||||
elif store == 'BKP':
|
||||
path = self.config.get(tag,'BKPMNT')
|
||||
else:
|
||||
print("EE - getMountPath: store %s is not allowed (%s) set path to SRC" % (store,tag))
|
||||
path = self.config.get('DEFAULT','SRC')
|
||||
#print('PATH',tag,path)
|
||||
|
||||
_ssh = path.split(':')
|
||||
if len(_ssh) == 1:
|
||||
path = _ssh[0]
|
||||
SSH = None
|
||||
elif len(_ssh) == 2:
|
||||
userhost,path = _ssh
|
||||
port = '22'
|
||||
SSH = [userhost,port]
|
||||
elif len(_ssh) == 3:
|
||||
userhost,port,path = _ssh
|
||||
SSH = [userhost,port]
|
||||
|
||||
if shlogin:
|
||||
return(SSH)
|
||||
else:
|
||||
if original:
|
||||
sshout = ''
|
||||
if SSH != None: sshout=':'.join(SSH)+':'
|
||||
return(sshout+'/'+path.strip('/'))
|
||||
else:
|
||||
return('/'+path.strip('/'))
|
||||
|
||||
# avoid deleting of / - but it's buggy, so return above is inserted
|
||||
if '/'+path.strip('/') != "/":
|
||||
return('/'+path.strip('/'))
|
||||
else:
|
||||
return(None)
|
||||
|
||||
def getSSHLogin(self,store='SRC',tag='DEFAULT'):
|
||||
if self.getMountPath(store=store,tag=tag,shlogin=True) is None:
|
||||
return(None)
|
||||
else:
|
||||
uh,p = self.getMountPath(store=store,tag=tag,shlogin=True)
|
||||
return('ssh -p %s %s ' % (p,uh))
|
||||
|
||||
|
||||
def getStoreName(self,store='SRC',tag='DEFAULT'):
|
||||
if store == 'SRC':
|
||||
try:
|
||||
path = self.config.get(tag,'srcstore').strip('/')
|
||||
except:
|
||||
path = self.config.get('DEFAULT','srcstore').strip('/')
|
||||
elif store == 'SNP':
|
||||
try:
|
||||
path = self.__trnName(self.config.get(tag,'snpstore').strip('/'))
|
||||
except:
|
||||
path = self.__trnName(self.config.get('DEFAULT','snpstore').strip('/'))
|
||||
elif store == 'BKP':
|
||||
try:
|
||||
path = self.__trnName(self.config.get(tag,'bkpstore').strip('/'))
|
||||
except:
|
||||
path = self.__trnName(self.config.get('DEFAULT','bkpstore').strip('/'))
|
||||
if '/'+path.strip('/') != "/":
|
||||
return(path.strip('/'))
|
||||
else:
|
||||
return('')
|
||||
|
||||
def getStorePath(self,store='SRC',tag='DEFAULT',original=False):
|
||||
sn = '/'+self.getStoreName(store=store,tag=tag) if len(self.getStoreName(store=store,tag=tag)) > 0 else ''
|
||||
return(self.getMountPath(store=store,tag=tag,original=original) + sn)
|
||||
|
||||
def cmdsh(self,tag='DEFAULT',store='SRC',cmd=''):
|
||||
if self.getssh(tag,store) == None:
|
||||
return('',subprocess.check_output(cmd, shell=True).decode(),'')
|
||||
else:
|
||||
out = ''
|
||||
conn = self.getssh(tag,store)
|
||||
connect(conn)
|
||||
return conn['conn'].exec_command(cmd)
|
||||
|
||||
def remotecommand(self,tag='DEFAULT',store='SRC',cmd='',stderr=None):
|
||||
if self.getssh(tag,store) == None:
|
||||
#print("noconn")
|
||||
try:
|
||||
ret = subprocess.run(cmd,stderr=stderr,stdout=subprocess.PIPE)
|
||||
if ret.returncode > 0:
|
||||
pass
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise
|
||||
return ret.stdout.decode("utf-8").rstrip('/n')
|
||||
|
||||
else:
|
||||
#print("conn",self.ssh[tag][store]['host'])
|
||||
out = ''
|
||||
conn = self.getssh(tag,store)
|
||||
if connect(conn):
|
||||
stdin, stdout, stderr = conn['conn'].exec_command(' '.join(cmd))
|
||||
if not stdout:
|
||||
#print("Xr")
|
||||
out = stdout.readlines()
|
||||
err = stderr.readlines()
|
||||
return(''.join(out) if len(err) == 0 else False)
|
||||
else:
|
||||
#print("Yr",stdout.read().decode("utf-8"))
|
||||
return(stdout.read().decode("utf-8"))
|
||||
else:
|
||||
print("Host not reachable (remcomd): %s" % (conn['host']))
|
||||
return('')
|
||||
|
||||
|
||||
def getDevice(self,store='SRC',tag='DEFAULT'):
|
||||
mp = self.getMountPath(store=store,tag=tag,original=False)
|
||||
conn = self.getssh(tag,store)
|
||||
connect(conn)
|
||||
mi = MountInfo(conn=conn)
|
||||
amount=mi.fstype(mp)
|
||||
if mi.fstype(mp) == 'autofs':
|
||||
try:
|
||||
Myos().stat(self.getStorePath(store=store,tag=tag),conn=conn)
|
||||
except:
|
||||
Myos().stat(os.path.dirname(self.getStorePath(store=store,tag=tag)),conn=conn)
|
||||
mi = MountInfo(conn=self.getssh(tag,store))
|
||||
return(mi.device(mp) if mi.fstype(mp) != 'autofs' else None)
|
||||
|
||||
def getUUID(self,store='SRC',tag='DEFAULT'):
|
||||
try:
|
||||
device = self.getDevice(store=store,tag=tag)
|
||||
except FileNotFoundError:
|
||||
#print("UID_NOENT")
|
||||
#raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), store + " " + tag)
|
||||
raise
|
||||
except:
|
||||
return None
|
||||
|
||||
#print("DEVICE",device,store,tag)
|
||||
if device == None: return None
|
||||
cmd = ['/sbin/blkid', device.rstrip("\n"), '-o', 'value', '-s', 'UUID']
|
||||
uuid = self.remotecommand(tag,store,cmd)
|
||||
#print("UUID",uuid,store,tag,' '.join(cmd))
|
||||
return uuid.rstrip('\n') if uuid.rstrip('\n') != '' else None
|
||||
|
||||
|
||||
def setBKPPath(self,mount):
|
||||
#self._read()
|
||||
self.config['DEFAULT']['BKPMNT'] = mount
|
||||
|
||||
def setBKPStore(self,store):
|
||||
#self._read()
|
||||
self.config['DEFAULT']['bkpstore'] = store
|
||||
|
||||
def setSNPPath(self,mount):
|
||||
#self._read()
|
||||
self.config['DEFAULT']['SNPMNT'] = mount
|
||||
|
||||
def setSNPStore(self,store):
|
||||
#self._read()
|
||||
self.config['DEFAULT']['snpstore'] = store
|
||||
|
||||
def getInterval(self,intv='misc'):
|
||||
#self._read()
|
||||
try:
|
||||
return(self.config.get(intv,'interval'))
|
||||
except:
|
||||
return(self.config.get('DEFAULT','interval'))
|
||||
|
||||
def getTransfer(self,intv='misc'):
|
||||
#self._read()
|
||||
try:
|
||||
return(s2bool(self.config.get(intv,'transfer')))
|
||||
except:
|
||||
return(s2bool(self.config.get('DEFAULT','transfer')))
|
||||
|
||||
def getSymLink(self,intv='misc'):
|
||||
#self._read()
|
||||
try:
|
||||
return(self.config.get(intv,'symlink'))
|
||||
except:
|
||||
return(self.config.get('DEFAULT','symlink'))
|
||||
|
||||
def getIsDefault(self,intv='misc'):
|
||||
#self._read()
|
||||
try:
|
||||
self.config.get(intv,'interval')
|
||||
return(intv)
|
||||
except:
|
||||
return('default')
|
||||
|
||||
def getVolumes(self,tag='default'):
|
||||
#self._read()
|
||||
VOLSTRANS = []
|
||||
try:
|
||||
VOLS = self.config.get(tag,'volumes')
|
||||
except:
|
||||
VOLS = self.config.get('DEFAULT','volumes')
|
||||
for vol in VOLS.split(','):
|
||||
VOLSTRANS.append(self.__trnName(vol))
|
||||
return(VOLSTRANS)
|
||||
|
||||
def ListIntVolumes(self):
|
||||
#self._read()
|
||||
VOLSTRANS = []
|
||||
for intv in self.ListIntervals():
|
||||
try:
|
||||
VOLS = self.config.get(intv,'volumes')
|
||||
except:
|
||||
VOLS = self.config.get('DEFAULT','volumes')
|
||||
VOLS = VOLS.split(',')
|
||||
for i, item in enumerate(VOLS):
|
||||
VOLS[i] = self.__trnName(item)
|
||||
VOLSTRANS.append('\t'+intv+': '+' '.join(VOLS))
|
||||
return(VOLSTRANS)
|
||||
|
||||
def getIgnores(self,intv='misc'):
|
||||
try:
|
||||
r = self.config.get(intv,'ignore')
|
||||
except:
|
||||
try:
|
||||
r = self.config.get('DEFAULT','ignore')
|
||||
except:
|
||||
r=None
|
||||
return(None if r == '' or r == None else r)
|
||||
|
||||
def getNotification(self,intv='misc'):
|
||||
#print('GN',intv)
|
||||
try:
|
||||
r = self.config.get(intv,'notification')
|
||||
except:
|
||||
try:
|
||||
r = self.config.get('DEFAULT','notification')
|
||||
except:
|
||||
r=None
|
||||
return(None if r == '' or r == None else r)
|
||||
|
||||
def getUrgency(self,intv='misc'):
|
||||
#print('GU',intv)
|
||||
try:
|
||||
r = self.config.get(intv,'notification_urgency')
|
||||
except:
|
||||
try:
|
||||
r = self.config.get('DEFAULT','notification_urgency')
|
||||
except:
|
||||
r=None
|
||||
return(None if r == '' or r == None else r)
|
||||
|
||||
def __trnName(self,short):
|
||||
ret = list()
|
||||
for sh in short.split(','):
|
||||
if sh == "$S": ret.append(self.syssubvol)
|
||||
elif sh == "$h": ret.append(self.hostname)
|
||||
else: ret.append(sh)
|
||||
return(','.join(ret))
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
"""Emmitter functionality."""
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
|
||||
|
||||
class Emitter(dbus.service.Object):
|
||||
"""Emitter DBUS service object."""
|
||||
|
||||
def __init__(self, conn=None, object_path=None, bus_name=None):
|
||||
"""Initialize the emitter DBUS service object."""
|
||||
dbus.service.Object.__init__(self, conn=conn, object_path=object_path)
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.mkbackup.Status')
|
||||
def update(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a update signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.mkbackup.Status')
|
||||
def start(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a start signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.mkbackup.Status')
|
||||
def finished(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a finished signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.mkbackup.Status')
|
||||
def reset(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a reset signal')
|
||||
|
||||
""" Example to use
|
||||
progress = Emitter(dbus.SystemBus(),
|
||||
'/at/xundeenergie/mkbackup/Status')
|
||||
|
||||
progress.start(
|
||||
{'intv': 'hourly'})
|
||||
|
||||
progress.update(
|
||||
{'intv': 'hourly', 'progr': 5})
|
||||
|
||||
progress.finished(
|
||||
{'intv': 'hourly'})
|
||||
|
||||
progress.reset(
|
||||
{'intv': 'hourly'})
|
||||
"""
|
41
files/usr/lib/python3/dist-packages/mkbackup/service
Executable file
41
files/usr/lib/python3/dist-packages/mkbackup/service
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/python3 -d
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import dbus, dbus.service, dbus.exceptions
|
||||
import sys
|
||||
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from gi.repository import GLib
|
||||
|
||||
# Initialize a main loop
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
loop = GLib.MainLoop()
|
||||
|
||||
# Declare a name where our service can be reached
|
||||
try:
|
||||
sysbus_name = dbus.service.BusName("at.xundeenergie",
|
||||
bus=dbus.SystemBus(),
|
||||
do_not_queue=True)
|
||||
except dbus.exceptions.NameExistsException:
|
||||
print("service is already running")
|
||||
sys.exit(1)
|
||||
|
||||
# Run the loop
|
||||
try:
|
||||
# Create our initial objects
|
||||
from services.mkbackup import MkBackup
|
||||
#from services.notifications import AllStati
|
||||
#from services.notifications import Properties
|
||||
#from services.mkbackup import Properties
|
||||
MkBackup(sysbus_name, "/at/xundeenergie/mkbackup/Intervals")
|
||||
#AllStati(sysbus_name, "/at/xundeenergie/mkbackup")
|
||||
#Properties(sysbus_name, "/at/xundeenergie/mkbackup")
|
||||
#Properties(sysbus_name, dbus_interface="/at/xundeenergie/mkbackup")
|
||||
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
print("keyboard interrupt received")
|
||||
except Exception as e:
|
||||
print("Unexpected exception occurred: '{}'".format(str(e)))
|
||||
finally:
|
||||
loop.quit()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,117 @@
|
|||
import dbus
|
||||
import dbus.service
|
||||
import random
|
||||
import time
|
||||
import os
|
||||
|
||||
from mkbackup_btrfs_config import Config, MountInfo, connect, Myos, __version__
|
||||
|
||||
config = Config()
|
||||
|
||||
class MkBackup:
|
||||
def __init__(self, bus_name, base_path):
|
||||
# self._bus = dbus.SystemBus()
|
||||
# self.notification = Notification()
|
||||
Intervals(bus_name, base_path)
|
||||
|
||||
for intv in config.ListIntervals():
|
||||
Properties(bus_name, os.path.join(base_path, intv), intv)
|
||||
|
||||
class Intervals(dbus.service.Object):
|
||||
def __init__(self, bus_name, bus_path):
|
||||
super().__init__(bus_name, bus_path)
|
||||
|
||||
@dbus.service.method(dbus_interface='at.xundeenergie.mkbackup.Intervals',
|
||||
in_signature='', out_signature='v')
|
||||
def Names(self):
|
||||
return config.ListIntervals()
|
||||
|
||||
class Properties(dbus.service.Object):
|
||||
def __init__(self, bus_name, bus_path, interval):
|
||||
super().__init__(bus_name, bus_path)
|
||||
self.interface = "at.xundeenergie.mkbackup.Status"
|
||||
self.interval = interval
|
||||
self.STATI = ['reset', 'stop', 'running', 'finished']
|
||||
self.properties = dict()
|
||||
self.properties[self.interface] = dict()
|
||||
self.properties[self.interface]['progress'] = 0 # 0-100
|
||||
self.properties[self.interface]['status'] = 'stop' # stop, running, finished, reset
|
||||
self.properties[self.interface]['transfer'] = config.getTransfer(interval)
|
||||
self.properties[self.interface]['lastrun'] = 0 # datetime
|
||||
self.properties[self.interface]['finished'] = True # Boolean
|
||||
self.properties[self.interface]['name'] = interval # Boolean
|
||||
self.properties['function'] = dict()
|
||||
self.properties['function']['progress'] = self.update_progress
|
||||
self.properties['function']['status'] = self.update_status
|
||||
# self.properties['function']['lastrun'] = self.update_lastrun
|
||||
from dbus import Interface
|
||||
|
||||
def update_progress(self, interface, incr):
|
||||
print("Update progress: %i / %i, %s" % (float(incr),
|
||||
self.properties[interface]['progress'],self.properties[interface]['status']))
|
||||
if self.properties[interface]['status'] == 'running':
|
||||
if 0 < self.properties[interface]['progress'] + float(incr) < 100:
|
||||
#self.properties[interface]['progress'] = self.properties[interface]['progress'] + float(incr)
|
||||
self.properties[interface]['progress'] += float(incr)
|
||||
elif self.properties[interface]['progress'] + float(incr) >= 100:
|
||||
self.properties[interface]['progress'] = 99
|
||||
else:
|
||||
print('B', incr, type(incr))
|
||||
return self.properties[interface]['progress']
|
||||
|
||||
def update_status(self, interface, status):
|
||||
if status in self.STATI:
|
||||
print("Update status: %s" % status)
|
||||
print("Status: ", self.properties[interface]['status'])
|
||||
if status == 'finished':
|
||||
self.properties[interface]['status'] = status
|
||||
self.properties[interface]['progress'] = 100
|
||||
elif status == 'stop':
|
||||
self.properties[interface]['status'] = status
|
||||
self.properties[interface]['progres'] = 0
|
||||
elif status == 'reset':
|
||||
self.properties[interface]['status'] = 'running'
|
||||
self.properties[interface]['progress'] = 0
|
||||
print("status: ", self.properties[interface]['status'])
|
||||
return self.properties[interface]['status']
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='ss', out_signature='v')
|
||||
def Get(self, interface_name, property_name):
|
||||
return self.GetAll(interface_name)[property_name]
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='s', out_signature='a{sv}')
|
||||
def GetAll(self, interface_name):
|
||||
if interface_name == self.interface:
|
||||
return self.properties[interface_name]
|
||||
else:
|
||||
raise dbus.exceptions.DBusException(
|
||||
'at.xundeenergie.mkbackup.UnknownInterface',
|
||||
'The Foo object does not implement the %s interface'
|
||||
% interface_name)
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='ssv')
|
||||
def Set(self, interface_name, property_name, new_value):
|
||||
# validate the property name and value, update internal state…
|
||||
"""https://recalll.co/ask/v/topic/D-Bus-D-Feet-Send-Dictionary-of-String%2CVariants-in-Python-Syntax/5565e1372bd273d7108b7b82
|
||||
__import__('gi.repository.GLib', globals(), locals(), ['Variant']).Variant("s", "value")"""
|
||||
if interface_name in self.properties:
|
||||
if property_name in self.properties[interface_name]:
|
||||
func = self.properties['function'].get(property_name)
|
||||
new_value = func(interface_name, new_value)
|
||||
#self.properties[str(interface_name)][str(property_name)] = new_value
|
||||
self.PropertiesChanged(interface_name,
|
||||
{ property_name: new_value, 'interval': self.interval}, [])
|
||||
else:
|
||||
raise dbus.exceptions.DBusException(
|
||||
'at.xundeenergie.mkbackup.UnknownInterface',
|
||||
'The Foo object does not implement the %s interface'
|
||||
% interface_name)
|
||||
|
||||
@dbus.service.signal(dbus.PROPERTIES_IFACE,
|
||||
signature='sa{sv}as')
|
||||
def PropertiesChanged(self, interface_name, changed_properties,
|
||||
invalidated_properties):
|
||||
pass
|
|
@ -0,0 +1,242 @@
|
|||
import dbus
|
||||
import dbus.service
|
||||
import random
|
||||
import time
|
||||
import os
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from mkbackup_btrfs_config import Config, MountInfo, connect, Myos, __version__
|
||||
config = Config()
|
||||
|
||||
from system_notification_emitter import Emitter
|
||||
class Notification(Emitter):
|
||||
def __init__(self):
|
||||
super().__init__(conn=dbus.SystemBus(), object_path='/at/xundeenergie/notifications/advanced/Notification')
|
||||
|
||||
class Intervals:
|
||||
def __init__(self, bus_name, base_path="/at/xundeenergie/mkbackup"):
|
||||
#super().__init__(bus_name, "/at/xundeenergie/mkbackup")
|
||||
self.base_path = base_path
|
||||
self._bus = dbus.SystemBus()
|
||||
# self.stati = dict()
|
||||
# self.stati[None] = None
|
||||
self.notification = Notification()
|
||||
|
||||
for intv in config.ListIntervals():
|
||||
# self.stati[intv] = dict()
|
||||
# self.stati[intv]['progress'] = 0
|
||||
# self.stati[intv]['trans'] = config.getTransfer(intv)
|
||||
# self.stati[intv]['lastrun'] = 0
|
||||
# self.stati[intv]['finished'] = True
|
||||
#self._set_listeners(os.path.join(base_path, intv))
|
||||
# self.stati[intv]['methods'] = Properties(bus_name,
|
||||
# os.path.join(base_path, intv))
|
||||
Properties(bus_name, os.path.join(base_path, intv), intv)
|
||||
|
||||
def _set_listeners(self,dbus_path):
|
||||
"""
|
||||
dbus-send --system "/at/xundeenergie/mkbackup"
|
||||
--dest="at.xundeenergie.mkbackup"
|
||||
"at.xundeenergie.mkbackup.Status.progress" string:hourly "int16:10"
|
||||
"""
|
||||
update = self._bus.add_signal_receiver(
|
||||
path=dbus_path,
|
||||
handler_function=self._progress,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='update')
|
||||
|
||||
finished = self._bus.add_signal_receiver(
|
||||
path=dbus_path,
|
||||
handler_function=self._finished,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='finished')
|
||||
|
||||
start = self._bus.add_signal_receiver(
|
||||
path=dbus_path,
|
||||
handler_function=self._start,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='start')
|
||||
|
||||
reset = self._bus.add_signal_receiver(
|
||||
path=dbus_path,
|
||||
handler_function=self._reset,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='reset')
|
||||
|
||||
def _remove_listeners(self,intv):
|
||||
update = self._bus.remove_signal_receiver(
|
||||
path=dbus_path,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='update')
|
||||
|
||||
finished = self._bus.remove_signal_receiver(
|
||||
path=dbus_path,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='finished')
|
||||
|
||||
start = self._bus.remove_signal_receiver(
|
||||
path=dbus_path,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='start')
|
||||
|
||||
reset = self._bus.remove_signal_receiver(
|
||||
path=dbus_path,
|
||||
dbus_interface="at.xundeenergie.mkbackup.Status",
|
||||
signal_name='reset')
|
||||
|
||||
try:
|
||||
del self.state[intv]
|
||||
except KeyError as ex:
|
||||
print("No such key: '%s'" % ex.message)
|
||||
|
||||
|
||||
def _start(self, intv):
|
||||
self.stati[intv]['progress'] = 0
|
||||
if self.stati[intv]['finished']:
|
||||
print("Reset %s first" % (intv))
|
||||
return
|
||||
print("%s start" % (intv))
|
||||
|
||||
def _progress(self, intv, progr):
|
||||
if progr >= 0 and not self.stati[intv]['finished']:
|
||||
if 0 < self.stati[intv]['progress'] + progr <= 99:
|
||||
self.stati[intv]['progress'] += progr
|
||||
print("%s run progress: %i/100%%" % (str(intv),
|
||||
int(self.stati[intv]['progress'])))
|
||||
else:
|
||||
self.stati[intv]['progress'] = 99
|
||||
self.sig_update(intv, self.stati[intv]['progress'])
|
||||
print("Progress: %s" % (self.stati[intv]['progress']))
|
||||
|
||||
def _finished(self, intv):
|
||||
self.stati[intv]['finished'] = True
|
||||
self.stati[intv]['progress'] = 100
|
||||
self._send_notification(body="%s finished" % (intv))
|
||||
self.sig_update(intv, self.stati[intv]['progress'])
|
||||
print("%s finished" % (intv))
|
||||
|
||||
def _reset(self, intv):
|
||||
self.stati[intv]['finished'] = False
|
||||
self.stati[intv]['progress'] = 0
|
||||
print("%s reset" % (intv))
|
||||
|
||||
def _send_notification(self, header="backup", body="Unconfigured Message"):
|
||||
msg = dict()
|
||||
msg['sender'] = "mkbackup"
|
||||
msg['header'] = header
|
||||
msg['body'] = body
|
||||
self.notification.normal(msg)
|
||||
|
||||
|
||||
class AllStati(dbus.service.Object):
|
||||
def __init__(self, bus_name, base_path="/at/xundeenergie/mkbackup"):
|
||||
super().__init__(bus_name, base_path)
|
||||
|
||||
@dbus.service.method("at.xundeenergie.mkbackup",
|
||||
in_signature='', out_signature='v')
|
||||
def Intervals(self):
|
||||
return config.ListIntervals()
|
||||
|
||||
MY_INTERFACE = 'at.xundeenergie.mkbackup.Properties'
|
||||
|
||||
class Status(dbus.service.Object):
|
||||
def __init__(self, bus_name, base_path, intv, stati):
|
||||
super().__init__(bus_name, base_path+'/'+intv)
|
||||
self.interface_name = base_path+'/'+intv
|
||||
self.property_name = intv
|
||||
self.status = stati
|
||||
|
||||
@dbus.service.method("at.xundeenergie.mkbackup.Status",
|
||||
in_signature='', out_signature='v')
|
||||
def Progress(self):
|
||||
return self.status['progress']
|
||||
|
||||
@dbus.service.method("at.xundeenergie.mkbackup.Status",
|
||||
in_signature='', out_signature='a{sv}')
|
||||
def Props(self):
|
||||
return {
|
||||
'progress': self.status['progress'],
|
||||
'transfer': self.status['trans'],
|
||||
'lastrun' : self.status['lastrun'],
|
||||
'finished': self.status['finished']
|
||||
}
|
||||
|
||||
@dbus.service.signal("at.xundeenergie.mkbackup.Status", signature='si')
|
||||
def sig_update(self, intv, progr):
|
||||
pass
|
||||
|
||||
@dbus.service.signal("at.xundeenergie.mkbackup.Status", signature='si')
|
||||
def sig_finished(self, intv, progr):
|
||||
pass
|
||||
|
||||
class Properties(dbus.service.Object):
|
||||
def __init__(self, bus_name, bus_path, interval):
|
||||
super().__init__(bus_name, bus_path)
|
||||
self.interface_name = dbus.PROPERTIES_IFACE
|
||||
self.interface = "at.xundeenergie.mkbackup.Status"
|
||||
self.interval = interval
|
||||
self.properties = dict()
|
||||
self.properties[self.interface] = dict()
|
||||
self.properties[self.interface]['progress'] = 0
|
||||
self.properties[self.interface]['transfer'] = config.getTransfer(interval)
|
||||
self.properties[self.interface]['lastrun'] = 0
|
||||
self.properties[self.interface]['finished'] = True
|
||||
from dbus import Interface
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='ss', out_signature='v')
|
||||
def Get(self, interface_name, property_name):
|
||||
return self.GetAll(interface_name)[property_name]
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='s', out_signature='a{sv}')
|
||||
def GetAll(self, interface_name):
|
||||
print('I', interface_name, 'sI', self.interface, 'P', self.properties,
|
||||
'PP',self.properties[interface_name])
|
||||
if interface_name == self.interface:
|
||||
return self.properties[interface_name]
|
||||
else:
|
||||
raise dbus.exceptions.DBusException(
|
||||
'com.example.UnknownInterface',
|
||||
'The Foo object does not implement the %s interface'
|
||||
% interface_name)
|
||||
|
||||
@dbus.service.method(dbus.PROPERTIES_IFACE,
|
||||
in_signature='ssv')
|
||||
def Set(self, interface_name, property_name, new_value):
|
||||
# validate the property name and value, update internal state…
|
||||
"""https://recalll.co/ask/v/topic/D-Bus-D-Feet-Send-Dictionary-of-String%2CVariants-in-Python-Syntax/5565e1372bd273d7108b7b82
|
||||
__import__('gi.repository.GLib', globals(), locals(), ['Variant']).Variant("s", "value")"""
|
||||
if interface_name == self.interface:
|
||||
self.properties[str(interface_name)][str(property_name)] = new_value
|
||||
self.PropertiesChanged(str(interface_name),
|
||||
{ str(property_name): new_value }, [])
|
||||
|
||||
@dbus.service.signal(dbus.PROPERTIES_IFACE,
|
||||
signature='sa{sv}as')
|
||||
def PropertiesChanged(self, interface_name, changed_properties,
|
||||
invalidated_properties):
|
||||
pass
|
||||
#class Notifications(dbus.service.Object):
|
||||
# def __init__(self, bus_name):
|
||||
# super().__init__(bus_name, "/at/xundeenergie/mkbackup/Status")
|
||||
#
|
||||
# random.seed()
|
||||
#
|
||||
# @dbus.service.method("at.xundeenergie.mkbackup.Status",
|
||||
# in_signature='i', out_signature='s')
|
||||
# def quick(self, bits=8):
|
||||
# return str(random.getrandbits(bits))
|
||||
#
|
||||
# @dbus.service.method("at.xundeenergie.mkbackup.Status",
|
||||
# in_signature='i', out_signature='s')
|
||||
# def slow(self, bits=8):
|
||||
# thread = SlowThread(bits, self.slow_result)
|
||||
# return thread.thread_id
|
||||
#
|
||||
# @dbus.service.signal("at.xundeenergie.mkbackup.Status", signature='ss')
|
||||
# def slow_result(self, thread_id, result):
|
||||
# pass
|
||||
#
|
||||
#
|
|
@ -0,0 +1,47 @@
|
|||
import dbus.service
|
||||
import random
|
||||
import time
|
||||
|
||||
import threading
|
||||
class SlowThread(object):
|
||||
def __init__(self, bits, callback):
|
||||
self._callback = callback
|
||||
self.result = ''
|
||||
|
||||
self.thread = threading.Thread(target=self.work, args=(bits,))
|
||||
self.thread.start()
|
||||
self.thread_id = str(self.thread.ident)
|
||||
|
||||
def work(self, bits):
|
||||
num = ''
|
||||
|
||||
while True:
|
||||
num += str(random.randint(0, 1))
|
||||
bits -= 1
|
||||
time.sleep(1)
|
||||
|
||||
if bits <= 0:
|
||||
break
|
||||
|
||||
self._callback(self.thread_id, str(int(num, 2)))
|
||||
|
||||
class RandomData(dbus.service.Object):
|
||||
def __init__(self, bus_name):
|
||||
super().__init__(bus_name, "/com/larry_price/test/RandomData")
|
||||
|
||||
random.seed()
|
||||
|
||||
@dbus.service.method("com.larry_price.test.RandomData",
|
||||
in_signature='i', out_signature='s')
|
||||
def quick(self, bits=8):
|
||||
return str(random.getrandbits(bits))
|
||||
|
||||
@dbus.service.method("com.larry_price.test.RandomData",
|
||||
in_signature='i', out_signature='s')
|
||||
def slow(self, bits=8):
|
||||
thread = SlowThread(bits, self.slow_result)
|
||||
return thread.thread_id
|
||||
|
||||
@dbus.service.signal("com.larry_price.test.RandomData", signature='ss')
|
||||
def slow_result(self, thread_id, result):
|
||||
pass
|
|
@ -0,0 +1,38 @@
|
|||
"""Emmitter functionality."""
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
|
||||
|
||||
class Emitter(dbus.service.Object):
|
||||
"""Emitter DBUS service object."""
|
||||
|
||||
def __init__(self, conn=None, object_path=None, bus_name=None):
|
||||
"""Initialize the emitter DBUS service object."""
|
||||
dbus.service.Object.__init__(self, conn=conn, object_path=object_path)
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.Notification')
|
||||
def low(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a low test signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.Notification')
|
||||
def normal(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a normal test signal')
|
||||
|
||||
@dbus.service.signal(dbus_interface='at.xundeenergie.Notification')
|
||||
def critical(self,*args,**kwargs):
|
||||
"""Emmit a test signal."""
|
||||
print('Emitted a critical test signal')
|
||||
|
||||
""" Example to use
|
||||
Simple_Notification = Emitter(dbus.SystemBus(),
|
||||
'/at/xundeenergie/notifications/simple/Notification')
|
||||
Advanced_Notification = Emitter(dbus.SystemBus(),
|
||||
'/at/xundeenergie/notifications/advanced/Notification')
|
||||
|
||||
#Simple_Notification.low('M')
|
||||
Advanced_Notification.normal(
|
||||
{'sender': 'emitter1.py', 'header': 'Testmessage', 'body': 'Test Body'})
|
||||
"""
|
9
files/usr/lib/systemd/scripts/btrfs-action.sh
Executable file
9
files/usr/lib/systemd/scripts/btrfs-action.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
DEVICE="$1"; shift
|
||||
ACTION="$1"; shift
|
||||
MAINPID="$1"
|
||||
BTRFS=/bin/btrfs
|
||||
|
||||
[ x"$MAINPID" = "x" ] && exit 0
|
||||
/bin/ps h -o command -p "$MAINPID" && $BTRFS $ACTION cancel "$DEVICE" || exit 0
|
137
files/usr/lib/systemd/scripts/mksnapshot-create-volume.sh
Executable file
137
files/usr/lib/systemd/scripts/mksnapshot-create-volume.sh
Executable file
|
@ -0,0 +1,137 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Create udev-rule and mount-entry for new backup-volume
|
||||
|
||||
ACTION=$1
|
||||
case $2 in
|
||||
-u)
|
||||
UUID=$3
|
||||
DEV=$(readlink -f /dev/disk/by-uuid/$UUID)
|
||||
PARTUUID="$(blkid /dev/disk/by-uuid/$UUID -o value -s PARTUUID)"
|
||||
;;
|
||||
u-*)
|
||||
UUID=${2#u-}
|
||||
DEV=$(readlink -f /dev/disk/by-uuid/$UUID)
|
||||
PARTUUID="$(blkid /dev/disk/by-uuid/$UUID -o value -s PARTUUID)"
|
||||
;;
|
||||
-p)
|
||||
PARTUUID=$3
|
||||
DEV=$(readlink -f /dev/disk/by-partuuid/$PARTUUID)
|
||||
UUID="$(blkid /dev/disk/by-partuuid/$PARTUUID -o value -s UUID)"
|
||||
;;
|
||||
p-*)
|
||||
PARTUUID=${2#p-}
|
||||
DEV=$(readlink -f /dev/disk/by-partuuid/$PARTUUID)
|
||||
UUID="$(blkid /dev/disk/by-partuuid/$PARTUUID -o value -s UUID)"
|
||||
;;
|
||||
d-*)
|
||||
DEV=${2#d-}
|
||||
UUID="$(blkid $DEV -o value -s UUID)"
|
||||
PARTUUID="$(blkid $DEV -o value -s PARTUUID)"
|
||||
;;
|
||||
*)
|
||||
DEV="$(/bin/systemd-escape -p -u $2)"
|
||||
UUID="$(blkid $DEV -o value -s UUID)"
|
||||
#PARTUUID="$(blkid $DEV -o value -s PARTUUID)"
|
||||
PRE="d-"
|
||||
;;
|
||||
esac
|
||||
|
||||
#DESTUDEV="/tmp/"
|
||||
#DESTSYSTEMD="/tmp/"
|
||||
DESTUDEV="/etc/udev/rules.d/"
|
||||
DESTSYSTEMD="/etc/systemd/system/"
|
||||
|
||||
SYSTEMCTL="/bin/systemctl"
|
||||
|
||||
echo "$ACTION ${DEV} | ${UUID} | ${PARTUUID}"
|
||||
|
||||
sleep 1
|
||||
|
||||
if [ "$DEV"x = "x" ]
|
||||
then
|
||||
TYPE="btrfs"
|
||||
else
|
||||
TYPE="$(blkid $DEV -o value -s TYPE)"
|
||||
echo "T $TYPE | $DEV"
|
||||
fi
|
||||
|
||||
if [ "$PARTUUID"x = "x" ]; then
|
||||
if [ "$UUID"x = "x" ]; then
|
||||
echo "$PARTUUID | $UUID | $DEV is no valid device"
|
||||
exit 3
|
||||
else
|
||||
DUUID="$UUID" #DUUID is uuid which is taken to use
|
||||
SUUID="ID_FS_UUID" #SUUID is the string for the udev-rule it's UUID or PARTUUID
|
||||
ID="uuid" #ID is also for the udev-rule. To look in /dev/disk/by-uuid or /dev/disk/by-partuuid
|
||||
PRE="u-"
|
||||
fi
|
||||
else
|
||||
DUUID="$PARTUUID"
|
||||
SUUID="ID_PART_ENTRY_UUID"
|
||||
ID="partuuid"
|
||||
PRE="p-"
|
||||
fi
|
||||
|
||||
#echo "$DUUID | $SUUID | $ID | $PRE"
|
||||
# Start by udev
|
||||
start () {
|
||||
|
||||
mkdir -p "${DESTSYSTEMD}var-cache-backup.mount.d/"
|
||||
|
||||
cat <<EOF > "${DESTSYSTEMD}var-cache-backup.mount.d/source.conf"
|
||||
[Mount]
|
||||
What=/dev/disk/by-${ID}/${DUUID}
|
||||
EOF
|
||||
|
||||
$SYSTEMCTL daemon-reload
|
||||
|
||||
}
|
||||
|
||||
# Create udev-Rule for new external drive
|
||||
register () {
|
||||
cat <<EOF > "${DESTUDEV}99-ext-bkp-volume-${PRE}${DUUID}.rules"
|
||||
ACTION=="add", KERNEL=="sd*", SUBSYSTEMS=="usb", ENV{${SUUID}}=="$DUUID", SYMLINK+="disk/mars", TAG+="systemd", ENV{SYSTEMD_WANTS}+="mkbackup-external@${PRE}${DUUID}.service", ENV{SYSTEMD_WANTS}+="mkbackup@BKP.target", ENV{SYSTEMD_WANTS}+="smartctl-fast@$(/bin/systemd-escape -p /dev/disk/by-${ID}/${DUUID}).service"
|
||||
|
||||
ACTION=="remove", KERNEL=="sd*", SUBSYSTEMS=="usb", ENV{${SUUID}}="$DUUID", \
|
||||
RUN+="${SYSTEMCTL} --no-block stop mkbackup@BKP.target"
|
||||
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
|
||||
# delete udev-rule, if external drive is not longer in use for backups.
|
||||
unregister () {
|
||||
[ -e "${DESTUDEV}99-ext-bkp-volume-${PRE}${DUUID}.rules" ] && rm "${DESTUDEV}99-ext-bkp-volume-${PRE}${DUUID}.rules"
|
||||
}
|
||||
|
||||
case $TYPE in
|
||||
btrfs)
|
||||
;;
|
||||
*)
|
||||
echo "$DEV isn't a btrfs-filesystem. Exiting"; exit 1;;
|
||||
esac
|
||||
|
||||
case $ACTION in
|
||||
register)
|
||||
#setup udev-rule for device
|
||||
register
|
||||
;;
|
||||
unregister)
|
||||
#delete udev-rule for device
|
||||
unregister ;;
|
||||
start)
|
||||
#activate device
|
||||
start;;
|
||||
stop)
|
||||
#deactivate device
|
||||
stop ;;
|
||||
*)
|
||||
echo "$ACTION not recognized";
|
||||
exit 2;;
|
||||
esac
|
||||
|
||||
$SYSTEMCTL daemon-reload
|
||||
#/bin/systemctl
|
||||
exit 0
|
12
files/usr/lib/systemd/scripts/systemd-btrfs-email
Executable file
12
files/usr/lib/systemd/scripts/systemd-btrfs-email
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
/usr/sbin/sendmail -t <<ERRMAIL
|
||||
To: $1
|
||||
From: systemd <root@$HOSTNAME>
|
||||
Subject: $2
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
|
||||
$(btrfs scrub status "$3")
|
||||
|
||||
ERRMAIL
|
11
files/usr/lib/systemd/user/mkbackup-userdir.path
Normal file
11
files/usr/lib/systemd/user/mkbackup-userdir.path
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
#Description=Starts mounting backups to %h/backup, if this directory exists
|
||||
#BindsTo=mkbackup-userdir.path
|
||||
Conflicts=shutdown.target sleep.target suspend.target
|
||||
|
||||
[Path]
|
||||
PathExists=%h/backup
|
||||
Unit=mkbackup-userdir.service
|
||||
|
||||
[Install]
|
||||
WantedBy=paths.target
|
10
files/usr/lib/systemd/user/mkbackup-userdir.service
Normal file
10
files/usr/lib/systemd/user/mkbackup-userdir.service
Normal file
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Show all userspezific backups in %h/backup
|
||||
BindsTo=mkbackup-userdir.path
|
||||
Conflicts=shutdown.target reboot.target umount.target
|
||||
Before=shutdown.target reboot.target sleep.target umount.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/MksnapshotFS.py -f %h/backup -o ro,allow_root
|
||||
ExecStop=-/bin/fusermount -u %h/backup
|
||||
Restart=on-success
|
|
@ -0,0 +1 @@
|
|||
../mkbackup-userdir.path
|
1
files/usr/local/bin/mkbackup
Symbolic link
1
files/usr/local/bin/mkbackup
Symbolic link
|
@ -0,0 +1 @@
|
|||
mkbackup-btrfs
|
2156
files/usr/local/bin/mkbackup-btrfs
Executable file
2156
files/usr/local/bin/mkbackup-btrfs
Executable file
File diff suppressed because it is too large
Load diff
15
files/usr/local/bin/syssubvol.old
Executable file
15
files/usr/local/bin/syssubvol.old
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
#mawk '$5 ~ "^/$" {gsub ("/","",$4);print $4}' /proc/self/mountinfo
|
||||
|
||||
#SV1=$(basename "$(grub-mkrelpath /)")
|
||||
SV1="XXX"
|
||||
|
||||
[ -x /bin/cat ] || exit 1
|
||||
SV2=$(grep -sq "=subvol=" /proc/cmdline && /bin/cat /proc/cmdline |sed 's/^.*=subvol=\([^ ]*\) .*$/\1/')
|
||||
|
||||
#echo "SV1: $SV1 SV2: $SV2"
|
||||
if [ "$SV1" == "$SV2" ];then
|
||||
echo "$SV1"
|
||||
else
|
||||
echo "$SV2"
|
||||
fi
|
27
files/usr/share/doc/mkbackup-btrfs/fstab.mkbackup.example
Executable file
27
files/usr/share/doc/mkbackup-btrfs/fstab.mkbackup.example
Executable file
|
@ -0,0 +1,27 @@
|
|||
#####################################
|
||||
## System - Lokal
|
||||
#####################################
|
||||
# Hier kein subvol angeben. Ist in grub.cfg "rootflags=subvol=@debian"
|
||||
LABEL=debian / btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime 0 0
|
||||
#LABEL=debian /boot/grub/x86_64-efi btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/boot-grub-x86_64-efi 0 0
|
||||
LABEL=debian /home btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/home 0 0
|
||||
LABEL=debian /usr/local btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/usr-local 0 0
|
||||
LABEL=debian /opt btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/opt 0 0
|
||||
LABEL=debian /var/opt btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-opt 0 0
|
||||
LABEL=debian /var/log btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-log 0 0
|
||||
LABEL=debian /var/lib/mpd btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-lib-mpd 0 0
|
||||
#LABEL=debian /var/lib/named btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-lib-named 0 0
|
||||
LABEL=debian /var/spool btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-spool 0 0
|
||||
LABEL=debian /var/spool/dovecot btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-spool-dovecot 0 0
|
||||
LABEL=debian /var/tmp btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-tmp 0 0
|
||||
#LABEL=debian /var/www btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-www 0 0
|
||||
LABEL=debian /var/cache btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/var-cache 0 0
|
||||
#LABEL=debian /srv btrfs defaults,compress=lzo,nospace_cache,autodefrag,noinode_cache,noatime,subvol=__ALWAYSCURRENT__/srv 0 0
|
||||
tmpfs /tmp tmpfs nosuid,size=25% 0 0
|
||||
|
||||
|
||||
UUID=2AE9-56D2 /boot/efi vfat umask=0077 0 0
|
||||
####################################
|
||||
## BACKUP
|
||||
####################################
|
||||
## Ist über unit-Files in /etc/systemd/system geregelt
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the GNOME nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
var Gettext = imports.gettext;
|
||||
var Gio = imports.gi.Gio;
|
||||
|
||||
var Config = imports.misc.config;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
/**
|
||||
* initTranslations:
|
||||
* @domain: (optional): the gettext domain to use
|
||||
*
|
||||
* Initialize Gettext to load translations from extensionsdir/locale.
|
||||
* If @domain is not provided, it will be taken from metadata['gettext-domain']
|
||||
*/
|
||||
function initTranslations(domain) {
|
||||
var extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
domain = domain || extension.metadata['gettext-domain'];
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the locale files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell
|
||||
var localeDir = extension.dir.get_child('locale');
|
||||
if (localeDir.query_exists(null))
|
||||
Gettext.bindtextdomain(domain, localeDir.get_path());
|
||||
else
|
||||
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettings:
|
||||
* @schema: (optional): the GSettings schema id
|
||||
*
|
||||
* Builds and return a GSettings schema for @schema, using schema files
|
||||
* in extensionsdir/schemas. If @schema is not provided, it is taken from
|
||||
* metadata['settings-schema'].
|
||||
*/
|
||||
function getSettings(schema) {
|
||||
var extension = ExtensionUtils.getCurrentExtension();
|
||||
|
||||
schema = schema || extension.metadata['settings-schema'];
|
||||
|
||||
var GioSSS = Gio.SettingsSchemaSource;
|
||||
|
||||
// check if this extension was built with "make zip-file", and thus
|
||||
// has the schema files in a subfolder
|
||||
// otherwise assume that extension has been installed in the
|
||||
// same prefix as gnome-shell (and therefore schemas are available
|
||||
// in the standard folders)
|
||||
var schemaDir = extension.dir.get_child('schemas');
|
||||
var schemaSource;
|
||||
if (schemaDir.query_exists(null))
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||
GioSSS.get_default(),
|
||||
false);
|
||||
else
|
||||
schemaSource = GioSSS.get_default();
|
||||
|
||||
var schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj)
|
||||
throw new Error('Schema ' + schema + ' could not be found for extension '
|
||||
+ extension.metadata.uuid + '. Please check your installation.');
|
||||
|
||||
return new Gio.Settings({ settings_schema: schemaObj });
|
||||
}
|
||||
|
|
@ -0,0 +1,638 @@
|
|||
var GLib = imports.gi.GLib;
|
||||
var Lang = imports.lang;
|
||||
var Main = imports.ui.main;
|
||||
var PanelMenu = imports.ui.panelMenu;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
var St = imports.gi.St;
|
||||
var Shell = imports.gi.Shell;
|
||||
|
||||
var Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
var _ = Gettext.gettext;
|
||||
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
var Me = ExtensionUtils.getCurrentExtension();
|
||||
var Convenience = Me.imports.convenience;
|
||||
var Util = imports.misc.util;
|
||||
var PopupServiceItem = Me.imports.popupServiceItem.PopupServiceItem;
|
||||
var PopupTargetItem = Me.imports.popupTargetItem.PopupTargetItem;
|
||||
var PopupMenuItem = Me.imports.popupManuallyItem.PopupServiceItem;
|
||||
var MountMenuItem = Me.imports.popupMountItem.MountMenuItem;
|
||||
var DriveMenuItem = Me.imports.popupDriveItem.DriveMenuItem;
|
||||
var VolMenuItem = Me.imports.popupBkpVolumItem.PopupBKPItem;
|
||||
var Gio = imports.gi.Gio;
|
||||
var Mainloop = imports.mainloop;
|
||||
|
||||
var refreshTime = 3.0;
|
||||
var MainLabel;
|
||||
var icon;
|
||||
var MainIcon;
|
||||
var ExtIcon;
|
||||
var extMediaName = 'external backup-drive';
|
||||
var Drives = new Object();
|
||||
|
||||
//var DisabledIcon = 'my-caffeine-off-symbolic';
|
||||
//var EnabledIcon = 'my-caffeine-on-symbolic';
|
||||
var DisabledIcon = 'system-run-symbolic';
|
||||
var EnabledIcon = 'system-run-symbolic';
|
||||
//var ConfFile = '/etc/mkbackup-btrfs.conf';
|
||||
var ConfFile = '/tmp/mkbackup-btrfs.conf.tmp';
|
||||
|
||||
|
||||
var BackupManager = new Lang.Class({
|
||||
Name: 'BackupManager',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
_entries: [],
|
||||
|
||||
_init: function() {
|
||||
|
||||
this._drives = [ ];
|
||||
this._volumes = [ ];
|
||||
this._mounts = [ ];
|
||||
|
||||
//Set a FileMonitor on the config-File. So the Config-File is only
|
||||
//read, when it changed.
|
||||
this.GF = Gio.File.new_for_path(ConfFile);
|
||||
//this._monitorConf = this.GF.monitor_file(Gio.FileMonitorFlags.NONE,null,null,null)
|
||||
this._monitorConf = this.GF.monitor_file(Gio.FileMonitorFlags.NONE,null)
|
||||
this._monitorConf.connect("changed", Lang.bind(this, function(monitor, file, o, event) {
|
||||
// without this test, _loadConfig() is called more than once!!
|
||||
if (event == Gio.FileMonitorEvent.CHANGES_DONE_HINT && ! /~$/.test(file.get_basename())) {
|
||||
this._loadConfig();
|
||||
}
|
||||
}));
|
||||
|
||||
this._loadConfig();
|
||||
this.watchvolume = this._run_command('systemd-escape --path '+this.bkpmnt)+'.mount'
|
||||
|
||||
PanelMenu.Button.prototype._init.call(this, 0.0);
|
||||
|
||||
var hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
|
||||
MainIcon = new St.Icon({icon_name: 'drive-harddisk-usb-symbolic',
|
||||
style_class: 'system-status-icon'});
|
||||
ExtIcon = new St.Icon({icon_name: 'drive-harddisk-usb-symbolic',
|
||||
style_class: 'system-status-icon'});
|
||||
ExtIcon.hide();
|
||||
|
||||
MainLabel = new St.Label({ text: '---',
|
||||
});
|
||||
|
||||
hbox.add_child(MainLabel);
|
||||
hbox.add_child(MainIcon);
|
||||
hbox.add_child(ExtIcon);
|
||||
hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
|
||||
|
||||
this.actor.add_actor(hbox);
|
||||
this.actor.add_style_class_name('panel-status-button');
|
||||
this.actor.connect('button-press-event', Lang.bind(this, function() {
|
||||
this._refresh();
|
||||
}));
|
||||
|
||||
Main.panel.addToStatusArea('backupManager', this);
|
||||
|
||||
//this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
// Fill the Menu
|
||||
// First a Entry to open backup-Location
|
||||
var bkpitem = this.menu.addAction(_("Open Backups"), function(event) {
|
||||
var context = global.create_app_launch_context(event.get_time(), -1);
|
||||
var GF = Gio.File.new_for_path('backup');
|
||||
Gio.AppInfo.launch_default_for_uri(GF.get_uri(),context);
|
||||
});
|
||||
|
||||
this.extItem = new PopupTargetItem(extMediaName, this._check_service('mkbackup@BKP.target','active'));
|
||||
this.extItem.connect('toggled', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand('mkbackup@BKP.target',
|
||||
(this._check_service('mkbackup@BKP.target','active') ? 'stop' : 'start'),
|
||||
'system'));
|
||||
}));
|
||||
this.menu.addMenuItem(this.extItem);
|
||||
|
||||
this.snapitem = new PopupMenu.PopupMenuItem(_("Take snapshot now (tag: manually)"));
|
||||
this.snapitem.connect('activate', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand('mkbackup@manually.service', 'restart', 'system'));
|
||||
this.menu.close();
|
||||
}));
|
||||
this.menu.addMenuItem(this.snapitem);
|
||||
|
||||
this.bkpsubmenu = new PopupMenu.PopupSubMenuMenuItem(_("Backup-Intervalle"), true);
|
||||
this.bkpsubmenu.icon.icon_name = 'system-run-symbolic';
|
||||
this.menu.addMenuItem(this.bkpsubmenu);
|
||||
|
||||
//this.descrsubmenu = new PopupMenu.PopupSubMenuMenuItem(_("Info"), true);
|
||||
//this.descrsubmenu.icon.icon_name = 'system-run-symbolic';
|
||||
//this.bkpsubmenu.addMenuItem(this.descrsubmenu);
|
||||
|
||||
if(this._entries.length > 0)
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._refreshID = Mainloop.timeout_add_seconds(refreshTime, Lang.bind(this, this._refresh_panel));
|
||||
// New Volumes to use as backup-media
|
||||
this._monitor = Gio.VolumeMonitor.get();
|
||||
|
||||
this._addedDriveId = this._monitor.connect('drive-connected', Lang.bind(this, function(monitor, drive) {
|
||||
log('DRIVE CONNECTED',drive.get_name());
|
||||
this._DriveAdded(drive);
|
||||
}));
|
||||
|
||||
this._removedDriveId = this._monitor.connect('drive-disconnected', Lang.bind(this, function(monitor, drive) {
|
||||
log('DRIVE DISCONNECTED',drive.get_name(),this._removedDriveId)
|
||||
this._DriveRemoved(drive);
|
||||
//this.extItem.label.text = _("external backup-drive");
|
||||
//this.drvsubmenu.destroy();
|
||||
}));
|
||||
|
||||
this._addedVolumeId = this._monitor.connect('volume-added', Lang.bind(this, function(monitor, volume){
|
||||
this._addVolume(volume);
|
||||
this._VolumeAdded(volume);
|
||||
log('VOLUMES',JSON.stringify(this._drives))
|
||||
}))
|
||||
|
||||
this._removedVolumeId = this._monitor.connect('volume-removed', Lang.bind(this, function(monitor, volume){
|
||||
//this._VolumeRemoved(volume);
|
||||
}))
|
||||
|
||||
/*this._monitor.get_volumes().forEach(Lang.bind(this, function(volume) {
|
||||
this._VolumeAdded(volume);
|
||||
}));
|
||||
|
||||
|
||||
this._addedMountId = this._monitor.connect('mount-added', Lang.bind(this, function(monitor, mount) {
|
||||
log('MOUNT CONNECTED',mount.get_name())
|
||||
log(mount.get_name())
|
||||
var volume = mount.get_volume()
|
||||
log(volume.get_name(),volume.get_uuid())
|
||||
//log(mount.get_name())
|
||||
//this._showDrive(drive);
|
||||
}));*/
|
||||
|
||||
//log(Gio.UnixMountPoint.get_device_path('/var/cache/backup'))
|
||||
return;
|
||||
},
|
||||
|
||||
_DriveAdded: function(drive) {
|
||||
var Dident = drive.enumerate_identifiers();
|
||||
var u_dev = drive.get_identifier('unix-device');
|
||||
var d_name = drive.get_name();
|
||||
//log('DRIVE unix-device',u_dev,d_name)
|
||||
this._drives[d_name] = new Object()
|
||||
this._drives[d_name]['drive'] = drive;
|
||||
this._drives[d_name]['device'] = drive.get_identifier('unix-device');
|
||||
//this._drives[d_name]['uuid'] = drive.get_identifier('uuid');
|
||||
this._drives[d_name]['volumes'] = new Object()
|
||||
log("ABCDE",this._drives[d_name]['device']);
|
||||
/*if (drive.has_volumes()) {
|
||||
log('DHV',drive.get_volumes());
|
||||
var VList = drive.get_volumes();
|
||||
VList.forEach(Lang.bind(this, function(volume){
|
||||
var v_name = volume.get_name();
|
||||
log(v_name)
|
||||
this._drives[d_name]['volumes'][v_name] = this._addVolume(volume);
|
||||
}));
|
||||
//VList.free()
|
||||
} else {
|
||||
log('DHNV',drive.get_volumes());
|
||||
};
|
||||
log('VOLUMES',JSON.stringify(this._drives))
|
||||
log('X',this._drives['ST1000LM024 HN-M101MBB']['device'])
|
||||
*/
|
||||
/*
|
||||
this.drvsubmenu = new PopupMenu.PopupSubMenuMenuItem(drive.get_name(), true);
|
||||
this.drvsubmenu.icon.icon_name = 'drive-harddisk-usb-symbolic';
|
||||
this.menu.addMenuItem(this.drvsubmenu);
|
||||
*/
|
||||
//log('Drive added',drive.get_name())
|
||||
//this.drvsubmenu = new PopupMenu.PopupSubMenuMenuItem(_(drive.get_name()), true);
|
||||
//this.drvsubmenu.icon.icon_name = 'drive-harddisk-usb-symbolic';
|
||||
//this.drvsubmenu.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
},
|
||||
|
||||
_addVolume: function(volume) {
|
||||
var drives = volume.get_drive();
|
||||
//log('D',drives.get_name())
|
||||
//log('V',volume.get_name())
|
||||
//this._drives[drives.get_name()][volume.get_name()] = volume;
|
||||
volume.enumerate_identifiers();
|
||||
var vol = new Object()
|
||||
//log(volume.get_identifier('uuid'))
|
||||
//log(volume.get_identifier('unix-device'))
|
||||
//log(volume.get_identifier('class'))
|
||||
//log(volume.get_identifier('label'))
|
||||
//vol['volume'] = volume;
|
||||
//vol['uuid'] = volume.get_identifier('uuid');
|
||||
//vol['u_device'] = volume.get_identifier('unix-device');
|
||||
//vol['vclass'] = volume.get_identifier('class');
|
||||
//vol['label'] = volume.get_identifier('label');
|
||||
//this._drives[drives.get_name()]['volumes'][volume.get_name()] = v_name;
|
||||
return vol
|
||||
},
|
||||
|
||||
_DriveRemoved: function(drive) {
|
||||
var me = this.menu._getMenuItems();
|
||||
this._removeItemByLabel(this.menu, drive.get_name());
|
||||
|
||||
|
||||
//this.drvsubmenu.destroy();
|
||||
},
|
||||
|
||||
_VolumeRemoved: function(volume) {
|
||||
//Volume is removed, after it's mounted from system(d)
|
||||
log('VOLUME REMOVED',volume.get_name())
|
||||
},
|
||||
|
||||
_VolumeAdded: function(volume) {
|
||||
//Volume is added, after it's unmounted from system(d)
|
||||
log('VOLUME CONNECTED',volume.get_name())
|
||||
log('ID',this._addedVolumeId)
|
||||
if (volume.get_drive() == null)
|
||||
return
|
||||
var drive = volume.get_drive()
|
||||
if ( !volume.can_mount() || !drive.is_removable())
|
||||
return
|
||||
log(volume.get_mount())
|
||||
log(volume.enumerate_identifiers())
|
||||
log(volume.get_identifier('uuid'))
|
||||
log(volume.get_identifier('unix-device'))
|
||||
log(volume.get_identifier('class'))
|
||||
log(volume.get_identifier('label'))
|
||||
var dr = volume.get_drive()
|
||||
this._drives[dr.get_name()] = dr
|
||||
log(dr.get_name())
|
||||
log(dr.enumerate_identifiers())
|
||||
log(dr.get_identifier('unix-device'))
|
||||
|
||||
var me
|
||||
try {
|
||||
me = this.drvsubmenu.menu._getMenuItems();
|
||||
} catch(e) {
|
||||
this._DriveAdded(drive)
|
||||
}
|
||||
//me = this.drvsubmenu.menu._getMenuItems();
|
||||
|
||||
var menuItem = new VolMenuItem(volume, false);
|
||||
|
||||
var VF = Gio.File.new_for_path('/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules');
|
||||
if (VF.query_exists(null)) {
|
||||
log('UDEV exists','/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules')
|
||||
menuItem.setToggleState(true);
|
||||
} else {
|
||||
log('UDEV not exists','/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules')
|
||||
menuItem.setToggleState(false);
|
||||
}
|
||||
|
||||
|
||||
//this._removeItemByLabel(this.drvsubmenu.menu, volume.get_name());
|
||||
//this.drvsubmenu.menu.addMenuItem(menuItem);
|
||||
var connID = menuItem.connect('toggled', Lang.bind(this, function() {
|
||||
log('ACTIVE?',menuItem.state,menuItem.label.text)
|
||||
if (menuItem.state) {
|
||||
reg = 'register'
|
||||
} else {
|
||||
reg = 'unregister'
|
||||
}
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand('mkbackup-'+reg+'@'+volume.get_identifier('unix-device')+'.service', 'start', 'system'));
|
||||
}));
|
||||
log(connID,volume.get_name())
|
||||
//this.menu.addMenuItem(this.drvsubmenu,1);
|
||||
},
|
||||
|
||||
_removeItemByLabel: function(menu, label) {
|
||||
log('LAB',label)
|
||||
var children = menu._getMenuItems();
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var item = children[i];
|
||||
log('REM',item.label.text,label)
|
||||
if (item.label.text == label)
|
||||
log('DESTROY',item.label.text)
|
||||
//item.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
_addMount: function(mount) {
|
||||
var item = new MountMenuItem(mount);
|
||||
this._mounts.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
},
|
||||
|
||||
_removeMount: function(mount) {
|
||||
for (var i = 0; i < this._mounts.length; i++) {
|
||||
var item = this._mounts[i];
|
||||
if (item.mount == mount) {
|
||||
item.destroy();
|
||||
this._mounts.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log ('Removing a mount that was never added to the menu');
|
||||
},
|
||||
|
||||
_run_command: function(COMMAND) {
|
||||
var output = "";
|
||||
try {
|
||||
//output = GLib.spawn_command_line_sync(COMMAND, null, null, null, null);
|
||||
output = GLib.spawn_command_line_sync(COMMAND);
|
||||
} catch(e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return output[1].toString().replace(/\n$/, "") + "";
|
||||
},
|
||||
|
||||
|
||||
|
||||
_showVolume: function(volume) {
|
||||
var drive = volume.get_drive();
|
||||
var mount = volume.get_mount();
|
||||
if (drive != null && drive.is_removable()){
|
||||
log('SVDRIVE',drive.get_name(),drive.is_removable())
|
||||
log('SVVOL',volume.get_name(),volume.get_uuid(),drive.is_removable());
|
||||
}
|
||||
},
|
||||
|
||||
_showDrive: function(drive) {
|
||||
//extMediaName = 'external backup-drive';
|
||||
log('SDDRIVE',drive.get_name(),drive.get_volumes(),drive.has_media(),drive.is_removable(),drive.has_volumes(),drive.enumerate_identifiers());
|
||||
if (drive.is_removable() && drive.has_volumes()) {
|
||||
extMediaName = drive.get_name();
|
||||
log('SDDREMOV')
|
||||
|
||||
drive.get_volumes().forEach(Lang.bind(this, function(volume) {
|
||||
if (volume.can_mount()){
|
||||
log('SDVOL',volume.get_name());
|
||||
if ( volume.get_mount() != null ) {
|
||||
var mount = volume.get_mount();
|
||||
log('SDMR',mount.get_root());
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
log(drive.is_removable(),drive.has_volumes(),drive.get_volumes());
|
||||
}
|
||||
},
|
||||
|
||||
_checkMount: function(mount) {
|
||||
log('CHECK MOUNT '+mount.can_unmount()+' '+mount.can_eject());
|
||||
log('VOLUME '+mount.get_volume());
|
||||
return(mount.can_unmount() || mount.can_eject())
|
||||
},
|
||||
|
||||
_getCommand: function(service, action, type) {
|
||||
var command = "systemctl"
|
||||
|
||||
command += " " + action
|
||||
command += " " + service
|
||||
command += " --" + type
|
||||
if (type == "system" && (action != 'is-active' && action != 'is-enabled'))
|
||||
command = "pkexec --user root " + command
|
||||
|
||||
return 'sh -c "' + command + '; exit;"'
|
||||
},
|
||||
|
||||
_getDriveMounted: function(udevice) {
|
||||
command = "/bin/grep"
|
||||
command += " " + udevice
|
||||
command += "/proc/mounts"
|
||||
return 'sh -c "' + command + '; exit:"'
|
||||
},
|
||||
|
||||
_refresh_panel : function() {
|
||||
|
||||
//log('YY',this._drives['ST1000LM024 HN-M101MBB']['volumes'])
|
||||
//log('YY',this._drives['ST1000LM024 HN-M101MBB'])
|
||||
var active = false;
|
||||
var volumes = []
|
||||
var mounted = false;
|
||||
volumes.push(this.watchvolume)
|
||||
|
||||
// TODO: find all Volumes on the drive, holding the backup and add this
|
||||
// volumes to the list
|
||||
volumes.push('home-jakob-Videos-extern.mount')
|
||||
volumes.push('home-media.mount')
|
||||
//for (var d in this._drives['ST1000LM024 HN-M101MBB']) {
|
||||
/*for (var d in this._drives) {
|
||||
log("D",d,this._drives[d].get_name());
|
||||
};*/
|
||||
|
||||
this.aout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(this.services.join(' '), 'is-active', 'system'))[1].toString().split('\n');
|
||||
//log(this.aout.indexOf('active'));
|
||||
|
||||
var apos = this.aout.indexOf('active')
|
||||
active = this.aout.indexOf('active') >= 0
|
||||
|
||||
var vout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(volumes.join(' '), 'is-active', 'system'))[1].toString().split('\n');
|
||||
mounted = vout.indexOf('active') >= 0
|
||||
|
||||
this.bkpsubmenu.icon.style = (active ? "color: #ff0000;" : "color: revert;");
|
||||
//this.bkpsubmenu.icon.style_class = (active ? "system-status-icon" : "system-status-icon-red");
|
||||
MainIcon.style = (mounted ? "color: #ff0000;" : "color: revert;");
|
||||
ExtIcon.style = (mounted ? "color: #ff0000;" : "color: revert;");
|
||||
(mounted ? ExtIcon.show() : ExtIcon.hide());
|
||||
|
||||
//ExtIcon.actor = (mounted ? "visibile = true;" : "visible = false;");
|
||||
var mlabel = (mounted ? _("mounted") : "");
|
||||
var alabel = (active ? this.services[apos] : "");
|
||||
MainLabel.set_text(mlabel + ' ' + alabel);
|
||||
//MainLabel.set_text(mounted ? _("mounted") : "");
|
||||
//MainLabel.set_text(active ? this.services[apos] : "");
|
||||
|
||||
if (this.menu.isOpen) {
|
||||
//Menu is open
|
||||
var me = this.menu._getMenuItems();
|
||||
me.forEach(Lang.bind(this, function(item) {
|
||||
//log(item.label.text)
|
||||
if ( item.label.text == extMediaName ) {
|
||||
item.setToggleState(this._check_service('mkbackup@BKP.target','active'));
|
||||
}
|
||||
}));
|
||||
if (this.bkpsubmenu.menu.isOpen) {
|
||||
//Submenu Backu-intervals open
|
||||
this._refresh();
|
||||
};
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
if (this._refreshID != 0)
|
||||
Mainloop.source_remove(this._refreshID);
|
||||
this._refreshID = Mainloop.timeout_add_seconds(refreshTime, Lang.bind(this, this._refresh_panel));
|
||||
GLib.Source.set_name_by_id(this._refreshID, '[gnome-shell] this._refresh_panel');
|
||||
return false;
|
||||
},
|
||||
|
||||
_check_service: function(service,stat) {
|
||||
var [_, aout, aerr, astat] = GLib.spawn_command_line_sync(
|
||||
this._getCommand(service, 'is-'+stat, 'system'));
|
||||
return (astat == 0);
|
||||
},
|
||||
|
||||
_refresh: function() {
|
||||
var me = this.bkpsubmenu.menu._getMenuItems();
|
||||
//log(this.services.join(' '))
|
||||
|
||||
var eout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(this.services.join(' '), 'is-enabled', 'system'))[1].toString().split('\n');
|
||||
//log(eout);
|
||||
|
||||
this.aout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(this.services.join(' '), 'is-active', 'system'))[1].toString().split('\n');
|
||||
|
||||
this._entries.forEach(Lang.bind(this, function(service,index,arr) {
|
||||
if (! arr[index].enabled == (eout[index] == 'enabled'))
|
||||
arr[index].changed = true
|
||||
arr[index].enabled = (eout[index] == 'enabled' ? true : false)
|
||||
|
||||
if (! arr[index].active == (this.aout[index] == 'active'))
|
||||
arr[index].changed = true
|
||||
arr[index].active = (this.aout[index] == 'active' ? true : false)
|
||||
}));
|
||||
|
||||
this._entries.forEach(Lang.bind(this, function(service,index,arr) {
|
||||
var serviceItem
|
||||
me.forEach(Lang.bind(this, function(item) {
|
||||
if ( item.label.text == service['descr']+' ('+service['name'] + ')' ) {
|
||||
arr[index].found = true;
|
||||
serviceItem = item;
|
||||
me.splice(me.indexOf(item),1);
|
||||
}
|
||||
}));
|
||||
|
||||
if ( arr[index].changed ) {
|
||||
if (arr[index].found) {
|
||||
if ( service.staticserv ) {
|
||||
serviceItem.setToggleState(service.active);
|
||||
} else {
|
||||
serviceItem.setToggleState(service.enabled);
|
||||
}
|
||||
} else {
|
||||
if ( service.staticserv ) {
|
||||
serviceItem = new PopupTargetItem(service['descr']+' ('+service['name'] + ')', service.active);
|
||||
this.bkpsubmenu.menu.addMenuItem(serviceItem);
|
||||
|
||||
serviceItem.connect('toggled', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand(service['service'], (this._check_service(service.service, 'active') ? 'stop' : 'start'), service["type"]));
|
||||
}));
|
||||
} else {
|
||||
serviceItem = new PopupServiceItem(service['descr']+' ('+service['name'] + ')', service.enabled);
|
||||
this.bkpsubmenu.menu.addMenuItem(serviceItem);
|
||||
|
||||
serviceItem.connect('toggled', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand(service['service'], (this._check_service(service.service, 'enabled') ? 'disable' : 'enable'), service["type"]));
|
||||
}));
|
||||
|
||||
serviceItem.actionButton.connect('clicked', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand(service['service'], (this._check_service(service.service, 'active') ? 'stop' : 'start'), service["type"]));
|
||||
this.menu.close();
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (serviceItem.actionButton.child) {
|
||||
serviceItem.actionButton.child.icon_name = (service.active ? EnabledIcon : DisabledIcon);
|
||||
serviceItem.actionButton.child.style = (service.active ? "color: #ff0000;" : "color: revert;");
|
||||
};
|
||||
if (serviceItem.transferButton) {
|
||||
serviceItem.transferButton.style = (service.tr ? "text-decoration: revert;" : "text-decoration: line-through;");
|
||||
};
|
||||
if (serviceItem.descriptionLabel) {
|
||||
serviceItem.descriptionLabel.label = service.descr;
|
||||
};
|
||||
|
||||
|
||||
//log('Changed',service.service)
|
||||
}
|
||||
//log('X',arr[index].service,arr[index].enabled,arr[index].active,arr[index].changed)
|
||||
arr[index].changed = false;
|
||||
}));
|
||||
|
||||
this.bkpsubmenu.menu._getMenuItems().forEach(Lang.bind(this, function(item) {
|
||||
var mic = 0
|
||||
if ( me.length > mic ) {
|
||||
for (var i = mic; i < me.length; i++) {
|
||||
if (item == me[i]) {
|
||||
log('DESTROY',me[i].label.text);
|
||||
item.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_loadConfig: function() {
|
||||
//log('LOAD CONFIG')
|
||||
var intervals
|
||||
this.services = []
|
||||
var kf = new GLib.KeyFile()
|
||||
var obj = new Object();
|
||||
this._entries = [];
|
||||
|
||||
if(kf.load_from_file(ConfFile,GLib.KeyFileFlags.NONE)){
|
||||
//intervals = kf.get_groups()[0];
|
||||
this.bkpmnt = kf.get_value('DEFAULT','bkpmnt')
|
||||
|
||||
//log('BKP',this.bkppath)
|
||||
kf.get_groups()[0].forEach(Lang.bind(this, function(interval) {
|
||||
var obj = new Object();
|
||||
var i = ""
|
||||
if (interval === 'DEFAULT')
|
||||
i = 'misc'
|
||||
else
|
||||
i = interval
|
||||
obj.name = i +' backups';
|
||||
obj.interval = interval;
|
||||
obj.service = "mkbackup@"+i+".service";
|
||||
this.services.push(obj.service)
|
||||
obj.type = "system";
|
||||
obj.enabled = false;
|
||||
obj.active = false;
|
||||
obj.changed = true;
|
||||
obj.found = false;
|
||||
obj.staticserv = false;
|
||||
|
||||
try {
|
||||
obj.tr = (kf.get_value(interval,"transfer").toLowerCase() === "true");
|
||||
} catch(err) {
|
||||
obj.tr = (kf.get_value("DEFAULT","transfer").toLowerCase() === "true");
|
||||
}
|
||||
|
||||
try {
|
||||
obj.descr = (kf.get_value(interval,"description"));
|
||||
} catch(err) {
|
||||
try {
|
||||
obj.descr = (kf.get_value("DEFAULT","description"));
|
||||
} catch(err) {
|
||||
obj.descr = "";
|
||||
}
|
||||
}
|
||||
this._entries.push(obj)}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var backupManager;
|
||||
|
||||
function init(extensionMeta) {
|
||||
//Convenience.initTranslations();
|
||||
var theme = imports.gi.Gtk.IconTheme.get_default();
|
||||
theme.append_search_path(extensionMeta.path + "/icons");
|
||||
}
|
||||
|
||||
|
||||
function enable() {
|
||||
backupManager = new BackupManager();
|
||||
|
||||
}
|
||||
|
||||
function disable() {
|
||||
backupManager.destroy();
|
||||
}
|
|
@ -0,0 +1,576 @@
|
|||
var GLib = imports.gi.GLib;
|
||||
var Lang = imports.lang;
|
||||
var Main = imports.ui.main;
|
||||
var PanelMenu = imports.ui.panelMenu;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
var St = imports.gi.St;
|
||||
var Shell = imports.gi.Shell;
|
||||
|
||||
var Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
var _ = Gettext.gettext;
|
||||
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
var Me = ExtensionUtils.getCurrentExtension();
|
||||
var Convenience = Me.imports.convenience;
|
||||
var Util = imports.misc.util;
|
||||
var PopupServiceItem = Me.imports.popupServiceItem.PopupServiceItem;
|
||||
var PopupTargetItem = Me.imports.popupTargetItem.PopupTargetItem;
|
||||
var PopupMenuItem = Me.imports.popupManuallyItem.PopupServiceItem;
|
||||
var MountMenuItem = Me.imports.popupMountItem.MountMenuItem;
|
||||
var DriveMenuItem = Me.imports.popupDriveItem.DriveMenuItem;
|
||||
var VolMenuItem = Me.imports.popupBkpVolumItem.PopupBKPItem;
|
||||
var Gio = imports.gi.Gio;
|
||||
var Mainloop = imports.mainloop;
|
||||
|
||||
var refreshTime = 3.0;
|
||||
var MainLabel;
|
||||
var icon;
|
||||
var MainIcon;
|
||||
var extMediaName = 'external backup-drive';
|
||||
var Drives = new Object();
|
||||
|
||||
//var DisabledIcon = 'my-caffeine-off-symbolic';
|
||||
//var EnabledIcon = 'my-caffeine-on-symbolic';
|
||||
var DisabledIcon = 'system-run-symbolic';
|
||||
var EnabledIcon = 'system-run-symbolic';
|
||||
|
||||
|
||||
var BackupManager = new Lang.Class({
|
||||
Name: 'BackupManager',
|
||||
Extends: PanelMenu.Button,
|
||||
|
||||
_entries: [],
|
||||
|
||||
_init: function() {
|
||||
|
||||
this._drives = [ ];
|
||||
this._volumes = [ ];
|
||||
this._mounts = [ ];
|
||||
|
||||
//Set a FileMonitor on the config-File. So the Config-File is only
|
||||
//read, when it changed.
|
||||
this.GF = Gio.File.new_for_path('/etc/mksnapshot.conf');
|
||||
this._monitorConf = this.GF.monitor_file(Gio.FileMonitorFlags.NONE,null,null,null)
|
||||
this._monitorConf.connect("changed", Lang.bind(this, function(monitor, file, o, event) {
|
||||
// without this test, _loadConfig() is called more than once!!
|
||||
if (event == Gio.FileMonitorEvent.CHANGES_DONE_HINT && ! /~$/.test(file.get_basename())) {
|
||||
this._loadConfig();
|
||||
}
|
||||
}));
|
||||
|
||||
this._loadConfig();
|
||||
this.watchvolume = this._run_command('systemd-escape --path '+this.bkpmnt)+'.mount'
|
||||
|
||||
PanelMenu.Button.prototype._init.call(this, 0.0);
|
||||
|
||||
var hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
|
||||
MainIcon = new St.Icon({icon_name: 'drive-harddisk-usb-symbolic',
|
||||
style_class: 'system-status-icon'});
|
||||
|
||||
MainLabel = new St.Label({ text: '---',
|
||||
});
|
||||
|
||||
hbox.add_child(MainLabel);
|
||||
hbox.add_child(MainIcon);
|
||||
hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
|
||||
|
||||
this.actor.add_actor(hbox);
|
||||
this.actor.add_style_class_name('panel-status-button');
|
||||
this.actor.connect('button-press-event', Lang.bind(this, function() {
|
||||
this._refresh();
|
||||
}));
|
||||
|
||||
Main.panel.addToStatusArea('backupManager', this);
|
||||
|
||||
//this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
// Fill the Menu
|
||||
// First a Entry to open backup-Location
|
||||
var bkpitem = this.menu.addAction(_("Open Backups"), function(event) {
|
||||
var context = global.create_app_launch_context(event.get_time(), -1);
|
||||
var GF = Gio.File.new_for_path('backup');
|
||||
Gio.AppInfo.launch_default_for_uri(GF.get_uri(),context);
|
||||
});
|
||||
|
||||
this.extItem = new PopupTargetItem(extMediaName, this._check_service('mkbackup@BKP.target','active'));
|
||||
this.extItem.connect('toggled', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand('mkbackup@BKP.target',
|
||||
(this._check_service('mkbackup@BKP.target','active') ? 'stop' : 'start'),
|
||||
'system'));
|
||||
}));
|
||||
this.menu.addMenuItem(this.extItem);
|
||||
|
||||
this.snapitem = new PopupMenu.PopupMenuItem(_("Take snapshot now"));
|
||||
this.snapitem.connect('activate', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand('mkbackup@manually.service', 'restart', 'system'));
|
||||
this.menu.close();
|
||||
}));
|
||||
this.menu.addMenuItem(this.snapitem);
|
||||
|
||||
this.bkpsubmenu = new PopupMenu.PopupSubMenuMenuItem(_("Backup-Intervalle"), true);
|
||||
this.bkpsubmenu.icon.icon_name = 'system-run-symbolic';
|
||||
this.menu.addMenuItem(this.bkpsubmenu);
|
||||
|
||||
if(this._entries.length > 0)
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._refreshID = Mainloop.timeout_add_seconds(refreshTime, Lang.bind(this, this._refresh_panel));
|
||||
// New Volumes to use as backup-media
|
||||
this._monitor = Gio.VolumeMonitor.get();
|
||||
|
||||
this._removedDriveId = this._monitor.connect('drive-disconnected', Lang.bind(this, function(monitor, drive) {
|
||||
log('DRIVE DISCONNECTED',drive.get_name(),this._removedDriveId)
|
||||
this._DriveRemoved(drive);
|
||||
//this.extItem.label.text = _("external backup-drive");
|
||||
//this.drvsubmenu.destroy();
|
||||
}));
|
||||
|
||||
this._addedDriveId = this._monitor.connect('drive-connected', Lang.bind(this, function(monitor, drive) {
|
||||
log('DRIVE CONNECTED',drive.get_name());
|
||||
this._DriveAdded(drive);
|
||||
}));
|
||||
|
||||
this._addedVolumeId = this._monitor.connect('volume-added', Lang.bind(this, function(monitor, volume){
|
||||
this._VolumeAdded(volume);
|
||||
log('VOLUMES',JSON.stringify(this._drives))
|
||||
}))
|
||||
|
||||
this._removedVolumeId = this._monitor.connect('volume-removed', Lang.bind(this, function(monitor, volume){
|
||||
this._VolumeRemoved(volume);
|
||||
}))
|
||||
|
||||
/*this._monitor.get_volumes().forEach(Lang.bind(this, function(volume) {
|
||||
this._VolumeAdded(volume);
|
||||
}));
|
||||
|
||||
|
||||
this._addedMountId = this._monitor.connect('mount-added', Lang.bind(this, function(monitor, mount) {
|
||||
log('MOUNT CONNECTED',mount.get_name())
|
||||
log(mount.get_name())
|
||||
var volume = mount.get_volume()
|
||||
log(volume.get_name(),volume.get_uuid())
|
||||
//log(mount.get_name())
|
||||
//this._showDrive(drive);
|
||||
}));*/
|
||||
|
||||
//log(Gio.UnixMountPoint.get_device_path('/var/cache/backup'))
|
||||
return;
|
||||
},
|
||||
|
||||
_DriveAdded: function(drive) {
|
||||
var Dident = drive.enumerate_identifiers();
|
||||
var u_dev = drive.get_identifier('unix-device');
|
||||
var d_name = drive.get_name();
|
||||
log('DRIVE unix-device',u_dev,d_name)
|
||||
this._drives[d_name] = new Object()
|
||||
this._drives[d_name]['drive'] = drive;
|
||||
this._drives[d_name]['device'] = drive.get_identifier('unix-device');
|
||||
//this._drives[d_name]['uuid'] = drive.get_identifier('uuid');
|
||||
this._drives[d_name]['volumes'] = new Object()
|
||||
if (drive.has_volumes()) {
|
||||
log(drive.get_volumes());
|
||||
var VList = drive.get_volumes();
|
||||
VList.forEach(Lang.bind(this, function(volume){
|
||||
var v_name = volume.get_name();
|
||||
log(v_name)
|
||||
this._drives[d_name]['volumes'][v_name] = this._addVolume(volume);
|
||||
}));
|
||||
//VList.free()
|
||||
};
|
||||
log('VOLUMES',JSON.stringify(this._drives))
|
||||
|
||||
this.drvsubmenu = new PopupMenu.PopupSubMenuMenuItem(drive.get_name(), true);
|
||||
this.drvsubmenu.icon.icon_name = 'drive-harddisk-usb-symbolic';
|
||||
this.menu.addMenuItem(this.drvsubmenu);
|
||||
|
||||
//log('Drive added',drive.get_name())
|
||||
//this.drvsubmenu = new PopupMenu.PopupSubMenuMenuItem(_(drive.get_name()), true);
|
||||
//this.drvsubmenu.icon.icon_name = 'drive-harddisk-usb-symbolic';
|
||||
//this.drvsubmenu.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
},
|
||||
|
||||
_addVolume: function(volume) {
|
||||
volume.enumerate_identifiers();
|
||||
var vol = new Object()
|
||||
log(volume.get_identifier('uuid'))
|
||||
log(volume.get_identifier('unix-device'))
|
||||
log(volume.get_identifier('class'))
|
||||
log(volume.get_identifier('label'))
|
||||
vol['volume'] = volume;
|
||||
vol['uuid'] = volume.get_identifier('uuid');
|
||||
vol['u_device'] = volume.get_identifier('unix-device');
|
||||
vol['vclass'] = volume.get_identifier('class');
|
||||
vol['label'] = volume.get_identifier('label');
|
||||
return vol
|
||||
},
|
||||
|
||||
_DriveRemoved: function(drive) {
|
||||
var me = this.menu._getMenuItems();
|
||||
this._removeItemByLabel(this.menu, drive.get_name());
|
||||
|
||||
|
||||
//this.drvsubmenu.destroy();
|
||||
},
|
||||
|
||||
_VolumeRemoved: function(volume) {
|
||||
//Volume is removed, after it's mounted from system(d)
|
||||
log('VOLUME REMOVED',volume.get_name())
|
||||
},
|
||||
|
||||
_VolumeAdded: function(volume) {
|
||||
//Volume is added, after it's unmounted from system(d)
|
||||
log('VOLUME CONNECTED',volume.get_name())
|
||||
log('ID',this._addedVolumeId)
|
||||
if (volume.get_drive() == null)
|
||||
return
|
||||
var drive = volume.get_drive()
|
||||
if ( !volume.can_mount() || !drive.is_removable())
|
||||
return
|
||||
log(volume.get_mount())
|
||||
log(volume.enumerate_identifiers())
|
||||
log(volume.get_identifier('uuid'))
|
||||
log(volume.get_identifier('unix-device'))
|
||||
log(volume.get_identifier('class'))
|
||||
log(volume.get_identifier('label'))
|
||||
var dr = volume.get_drive()
|
||||
log(dr.get_name())
|
||||
log(dr.enumerate_identifiers())
|
||||
log(dr.get_identifier('unix-device'))
|
||||
|
||||
var me
|
||||
try {
|
||||
me = this.drvsubmenu.menu._getMenuItems();
|
||||
} catch(e) {
|
||||
this._DriveAdded(drive)
|
||||
}
|
||||
me = this.drvsubmenu.menu._getMenuItems();
|
||||
|
||||
var menuItem = new VolMenuItem(volume, false);
|
||||
|
||||
var VF = Gio.File.new_for_path('/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules');
|
||||
if (VF.query_exists(null)) {
|
||||
log('UDEV exists','/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules')
|
||||
menuItem.setToggleState(true);
|
||||
} else {
|
||||
log('UDEV not exists','/etc/udev/rules.d/99-ext-bkp-volume-u-'+volume.get_identifier('uuid')+'.rules')
|
||||
menuItem.setToggleState(false);
|
||||
}
|
||||
|
||||
|
||||
//this._removeItemByLabel(this.drvsubmenu.menu, volume.get_name());
|
||||
this.drvsubmenu.menu.addMenuItem(menuItem);
|
||||
var connID = menuItem.connect('toggled', Lang.bind(this, function() {
|
||||
log('ACTIVE?',menuItem.state,menuItem.label.text)
|
||||
if (menuItem.state) {
|
||||
reg = 'register'
|
||||
} else {
|
||||
reg = 'unregister'
|
||||
}
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand('mkbackup-'+reg+'@'+volume.get_identifier('unix-device')+'.service', 'start', 'system'));
|
||||
}));
|
||||
log(connID,volume.get_name())
|
||||
this.menu.addMenuItem(this.drvsubmenu,1);
|
||||
},
|
||||
|
||||
_removeItemByLabel: function(menu, label) {
|
||||
log('LAB',label)
|
||||
var children = menu._getMenuItems();
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var item = children[i];
|
||||
log('REM',item.label.text,label)
|
||||
if (item.label.text == label)
|
||||
log('DESTROY',item.label.text)
|
||||
//item.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
_addMount: function(mount) {
|
||||
var item = new MountMenuItem(mount);
|
||||
this._mounts.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
},
|
||||
|
||||
_removeMount: function(mount) {
|
||||
for (var i = 0; i < this._mounts.length; i++) {
|
||||
var item = this._mounts[i];
|
||||
if (item.mount == mount) {
|
||||
item.destroy();
|
||||
this._mounts.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log ('Removing a mount that was never added to the menu');
|
||||
},
|
||||
|
||||
_run_command: function(COMMAND) {
|
||||
var output = "";
|
||||
try {
|
||||
output = GLib.spawn_command_line_sync(COMMAND, null, null, null, null);
|
||||
} catch(e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return output[1].toString().replace(/\n$/, "") + "";
|
||||
},
|
||||
|
||||
|
||||
|
||||
_showVolume: function(volume) {
|
||||
var drive = volume.get_drive();
|
||||
var mount = volume.get_mount();
|
||||
if (drive != null && drive.is_removable()){
|
||||
log('SVDRIVE',drive.get_name(),drive.is_removable())
|
||||
log('SVVOL',volume.get_name(),volume.get_uuid(),drive.is_removable());
|
||||
}
|
||||
},
|
||||
|
||||
_showDrive: function(drive) {
|
||||
//extMediaName = 'external backup-drive';
|
||||
log('SDDRIVE',drive.get_name(),drive.get_volumes(),drive.has_media(),drive.is_removable(),drive.has_volumes(),drive.enumerate_identifiers());
|
||||
if (drive.is_removable() && drive.has_volumes()) {
|
||||
//extMediaName = drive.get_name();
|
||||
log('SDDREMOV')
|
||||
|
||||
drive.get_volumes().forEach(Lang.bind(this, function(volume) {
|
||||
if (volume.can_mount()){
|
||||
log('SDVOL',volume.get_name());
|
||||
if ( volume.get_mount() != null ) {
|
||||
var mount = volume.get_mount();
|
||||
log('SDMR',mount.get_root());
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
log(drive.is_removable(),drive.has_volumes(),drive.get_volumes());
|
||||
}
|
||||
},
|
||||
|
||||
_checkMount: function(mount) {
|
||||
log('CHECK MOUNT '+mount.can_unmount()+' '+mount.can_eject());
|
||||
log('VOLUME '+mount.get_volume());
|
||||
return(mount.can_unmount() || mount.can_eject())
|
||||
},
|
||||
|
||||
_getCommand: function(service, action, type) {
|
||||
var command = "systemctl"
|
||||
|
||||
command += " " + action
|
||||
command += " " + service
|
||||
command += " --" + type
|
||||
if (type == "system" && (action != 'is-active' && action != 'is-enabled'))
|
||||
command = "pkexec --user root " + command
|
||||
|
||||
return 'sh -c "' + command + '; exit;"'
|
||||
},
|
||||
|
||||
_refresh_panel : function() {
|
||||
|
||||
var active = false;
|
||||
var volumes = []
|
||||
var mounted = false;
|
||||
volumes.push(this.watchvolume)
|
||||
|
||||
// TODO: find all Volumes on the drive, holding the backup and add this
|
||||
// volumes to the list
|
||||
volumes.push('home-jakob-Videos-extern.mount')
|
||||
volumes.push('home-media.mount')
|
||||
|
||||
this.aout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(this.services.join(' '), 'is-active', 'system'))[1].toString().split('\n');
|
||||
//log(this.aout);
|
||||
|
||||
active = this.aout.indexOf('active') >= 0
|
||||
|
||||
var vout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(volumes.join(' '), 'is-active', 'system'))[1].toString().split('\n');
|
||||
mounted = vout.indexOf('active') >= 0
|
||||
|
||||
this.bkpsubmenu.icon.style = (active ? "color: #ff0000;" : "color: revert;");
|
||||
MainIcon.style = (mounted ? "color: #ff0000;" : "color: revert;");
|
||||
MainLabel.set_text(mounted ? _("mounted") : "");
|
||||
|
||||
if (this.menu.isOpen) {
|
||||
//Menu is open
|
||||
var me = this.menu._getMenuItems();
|
||||
me.forEach(Lang.bind(this, function(item) {
|
||||
//log(item.label.text)
|
||||
if ( item.label.text == extMediaName ) {
|
||||
item.setToggleState(this._check_service('mkbackup@BKP.target','active'));
|
||||
}
|
||||
}));
|
||||
if (this.bkpsubmenu.menu.isOpen) {
|
||||
//Submenu Backu-intervals open
|
||||
this._refresh();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._refreshID != 0)
|
||||
Mainloop.source_remove(this._refreshID);
|
||||
this._refreshID = Mainloop.timeout_add_seconds(refreshTime, Lang.bind(this, this._refresh_panel));
|
||||
GLib.Source.set_name_by_id(this._refreshID, '[gnome-shell] this._refresh_panel');
|
||||
return false;
|
||||
},
|
||||
|
||||
_check_service: function(service,stat) {
|
||||
var [_, aout, aerr, astat] = GLib.spawn_command_line_sync(
|
||||
this._getCommand(service, 'is-'+stat, 'system'));
|
||||
return (astat == 0);
|
||||
},
|
||||
|
||||
_refresh: function() {
|
||||
var me = this.bkpsubmenu.menu._getMenuItems();
|
||||
//log(this.services.join(' '))
|
||||
|
||||
var eout = GLib.spawn_command_line_sync(
|
||||
this._getCommand(this.services.join(' '), 'is-enabled', 'system'))[1].toString().split('\n');
|
||||
//log(eout);
|
||||
|
||||
|
||||
this._entries.forEach(Lang.bind(this, function(service,index,arr) {
|
||||
if (! arr[index].enabled == (eout[index] == 'enabled'))
|
||||
arr[index].changed = true
|
||||
arr[index].enabled = (eout[index] == 'enabled' ? true : false)
|
||||
|
||||
if (! arr[index].active == (this.aout[index] == 'active'))
|
||||
arr[index].changed = true
|
||||
arr[index].active = (this.aout[index] == 'active' ? true : false)
|
||||
}));
|
||||
|
||||
this._entries.forEach(Lang.bind(this, function(service,index,arr) {
|
||||
var serviceItem
|
||||
me.forEach(Lang.bind(this, function(item) {
|
||||
if ( item.label.text == service['name'] ) {
|
||||
arr[index].found = true;
|
||||
serviceItem = item;
|
||||
me.splice(me.indexOf(item),1);
|
||||
}
|
||||
}));
|
||||
|
||||
if ( arr[index].changed ) {
|
||||
if (arr[index].found) {
|
||||
//log('update',service['name']);
|
||||
if ( service.staticserv ) {
|
||||
serviceItem.setToggleState(service.active);
|
||||
} else {
|
||||
serviceItem.setToggleState(service.enabled);
|
||||
}
|
||||
} else {
|
||||
//log('new',service['name']);
|
||||
if ( service.staticserv ) {
|
||||
serviceItem = new PopupTargetItem(service['name'], service.active);
|
||||
this.bkpsubmenu.menu.addMenuItem(serviceItem);
|
||||
|
||||
serviceItem.connect('toggled', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand(service['service'], (this._check_service(service.service, 'active') ? 'stop' : 'start'), service["type"]));
|
||||
}));
|
||||
} else {
|
||||
serviceItem = new PopupServiceItem(service['name'], service.enabled);
|
||||
this.bkpsubmenu.menu.addMenuItem(serviceItem);
|
||||
|
||||
serviceItem.connect('toggled', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand(service['service'], (this._check_service(service.service, 'enabled') ? 'disable' : 'enable'), service["type"]));
|
||||
}));
|
||||
|
||||
serviceItem.actionButton.connect('clicked', Lang.bind(this, function() {
|
||||
GLib.spawn_command_line_async(
|
||||
this._getCommand(service['service'], (this._check_service(service.service, 'active') ? 'stop' : 'start'), service["type"]));
|
||||
this.menu.close();
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (serviceItem.actionButton.child) {
|
||||
serviceItem.actionButton.child.icon_name = (service.active ? EnabledIcon : DisabledIcon);
|
||||
serviceItem.actionButton.child.style = (service.active ? "color: #ff0000;" : "color: revert;");
|
||||
};
|
||||
if (serviceItem.transferButton) {
|
||||
serviceItem.transferButton.style = (service.tr ? "text-decoration: revert;" : "text-decoration: line-through;");
|
||||
};
|
||||
|
||||
|
||||
//log('Changed',service.service)
|
||||
}
|
||||
//log('X',arr[index].service,arr[index].enabled,arr[index].active,arr[index].changed)
|
||||
arr[index].changed = false;
|
||||
}));
|
||||
|
||||
this.bkpsubmenu.menu._getMenuItems().forEach(Lang.bind(this, function(item) {
|
||||
var mic = 0
|
||||
if ( me.length > mic ) {
|
||||
for (var i = mic; i < me.length; i++) {
|
||||
if (item == me[i]) {
|
||||
log('DESTROY',me[i].label.text);
|
||||
item.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_loadConfig: function() {
|
||||
//log('LOAD CONFIG')
|
||||
var intervals
|
||||
this.services = []
|
||||
var kf = new GLib.KeyFile()
|
||||
var obj = new Object();
|
||||
this._entries = [];
|
||||
|
||||
if(kf.load_from_file('/etc/mksnapshot.conf',GLib.KeyFileFlags.NONE)){
|
||||
//intervals = kf.get_groups()[0];
|
||||
this.bkpmnt = kf.get_value('DEFAULT','bkpmnt')
|
||||
|
||||
//log('BKP',this.bkppath)
|
||||
kf.get_groups()[0].forEach(Lang.bind(this, function(interval) {
|
||||
var obj = new Object();
|
||||
var i = ""
|
||||
if (interval === 'DEFAULT')
|
||||
i = 'misc'
|
||||
else
|
||||
i = interval
|
||||
obj.name = i +' backups';
|
||||
obj.interval = interval;
|
||||
obj.service = "mkbackup@"+i+".service";
|
||||
this.services.push(obj.service)
|
||||
obj.type = "system";
|
||||
obj.enabled = false;
|
||||
obj.active = false;
|
||||
obj.changed = true;
|
||||
obj.found = false;
|
||||
obj.staticserv = false;
|
||||
|
||||
try {
|
||||
obj.tr = (kf.get_value(interval,"transfer").toLowerCase() === "true");
|
||||
} catch(err) {
|
||||
obj.tr = (kf.get_value("DEFAULT","transfer").toLowerCase() === "true");
|
||||
}
|
||||
this._entries.push(obj)}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var backupManager;
|
||||
|
||||
function init(extensionMeta) {
|
||||
//Convenience.initTranslations();
|
||||
var theme = imports.gi.Gtk.IconTheme.get_default();
|
||||
theme.append_search_path(extensionMeta.path + "/icons");
|
||||
}
|
||||
|
||||
|
||||
function enable() {
|
||||
backupManager = new BackupManager();
|
||||
|
||||
}
|
||||
|
||||
function disable() {
|
||||
backupManager.destroy();
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="caffeine-on-symbolic.svg"
|
||||
height="16"
|
||||
id="svg7384"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
version="1.1"
|
||||
width="16">
|
||||
<metadata
|
||||
id="metadata90">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Gnome Symbolic Icon Theme</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
inkscape:bbox-paths="false"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
inkscape:current-layer="layer11"
|
||||
inkscape:cx="6.0760965"
|
||||
inkscape:cy="6.333427"
|
||||
gridtolerance="10"
|
||||
inkscape:guide-bbox="true"
|
||||
guidetolerance="10"
|
||||
id="namedview88"
|
||||
inkscape:object-nodes="false"
|
||||
inkscape:object-paths="false"
|
||||
objecttolerance="10"
|
||||
pagecolor="#555753"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
showborder="false"
|
||||
showgrid="true"
|
||||
showguides="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:snap-bbox-midpoints="false"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:snap-grids="true"
|
||||
inkscape:snap-nodes="false"
|
||||
inkscape:snap-others="false"
|
||||
inkscape:snap-to-guides="true"
|
||||
inkscape:window-height="741"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="-3"
|
||||
inkscape:zoom="32">
|
||||
<inkscape:grid
|
||||
empspacing="2"
|
||||
enabled="true"
|
||||
id="grid4866"
|
||||
originx="-42.000009px"
|
||||
originy="412px"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1px"
|
||||
spacingy="1px"
|
||||
type="xygrid"
|
||||
visible="true" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="3.4692426,9.4354561"
|
||||
id="guide4029" />
|
||||
</sodipodi:namedview>
|
||||
<title
|
||||
id="title9167">Gnome Symbolic Icon Theme</title>
|
||||
<defs
|
||||
id="defs7386" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer9"
|
||||
inkscape:label="status"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer10"
|
||||
inkscape:label="devices"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer11"
|
||||
inkscape:label="apps"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 283.12476,634.39715 c 0.0901,2.39982 -2.5e-4,4.90096 0.90536,7.16242 0.56561,1.4515 1.83385,2.55431 3.30794,2.90589 1.73814,0.41114 3.64376,0.40412 5.29688,-0.34623 1.60312,-0.72608 2.57953,-2.39439 2.98402,-4.09315 0.48766,-1.97915 0.54999,-4.03862 0.59785,-6.06897 -0.18011,-1.15523 -1.25844,-1.84731 -2.20555,-2.3009 -2.30084,-1.01772 -4.91035,-1.09851 -7.34309,-0.60961 -1.29974,0.30095 -2.77781,0.86169 -3.36661,2.20531 -0.1485,0.35955 -0.20354,0.75631 -0.1768,1.14524 z m 11.6374,0.11136 c -0.0441,0.91126 -0.96505,1.35565 -1.68872,1.6268 -1.99848,0.66073 -4.17612,0.68978 -6.22022,0.24168 -0.87456,-0.24018 -1.9114,-0.56182 -2.32332,-1.47943 -0.29096,-0.86897 0.46051,-1.62212 1.18574,-1.91228 1.63288,-0.67936 3.44794,-0.70226 5.17963,-0.56565 1.19133,0.15082 2.47787,0.35598 3.42954,1.17357 0.24954,0.23294 0.4418,0.55854 0.43735,0.91531 z"
|
||||
id="path24839-7-0-8"
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.15345359;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Sans;-inkscape-font-specification:Sans" />
|
||||
<path
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.64499998;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
d="m 296.01831,634.29869 c -0.42246,0.1058 -0.81557,0.29843 -1.20581,0.48706 0.25,0.48958 0.5,0.97917 0.75,1.46875 0.52953,-0.26079 1.07925,-0.48948 1.65621,-0.62136 0.19598,0.29034 0.0613,0.65559 0.0965,0.98001 -0.0265,0.76535 -0.0891,1.53332 -0.2618,2.281 -0.19796,0.24661 -0.50311,0.0211 -0.71411,-0.0864 -0.30932,-0.0721 -0.53729,-0.31117 -0.83024,-0.41286 -0.12503,-0.008 -0.14438,0.15951 -0.217,0.23727 -0.23278,0.37411 -0.46557,0.74823 -0.69835,1.12235 0.81565,0.49866 1.72225,0.99381 2.70984,0.93005 0.59894,-0.0615 1.14074,-0.51865 1.25926,-1.11639 0.18671,-0.66571 0.27615,-1.35495 0.35899,-2.03989 0.0715,-0.82121 0.0983,-1.67218 -0.1491,-2.46792 -0.15932,-0.48309 -0.5391,-0.9535 -1.0785,-0.99879 -0.5706,-0.0824 -1.12444,0.12918 -1.67588,0.23714 z"
|
||||
id="path6047"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="on"
|
||||
style="display:none">
|
||||
<path
|
||||
id="path4114"
|
||||
style="fill:none;stroke:#ffffff;stroke-width:1.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 4.2625534,6.1750293 c 0,0 1.1491587,-0.6278903 1.1863897,-1.6606918 C 5.4861331,3.4826637 4.4603025,2.976995 4.4672485,2.0317235 4.4736485,1.1539862 5.6337624,0.28329388 5.6337624,0.28329388"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4114"
|
||||
id="use4118"
|
||||
transform="translate(3.3258259,0)"
|
||||
width="16"
|
||||
height="16" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="off"
|
||||
style="display:inline">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 6.555174,3.6689621 c -1.97865,-0.004 -4.74917,0.59448 -4.36893,1.9299 0.27853,0.9782 2.7468,1.37112 4.2822,1.34057 2.39093,-0.0476 3.99564,-0.15186 4.49936,-1.16589 0.66845,-1.34563 -2.32411,-2.10075 -4.41263,-2.10458 z"
|
||||
id="path6309"
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
sodipodi:nodetypes="sssss" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer13"
|
||||
inkscape:label="places"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer14"
|
||||
inkscape:label="mimetypes"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer15"
|
||||
inkscape:label="emblems"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="g71291"
|
||||
inkscape:label="emotes"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="g4953"
|
||||
inkscape:label="categories"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer12"
|
||||
inkscape:label="actions"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
</svg>
|
After Width: | Height: | Size: 8.3 KiB |
|
@ -0,0 +1,178 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="caffeine-on-symbolic.svg"
|
||||
height="16"
|
||||
id="svg7384"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
version="1.1"
|
||||
width="16">
|
||||
<metadata
|
||||
id="metadata90">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Gnome Symbolic Icon Theme</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
inkscape:bbox-paths="false"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
inkscape:current-layer="layer11"
|
||||
inkscape:cx="6.0760965"
|
||||
inkscape:cy="6.333427"
|
||||
gridtolerance="10"
|
||||
inkscape:guide-bbox="true"
|
||||
guidetolerance="10"
|
||||
id="namedview88"
|
||||
inkscape:object-nodes="false"
|
||||
inkscape:object-paths="false"
|
||||
objecttolerance="10"
|
||||
pagecolor="#555753"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
showborder="false"
|
||||
showgrid="true"
|
||||
showguides="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:snap-bbox-midpoints="false"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:snap-grids="true"
|
||||
inkscape:snap-nodes="false"
|
||||
inkscape:snap-others="false"
|
||||
inkscape:snap-to-guides="true"
|
||||
inkscape:window-height="741"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="-3"
|
||||
inkscape:zoom="32">
|
||||
<inkscape:grid
|
||||
empspacing="2"
|
||||
enabled="true"
|
||||
id="grid4866"
|
||||
originx="-42.000009px"
|
||||
originy="412px"
|
||||
snapvisiblegridlinesonly="true"
|
||||
spacingx="1px"
|
||||
spacingy="1px"
|
||||
type="xygrid"
|
||||
visible="true" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="3.4692426,9.4354561"
|
||||
id="guide4029" />
|
||||
</sodipodi:namedview>
|
||||
<title
|
||||
id="title9167">Gnome Symbolic Icon Theme</title>
|
||||
<defs
|
||||
id="defs7386" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer9"
|
||||
inkscape:label="status"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer10"
|
||||
inkscape:label="devices"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer11"
|
||||
inkscape:label="apps"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 283.12476,634.39715 c 0.0901,2.39982 -2.5e-4,4.90096 0.90536,7.16242 0.56561,1.4515 1.83385,2.55431 3.30794,2.90589 1.73814,0.41114 3.64376,0.40412 5.29688,-0.34623 1.60312,-0.72608 2.57953,-2.39439 2.98402,-4.09315 0.48766,-1.97915 0.54999,-4.03862 0.59785,-6.06897 -0.18011,-1.15523 -1.25844,-1.84731 -2.20555,-2.3009 -2.30084,-1.01772 -4.91035,-1.09851 -7.34309,-0.60961 -1.29974,0.30095 -2.77781,0.86169 -3.36661,2.20531 -0.1485,0.35955 -0.20354,0.75631 -0.1768,1.14524 z m 11.6374,0.11136 c -0.0441,0.91126 -0.96505,1.35565 -1.68872,1.6268 -1.99848,0.66073 -4.17612,0.68978 -6.22022,0.24168 -0.87456,-0.24018 -1.9114,-0.56182 -2.32332,-1.47943 -0.29096,-0.86897 0.46051,-1.62212 1.18574,-1.91228 1.63288,-0.67936 3.44794,-0.70226 5.17963,-0.56565 1.19133,0.15082 2.47787,0.35598 3.42954,1.17357 0.24954,0.23294 0.4418,0.55854 0.43735,0.91531 z"
|
||||
id="path24839-7-0-8"
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.15345359;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Sans;-inkscape-font-specification:Sans" />
|
||||
<path
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.64499998;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
d="m 296.01831,634.29869 c -0.42246,0.1058 -0.81557,0.29843 -1.20581,0.48706 0.25,0.48958 0.5,0.97917 0.75,1.46875 0.52953,-0.26079 1.07925,-0.48948 1.65621,-0.62136 0.19598,0.29034 0.0613,0.65559 0.0965,0.98001 -0.0265,0.76535 -0.0891,1.53332 -0.2618,2.281 -0.19796,0.24661 -0.50311,0.0211 -0.71411,-0.0864 -0.30932,-0.0721 -0.53729,-0.31117 -0.83024,-0.41286 -0.12503,-0.008 -0.14438,0.15951 -0.217,0.23727 -0.23278,0.37411 -0.46557,0.74823 -0.69835,1.12235 0.81565,0.49866 1.72225,0.99381 2.70984,0.93005 0.59894,-0.0615 1.14074,-0.51865 1.25926,-1.11639 0.18671,-0.66571 0.27615,-1.35495 0.35899,-2.03989 0.0715,-0.82121 0.0983,-1.67218 -0.1491,-2.46792 -0.15932,-0.48309 -0.5391,-0.9535 -1.0785,-0.99879 -0.5706,-0.0824 -1.12444,0.12918 -1.67588,0.23714 z"
|
||||
id="path6047"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="on"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path4114"
|
||||
style="fill:none;stroke:#ffffff;stroke-width:1.29999995;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 4.2625534,6.1750293 c 0,0 1.1491587,-0.6278903 1.1863897,-1.6606918 C 5.4861331,3.4826637 4.4603025,2.976995 4.4672485,2.0317235 4.4736485,1.1539862 5.6337624,0.28329388 5.6337624,0.28329388"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path4114"
|
||||
id="use4118"
|
||||
transform="translate(3.3258259,0)"
|
||||
width="16"
|
||||
height="16" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="off"
|
||||
style="display:none">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 6.555174,3.6689621 c -1.97865,-0.004 -4.74917,0.59448 -4.36893,1.9299 0.27853,0.9782 2.7468,1.37112 4.2822,1.34057 2.39093,-0.0476 3.99564,-0.15186 4.49936,-1.16589 0.66845,-1.34563 -2.32411,-2.10075 -4.41263,-2.10458 z"
|
||||
id="path6309"
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
sodipodi:nodetypes="sssss" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer13"
|
||||
inkscape:label="places"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer14"
|
||||
inkscape:label="mimetypes"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer15"
|
||||
inkscape:label="emblems"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="g71291"
|
||||
inkscape:label="emotes"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="g4953"
|
||||
inkscape:label="categories"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer12"
|
||||
inkscape:label="actions"
|
||||
style="display:inline"
|
||||
transform="translate(-283.00021,-629)" />
|
||||
</svg>
|
After Width: | Height: | Size: 8.3 KiB |
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"_generated": "Generated by SweetTooth, do not edit",
|
||||
"description": "Toggle systemd services on/off from a popup menu in the top gnome panel. Can be used to start services like apache2, mysql, postgres. It uses `pkexec' to run `sytemctl'. If you want to start services without entering a password you have to polkit policy file. An example policy file can be found in the github repository.",
|
||||
"name": "Zeitmaschine",
|
||||
"settings-schema": "org.gnome.shell.extensions.zeitmaschine",
|
||||
"shell-version": [
|
||||
"3.14",
|
||||
"3.16",
|
||||
"3.18",
|
||||
"3.20",
|
||||
"3.22"
|
||||
],
|
||||
"url": "https://github.com/xundeenergie/gnome-shell-extension-zeitmaschine",
|
||||
"uuid": "zeitmaschine@xundeenergie.at",
|
||||
"version": 11
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Gio = imports.gi.Gio;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
var DisabledIcon = 'my-caffeine-off-symbolic';
|
||||
//var DisabledIcon = 'gnome-spinner';
|
||||
|
||||
var PopupBKPItem = new Lang.Class({
|
||||
Name: 'PopupBKPItem',
|
||||
Extends: PopupMenu.PopupSwitchMenuItem,
|
||||
|
||||
_init: function(mount, active, params) {
|
||||
this.parent(mount.get_name(), active, params);
|
||||
log('YY',mount.get_name())
|
||||
|
||||
this.label = new St.Label({ text: mount.get_name()});
|
||||
this.actor.add(this.label, { expand: true });
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
this.mount = mount;
|
||||
|
||||
var ejectIcon = new St.Icon({ icon_name: 'media-eject-symbolic',
|
||||
style_class: 'popup-menu-icon ' });
|
||||
var ejectButton = new St.Button({ child: ejectIcon });
|
||||
ejectButton.connect('clicked', Lang.bind(this, this._eject));
|
||||
this.actor.add(ejectButton);
|
||||
|
||||
this._changedId = mount.connect('changed', Lang.bind(this, this._syncVisibility));
|
||||
this._syncVisibility();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._changedId) {
|
||||
this.mount.disconnect(this._changedId);
|
||||
this._changedId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_isInteresting: function() {
|
||||
//return true
|
||||
if (!this.mount.can_eject() && !this.mount.can_unmount())
|
||||
return false;
|
||||
if (this.mount.is_shadowed())
|
||||
return false;
|
||||
|
||||
var volume = this.mount.get_volume();
|
||||
|
||||
if (volume == null) {
|
||||
// probably a GDaemonMount, could be network or
|
||||
// local, but we can't tell; assume it's local for now
|
||||
return true;
|
||||
}
|
||||
|
||||
return volume.get_identifier('class') != 'network';
|
||||
},
|
||||
|
||||
_syncVisibility: function() {
|
||||
this.actor.visible = this._isInteresting();
|
||||
},
|
||||
|
||||
_eject: function() {
|
||||
var mountOp = new ShellMountOperation.ShellMountOperation(this.mount);
|
||||
|
||||
if (this.mount.can_eject())
|
||||
this.mount.eject_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._ejectFinish));
|
||||
else
|
||||
this.mount.unmount_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._unmountFinish));
|
||||
},
|
||||
|
||||
_unmountFinish: function(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_ejectFinish: function(mount, result) {
|
||||
try {
|
||||
mount.eject_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_reportFailure: function(exception) {
|
||||
var msg = _("Ejecting drive '%s' failed:").format(this.mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
},
|
||||
|
||||
/*activate: function(event) {
|
||||
var context = global.create_app_launch_context(event.get_time(), -1);
|
||||
Gio.AppInfo.launch_default_for_uri(this.mount.get_root().get_uri(),
|
||||
context);
|
||||
|
||||
this.parent(event);
|
||||
}*/
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
var DriveMenuItem = new Lang.Class({
|
||||
Name: 'DriveMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
_init: function(drive) {
|
||||
this.parent();
|
||||
|
||||
this.label = new St.Label({ text: drive.get_name() });
|
||||
this.actor.add(this.label, { expand: true });
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
this.drive = drive;
|
||||
|
||||
var ejectIcon = new St.Icon({ icon_name: 'drive-harddisk-usb-symbolic',
|
||||
style_class: 'popup-menu-icon ' });
|
||||
//var ejectIcon = mount.get_icon();
|
||||
var ejectButton = new St.Button({ child: ejectIcon });
|
||||
// ejectButton.connect('clicked', Lang.bind(this, this._eject));
|
||||
this.actor.add(ejectButton);
|
||||
|
||||
// this._changedId = mount.connect('changed', Lang.bind(this, this._syncVisibility));
|
||||
// this._syncVisibility();
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
var DisabledIcon = 'my-caffeine-off-symbolic';
|
||||
//var DisabledIcon = 'gnome-spinner';
|
||||
|
||||
var PopupServiceItem = new Lang.Class({
|
||||
Name: 'PopupServiceItem',
|
||||
Extends: PopupMenu.PopupMenuItem,
|
||||
|
||||
_init: function(text, active, params) {
|
||||
this.parent(text, active, params);
|
||||
|
||||
this.actionButton = new St.Button({
|
||||
x_align: 1,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: 'restart',
|
||||
style_class: 'system-menu-action services-systemd-button-reload' });
|
||||
|
||||
var icon = new St.Icon({ icon_name: DisabledIcon })
|
||||
this.actionButton.child = icon;
|
||||
this.actor.add(this.actionButton, { expand: false, x_align: St.Align.END });
|
||||
|
||||
/*this.ejectButton = new St.Button({ x_align: 1,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: 'eject',
|
||||
style_class: 'system-menu-action services-systemd-button-reload' });
|
||||
|
||||
this.ejectButton.child = new St.Icon({ icon_name: 'media-eject-symbolic' });
|
||||
this.actor.add(this.ejectButton, { expand: false, x_align: St.Align.END });*/
|
||||
}
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
var MountMenuItem = new Lang.Class({
|
||||
Name: 'MountMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
_init: function(mount) {
|
||||
this.parent();
|
||||
|
||||
this.label = new St.Label({ text: mount.get_name() });
|
||||
this.actor.add(this.label, { expand: true });
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
this.mount = mount;
|
||||
|
||||
var ejectIcon = new St.Icon({ icon_name: 'media-eject-symbolic',
|
||||
style_class: 'popup-menu-icon ' });
|
||||
//var ejectIcon = mount.get_icon();
|
||||
var ejectButton = new St.Button({ child: ejectIcon });
|
||||
ejectButton.connect('clicked', Lang.bind(this, this._eject));
|
||||
this.actor.add(ejectButton);
|
||||
|
||||
this._changedId = mount.connect('changed', Lang.bind(this, this._syncVisibility));
|
||||
this._syncVisibility();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._changedId) {
|
||||
this.mount.disconnect(this._changedId);
|
||||
this._changedId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_isInteresting: function() {
|
||||
if (!this.mount.can_eject() && !this.mount.can_unmount())
|
||||
return false;
|
||||
if (this.mount.is_shadowed())
|
||||
return false;
|
||||
|
||||
var volume = this.mount.get_volume();
|
||||
|
||||
if (volume == null) {
|
||||
// probably a GDaemonMount, could be network or
|
||||
// local, but we can't tell; assume it's local for now
|
||||
return true;
|
||||
}
|
||||
|
||||
return volume.get_identifier('class') != 'network';
|
||||
},
|
||||
|
||||
_syncVisibility: function() {
|
||||
this.actor.visible = this._isInteresting();
|
||||
},
|
||||
|
||||
_eject: function() {
|
||||
var mountOp = new ShellMountOperation.ShellMountOperation(this.mount);
|
||||
|
||||
if (this.mount.can_eject())
|
||||
this.mount.eject_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._ejectFinish));
|
||||
else
|
||||
this.mount.unmount_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._unmountFinish));
|
||||
},
|
||||
|
||||
_unmountFinish: function(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_ejectFinish: function(mount, result) {
|
||||
try {
|
||||
mount.eject_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_reportFailure: function(exception) {
|
||||
var msg = _("Ejecting drive '%s' failed:").format(this.mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
var context = global.create_app_launch_context(event.get_time(), -1);
|
||||
Gio.AppInfo.launch_default_for_uri(this.mount.get_root().get_uri(),
|
||||
context);
|
||||
|
||||
this.parent(event);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
var DisabledIcon = 'my-caffeine-off-symbolic';
|
||||
var description = "Beschreibung";
|
||||
//var DisabledIcon = 'gnome-spinner';
|
||||
|
||||
var PopupServiceItem = new Lang.Class({
|
||||
Name: 'PopupServiceItem',
|
||||
Extends: PopupMenu.PopupSwitchMenuItem,
|
||||
|
||||
_init: function(text, active, params) {
|
||||
this.parent(text, active, params);
|
||||
|
||||
/* this.descriptionLabel = new St.Button({
|
||||
label: description,
|
||||
reactive: false,
|
||||
x_align: St.Align.START,
|
||||
can_focus: false,
|
||||
accessible_name: 'description'});
|
||||
this.actor.add(this.descriptionLabel, {expand: true});
|
||||
*/
|
||||
this.actionButton = new St.Button({
|
||||
x_align: 1,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: 'restart',
|
||||
style_class: 'system-menu-action services-systemd-button-reload' });
|
||||
|
||||
var icon = new St.Icon({ icon_name: DisabledIcon })
|
||||
this.actionButton.child = icon;
|
||||
this.actor.add(this.actionButton, { expand: false, x_align: St.Align.END });
|
||||
|
||||
this.transferButton = new St.Button({
|
||||
label: 'transfer',
|
||||
x_align: 1,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: 'transfer',
|
||||
style_class: 'system-menu-action services-systemd-button-transfer' });
|
||||
|
||||
//this.transferButton.child = new St.Icon({ icon_name: 'media-eject-symbolic' });
|
||||
this.actor.add(this.transferButton, { expand: false, x_align: St.Align.END });
|
||||
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
var PopupTargetItem = new Lang.Class({
|
||||
Name: 'PopupServiceItem',
|
||||
Extends: PopupMenu.PopupSwitchMenuItem,
|
||||
|
||||
_init: function(text, active, params) {
|
||||
this.parent(text, active, params);
|
||||
|
||||
this.actionButton = new St.Button({ x_align: 1,
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
track_hover: true,
|
||||
accessible_name: 'restart',
|
||||
style_class: 'system-menu-action services-systemd-button-reload' });
|
||||
|
||||
}});
|
||||
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
var Lang = imports.lang;
|
||||
var PopupMenu = imports.ui.popupMenu;
|
||||
var St = imports.gi.St;
|
||||
var Clutter = imports.gi.Clutter;
|
||||
var Util = imports.misc.util;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
|
||||
var ExtensionSystem = imports.ui.extensionSystem;
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
var MountMenuItem = new Lang.Class({
|
||||
Name: 'MountMenuItem',
|
||||
Extends: PopupMenu.PopupBaseMenuItem,
|
||||
|
||||
_init: function(mount) {
|
||||
this.parent();
|
||||
|
||||
this.label = new St.Label({ text: mount.get_name() });
|
||||
this.actor.add(this.label, { expand: true });
|
||||
this.actor.label_actor = this.label;
|
||||
|
||||
this.mount = mount;
|
||||
|
||||
var ejectIcon = new St.Icon({ icon_name: 'media-eject-symbolic',
|
||||
style_class: 'popup-menu-icon ' });
|
||||
//var ejectIcon = mount.get_icon();
|
||||
var ejectButton = new St.Button({ child: ejectIcon });
|
||||
ejectButton.connect('clicked', Lang.bind(this, this._eject));
|
||||
this.actor.add(ejectButton);
|
||||
|
||||
this._changedId = mount.connect('changed', Lang.bind(this, this._syncVisibility));
|
||||
this._syncVisibility();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._changedId) {
|
||||
this.mount.disconnect(this._changedId);
|
||||
this._changedId = 0;
|
||||
}
|
||||
|
||||
this.parent();
|
||||
},
|
||||
|
||||
_isInteresting: function() {
|
||||
if (!this.mount.can_eject() && !this.mount.can_unmount())
|
||||
return false;
|
||||
if (this.mount.is_shadowed())
|
||||
return false;
|
||||
|
||||
var volume = this.mount.get_volume();
|
||||
|
||||
if (volume == null) {
|
||||
// probably a GDaemonMount, could be network or
|
||||
// local, but we can't tell; assume it's local for now
|
||||
return true;
|
||||
}
|
||||
|
||||
return volume.get_identifier('class') != 'network';
|
||||
},
|
||||
|
||||
_syncVisibility: function() {
|
||||
this.actor.visible = this._isInteresting();
|
||||
},
|
||||
|
||||
_eject: function() {
|
||||
var mountOp = new ShellMountOperation.ShellMountOperation(this.mount);
|
||||
|
||||
if (this.mount.can_eject())
|
||||
this.mount.eject_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._ejectFinish));
|
||||
else
|
||||
this.mount.unmount_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
mountOp.mountOp,
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._unmountFinish));
|
||||
},
|
||||
|
||||
_unmountFinish: function(mount, result) {
|
||||
try {
|
||||
mount.unmount_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_ejectFinish: function(mount, result) {
|
||||
try {
|
||||
mount.eject_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_reportFailure: function(exception) {
|
||||
var msg = _("Ejecting drive '%s' failed:").format(this.mount.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
var context = global.create_app_launch_context(event.get_time(), -1);
|
||||
Gio.AppInfo.launch_default_for_uri(this.mount.get_root().get_uri(),
|
||||
context);
|
||||
|
||||
this.parent(event);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,338 @@
|
|||
var GLib = imports.gi.GLib;
|
||||
var Gio = imports.gi.Gio;
|
||||
var Gtk = imports.gi.Gtk;
|
||||
var GObject = imports.gi.GObject;
|
||||
var Lang = imports.lang;
|
||||
|
||||
var ExtensionUtils = imports.misc.extensionUtils;
|
||||
var Me = ExtensionUtils.getCurrentExtension();
|
||||
var Convenience = Me.imports.convenience;
|
||||
|
||||
|
||||
var ServicesSystemdSettings = new GObject.Class({
|
||||
Name: 'Services-Systemd-Settings',
|
||||
Extends: Gtk.Grid,
|
||||
|
||||
_init : function(params) {
|
||||
// Gtk Grid init
|
||||
this.parent(params);
|
||||
this.set_orientation(Gtk.Orientation.VERTICAL);
|
||||
this.margin = 20;
|
||||
|
||||
// Open settings
|
||||
this._settings = Convenience.getSettings();
|
||||
this._settings.connect('changed', Lang.bind(this, this._refresh));
|
||||
|
||||
this._changedPermitted = false;
|
||||
|
||||
// Label
|
||||
var treeViewLabel = new Gtk.Label({ label: '<b>' + "Listed systemd Services:" + '</b>',
|
||||
use_markup: true,
|
||||
halign: Gtk.Align.START })
|
||||
this.add(treeViewLabel);
|
||||
|
||||
|
||||
// TreeView
|
||||
this._store = new Gtk.ListStore();
|
||||
this._store.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING]);
|
||||
|
||||
this._treeView = new Gtk.TreeView({ model: this._store,
|
||||
hexpand: true, vexpand: true });
|
||||
|
||||
var selection = this._treeView.get_selection();
|
||||
selection.set_mode(Gtk.SelectionMode.SINGLE);
|
||||
selection.connect ('changed', Lang.bind (this, this._onSelectionChanged));
|
||||
|
||||
|
||||
var appColumn = new Gtk.TreeViewColumn({ expand: true,
|
||||
title: "Label" });
|
||||
|
||||
var nameRenderer = new Gtk.CellRendererText;
|
||||
appColumn.pack_start(nameRenderer, true);
|
||||
appColumn.add_attribute(nameRenderer, "text", 0);
|
||||
this._treeView.append_column(appColumn);
|
||||
|
||||
var appColumn = new Gtk.TreeViewColumn({ expand: true,
|
||||
title: "Service" });
|
||||
|
||||
var nameRenderer = new Gtk.CellRendererText;
|
||||
appColumn.pack_start(nameRenderer, true);
|
||||
appColumn.add_attribute(nameRenderer, "text", 1);
|
||||
this._treeView.append_column(appColumn);
|
||||
|
||||
var appColumn = new Gtk.TreeViewColumn({ expand: true,
|
||||
title: "Type" });
|
||||
|
||||
var nameRenderer = new Gtk.CellRendererText;
|
||||
appColumn.pack_start(nameRenderer, true);
|
||||
appColumn.add_attribute(nameRenderer, "text", 2);
|
||||
this._treeView.append_column(appColumn);
|
||||
|
||||
this.add(this._treeView);
|
||||
|
||||
// Devare Toolbar
|
||||
var toolbar = new Gtk.Toolbar();
|
||||
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
|
||||
toolbar.halign = 2;
|
||||
this.add(toolbar);
|
||||
|
||||
var upButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_GO_UP });
|
||||
upButton.connect('clicked', Lang.bind(this, this._up));
|
||||
toolbar.add(upButton);
|
||||
|
||||
var downButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_GO_DOWN });
|
||||
downButton.connect('clicked', Lang.bind(this, this._down));
|
||||
toolbar.add(downButton);
|
||||
|
||||
var delButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_DELETE });
|
||||
delButton.connect('clicked', Lang.bind(this, this._devare));
|
||||
toolbar.add(delButton);
|
||||
|
||||
this._selDepButtons = [upButton, downButton, delButton]
|
||||
|
||||
// Add Grid
|
||||
var grid = new Gtk.Grid();
|
||||
|
||||
//// Label
|
||||
var labelName = new Gtk.Label({label: "Label: "});
|
||||
labelName.halign = 2;
|
||||
|
||||
this._displayName = new Gtk.Entry({ hexpand: true,
|
||||
margin_top: 5 });
|
||||
this._displayName.set_placeholder_text("Name in menu");
|
||||
|
||||
var labelService = new Gtk.Label({label: "Service: "});
|
||||
labelService.halign = 2;
|
||||
|
||||
this._availableSystemdServices = {
|
||||
//'system': this._getSystemdServicesList("system"),
|
||||
//'user': this._getSystemdServicesList("user"),
|
||||
'system': this._getSystemdTargetsList("system"),
|
||||
'user': this._getSystemdTargetsList("user"),
|
||||
}
|
||||
this._availableSystemdServices['all'] = this._availableSystemdServices['system'].concat(this._availableSystemdServices['user'])
|
||||
//this._availableSystemdServices['all'] = this._availableSystemdServices['systemtargets'].concat(this._availableSystemdServices['all'])
|
||||
//this._availableSystemdServices['all'] = this._availableSystemdServices['usertargets'].concat(this._availableSystemdServices['all'])
|
||||
|
||||
var sListStore = new Gtk.ListStore();
|
||||
sListStore.set_column_types([GObject.TYPE_STRING, GObject.TYPE_INT]);
|
||||
|
||||
for (var i in this._availableSystemdServices['all'])
|
||||
sListStore.set (sListStore.append(), [0], [this._availableSystemdServices['all'][i]]);
|
||||
|
||||
this._systemName = new Gtk.Entry()
|
||||
this._systemName.set_placeholder_text("Systemd service name");
|
||||
var compvarion = new Gtk.EntryCompvarion()
|
||||
this._systemName.set_compvarion(compvarion)
|
||||
compvarion.set_model(sListStore)
|
||||
|
||||
compvarion.set_text_column(0)
|
||||
|
||||
grid.attach(labelName, 1, 1, 1, 1);
|
||||
grid.attach_next_to(this._displayName, labelName, 1, 1, 1);
|
||||
|
||||
grid.attach(labelService, 1, 2, 1, 1);
|
||||
grid.attach_next_to(this._systemName,labelService, 1, 1, 1);
|
||||
|
||||
this.add(grid);
|
||||
|
||||
var toolbar = new Gtk.Toolbar();
|
||||
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
|
||||
toolbar.halign = 2;
|
||||
this.add(toolbar);
|
||||
|
||||
var addButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_ADD,
|
||||
label: "Add",
|
||||
is_important: true });
|
||||
|
||||
addButton.connect('clicked', Lang.bind(this, this._add));
|
||||
toolbar.add(addButton);
|
||||
|
||||
this._changedPermitted = true;
|
||||
this._refresh();
|
||||
this._onSelectionChanged();
|
||||
},
|
||||
|
||||
_getSystemdTargetsList: function(type) {
|
||||
var [_, out, err, stat] = GLib.spawn_command_line_sync('sh -c "systemctl --' + type + ' list-unit-files --type=target | tail -n +2 | head -n -2 | awk \'{print $1}\'"');
|
||||
var allFiltered = out.toString().split("\n");
|
||||
return allFiltered.sort(
|
||||
function (a, b) {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
})
|
||||
},
|
||||
|
||||
_getSystemdServicesList: function(type) {
|
||||
var [_, out, err, stat] = GLib.spawn_command_line_sync('sh -c "systemctl --' + type + ' list-unit-files --type=service | tail -n +2 | head -n -2 | awk \'{print $1}\'"');
|
||||
var allFiltered = out.toString().split("\n");
|
||||
return allFiltered.sort(
|
||||
function (a, b) {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
})
|
||||
},
|
||||
_getTypeOfService: function(service) {
|
||||
var type = "undefined"
|
||||
if (this._availableSystemdServices['systemtargets'].indexOf(service) != -1 && this._availableSystemdServices['system'].indexOf(service) != -1)
|
||||
type = "system"
|
||||
else if (this._availableSystemdServices['usertargets'].indexOf(service) != -1 && this._availableSystemdServices['user'].indexOf(service) != -1)
|
||||
type = "user"
|
||||
return type
|
||||
},
|
||||
|
||||
_getIdFromIter: function(iter) {
|
||||
var displayName = this._store.get_value(iter, 0);
|
||||
var serviceName = this._store.get_value(iter, 1);
|
||||
var type = this._store.get_value(iter, 2);
|
||||
return JSON.stringify({"name": displayName, "service": serviceName, "type": type});
|
||||
},
|
||||
|
||||
_add: function() {
|
||||
var displayName = this._displayName.text
|
||||
var serviceName = this._systemName.text
|
||||
|
||||
if (displayName.trim().length > 0 && serviceName.trim().length > 0 ) {
|
||||
var type = this._getTypeOfService(serviceName)
|
||||
if (type == "undefined") {
|
||||
this._messageDialog = new Gtk.MessageDialog ({
|
||||
title: "Warning",
|
||||
modal: true,
|
||||
buttons: Gtk.ButtonsType.OK,
|
||||
message_type: Gtk.MessageType.WARNING,
|
||||
text: "Service does not exist."
|
||||
});
|
||||
this._messageDialog.connect('response', Lang.bind(this, function() {
|
||||
this._messageDialog.close();
|
||||
}));
|
||||
this._messageDialog.show();
|
||||
} else {
|
||||
var id = JSON.stringify({"name": displayName, "service": serviceName, "type": type})
|
||||
var currentItems = this._settings.get_strv("zeitmaschine");
|
||||
var index = currentItems.indexOf(id);
|
||||
if (index < 0) {
|
||||
this._changedPermitted = false;
|
||||
currentItems.push(id);
|
||||
this._settings.set_strv("zeitmaschine", currentItems);
|
||||
this._store.set(this._store.append(), [0, 1, 2], [displayName, serviceName, type]);
|
||||
this._changedPermitted = true;
|
||||
}
|
||||
this._displayName.text = ""
|
||||
this._systemName.text = ""
|
||||
}
|
||||
|
||||
} else {
|
||||
this._messageDialog = new Gtk.MessageDialog ({
|
||||
//parent: this.get_toplevel(),
|
||||
title: "Warning",
|
||||
modal: true,
|
||||
buttons: Gtk.ButtonsType.OK,
|
||||
message_type: Gtk.MessageType.WARNING,
|
||||
text: "No label and/or service specified."
|
||||
});
|
||||
|
||||
this._messageDialog.connect ('response', Lang.bind(this, function() {
|
||||
this._messageDialog.close();
|
||||
}));
|
||||
this._messageDialog.show();
|
||||
}
|
||||
},
|
||||
|
||||
_up: function() {
|
||||
var [any, model, iter] = this._treeView.get_selection().get_selected();
|
||||
|
||||
if (any) {
|
||||
var index = this._settings.get_strv("zeitmaschine").indexOf(this._getIdFromIter(iter));
|
||||
this._move(index, index - 1)
|
||||
}
|
||||
},
|
||||
_down: function() {
|
||||
var [any, model, iter] = this._treeView.get_selection().get_selected();
|
||||
|
||||
if (any) {
|
||||
var index = this._settings.get_strv("zeitmaschine").indexOf(this._getIdFromIter(iter));
|
||||
this._move(index, index + 1)
|
||||
}
|
||||
},
|
||||
_move: function(oldIndex, newIndex) {
|
||||
var currentItems = this._settings.get_strv("zeitmaschine");
|
||||
|
||||
if (oldIndex < 0 || oldIndex >= currentItems.length ||
|
||||
newIndex < 0 || newIndex >= currentItems.length)
|
||||
return;
|
||||
|
||||
currentItems.splice(newIndex, 0, currentItems.splice(oldIndex, 1)[0]);
|
||||
|
||||
this._settings.set_strv("zeitmaschine", currentItems);
|
||||
|
||||
this._treeView.get_selection().unselect_all();
|
||||
this._treeView.get_selection().select_path(Gtk.TreePath.new_from_string(String(newIndex)));
|
||||
},
|
||||
_devare: function() {
|
||||
var [any, model, iter] = this._treeView.get_selection().get_selected();
|
||||
|
||||
if (any) {
|
||||
var currentItems = this._settings.get_strv("zeitmaschine");
|
||||
var index = currentItems.indexOf(this._getIdFromIter(iter));
|
||||
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
currentItems.splice(index, 1);
|
||||
this._settings.set_strv("zeitmaschine", currentItems);
|
||||
|
||||
this._store.remove(iter);
|
||||
}
|
||||
},
|
||||
_onSelectionChanged: function() {
|
||||
var [any, model, iter] = this._treeView.get_selection().get_selected();
|
||||
if (any) {
|
||||
this._selDepButtons.forEach(function(value) {
|
||||
value.set_sensitive(true)
|
||||
});
|
||||
} else {
|
||||
this._selDepButtons.forEach(function(value) {
|
||||
value.set_sensitive(false)
|
||||
});
|
||||
}
|
||||
},
|
||||
_refresh: function() {
|
||||
if (!this._changedPermitted)
|
||||
return;
|
||||
|
||||
this._store.clear();
|
||||
|
||||
var currentItems = this._settings.get_strv("zeitmaschien");
|
||||
var validItems = [ ];
|
||||
|
||||
for (var i = 0; i < currentItems.length; i++) {
|
||||
var entry = JSON.parse(currentItems[i]);
|
||||
// REMOVE NOT EXISTING ENTRIES
|
||||
if (this._availableSystemdServices["all"].indexOf(entry["service"]) < 0)
|
||||
continue;
|
||||
|
||||
// COMPABILITY
|
||||
if(!("type" in entry))
|
||||
entry["type"] = this._getTypeOfService(entry["service"])
|
||||
|
||||
validItems.push(JSON.stringify(entry));
|
||||
|
||||
var iter = this._store.append();
|
||||
this._store.set(iter,
|
||||
[0, 1, 2],
|
||||
[entry["name"], entry["service"], entry["type"]]);
|
||||
}
|
||||
|
||||
this._changedPermitted = false
|
||||
this._settings.set_strv("zeitmaschine", validItems);
|
||||
this._changedPermitted = true
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
}
|
||||
|
||||
function buildPrefsWidget() {
|
||||
var widget = new ServicesSystemdSettings();
|
||||
widget.show_all();
|
||||
|
||||
return widget;
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist gettext-domain="gnome-shell-extensions-zeitmaschine">
|
||||
<schema path="/org/gnome/shell/extensions/services/" id="org.gnome.shell.extensions.zeitmaschine">
|
||||
<key type="as" name="zeitmaschine">
|
||||
<default>[ ]</default>
|
||||
<summary>Systemd service list</summary>
|
||||
<description>A list of serrvice which are shown</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
|
@ -0,0 +1,23 @@
|
|||
.system-menu-action.services-systemd-button-transfer {
|
||||
height: 18px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.system-menu-action.services-systemd-button-reload {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.disk-active-icon {
|
||||
icon-size: 1.09em;
|
||||
padding: 0 5px;
|
||||
color: #ff0000;
|
||||
}
|
||||
.system-status-icon-red {
|
||||
icon-size: 1.09em;
|
||||
padding: 0 5px;
|
||||
color: #ff0000;
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=Notifications service for mkbackup
|
||||
Name[ar]=تنبيهات
|
||||
Name[ast]=Notificaciones
|
||||
Name[bg]=Уведомления
|
||||
Name[ca]=Notificacions
|
||||
Name[cs]=Oznámení
|
||||
Name[da]=Beskeder
|
||||
Name[de]=Benachrichtigungen für mkbackup
|
||||
Name[el]=Ειδοποιήσεις
|
||||
Name[en_AU]=Notifications
|
||||
Name[en_GB]=Notifications
|
||||
Name[es]=Notificaciones
|
||||
Name[eu]=Berri-emateak
|
||||
Name[fi]=Ilmoitukset
|
||||
Name[fr]=Notifications
|
||||
Name[gl]=Notificacións
|
||||
Name[he]=התראות
|
||||
Name[hr]=Obavijesti
|
||||
Name[hu]=Értesítések
|
||||
Name[id]=Notifikasi
|
||||
Name[is]=Tilkynningar
|
||||
Name[it]=Notifiche
|
||||
Name[ja]=通知
|
||||
Name[kk]=Хабарламалар
|
||||
Name[ko]=알림
|
||||
Name[lt]=Pranešimai
|
||||
Name[lv]=Paziņojumi
|
||||
Name[ms]=Pemberitahuan
|
||||
Name[nb]=Varsling
|
||||
Name[nl]=Meldingen
|
||||
Name[oc]=Notificacions
|
||||
Name[pa]=ਨੋਟੀਫਿਕੇਸ਼ਨ
|
||||
Name[pl]=Powiadomienia
|
||||
Name[pt]=Notificações
|
||||
Name[pt_BR]=Notificações
|
||||
Name[ro]=Notificări
|
||||
Name[ru]=Оповещения
|
||||
Name[sk]=Oznámenia
|
||||
Name[sl]=Obvestila
|
||||
Name[sq]=Njoftime
|
||||
Name[sr]=Обавештења
|
||||
Name[sv]=Notifieringar
|
||||
Name[th]=การแจ้งเหตุ
|
||||
Name[tr]=Bildiriler
|
||||
Name[ug]=ئۇقتۇرۇشلار
|
||||
Name[uk]=Сповіщення
|
||||
Name[vi]=Thông báo
|
||||
Name[zh_CN]=通知
|
||||
Name[zh_TW]=通知
|
||||
Comment=Customize how notifications appear on your screen
|
||||
Comment[ar]=خصص كيف تظهر التنبيهات على الشاشة
|
||||
Comment[ast]=Personaliza cómo apaecen les notificaciones na to pantalla
|
||||
Comment[bg]=Настройване на изгледа на уведомленията на екрана
|
||||
Comment[ca]=Personalitzeu com es mostren les notificacions
|
||||
Comment[cs]=Upravte způsob, jakým se budou oznámení zobrazovat
|
||||
Comment[da]=Tilpas hvordan beskeder fremkommer på din skærm
|
||||
Comment[de]=Das Erscheinungsbild von Benachrichtigungen anpassen
|
||||
Comment[el]=Προσαρμογή του τρόπου εμφάνισης των ειδοποιήσεων στην οθόνη
|
||||
Comment[en_AU]=Customise how notifications appear on your screen
|
||||
Comment[en_GB]=Customise how notifications appear on your screen
|
||||
Comment[es]=Personalice cómo aparecen las notificaciones en pantalla
|
||||
Comment[eu]=Pertsonalizatu berri-emateak zure pantailan nola agertuko diren
|
||||
Comment[fi]=Mukauta näytöllesi ilmestyvien ilmoitusten toimintaa
|
||||
Comment[fr]=Personnaliser la manière dont les notifications apparaissent sur votre écran
|
||||
Comment[gl]=Personalice como se mostran as notificacións na súa pantalla
|
||||
Comment[he]=התאם מראה התראות על המסך שלך
|
||||
Comment[hr]=Prilagodite kako će se obavijesti prikazivati na vašem ekranu
|
||||
Comment[hu]=A képernyőn megjelenő értesítések megjelenésének személyre szabása
|
||||
Comment[id]=Sesuaikan bagaimana notifikasi tampak di layar anda
|
||||
Comment[is]=Sérsníða hvernig tilkynningar birtast á skjánum þínum
|
||||
Comment[it]=Personalizzazione del modo in cui le notifiche appaiono sullo schermo
|
||||
Comment[ja]=画面上にどのように通知するか設定します
|
||||
Comment[kk]=Хабарламалар көрсетілуін таңдаңыз
|
||||
Comment[ko]=알림을 화면에 어떻게 띄울 것인지 설정합니다
|
||||
Comment[lt]=Tinkinti kaip atrodys pranešimai jūsų ekrane
|
||||
Comment[lv]=Pielāgojiet, kā paziņojumu parādās uz jūsu ekrāna
|
||||
Comment[ms]=Suaikan bagaimana pemberitahuan muncul atas skrin anda
|
||||
Comment[nb]=Tilpass visning av varsler på skjermen din
|
||||
Comment[nl]=Weergave van meldingen aanpassen
|
||||
Comment[oc]=Personalizar lo biais que las notificacions apareisson sus vòstre ecran
|
||||
Comment[pa]=ਕਸਟਮਾਈਜ਼ ਕਰੋ ਕਿ ਤੁਹਾਡੀ ਸਕਰੀਨ ਉੱਤੇ ਨੋਟੀਫਿਕੇਸ਼ਨ ਕਿੰਝ ਵੇਖਾਈ ਦੇਣ
|
||||
Comment[pl]=Konfiguruje ustwienia powiadamiania
|
||||
Comment[pt]=Personalizar o aspeto das notificações no ecrã
|
||||
Comment[pt_BR]=Personalize como as notificações devem aparecer na sua tela
|
||||
Comment[ro]=Personalizați cum apar pe ecran notificările
|
||||
Comment[ru]=Настройка отображения оповещений на вашем экране
|
||||
Comment[sk]=Prispôsobiť spôsob upozornenia na obrazovke
|
||||
Comment[sl]=Prilagodite prikaz obvestil na zaslonu
|
||||
Comment[sq]=Përshtasni mënyrën se si shfaqen njoftimet në ekranin tuaj
|
||||
Comment[sr]=Прилагодите начин приказа обавештења на екрану
|
||||
Comment[sv]=Anpassa hur notifieringar ska visas på din skärm
|
||||
Comment[th]=กำหนดหน้าตาของการแจ้งเหตุบนหน้าจอของคุณ
|
||||
Comment[tr]=Bildirilerin görünümünü özelleştirin
|
||||
Comment[ug]=ئۇقتۇرۇشلارنىڭ كۆرۈنۈش ئۇسۇلىنى ئۆزلەشتۈر
|
||||
Comment[uk]=Налаштуйте показ сповіщень на Вашому екрані
|
||||
Comment[vi]=Tùy chỉnh cách thông báo ẩn trên màn hình
|
||||
Comment[zh_CN]=自定义通知在您屏幕上的显示方式
|
||||
Comment[zh_TW]=自訂通知該如何在您螢幕上顯示
|
||||
Exec=/usr/lib/mkbackup-btrfs/mkbackup-desktop-notifications.py
|
||||
TryExec=/usr/lib/mkbackup-btrfs/mkbackup-desktop-notifications.py
|
||||
Icon=xfce4-notifyd
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
Type=Application
|
||||
Categories=GTK;Settings;DesktopSettings;
|
0
register
Normal file
0
register
Normal file
Loading…
Reference in a new issue