Install on Linux

This page installs Mugnsoft components as Linux services (systemd, Upstart or SysV are detected automatically). One script does everything for a component: download, signature check, configuration, service installation, registration with the Webserver and start. The manual steps it automates are listed below it.

Before you start

  • A 64-bit Linux host (Debian, Ubuntu, RHEL and derivatives, see requirements), and a root shell (sudo -i).
  • curl (or wget) and gpg:

# Debian / Ubuntu
apt-get install -y curl gnupg
# RHEL / Rocky / Alma / CentOS / Fedora
dnf install -y curl gnupg2
  • The network flows below open in your firewalls. Every component listens on its own API port, and the Webserver connects back to it:
From To Port (default) Why
Users’ browsers Webserver TCP 9090 (PortWEB) Web UI
Monitors, Integrators, Sentinel Agents Webserver TCP 8050 (PortAPI) Registration and data push
Webserver each Monitor / Integrator / Sentinel Agent its port (packages: monitor 8072, integrator 8060, sentinel 8078) The Webserver drives the component
  • Install the Webserver first: every other component needs two values it produces.
  • One component of each kind per host: the service names are fixed (MugnsoftWebserver, MugnsoftMonitor, MugnsoftIntegrator, MugnsoftDiscoveryAgent, MugnsoftWebserverFront).

Your values

The commands on this page use these example values. Replace them with yours; everything else is copied as-is.

Value Where it comes from Example (replace it!)
Webserver address IP or DNS name of the Webserver host, as the other hosts reach it 10.0.0.5
Webserver API port PortAPI of webserver.json (default) 8050
_sharedInternal Printed at the end of the Webserver installation (step 1), also in webserver.json 3f9c...e1 (64 hex characters)
License file license_MNS.dat sent by Mugnsoft, one per Webserver and per Integrator, bound to its host ./license_MNS.dat
Location Free text shown on the Webserver (monitor, sentinel) Paris

Download mugnsoft-install.sh onto each host, then run, as root, the line for the component that host gets:


curl -fsSLO https://doc.mugnsoft.com/materials/mugnsoft-install.sh

1. Webserver (first). Put your license_MNS.dat next to the script, then:


bash mugnsoft-install.sh webserver --license ./license_MNS.dat

The end of its output gives what the other hosts need, for example:


Webserver installed in /opt/mugnsoft/webserver
  Web UI:   https://<this host>:9090/   (admin / admin, change it at first login)
  API:      <this host>:8050   -> the --webserver value for the components
  _sharedInternal (pass it to every component as --shared-internal):
    3f9c...e1

2. Monitor probe, Integrator, Sentinel Agent, each on its host, with your Webserver address and _sharedInternal:


# monitor probe
bash mugnsoft-install.sh monitor    --webserver 10.0.0.5:8050 --shared-internal 3f9c...e1 --location Paris

# Integrator (it has its own license file)
bash mugnsoft-install.sh integrator --webserver 10.0.0.5:8050 --shared-internal 3f9c...e1 --license ./license_MNS.dat

# Sentinel Agent
bash mugnsoft-install.sh sentinel   --webserver 10.0.0.5:8050 --shared-internal 3f9c...e1 --location Paris

Each run ends with ... installed ... and registered with 10.0.0.5:8050. Then enable the component.

What the script does, in order, stopping at the first problem with a message saying what to fix:

  1. Checks the arguments, that it runs as root and that the install folder does not exist yet (it never overwrites an installation).
  2. Downloads mugnsoft-<component>-<version>.linux-amd64.tar.gz and its .sig from https://mugnsoft.com/bin/<version>/, and verifies the signature with Mugnsoft’s public GPG key (in a throw-away keyring).
  3. Extracts it to /opt/mugnsoft/<component folder> and makes the programs executable.
  4. Writes the values into the component’s .json file (and copies the license file).
  5. Runs <component> install: a monitor, Integrator or Sentinel Agent registers with the Webserver at this point, and the service is only created if that succeeds.
  6. Starts the service. The service is enabled at boot.
