macOS: support autosign/automount for BTC

Testing:

    $ test/cmdtest.py autosign_clean autosign_automount autosign_btc
This commit is contained in:
The MMGen Project 2024-08-26 14:44:09 +00:00
commit a24eed0826
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
10 changed files with 117 additions and 9 deletions

View file

@ -325,6 +325,7 @@ class Autosign:
dev_label = 'MMGEN_TX'
linux_mount_subdir = 'mmgen_autosign'
macOS_ramdisk_name = 'AutosignRamDisk'
wallet_subdir = 'autosign'
mn_fmts = {
@ -374,6 +375,9 @@ class Autosign:
to a directory! Please create the mountpoint and add an entry
to your fstab as described in this scripts help text.
"""
elif sys.platform == 'darwin':
self.dfl_mountpoint = f'/Volumes/{self.dev_label}'
self.dfl_shm_dir = f'/Volumes/{self.macOS_ramdisk_name}'
self.cfg = cfg
@ -385,12 +389,18 @@ class Autosign:
if sys.platform == 'linux':
self.mount_cmd = f'mount {self.mountpoint}'
self.umount_cmd = f'umount {self.mountpoint}'
elif sys.platform == 'darwin':
self.mount_cmd = f'diskutil mount {self.dev_label}'
self.umount_cmd = f'diskutil eject {self.dev_label}'
self.init_fixup()
# these use the ‘fixed-up’ values:
if sys.platform == 'linux':
self.dev_label_path = self.dev_label_dir / self.dev_label
elif sys.platform == 'darwin':
from .platform.darwin.util import MacOSRamDisk
self.ramdisk = MacOSRamDisk(cfg, self.macOS_ramdisk_name, 10, path=self.shm_dir)
self.keyfile = self.mountpoint / 'autosign.key'
@ -621,6 +631,9 @@ class Autosign:
except:
die(2,f"Unable to create wallet directory '{self.wallet_dir}'")
if sys.platform == 'darwin':
self.ramdisk.create()
remove_wallet_dir()
create_wallet_dir()
self.gen_key(no_unmount=True)
@ -717,6 +730,8 @@ class Autosign:
return True
if sys.platform == 'linux':
return self.dev_label_path.exists()
elif sys.platform == 'darwin':
return self.mountpoint.exists()
async def main_loop(self):
if not self.cfg.stealth_led:

View file

@ -1 +1 @@
July 2024
August 2024

View file

@ -1 +1 @@
15.0.dev2
15.0.dev3

View file

@ -87,12 +87,13 @@ On supported platforms (currently Orange Pi, Rock Pi and Raspberry Pi boards),
the status LED indicates whether the program is busy or in standby mode, i.e.
ready for device insertion or removal.
The removable device must have a partition labeled MMGEN_TX with a user-
writable root directory.
The removable device must have a partition with a filesystem labeled MMGEN_TX
and a user-writable root directory. For interoperability between OS-es, its
recommended to use the exFAT file system.
On both the signing and online machines the mountpoint {asi.mountpoint}
(as currently configured) must exist and /etc/fstab must contain the
following entry:
(as currently configured) must exist. Linux (not macOS) machines must have
an /etc/fstab with the following entry:
LABEL=MMGEN_TX {asi.mountpoint} auto noauto,user 0 0
@ -118,7 +119,7 @@ file path to the end of the command line. Multiple temporary wallets may
be created in this way and used for signing (note, however, that for XMR
operations only one wallet is supported).
Autosigning is currently available only on Linux-based platforms.
Autosigning is currently supported on Linux and macOS only.
SECURITY NOTE

0
mmgen/platform/__init__.py Executable file
View file

View file

54
mmgen/platform/darwin/util.py Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen-wallet
# https://gitlab.com/mmgen/mmgen-wallet
"""
platform.darwin.util: utilities for the macOS platform
"""
from pathlib import Path
from subprocess import run, PIPE, DEVNULL
from ...color import cyan
from ...obj import MMGenLabel
class RamDiskLabel(MMGenLabel):
max_len = 24
desc = 'ramdisk label'
class MacOSRamDisk:
desc = 'macOS ramdisk'
def __init__(self, cfg, label, size_in_MB, path=None):
self.cfg = cfg
self.label = RamDiskLabel(label)
self.size_in_MB = size_in_MB
self.dfl_path = Path('/Volumes') / self.label
self.path = Path(path) if path else self.dfl_path
def create(self, quiet=False):
redir = DEVNULL if quiet else None
if self.path.exists():
self.cfg._util.qmsg('{} {} [{}] already exists'.format(self.desc, self.label.hl(), self.path))
return
cp = run(['hdiutil', 'attach', '-nomount', f'ram://{2048 * self.size_in_MB}'], stdout=PIPE, check=True)
self.dev_name = cp.stdout.decode().strip()
self.cfg._util.qmsg('{} {} [{}]'.format(cyan(f'Created {self.desc}'), self.label.hl(), self.dev_name))
run(['diskutil', 'eraseVolume', 'APFS', self.label, self.dev_name], stdout=redir, check=True)
if self.path != self.dfl_path:
run(['diskutil', 'umount', self.label], stdout=redir, check=True)
self.path.mkdir(parents=True, exist_ok=True)
run(['diskutil', 'mount', '-mountPoint', str(self.path.absolute()), self.label], stdout=redir, check=True)
def destroy(self, quiet=False):
redir = DEVNULL if quiet else None
run(['diskutil', 'eject', self.label], stdout=redir, check=True)
if not quiet:
self.cfg._util.qmsg('{} {} [{}]'.format(cyan(f'Destroyed {self.desc}'), self.label.hl(), self.path))

View file

@ -56,6 +56,8 @@ packages =
mmgen.contrib
mmgen.data
mmgen.help
mmgen.platform
mmgen.platform.darwin
mmgen.proto
mmgen.proto.bch
mmgen.proto.btc

View file

@ -20,7 +20,7 @@
test.cmdtest_py_d.ct_autosign: Autosign tests for the cmdtest.py test suite
"""
import sys, os, time, shutil
import sys, os, time, shutil, atexit
from subprocess import run,DEVNULL
from pathlib import Path
@ -53,7 +53,7 @@ class CmdTestAutosignBase(CmdTestBase):
networks = ('btc',)
tmpdir_nums = [18]
color = True
platform_skip = ('win32', 'darwin')
platform_skip = ('win32',)
threaded = False
daemon_coins = []
@ -72,6 +72,9 @@ class CmdTestAutosignBase(CmdTestBase):
if not (cfg.skipping_deps or self.live):
self._create_removable_device()
if sys.platform == 'darwin' and not cfg.no_daemon_stop:
atexit.register(self._macOS_eject_disk, self.asi.dev_label)
self.opts = ['--coins='+','.join(self.coins)]
if not self.live:
@ -83,6 +86,9 @@ class CmdTestAutosignBase(CmdTestBase):
def __del__(self):
if hasattr(self,'have_dummy_control_files'):
LEDControl.delete_dummy_control_files()
if sys.platform == 'darwin' and not cfg.no_daemon_stop:
for label in (self.asi.dev_label, self.asi.ramdisk.label):
self._macOS_eject_disk(label)
def _create_autosign_instances(self,create_dirs):
d = {'offline': {'name':'asi'}}
@ -105,7 +111,12 @@ class CmdTestAutosignBase(CmdTestBase):
for k in ('mountpoint', 'shm_dir', 'wallet_dir', 'dev_label_dir'):
if subdir == 'online' and k in ('shm_dir', 'wallet_dir'):
continue
if sys.platform == 'darwin' and k != 'mountpoint':
continue
getattr(asi, k).mkdir(parents=True, exist_ok=True)
if sys.platform == 'darwin' and k == 'mountpoint':
asi.mountpoint.rmdir()
continue
setattr(self, data['name'], asi)
@ -116,6 +127,20 @@ class CmdTestAutosignBase(CmdTestBase):
run(f'truncate --size=10M {img}'.split(), check=True)
run(f'/sbin/mkfs.ext2 -E root_owner={os.getuid()}:{os.getgid()} {img}'.split(),
stdout=redir, stderr=redir, check=True)
elif sys.platform == 'darwin':
redir = None if cfg.exact_output or cfg.verbose else DEVNULL
run(f'hdiutil create -size 10M -fs exFAT -volname {self.asi.dev_label} {img}'.split(),
stdout=redir, check=True)
def _macOS_mount_fs_image(self, loc):
time.sleep(0.2)
run(f'hdiutil attach -mountpoint {loc.mountpoint} {loc.fs_image_path}.dmg'.split(), stdout=DEVNULL, check=True)
def _macOS_eject_disk(self, label):
try:
run(['diskutil' ,'eject', label], stdout=DEVNULL, stderr=DEVNULL)
except:
pass
def start_daemons(self):
self.spawn('',msg_only=True)
@ -140,6 +165,9 @@ class CmdTestAutosignBase(CmdTestBase):
mn_desc = mn_type or 'default'
mn_type = mn_type or 'mmgen'
if sys.platform == 'darwin' and not cfg.no_daemon_stop:
self._macOS_eject_disk(self.asi.ramdisk.label)
self.insert_device()
t = self.spawn(
@ -181,6 +209,9 @@ class CmdTestAutosignBase(CmdTestBase):
t.read()
self.remove_device()
if sys.platform == 'darwin' and not cfg.no_daemon_stop:
atexit.register(self._macOS_eject_disk, self.asi.ramdisk.label)
return t
def insert_device(self, asi='asi'):
@ -189,6 +220,8 @@ class CmdTestAutosignBase(CmdTestBase):
loc = getattr(self, asi)
if sys.platform == 'linux':
loc.dev_label_path.touch()
elif sys.platform == 'darwin':
self._macOS_mount_fs_image(loc)
def remove_device(self, asi='asi'):
if self.live:
@ -197,6 +230,8 @@ class CmdTestAutosignBase(CmdTestBase):
if sys.platform == 'linux':
if loc.dev_label_path.exists():
loc.dev_label_path.unlink()
elif sys.platform == 'darwin':
loc.do_umount(silent=True)
def _mount_ops(self, loc, cmd, *args, **kwargs):
return getattr(getattr(self,loc),cmd)(*args, silent=self.silent_mount, **kwargs)

View file

@ -18,4 +18,5 @@ class overlay_fake_Autosign:
Autosign.dev_label = 'MMGEN_TS_TX'
Autosign.linux_mount_subdir = 'mmgen_ts_autosign'
Autosign.macOS_ramdisk_name = 'TestAutosignRamDisk'
Autosign.init_fixup = overlay_fake_Autosign.init_fixup