Browse Source

Automate backups:

- backup wallet file on each channel creation
 - on android, a backup password is entered in settings
 - on desktop, the backup path is in settings
hard-fail-on-bad-server-string
ThomasV 5 years ago
parent
commit
2dad87cbb4
  1. 20
      electrum/gui/kivy/main_window.py
  2. 8
      electrum/gui/kivy/uix/dialogs/settings.py
  3. 15
      electrum/gui/qt/main_window.py
  4. 15
      electrum/gui/qt/settings_dialog.py
  5. 1
      electrum/lnworker.py
  6. 2
      electrum/util.py
  7. 26
      electrum/wallet.py

20
electrum/gui/kivy/main_window.py

@ -1199,21 +1199,31 @@ class ElectrumWindow(App):
on_success=on_success, on_failure=on_failure, is_change=1)
self._password_dialog.open()
def save_backup(self):
def change_backup_password(self):
from .uix.dialogs.password_dialog import PasswordDialog
from electrum.util import get_backup_dir
from electrum.storage import WalletStorage
if self._password_dialog is None:
self._password_dialog = PasswordDialog()
message = _("Create backup.") + '\n' + _("Enter your current PIN:")
def on_success(old_password, new_password):
new_path = os.path.join(get_backup_dir(self.electrum_config), self.wallet.basename() + '.backup')
self.wallet.save_backup(new_path, old_password=old_password, new_password=new_password)
self.show_info(_("Backup saved:") + f"\n{new_path}")
on_failure = lambda: self.show_error(_("PIN codes do not match"))
backup_pubkey = WalletStorage.get_eckey_from_password(new_password).get_public_key_hex()
# TODO: use a unique PIN for all wallets
self.electrum_config.set_key('pin_code', old_password)
self.electrum_config.set_key('backup_pubkey', backup_pubkey)
self.show_info(_("Backup password set"))
on_failure = lambda: self.show_error(_("Passwords do not match"))
self._password_dialog.init(self, wallet=self.wallet, msg=message,
on_success=on_success, on_failure=on_failure, is_change=1, is_backup=True)
self._password_dialog.open()
def save_backup(self):
new_path = self.wallet.save_backup()
if new_path:
self.show_info(_("Backup saved:") + f"\n{new_path}")
else:
self.show_error(_("Backup directory not configured"))
def export_private_keys(self, pk_label, addr):
if self.wallet.is_watching_only():
self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))

8
electrum/gui/kivy/uix/dialogs/settings.py