Option Default Use
--version <x.y.z> 4.2.0 Release to install
--dir <path> /opt/mugnsoft Parent folder of the installation
--package <file> download Install a package you already have, .tar.gz or .zip (its .sig must sit next to it)
--no-verify off Skip the signature check (not recommended)
--name <name> <component>-<short hostname> Component name on the Webserver: letters, digits, hyphens
--port <port> the package’s Component API port
--advertise <ip> auto-detected Monitor only: the IP the Webserver must dial to reach the probe (see Check the address)
--webserver-url <url> https://<webserver host>:9090 Integrator only: Web UI link used in notifications
--api-port, --web-port 8050, 9090 Webserver only
--front-pki <dir> — Webserver-Front only, see Install the Webserver-Front

bash mugnsoft-install.sh --help prints the same list.

#!/usr/bin/env bash
# mugnsoft-install.sh - download, verify, configure, install and start one Mugnsoft
# component as a Linux service (systemd, Upstart or SysV), in one command.
#
# Usage (as root):
#   bash mugnsoft-install.sh <component> [options]
#
# Components:
#   webserver        the Webserver (needs --license)
#   monitor          a monitor probe
#   integrator       an Integrator (needs --license)
#   sentinel         a Sentinel Agent (discovery_agent)
#   webserver-front  a Webserver-Front (needs --front-pki)
#
# Options:
#   --version <x.y.z>          release to install (default 4.2.0)
#   --dir <path>               parent folder (default /opt/mugnsoft); the component
#                              is installed in <path>/<package folder>
#   --package <file>           install this package (.tar.gz or .zip) instead of downloading it
#   --no-verify                skip the GPG signature check (not recommended)
#   webserver, integrator:
#   --license <file>           the license_MNS.dat received from Mugnsoft
#   webserver:
#   --api-port <port>          API port (default 8050)
#   --web-port <port>          web UI port (default 9090)
#   monitor, integrator, sentinel:
#   --webserver <host:port>    the Webserver API address (host:API port, e.g. 10.0.0.5:8050)
#   --shared-internal <value>  the "_sharedInternal" value of the Webserver's webserver.json
#   --name <name>              component name (default <component>-<short hostname>)
#   --port <port>              component API port (default: the package's)
#   --location <text>          location shown on the Webserver (monitor, sentinel)
#   --advertise <ip>           monitor only: IP address the Webserver must use to
#                              reach this probe (default: auto-detected)
#   --webserver-url <url>      integrator only: Webserver web UI URL used in
#                              notifications (default https://<webserver host>:9090)
#   webserver-front:
#   --front-pki <dir>          the front/ folder produced by mugnsoft-front-pki.sh
#
# Examples (replace the values with yours):
#   bash mugnsoft-install.sh webserver --license ./license_MNS.dat
#   bash mugnsoft-install.sh monitor --webserver 10.0.0.5:8050 --shared-internal 3f9c...e1 --location Paris

set -euo pipefail

VERSION=4.2.0 DIR=/opt/mugnsoft PACKAGE="" VERIFY=1
LICENSE="" API_PORT="" WEB_PORT="" WEBSERVER="" SHARED="" NAME="" PORT="" LOCATION=""
ADVERTISE="" WEBSERVER_URL="" FRONT_PKI=""
BASE_URL="https://mugnsoft.com/bin"

die()  { echo "ERROR: $*" >&2; exit 1; }
info() { echo "==> $*"; }
usage() { sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }
fetch() { if command -v curl >/dev/null; then curl -fsSL -o "$2" "$1"; else wget -q -O "$2" "$1"; fi; }

