Skip to content

Commit 7ef9e7a

Browse files
committed
Deployable PyInstaller app
o code signing not yet recognized as valid
1 parent 02e297d commit 7ef9e7a

File tree

2 files changed

+62
-31
lines changed

2 files changed

+62
-31
lines changed

GNUmakefile

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
# GNU 'make' file
33
#
44

5+
# Change to your own Apple Developer ID, if you want to code-sign the resultant .app
6+
DEVID ?= Developer ID Application: Perry Kundert (ZD8TVTCXDS)
7+
#DEVID ?= 3rd Party Mac Developer Application: Perry Kundert (ZD8TVTCXDS)
8+
BUNDLEID ?= ca.kundert.perry.SLIP39
9+
510
# PY[3] is the target Python interpreter. It must have pytest installed.
611
PY3 ?= python3
712

@@ -47,7 +52,7 @@ build-check:
4752
|| ( echo "\n*** Missing Python modules; run:\n\n $(PY3) -m pip install --upgrade pip setuptools wheel build\n" \
4853
&& false )
4954

50-
build: wheel app
55+
build: clean wheel app
5156

5257
wheel: dist/slip39-$(VERSION)-py3-none-any.whl
5358

@@ -60,16 +65,25 @@ install: dist/slip39-$(VERSION)-py3-none-any.whl FORCE
6065
$(PY3) -m pip install --force-reinstall $^[gui,serial,json]
6166

6267
# Generate, Sign and Zip the App package TODO: Code signing w/ Apple Developer ID
63-
app: dist/SLIP39.app.zip
68+
app: dist/SLIP39.app-$(VERSION).zip
6469

65-
dist/SLIP39.app.zip: dist/SLIP39.app
70+
#(cd dist; zip -r SLIP39.app-$(VERSION).zip SLIP39.app)
71+
# Create a ZIP archive suitable for notarization.
72+
dist/SLIP39.app-$(VERSION).zip: dist/SLIP39.app
6673
rm -f $@
67-
(cd dist; zip -r SLIP39.app.zip SLIP39.app)
74+
/usr/bin/ditto -c -k --keepParent "$(PWD)/$<" "$(PWD)/$@"
6875
@ls -last dist
6976

77+
# Rebuild the app; ensure we discard any partial/prior build and app artifacts
78+
# --codesign-identity "$(DEVID)" # Nope; must change CFBundleShottVersionString before signing? Also different team IDs?
7079
dist/SLIP39.app: SLIP39.py FORCE
71-
pyinstaller --noconfirm --windowed --onefile --collect-data shamir_mnemonic $<
72-
80+
rm -rf build $@*
81+
pyinstaller --noconfirm --windowed --onefile \
82+
--osx-bundle-identifier "$(BUNDLEID)" \
83+
--collect-data shamir_mnemonic $<
84+
sed -i "" -e "s/0.0.0/$(VERSION)/" "$@/Contents/Info.plist"
85+
cat $@/Contents/Info.plist
86+
codesign --force -s "$(DEVID)" $@
7387

7488
# Support uploading a new version of slip32 to pypi. Must:
7589
# o advance __version__ number in slip32/version.py

