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
154
155
156
157
158
159
160
|
---
- name: load board pin preset
include_vars: "boards/{{repeater_board}}.yaml"
- name: load RF region preset
include_vars: "rf/{{repeater_rf_preset}}.yaml"
- name: enable SPI for the LoRa HAT
become: yes
lineinfile:
path: "/boot/firmware/config.txt"
line: "dtparam=spi=on"
regexp: "^#?dtparam=spi="
register: spi_result
- name: remind to reboot for SPI to take effect
debug:
msg: "config.txt updated — reboot {{inventory_hostname}} for dtparam=spi=on to take effect"
when: spi_result.changed
- name: apt install openhop repeater dependencies
become: yes
apt:
name:
- "git"
- "python3-venv"
- "python3-dev"
- "build-essential"
# Raspberry Pi OS ships the spi/gpio groups and udev rules via
# raspberrypi-sys-mods; Ubuntu images have neither, so create them here.
- name: create spi and gpio device access groups
become: yes
group:
name: "{{item}}"
system: "yes"
loop:
- "spi"
- "gpio"
- name: grant spi and gpio groups access to their devices
become: yes
copy:
dest: "/etc/udev/rules.d/90-spi-gpio-groups.rules"
content: |
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
mode: "0644"
register: udev_result
- name: apply udev rules to existing devices
become: yes
shell: "udevadm control --reload-rules && udevadm trigger --subsystem-match=spidev --subsystem-match=gpio"
when: udev_result.changed
- name: add openhop repeater user
become: yes
user:
name: "{{repeater_user}}"
system: "yes"
shell: "/bin/false"
home: "{{repeater_data_dir}}"
create_home: "no"
groups: "spi,gpio"
append: "yes"
- name: create openhop repeater directories
become: yes
file:
path: "{{item.path}}"
state: "directory"
owner: "{{item.owner}}"
group: "{{item.owner}}"
mode: "{{item.mode}}"
loop:
- { path: "/opt/openhop_repeater", owner: "root", mode: "0755" }
- { path: "{{repeater_config_dir}}", owner: "root", mode: "0755" }
- { path: "{{repeater_data_dir}}", owner: "{{repeater_user}}", mode: "0750" }
- name: check installed openhop_repeater version
become: yes
stat:
path: "{{repeater_venv_dir}}/.installed-{{repeater_version}}"
register: repeater_installed
- name: install openhop_repeater into venv
become: yes
pip:
name: "git+{{repeater_repo}}@{{repeater_version}}"
virtualenv: "{{repeater_venv_dir}}"
virtualenv_command: "python3 -m venv"
when: not repeater_installed.stat.exists
notify: restart openhop-repeater
# The pip package does not ship these repo-root files, but the web UI's setup
# wizard reads them from the storage dir; without them the hardware step lists
# no options and cannot proceed.
- name: install web UI radio settings and presets
become: yes
get_url:
url: "{{repeater_repo}}/raw/{{repeater_version}}/{{item}}"
dest: "{{repeater_data_dir}}/{{item}}"
owner: "{{repeater_user}}"
group: "{{repeater_user}}"
mode: "0644"
force: "{{not repeater_installed.stat.exists}}"
loop:
- "radio-settings.json"
- "radio-presets.json"
- name: stamp installed openhop_repeater version
become: yes
copy:
dest: "{{repeater_venv_dir}}/.installed-{{repeater_version}}"
content: "{{repeater_version}}\n"
mode: "0644"
when: not repeater_installed.stat.exists
# Group-writable so the daemon can persist its generated jwt_secret and the
# setup-wizard credentials back into the config. Note the trade-off: every
# re-template wipes those daemon-written keys and restarts the service.
- name: configure openhop repeater
become: yes
template:
src: "config.yaml.j2"
dest: "{{repeater_config_dir}}/config.yaml"
owner: "root"
group: "{{repeater_user}}"
mode: "0660"
notify: restart openhop-repeater
# The web UI restarts the daemon after config changes via
# "sudo --non-interactive systemctl restart openhop-repeater"; the daemon's
# code expects exactly this sudoers file to exist.
- name: allow repeater user to restart its own service
become: yes
copy:
dest: "/etc/sudoers.d/openhop-repeater"
content: |
{{repeater_user}} ALL=(root) NOPASSWD: /usr/bin/systemctl restart openhop-repeater
{{repeater_user}} ALL=(root) NOPASSWD: /usr/bin/systemctl restart openhop-repeater.service
mode: "0440"
validate: "visudo -cf %s"
- name: install openhop-repeater systemd service
become: yes
template:
src: "openhop-repeater.service.j2"
dest: "/lib/systemd/system/openhop-repeater.service"
mode: "0644"
notify: restart openhop-repeater
- name: ensure openhop-repeater service is started
become: yes
systemd:
name: "openhop-repeater.service"
enabled: "yes"
state: "started"
daemon_reload: "yes"
|