[ $# -ge 1 ] || usage 1
COMPONENT="$1"; shift
case "$COMPONENT" in -h|--help) usage 0 ;; esac
while [ $# -gt 0 ]; do
  [ $# -ge 2 ] || [ "$1" = "--no-verify" ] || die "option $1 needs a value"
  case "$1" in
    --version) VERSION="$2" ;;          --dir) DIR="$2" ;;
    --package) PACKAGE="$2" ;;          --no-verify) VERIFY=0; shift; continue ;;
    --license) LICENSE="$2" ;;          --api-port) API_PORT="$2" ;;
    --web-port) WEB_PORT="$2" ;;        --webserver) WEBSERVER="$2" ;;
    --shared-internal) SHARED="$2" ;;   --name) NAME="$2" ;;
    --port) PORT="$2" ;;                --location) LOCATION="$2" ;;
    --advertise) ADVERTISE="$2" ;;      --webserver-url) WEBSERVER_URL="$2" ;;
    --front-pki) FRONT_PKI="$2" ;;
    *) die "unknown option: $1 (see --help)" ;;
  esac
  shift 2
done

# component -> package name, folder, binary, config file
case "$COMPONENT" in
  webserver)       PKG=webserver;        BIN=webserver ;;
  webserver-front) PKG=webserver-front;  BIN=webserver-front ;;
  monitor)         PKG=monitor;          BIN=monitor ;;
  integrator)      PKG=integrator;       BIN=integrator ;;
  sentinel|discovery_agent) COMPONENT=sentinel; PKG=discovery_agent; BIN=discovery_agent ;;
  *) die "unknown component '$COMPONENT' (webserver, monitor, integrator, sentinel, webserver-front)" ;;
esac
CONF="$BIN.json"
TARGET="$DIR/$PKG"

# ---- checks, before anything is written -------------------------------------
[ "$(id -u)" -eq 0 ] || die "run as root (sudo bash $0 ...)"
for c in tar gzip sed grep; do command -v "$c" >/dev/null || die "'$c' is required: install it first"; done
num() { [[ "$2" =~ ^[0-9]+$ ]] && [ "$2" -ge 1 ] && [ "$2" -le 65535 ] || die "$1 must be a port number, got '$2'"; }
safe() { [[ "$2" =~ ^[A-Za-z0-9._:/@+=,\ -]*$ ]] || die "$1 contains characters that are not allowed: '$2'"; }
[ -n "$API_PORT" ] && num --api-port "$API_PORT"
[ -n "$WEB_PORT" ] && num --web-port "$WEB_PORT"
[ -n "$PORT" ] && num --port "$PORT"
case "$COMPONENT" in
  webserver|integrator)
    [ -n "$LICENSE" ] || die "--license <license_MNS.dat> is required for the $COMPONENT"
    [ -f "$LICENSE" ] || die "license file not found: $LICENSE" ;;