slip39/App/main.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,19 @@ def groups_layout( names, group_threshold, groups, passphrase=None ):
6868
[
6969
sg.Text( "Seed File Name(s): ", size=prefix, **T_kwds ),
7070
sg.Input( f"{', '.join( names )}", key='-NAMES-', **I_kwds ),
71-
sg.Text( "(comma-separted)", **T_kwds ),
71+
sg.Text( "(optional; comma-separated)", **T_kwds ),
7272
]
7373
] + [
7474
[
7575
sg.Text( "Requires recovery of at least: ", size=prefix, **T_kwds ),
7676
sg.Input( f"{group_threshold}", key='-THRESHOLD-', **I_kwds ),
77-
sg.Text( f"of {len(groups)} SLIP-39 Recovery Groups", key='-RECOVERY-', **T_kwds ),
77+
sg.Text( f"(of {len(groups)} SLIP-39 Recovery Groups)", key='-RECOVERY-', **T_kwds ),
7878
],
7979
] + [
8080
[
8181
sg.Text( "Passphrase to encrypt Seed: ", size=prefix, **T_kwds ),
8282
sg.Input( f"{passphrase or ''}", key='-PASSPHRASE-', **I_kwds ),
83+
sg.Text( "(optional; must be remembered separately!!)", **T_kwds ),
8384
],
8485
] + [
8586
[
@@ -144,29 +145,21 @@ def app(
144145
window = None
145146
status = None
146147
event = False
147-
while event not in (sg.WIN_CLOSED, 'Exit',):
148+
events_termination = (sg.WIN_CLOSED, 'Exit',)
149+
while event not in events_termination:
148150
# Create window (for initial window.read()), or update status
149151
if window:
150152
window['-STATUS-'].update( status or 'OK' )
151-
window['-RECOVERY-'].update( f"of {len(groups)} SLIP-39 Recovery Groups", **T_kwds ),
153+
window['-RECOVERY-'].update( f"(of {len(groups)} SLIP-39 Recovery Groups)", **T_kwds ),
152154
else:
153-
window = sg.Window( f"{', '.join( names )} Mnemonic Cards", layout )
155+
window = sg.Window( f"{', '.join( names or [ 'SLIP39' ] )} Mnemonic Cards", layout )
154156

155157
status = None
156158
event, values = window.read()
157159
logging.info( f"{event}, {values}" )
158-
if not values:
160+
if not values or event in events_termination:
159161
continue
160162

161-
if '-TARGET-' in values:
162-
# A target directory has been selected;
163-
try:
164-
os.chdir( values['-TARGET-'] )
165-
except Exception as exc:
166-
status = f"Error changing to target directory {values['-TARGET-']}: {exc}"
167-
logging.exception( f"{status}" )
168-
continue
169-
170163
if event == '+':
171164
g = len(groups)
172165
name = f"Group {g+1}"
@@ -177,15 +170,17 @@ def app(
177170
window.extend_layout( window['-GROUP-NEEDS-'], [[ sg.Input( f"{needs[0]}", key=f"-G-NEED-{g}", **I_kwds ) ]] ) # noqa: 241
178171
window.extend_layout( window['-GROUP-SIZES-'], [[ sg.Input( f"{needs[1]}", key=f"-G-SIZE-{g}", **I_kwds ) ]] ) # noqa: 241
179172

173+
# A target directory must be selected; use it. This is where any output will be written.
174+
# It should usually be a removable volume, but we do not check for this.
180175
try:
181-
g_thr_val = values['-THRESHOLD-']
182-
g_thr = int( g_thr_val )
176+
os.chdir( values['-TARGET-'] )
183177
except Exception as exc:
184-
status = f"Error defining group threshold {g_thr_val}: {exc}"
178+
status = f"Error changing to target directory {values['-TARGET-']!r}: {exc}"
185179
logging.exception( f"{status}" )
186180
continue
187181

188-
# Collect up the specified Group names; ignores groups with an empty name
182+
# Collect up the specified Group names; ignores groups with an empty name (effectively
183+
# eliminating that group)
189184
g_rec = {}
190185
status = None
191186
for g in range( 16 ):
@@ -203,20 +198,34 @@ def app(
203198
logging.exception( f"{status}" )
204199
continue
205200

201+
# Confirm the selected Group Threshold requirement
202+
try:
203+
g_thr_val = values['-THRESHOLD-']
204+
g_thr = int( g_thr_val )
205+
assert 0 < g_thr <= len( g_rec ), \
206+
f"must be an integer between 1 and the number of groups ({len(g_rec)})"
207+
except Exception as exc:
208+
status = f"Error defining group threshold {g_thr_val!r}: {exc}"
209+
logging.exception( f"{status}" )
210+
continue
211+
206212
summary = f"Require {g_thr}/{len(g_rec)} Groups, from: {f', '.join( f'{n}({need}/{size})' for n,(need,size) in g_rec.items())}"
207213
passphrase = values['-PASSPHRASE-'].strip()
208214
if passphrase:
209215
summary += f", decrypted w/ passphrase {passphrase!r}"
210216
window['-SUMMARY-'].update( summary )
211217

218+
# Deduce the desired Seed names, defaulting to "SLIP39"
212219
names = [
213220
name.strip()
214-
for name in values['-NAMES-'].split( ',' )
221+
for name in ( values['-NAMES-'].strip() or "SLIP39" ).split( ',' )
215222
if name and name.strip()
216223
]
224+
225+
# Compute the SLIP39 Seed details
217226
details = {}
218227
try:
219-
for name in names or [ "SLIP39" ]:
228+
for name in names:
220229
details[name] = create(
221230
name = name,
222231
group_threshold = group_threshold,
@@ -229,12 +238,20 @@ def app(
229238
logging.exception( f"{status}" )
230239
continue
231240

232-
# If we get here, no failure status has been detected; we could save (details is now { "<filename>": <details> })
241+
# If we get here, no failure status has been detected, and SLIP39 mnemonic and account
242+
# details { "name": <details> } have been created; we can now save the PDFs; converted
243+
# details is now { "<filename>": <details> })
233244
if event == 'Save':
234-
details = write_pdfs(
235-
names = details,
236-
)
245+
try:
246+
details = write_pdfs(
247+
names = details,
248+
)
249+
except Exception as exc:
250+
status = f"Error saving PDF(s): {exc}"
251+
logging.exception( f"{status}" )
252+
continue
237253

254+
# Finally, if all has gone well -- display the resultant <name>/<filename>, and some derived account details
238255
name_len = max( len( name ) for name in details )
239256
status = '\n'.join(
240257
f"{name:>{name_len}} == " + ', '.join( f'{a.crypto} @ {a.path}: {a.address}' for a in details[name].accounts[0] )

0 commit comments

Comments
 (0)