summaryrefslogtreecommitdiff
path: root/roles/daq-node/deploy
blob: 1fa1a83fa77ac644dd390c615bd309fb363fefed (plain)
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env bash

set -eEuo pipefail

readonly REPO_DIR="/opt/fapg/fapg-daq"
readonly NODE_BIN="${REPO_DIR}/roles/sensor-node/bin/fapg-daq-node"

readonly SERVICE_NAME="fapg-daq-node.service"
readonly SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}"

readonly ENV_DIR="/etc/fapg-daq"
readonly ENV_FILE="${ENV_DIR}/daq.conf"

readonly STATE_DIR="/var/lib/fapg-daq"
readonly RUN_DIR="/run/fapg-daq"

readonly RUN_USER='fapg-daq'
readonly RUN_GROUP='fapg-daq'

readonly SERIAL_DEVICE='/dev/ttyUSB0'
readonly READ_INTERVAL=5
readonly MQTT_HOST='fapg-daq-five-01'
readonly MQTT_PORT='1883'
readonly MQTT_USERNAME='fapg_zero'
MQTT_PASSWORD=''

function die() {
    echo "ERROR: $*" >&2
    exit 1
}

function require_root() {
    if [[ "$(id -u)" -ne 0 ]]; then
        die "$0 requires superuser privileges."
    fi
}

function install_os_dependencies() {
    local dependencies='perl libnet-mqtt-simple-perl libdevice-serialport-perl'
    local missing_dependencies=''

    for dep in ${dependencies}; do
	if dpkg --get-selections "${dep}" | grep -w install > /dev/null 2>&1; then
	    echo "${dep} is already installed."
	else
	    echo "${dep} is not installed."
	    missing_dependencies+=" ${dep}"
	fi
    done

    if [[ -n "${missing_dependencies}" ]]; then
	apt-get install -y ${missing_dependencies}
    fi

    echo "Installed all OS dependencies."
}

# function install_perl_dependencies() {
#     local dependencies="Net::MQTT::Simple"
#     for dep in ${dependencies}; do
# 	if ! perl -M"${dep}" -e '1' > /dev/null 2>&1; then
#             cpanm "${dep}"
# 	fi
#     done

#     echo "Installed all Perl dependencies."
# }

create_runtime_user() {
    if ! getent group "${RUN_GROUP}" > /dev/null; then
        addgroup --system "${RUN_GROUP}"
    fi

    if ! id "${RUN_USER}" > /dev/null 2>&1; then
        adduser \
            --system \
            --ingroup "${RUN_GROUP}" \
            --home "${STATE_DIR}" \
            --no-create-home \
            --disabled-login \
            "${RUN_USER}"
    fi

    # USB serial devices are normally accessible to members of dialout.
    usermod -aG dialout "${RUN_USER}"

    echo "Created runtime user ${RUN_USER}, group ${RUN_GROUP}."
}

check_repo_layout() {
    [[ -d "${REPO_DIR}" ]] || die "Repo not found at ${REPO_DIR}."
    [[ -x "${NODE_BIN}" ]] || die "${NODE_BIN} does not exist or is not executable."

    mkdir -p "${STATE_DIR}" "${RUN_DIR}"
    chown "${RUN_USER}:${RUN_GROUP}" "${STATE_DIR}" "${RUN_DIR}"

    echo "Verified layout of repo at ${REPO_DIR}."
}

function write_env_file() {
    mkdir -p "${ENV_DIR}"

    # TODO: print diff of existing and proposed env file
    if [[ -f "${ENV_FILE}" ]]; then
        echo "${ENV_FILE} already exists, remove or edit manually if necessary."
        return
    fi

    if [[ -z "${MQTT_PASSWORD:-}" ]]; then
        read -rsp "MQTT password for user '${MQTT_USERNAME}': " MQTT_PASSWORD
    fi

    [[ -n "${MQTT_PASSWORD:-}" ]] || die "MQTT_PASSWORD is required."

    # TODO: unsafe!
    cat > "${ENV_FILE}" <<EOF
MQTT_SIMPLE_ALLOW_INSECURE_LOGIN=1

SERIAL_DEVICE="${SERIAL_DEVICE}"
READ_INTERVAL=${READ_INTERVAL}
MQTT_HOST="${MQTT_HOST}"
MQTT_PORT="${MQTT_PORT}"
MQTT_USERNAME="${MQTT_USERNAME}"
MQTT_PASSWORD="${MQTT_PASSWORD}"
EOF

    chown root:"${RUN_GROUP}" "${ENV_FILE}"
    chmod 0640 "${ENV_FILE}"
}

function write_systemd_service() {
    cat > "${SERVICE_FILE}" <<EOF
[Unit]
Description=FAPG DAQ sensor node
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=${RUN_USER}
Group=${RUN_GROUP}
SupplementaryGroups=dialout

WorkingDirectory=${REPO_DIR}
EnvironmentFile=${ENV_FILE}

ExecStart=${NODE_BIN}

Restart=always
RestartSec=5

# Basic hardening. Do not enable PrivateDevices=true because this service needs /dev/ttyUSB0.
NoNewPrivileges=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=${STATE_DIR} ${RUN_DIR}

[Install]
WantedBy=multi-user.target
EOF

    chmod 0644 "$SERVICE_FILE"
}

function enable_service() {
    systemctl daemon-reload
    systemctl enable --now "${SERVICE_NAME}"
}

show_status() {
    cat <<"EOF"
Deployment complete.
Current service state:
EOF
    systemctl --no-pager --full status "${SERVICE_NAME}"
}

function main() {
    require_root
    install_os_dependencies
    # install_perl_dependencies
    create_runtime_user
    check_repo_layout
    write_env_file
    write_systemd_service
    enable_service
    show_status
}

main "$@"
Copyright 2019--2026 Marius PETER