esac
case "$COMPONENT" in
  monitor|integrator|sentinel)
    [ -n "$WEBSERVER" ] || die "--webserver <host:port> is required (the Webserver API address)"
    [[ "$WEBSERVER" =~ ^[A-Za-z0-9.-]+:[0-9]+$ ]] || die "--webserver must look like host:port, e.g. 10.0.0.5:8050"
    [ -n "$SHARED" ] || die "--shared-internal is required: copy \"_sharedInternal\" from the Webserver's webserver.json"
    [[ "$SHARED" =~ ^[A-Za-z0-9._~!#%^*+=-]+$ ]] || die "--shared-internal holds unexpected characters"
    NAME="${NAME:-$COMPONENT-$(hostname -s)}"
    [[ "$NAME" =~ ^[A-Za-z0-9-]+$ ]] || die "--name may hold letters, digits and hyphens only: '$NAME'"
    safe --location "$LOCATION"; safe --advertise "$ADVERTISE"; safe --webserver-url "$WEBSERVER_URL" ;;
  webserver-front)
    [ -n "$FRONT_PKI" ] || die "--front-pki <dir> is required: the front/ folder made by mugnsoft-front-pki.sh"
    [ -f "$FRONT_PKI/config/ssl/certificates/webserver.pem" ] && [ -f "$FRONT_PKI/webserver-front.json" ] \
      || die "$FRONT_PKI does not look like the front/ folder of mugnsoft-front-pki.sh" ;;
esac
[ -e "$TARGET" ] && die "$TARGET already exists: this script installs, it does not upgrade (see the Upgrade section of the docs)"

WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT

# ---- 1. get the package -----------------------------------------------------
FILE="mugnsoft-$PKG-$VERSION.linux-amd64.tar.gz"
if [ -z "$PACKAGE" ]; then
  info "Downloading $FILE"
  fetch "$BASE_URL/$VERSION/$FILE" "$WORK/$FILE" || die "download failed: $BASE_URL/$VERSION/$FILE"
  [ "$VERIFY" -eq 1 ] && { fetch "$BASE_URL/$VERSION/$FILE.sig" "$WORK/$FILE.sig" || die "signature download failed"; }
  PACKAGE="$WORK/$FILE"
else
  [ -f "$PACKAGE" ] || die "package not found: $PACKAGE"
  [ "$VERIFY" -eq 1 ] && { [ -f "$PACKAGE.sig" ] || die "no $PACKAGE.sig next to the package: download it too, or pass --no-verify"; cp "$PACKAGE.sig" "$WORK/pkg.sig"; }
fi

# ---- 2. verify its signature -------------------------------------------------
if [ "$VERIFY" -eq 1 ]; then
  command -v gpg >/dev/null || die "'gpg' is required to verify the package (or pass --no-verify)"
  info "Verifying the GPG signature"
  SIG="$PACKAGE.sig"; [ -f "$WORK/pkg.sig" ] && SIG="$WORK/pkg.sig"
  export GNUPGHOME="$WORK/gnupg"; mkdir -m 700 "$GNUPGHOME"
  fetch "$BASE_URL/Mugnsoft_public.key" "$WORK/key" || die "could not download Mugnsoft_public.key"
  gpg --batch --quiet --import "$WORK/key" 2>/dev/null
  gpg --batch --verify "$SIG" "$PACKAGE" 2>/dev/null || die "SIGNATURE CHECK FAILED for $PACKAGE: do not install it"
  info "Signature OK"
else
  echo "WARNING: signature not verified (--no-verify)" >&2
fi

# ---- 3. extract ---------------------------------------------------------------
info "Extracting to $TARGET"
mkdir -p "$DIR"
mkdir "$WORK/x"
case "$PACKAGE" in
  *.zip) command -v unzip >/dev/null || die "'unzip' is required for a .zip package"; unzip -q "$PACKAGE" -d "$WORK/x" ;;
  *)     tar -xzf "$PACKAGE" -C "$WORK/x" ;;
