-
Notifications
You must be signed in to change notification settings - Fork 2
/
001_setup_sd_card
executable file
·153 lines (130 loc) · 5.78 KB
/
001_setup_sd_card
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env python3
import argparse
import os
import sys
import subprocess
import pathlib
import time
BASH_EXECUTABLE = '/bin/bash'
def parseArgs():
parser = argparse.ArgumentParser(
description=("Setup new sd card for raspberry pi. Must be run as sudo. Images disk, sets up wifi, sets up " +
"ssh. Run from laptop."),
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('--disk-num', dest='disk_num', action='store', required = True,
help='Disk number via `diskutil list`. This disk will be overwritten, so don\'t get this wrong.')
parser.add_argument('--disk-image', dest='disk_image', action='store', required = True,
help='Path to disk image. Download Raspberry Pi OS Lite image from https://www.raspberrypi.org/software/operating-systems/')
parser.add_argument('--device-path', dest='device_path', action='store', default='/Volumes/bootfs',
help='Mounted volume path after SD card has been imaged.')
parser.add_argument('--user-name', dest='user_name', action='store', required = True,
help='User account to create')
parser.add_argument('--user-password', dest='user_password', action='store', required = True,
help='Password for the user account')
parser.add_argument('--wifi-network-name', dest='wifi_network_name', action='store', required = True,
help='Name of the network to join')
parser.add_argument('--wifi-password', dest='wifi_password', action='store', required = True,
help='Password of the network to join')
parser.add_argument('--wifi-country-code', dest='wifi_country_code', action='store', default='US',
help='Your ISO-3166-1 two-letter country code. See: ' +
'https://www.iso.org/obp/ui/#search')
if os.getuid() != 0:
print("This script must be run as sudo.\n", file = sys.stderr)
parser.print_help()
sys.exit(1)
args = parser.parse_args()
args.device_path = args.device_path.rstrip('/')
return args
def validateDependencies():
try:
import passlib
except ImportError:
print("The passlib package is required. Please install it: " +
"'sudo -H PIP_BREAK_SYSTEM_PACKAGES=1 python3 -m pip install --upgrade passlib'.", file = sys.stderr)
sys.exit(1)
# https://www.raspberrypi.org/documentation/installation/installing-images/mac.md
def imageSdCard():
print("Writing disk image, may take up to 15 minutes...")
command(f'diskutil unmountDisk /dev/disk{args.disk_num}')
command(f'sudo dd bs=1m if={args.disk_image} of=/dev/rdisk{args.disk_num}; sync')
time.sleep(5)
# Wait a few seconds for args.device_path to remount again (I guess this takes some time?).
# Need it to be remounted before writing wifi and ssh files.
time_limit = time.time() + 10
while True:
if os.path.isdir(args.device_path):
break
time.sleep(0.5)
if time.time() > time_limit:
print(f"Failed to remount {args.device_path}.", file = sys.stderr)
sys.exit(1)
# prevent weirdness where subsequent writes are flakey?
time.sleep(5)
# https://www.raspberrypi.com/documentation/computers/configuration.html#configuring-a-user
# https://www.raspberrypi.com/news/raspberry-pi-bullseye-update-april-2022/
def setupUserAccount():
print("Setting up user account...")
import passlib.hash
# Use fewer hashing rounds than the default of 656000. This makes password based login faster. In testing,
# it reduced the time to log in on a pi4 using a password from ~8s to ~1s. Furthermore, this is the number
# of rounds that would be used by default if following the raspberry pi official docs for generating
# passwords.
#
# See: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha512_crypt.html#passlib.hash.sha512_crypt
password_hash = passlib.hash.sha512_crypt.using(rounds=5000).hash(args.user_password)
f = None
try:
f = open(f"{args.device_path}/userconf.txt", "w")
except PermissionError:
print('This script must be run as sudo', file = sys.stderr)
sys.exit(1)
f.write(f'{args.user_name}:{password_hash}')
f.close()
# https://raspberrypi.stackexchange.com/a/57023
def setupWifi():
print("Setting up wifi...")
my_dir = pathlib.Path(__file__).parent.absolute()
command(f'sudo {my_dir}/wifi_config --wifi-network-name {args.wifi_network_name} ' +
f'--wifi-password {args.wifi_password} --wifi-country-code {args.wifi_country_code} ' +
f'--device-path {args.device_path}')
def setupSsh():
print("Enabling ssh...")
command(f'sudo touch {args.device_path}/ssh')
def validate():
try:
command(f'sudo ls -l {args.device_path}/cmdline.txt')
except Exception:
print("ERROR: failed to validate raspbian install", file = sys.stderr)
sys.exit(1)
try:
command(f'sudo ls -l {args.device_path}/ssh')
except Exception:
print("ERROR: failed to validate ssh setup", file = sys.stderr)
sys.exit(1)
try:
command(f'sudo ls -l {args.device_path}/wpa_supplicant.conf')
except Exception:
print("ERROR: failed to validate wifi setup", file = sys.stderr)
sys.exit(1)
def finish():
print("Ejecting SD card...")
command(f'sudo diskutil eject /dev/rdisk{args.disk_num}')
print(f"""
SD card setup was successful! Please insert the SD card into the raspberry pi and boot it up.
Your raspberry pi will be accessible via:
ssh {args.user_name}@raspberrypi.local
password: {args.user_password}
""")
def command(command):
return (subprocess
.check_output(command, shell = True, executable = BASH_EXECUTABLE)
.decode("utf-8"))
args = parseArgs()
validateDependencies()
imageSdCard()
setupUserAccount()
setupWifi()
setupSsh()
validate()
finish()