@ -82,6 +82,11 @@ Builder.load_string('''
description: _("Send your change to separate addresses.")
message: _('Send excess coins to change addresses')
action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)
CardSeparator
SettingsItem:
title: _('Backups')
description: _("Set password for encrypted backups.")
action: root.change_backup_password
# disabled: there is currently only one coin selection policy
#CardSeparator
@ -121,6 +126,9 @@ class SettingsDialog(Factory.Popup):
def change_password(self, item, dt):
self.app.change_password(self.update)
def change_backup_password(self, dt):
self.app.change_backup_password()
def language_dialog(self, item, dt):
if self._language_dialog is None:
l = self.config.get('language', 'en_UK')

15
electrum/gui/qt/main_window.py

@ -562,20 +562,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.gui_object.new_window(filename)
def backup_wallet(self):
path = self.wallet.storage.path
wallet_folder = os.path.dirname(path)
filename, __ = QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder)
if not filename:
return
new_path = os.path.join(wallet_folder, filename)
if new_path == path:
return
try:
self.wallet.save_backup(new_path)
new_path = self.wallet.save_backup()
except BaseException as reason:
self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup"))
return
if new_path:
self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created"))
else:
self.show_message(_("You need to configure a backup directory in your preferences"), title=_("Backup not created"))
def update_recently_visited(self, filename):
recent = self.config.get('recently_open', [])
@ -617,7 +612,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
file_menu.addAction(_("&Save backup"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
file_menu.addAction(_("Delete"), self.remove_wallet)
file_menu.addSeparator()
file_menu.addAction(_("&Quit"), self.close)

15
electrum/gui/qt/settings_dialog.py

@ -145,6 +145,14 @@ class SettingsDialog(WindowModalDialog):
# lightning
lightning_widgets = []
backup_help = _("""A backup of your wallet file will be saved to that directory everytime you create a new channel. The backup cannot be used to perform lightning transactions; it may only be used to retrieve the funds in your open channels, using data loss protect (channels will be force closed).""")
backup_dir = self.config.get('backup_dir')
backup_dir_label = HelpLabel(_('Backup directory') + ':', backup_help)
self.backup_dir_e = QPushButton(backup_dir)
self.backup_dir_e.clicked.connect(self.select_backup_dir)
lightning_widgets.append((backup_dir_label, self.backup_dir_e))
help_persist = _("""If this option is checked, Electrum will persist as a daemon after
you close all your wallet windows. Your local watchtower will keep
running, and it will protect your channels even if your wallet is not
@ -546,6 +554,13 @@ that is always connected to the internet. Configure a port if you want it to be
if alias:
self.window.fetch_alias()
def select_backup_dir(self, b):
name = self.config.get('backup_dir', '')
dirname = QFileDialog.getExistingDirectory(self, "Select your SSL certificate file", name)
if dirname:
self.config.set_key('backup_dir', dirname)
self.backup_dir_e.setText(dirname)
def select_ssl_certfile(self, b):
name = self.config.get('ssl_certfile', '')
filename, __ = QFileDialog.getOpenFileName(self, "Select your SSL certificate file", name)

1
electrum/lnworker.py

@ -842,6 +842,7 @@ class LNWallet(LNWorker):
with self.lock:
self.channels[chan.channel_id] = chan
self.lnwatcher.add_channel(chan.funding_outpoint.to_str(), chan.get_funding_address())
self.wallet.save_backup()
@log_exceptions
async def add_peer(self, connect_str: str) -> Peer:

2
electrum/util.py

@ -442,7 +442,7 @@ def android_data_dir():
return PythonActivity.mActivity.getFilesDir().getPath() + '/data'
def get_backup_dir(config):
return android_backup_dir() if 'ANDROID_DATA' in os.environ else config.path
return android_backup_dir() if 'ANDROID_DATA' in os.environ else config.get('backup_dir')
def ensure_sparse_file(filename):

26
electrum/wallet.py

@ -51,7 +51,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler,
WalletFileException, BitcoinException, MultipleSpendMaxTxOutputs,
InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex)
from .util import PR_TYPE_ONCHAIN, PR_TYPE_LN
from .util import PR_TYPE_ONCHAIN, PR_TYPE_LN, get_backup_dir
from .simple_config import SimpleConfig
from .bitcoin import (COIN, is_address, address_to_script,
is_minikey, relayfee, dust_threshold)
@ -263,15 +263,25 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
if self.storage:
self.db.write(self.storage)
def save_backup(self, path, *, old_password=None, new_password=None):
def save_backup(self):
new_db = WalletDB(self.db.dump(), manual_upgrades=False)
new_db.put('is_backup', True)
new_storage = WalletStorage(path)
new_storage._encryption_version = StorageEncryptionVersion.PLAINTEXT
w2 = Wallet(new_db, new_storage, config=self.config)
if new_password:
w2.update_password(old_password, new_password, encrypt_storage=True)
w2.save_db()
new_path = os.path.join(get_backup_dir(self.config), self.basename() + '.backup')
if new_path is None:
return
new_storage = WalletStorage(new_path)
if 'ANDROID_DATA' in os.environ:
pin_code = self.config.get('pin_code')
w2 = Wallet(new_db, None, config=self.config)
w2.update_password(pin_code, None)
new_storage._encryption_version = StorageEncryptionVersion.USER_PASSWORD
new_storage.pubkey = self.config.get('backup_pubkey')
else:
new_storage._encryption_version = self.storage._encryption_version
new_storage.pubkey = self.storage.pubkey
new_db.set_modified(True)
new_db.write(new_storage)
return new_path
def has_lightning(self):
return bool(self.lnworker)

Loading…
Cancel
Save