esac
[ -d "$WORK/x/$PKG" ] || die "unexpected package layout: no $PKG/ folder inside"
mv "$WORK/x/$PKG" "$TARGET"
cd "$TARGET"
chmod +x "./$BIN"
[ -d exec ] && find exec -maxdepth 1 -type f \( -name ffmpeg -o -name harx \) -exec chmod +x {} +
[ -d exec/jdk/bin ] && chmod +x exec/jdk/bin/*
[ -f "$CONF" ] || die "$CONF missing from the package"

# set "key": "value" in a flat JSON file, adding the key when it is absent
setkey() {
  local key="$1" val="$2"
  if grep -q "\"$key\"[[:space:]]*:" "$CONF"; then
    sed -i -E "s|(\"$key\"[[:space:]]*:[[:space:]]*)\"[^\"]*\"|\1\"$val\"|" "$CONF"
  else
    sed -i -E "0,/\{/s|\{|{\n    \"$key\": \"$val\",|" "$CONF"
  fi
}

# ---- 4. configure ---------------------------------------------------------------
info "Configuring $CONF"
case "$COMPONENT" in
  webserver)
    cp "$LICENSE" ./license_MNS.dat
    [ -n "$API_PORT" ] && setkey PortAPI "$API_PORT"
    [ -n "$WEB_PORT" ] && setkey PortWEB "$WEB_PORT" ;;
  webserver-front)
    cp -r "$FRONT_PKI"/. . ;;
  *)
    setkey _sharedInternal "$SHARED"
    setkey webserver "$WEBSERVER"
    setkey name "$NAME"
    setkey description "Mugnsoft $COMPONENT on $(hostname -s)"
    [ -n "$PORT" ] && setkey port "$PORT"
    [ "$COMPONENT" != integrator ] && setkey location "$LOCATION"
    [ "$COMPONENT" = monitor ] && [ -n "$ADVERTISE" ] && setkey ip "$ADVERTISE"
    [ "$COMPONENT" = sentinel ] && setkey discoFSGMonitored "/"
    if [ "$COMPONENT" = integrator ]; then
      cp "$LICENSE" ./license_MNS.dat
      setkey os linux
      setkey webserverUrl "${WEBSERVER_URL:-https://${WEBSERVER%%:*}:9090}"
    fi ;;
esac

# ---- 5. install the service (components register with the Webserver here) ------
info "Installing the service"
OUT=$("./$BIN" install 2>&1) || true
echo "$OUT" | grep -v '^time=' || true
# "Service '<name>' installed." - not "Service '<name>' not installed."
echo "$OUT" | grep -q "Service '[^']*' installed\." || die "the service was not installed - read the messages above, fix, then run: cd $TARGET && ./$BIN install"

# ---- 6. start it ----------------------------------------------------------------
info "Starting the service"
"./$BIN" start 2>&1 | grep -v '^time=' || true

echo
case "$COMPONENT" in
  webserver)
    SI=$(sed -n -E 's/.*"_sharedInternal"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "$CONF")
    echo "Webserver installed in $TARGET"
    echo "  Web UI:   https://<this host>:$(sed -n -E 's/.*"PortWEB"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "$CONF")/   (admin / admin, change it at first login)"
    echo "  API:      <this host>:$(sed -n -E 's/.*"PortAPI"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "$CONF")   -> the --webserver value for the components"
    echo "  _sharedInternal (pass it to every component as --shared-internal):"
    echo "    $SI" ;;
  webserver-front)
    echo "Webserver-Front installed in $TARGET - now configure ADMIN (see the Webserver-Front install page)." ;;
  *)
    echo "$COMPONENT '$NAME' installed in $TARGET and registered with $WEBSERVER."
    echo "Last step: on the Webserver, open Settings (gear) > Components, right-click '$NAME' and choose enable server." ;;
esac

Option B — Step by step

The same steps by hand. Set the two variables first, then run the blocks in order.

1. Download and verify (on every host; COMPONENT is webserver, monitor, integrator or discovery_agent):


VERSION=4.2.0; COMPONENT=monitor          # <- change COMPONENT for each host
F=mugnsoft-$COMPONENT-$VERSION.linux-amd64.tar.gz
curl -fsSLO https://mugnsoft.com/bin/$VERSION/$F
curl -fsSLO https://mugnsoft.com/bin/$VERSION/$F.sig
curl -fsSLO https://mugnsoft.com/bin/Mugnsoft_public.key
gpg --import Mugnsoft_public.key
gpg --verify $F.sig $F                    # must print: Good signature from "Mugnsoft ..."
mkdir -p /opt/mugnsoft && tar -xzf $F -C /opt/mugnsoft && cd /opt/mugnsoft/$COMPONENT
chmod +x ./$COMPONENT

gpg adds WARNING: This key is not certified with a trusted signature!: that is expected, what matters is Good signature. A BAD signature means the file is corrupt or altered: delete it.

2. Webserver host: copy the license, install and start, then read _sharedInternal (the Webserver writes a random one into webserver.json at install):


cp /path/to/license_MNS.dat /opt/mugnsoft/webserver/
cd /opt/mugnsoft/webserver
./webserver install && ./webserver start
grep _sharedInternal webserver.json

3. Monitor / Integrator / Sentinel Agent host: edit the .json file of the component (monitor.json, integrator.json or discovery_agent.json) and set:

Key Set it to Note
_sharedInternal the Webserver’s value Must be identical to the Webserver’s
webserver 10.0.0.5:8050 Webserver address and API port
name a unique name, e.g. monitor-paris-1 Letters, digits, hyphens. The packaged value is an example
location, description your text The packaged values are examples
port keep, or another free port The Webserver must reach it
ip monitor only, optional The IP the Webserver must dial, when the host has several
webserverUrl https://10.0.0.5:9090 Integrator only
discoFSGMonitored / Sentinel Agent on Linux: the packaged C: matches no Linux file system

For the Integrator, also copy its license_MNS.dat into /opt/mugnsoft/integrator/. Then install (this registers the component) and start:


./monitor install && ./monitor start            # or ./integrator, ./discovery_agent

install must print status: Success twice (registration, then certificate) and Service '...' installed.

Enable the components

A registered component is pending until you enable it:

  1. Sign in to https://10.0.0.5:9090/ (admin / admin; you must choose a new password at first sign-in).
  2. Open the gear menu → components, right-click the new component → enable server. A success message confirms that the Webserver reached it.

Check the address. The Webserver connects to the IP address the component registered. A host with several network interfaces can register the wrong one: then enable server fails with getting access token: Failed. Make sure the component is running. Right-click the component → edit server, set the IP the Webserver can reach, save, then enable it again. For a monitor you can also pass --advertise <ip> (or set ip in monitor.json) before installing.

Manage the services

Task Command (from the component folder) systemd equivalent
Status — systemctl status MugnsoftMonitor
Stop / start / restart ./monitor stop, ./monitor start, ./monitor restart systemctl restart MugnsoftMonitor (or stop, start)
Run in the foreground (debugging) ./monitor stop && ./monitor run —
Register again with the Webserver ./monitor register —
Remove the service ./monitor uninstall (then delete the folder) —
Logs log/monitor.log in the component folder journalctl -u MugnsoftMonitor

Replace monitor / MugnsoftMonitor with webserver / MugnsoftWebserver, integrator / MugnsoftIntegrator, discovery_agent / MugnsoftDiscoveryAgent. Removing a component from the Webserver is done on its Components page (right-click → delete server).

To upgrade, follow the order and the checks in the release notes of the target version; this page covers new installations.

Monitor probes: browsers for EUM journeys

URL, API, TCP, ping and the other monitor types need nothing more. EUM (Web UI) journeys need a browser on the probe host: install Chrome, Edge or Firefox there. At start, the monitor downloads the matching webdriver if it can reach the Internet. Otherwise, download the driver that matches the browser version yourself and put it in /opt/mugnsoft/monitor/drivers/:

Browser Driver Download
Chrome chromedriver Chrome for Testing
Firefox geckodriver geckodriver releases
Edge msedgedriver Edge WebDriver

To record journeys, use Mugnsoft Pulse or the Mugnsoft IDE.

Troubleshooting

Message Cause and fix
self pre-registering ... status: Failed / Make sure that component can reach the Webserver API endpoint. The host cannot reach webserver on the API port: check the value, the Webserver service, the firewalls. Test with curl -k https://10.0.0.5:8050/ping (answers {"message":"pong"})
a server is already defined with this name A component with this name exists on the Webserver (an earlier attempt). Delete it on the Components page, or choose another --name, then run install again
a server is already defined with this IP and port A component already registered this IP address and port (a previous installation of this host). Delete it on the Components page, or use another --port, then run install again
No license .../license_MNS.dat file found Integrator: copy its license file into its folder (--license with the script)
license not valid for this machine The license file belongs to another host: licenses are bound to the host (machine-id on Linux)
enable server: getting access token: Failed The Webserver cannot reach the component: wrong registered IP (see Check the address), or the component’s port is blocked
enable server / Push Settings: rejected the internal login (HTTP 401) _sharedInternal differs from the Webserver’s: fix it in the component’s .json and restart the component
<folder> already exists (script) The script never overwrites an installation: choose another --dir, or remove the old one first (uninstall)
SIGNATURE CHECK FAILED (script) / BAD signature The package is corrupt or altered: download it again; never install it

See also

Translations