@@ -0,0 +1,13 @@ | |||||
#Swap files | |||||
*.swp | |||||
design | |||||
testfile | |||||
testbed.py | |||||
randoms | |||||
*.lock | |||||
*.pyc | |||||
*.pyo | |||||
*.toml |
@@ -0,0 +1,21 @@ | |||||
MIT License | |||||
Copyright (c) 2019 Hussar | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@@ -1,9 +1,23 @@ | |||||
# Debian Update Notifier | # Debian Update Notifier | ||||
Update Notifier is a simple and easy-to-use application that uses chronjobs for scheduling package updates. It takes a password and runs ``sudo apt update`` in a subprocess. It stores no passwords in any files. | |||||
Update Notifier is a simple and easy-to-use application that uses chronjobs for scheduling package updates. It pipes a password stored in memory to sudo subprocesses and stores no passwords in any plain text files. | |||||
// [Insert picture of notification popup] | |||||
![screenshot](/assets/gui-screenshot.png) | |||||
## Usage | |||||
`` ./setup.py 15`` | |||||
## Requirements | |||||
PySide2, trio, and sudo. | |||||
Sets the chronjob to run the update.py script at the current location every 15 days. Setup expects an integer representing the update interval. | |||||
``pip3 install PySide2 trio`` | |||||
``$ apt install sudo`` | |||||
## Install | |||||
`` ./ChronInstall.sh [n]`` | |||||
Sets the chronjob to run the update.py script at the current location every n days. The default is 30 if nothing is entered. The update script can also be run manually. | |||||
## TODO | |||||
* Handle subprocess termination after cancel button is pressed | |||||
* Handle ctrl-c terminal press | |||||
* Handle Cancel and other errors | |||||
* Add chronjob install script | |||||
* Add icon |
@@ -0,0 +1,45 @@ | |||||
#!/bin/bash | |||||
distResult=$(grep "PRETTY_NAME=" < /etc/*-release) | |||||
echo "$distResult" | |||||
updateInterval="$1" | |||||
updatePath="$(pwd)/update.py" | |||||
if [ -z "$updateInterval" ]; then | |||||
updateInterval=30 | |||||
echo $updateInterval | |||||
fi | |||||
#Check that the running distro is debian and update.py exists | |||||
if [[ "$distResult" != *"Debian"* ]]; then | |||||
echo 'Identified distribution is not debian. Quiting...' | |||||
exit 1 | |||||
fi | |||||
if [ ! -f "$updatePath" ]; then | |||||
echo 'update.py not found. Quitting...' | |||||
exit 1 | |||||
fi | |||||
#Check if there is too many arguments | |||||
if [ "$#" -gt "1" ]; then | |||||
echo "Too many arguments. There are $#." | |||||
exit 1 | |||||
fi | |||||
retes='^[0-9]+$' | |||||
if ! [[ $updateInterval =~ $retes ]]; then | |||||
echo 'interval has non int value. Quiting.' | |||||
exit 1 | |||||
fi | |||||
echo 'Starting' | |||||
#inTime = '17 * * * * root cd / && run-parts --report /etc/cron.hourly' | |||||
insert="$updateInterval 5 pkg.updates $updatePath" | |||||
currentLine=$(cat /etc/anacrontab | grep 'pkg.updates') | |||||
if [ "$currentLine" = "" ]; then | |||||
echo "$insert" >> /etc/anacrontab | |||||
else | |||||
sed -i "/pkg.updates/ c\\$insert" /etc/anacrontab | |||||
fi |
@@ -23,9 +23,9 @@ class UpdatePrompt(QDialog): | |||||
layout = QVBoxLayout() | layout = QVBoxLayout() | ||||
btnLayout = QHBoxLayout() | btnLayout = QHBoxLayout() | ||||
self.centStack = QStackedWidget() | self.centStack = QStackedWidget() | ||||
updateButton = QPushButton('Update') | |||||
cancelButton = QPushButton('Cancel') | |||||
notifyLabel = QLabel('There are updates scheduled') | |||||
self.updateButton = QPushButton('Update') | |||||
self.cancelButton = QPushButton('Cancel') | |||||
notifyLabel = QLabel('There are upgrades scheduled') | |||||
self.inputBox = QLineEdit() | self.inputBox = QLineEdit() | ||||
self.outputBox = QTextBrowser() | self.outputBox = QTextBrowser() | ||||
#refreshIcon = QIcon.fromTheme('process-working') | #refreshIcon = QIcon.fromTheme('process-working') | ||||
@@ -36,8 +36,8 @@ class UpdatePrompt(QDialog): | |||||
layout.addWidget(self.centStack) | layout.addWidget(self.centStack) | ||||
layout.addWidget(self.inputBox) | layout.addWidget(self.inputBox) | ||||
layout.addLayout(btnLayout) | layout.addLayout(btnLayout) | ||||
btnLayout.addWidget(cancelButton) | |||||
btnLayout.addWidget(updateButton) | |||||
btnLayout.addWidget(self.cancelButton) | |||||
btnLayout.addWidget(self.updateButton) | |||||
self.centStack.addWidget(refreshAnimation) | self.centStack.addWidget(refreshAnimation) | ||||
self.centStack.addWidget(self.outputBox) | self.centStack.addWidget(self.outputBox) | ||||
@@ -48,14 +48,15 @@ class UpdatePrompt(QDialog): | |||||
self.inputBox.setEchoMode(QLineEdit.Password) | self.inputBox.setEchoMode(QLineEdit.Password) | ||||
self.inputBox.setFocus() | self.inputBox.setFocus() | ||||
self.inputBox.returnPressed.connect(self.pkgUpdates) | self.inputBox.returnPressed.connect(self.pkgUpdates) | ||||
updateButton.clicked.connect(self.pkgUpdates) | |||||
cancelButton.clicked.connect(self.cancelUpdates) | |||||
self.updateButton.clicked.connect(self.pkgUpdates) | |||||
self.cancelButton.clicked.connect(self.cancelUpdates) | |||||
self.updateButton.setDefault(True) | |||||
self.centStack.setCurrentIndex(1) | self.centStack.setCurrentIndex(1) | ||||
notifyLabel.setAlignment(Qt.AlignTop) | notifyLabel.setAlignment(Qt.AlignTop) | ||||
self.outputBox.setReadOnly(True) | self.outputBox.setReadOnly(True) | ||||
#self.outputBox.setAlignment(Qt.AlignTop) | #self.outputBox.setAlignment(Qt.AlignTop) | ||||
self.setWindowTitle('Package Updates') | |||||
self.setWindowTitle('Package Upgrades') | |||||
self.setLayout(layout) | self.setLayout(layout) | ||||
self.resize(450, 250) | self.resize(450, 250) | ||||
return | return | ||||
@@ -123,9 +124,14 @@ class UpdatePrompt(QDialog): | |||||
self.passError('The password field cannot be empty') | self.passError('The password field cannot be empty') | ||||
return | return | ||||
self.inputBox.clear() | |||||
self.inputBox.setDisabled(True) | |||||
self.updateButton.setDisabled(True) | |||||
trio.run(self.asetup, password) | trio.run(self.asetup, password) | ||||
self.centStack.setCurrentIndex(1) | self.centStack.setCurrentIndex(1) | ||||
self.refreshIcon.stop() | self.refreshIcon.stop() | ||||
self.updateButton.setDisabled(False) | |||||
self.inputBox.setDisabled(False) | |||||
return | return | ||||
def passError(self, s): | def passError(self, s): | ||||
@@ -143,6 +149,7 @@ class UpdatePrompt(QDialog): | |||||
return | return | ||||
def cancelUpdates(self): | def cancelUpdates(self): | ||||
#Needs way of closing subprocess during async run | |||||
self.reject() | self.reject() | ||||
return | return | ||||