# How to create and configure `.cmd` and `.substitutions` files

Prerequisites:

* [Installation how-to](./install_update_remove_how_to.md#how-to-install).
* [Firewall how-to for SSH](./firewall_how_to.md#firewall-configuration-for-ssh).
* [Firewall how-to for EPICS](./firewall_how_to.md#standard-firewall-configuration-for-epics).
* [SSH how-to](./ssh_how_to.md).

```{seealso}
See [`.cmd`, `.template` and `.substitutions` explanations](../../explanations/cmd_and_template_and_sub_explanations.md)
for more explanations about how to use {{cmd}}, {{template}} and {{substitutions}} files.
```

---

## `.cmd` creation and configuration

```{seealso}
For a definition of the {{cmd}} file, see its glossary definition : {{cmd}} 
```

```{seealso}
For more details about the {{cmd}} file,
see [the `.cmd` explanations](../../explanations/cmd_and_template_and_sub_explanations.md#cmd-explanations)
```

You have to create and configure your {{cmd}} file
(e.g. `myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd`),
in order to do so you can use the [st_tests.cmd](sshmonitor-file-gitlab-url:iocBoot/iocsshmonitor/st_tests.cmd) file
as a reference/example (it is used for the {{SSH_Monitor}} tests).
The [st_tests.cmd](sshmonitor-file-gitlab-url:iocBoot/iocsshmonitor/st_tests.cmd) file is similar to the one below:

```{code} bash
$ vi myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd

    > #!../../bin/linux-x86_64/myTargetMonitoring
    >
    > ###################################################################################################
    > # README
    > ########
    > #
    > # ⚠️  This `.cmd` file can be used as a template for your own `.cmd`. If you do so, make sur
    > # to follow the `ℹ️ signs!
    >
    > ###################################################################################################
    > # ENVIRONMENT SETTINGS
    > ######################
    > #
    >
    > < envPaths
    >
    > # ℹ️ Change the prompt for the IOC Shell (useful when running multiple IOC programs in parallel,
    > # because it helps identifying them clearly):
    > epicsEnvSet("IOCSH_PS1", "Target1 Monitoring> ")
    >
    > # Set an environment variable locating your `.substitutions` files:
    > epicsEnvSet("SUBSTITUTIONS_FILES", "${TOP}/db/*.sub*")
    >
    > # ℹ️ If you want to run multiple IOC programs on the same machine, then change this TCP port (use a
    > # different port per IOC program, default port is 5064 for Channel Access):
    > epicsEnvSet("EPICS_CAS_SERVER_PORT", "5064")  
    >                                               
    > ###################################################################################################
    > # SSH_OPTS_AND_ARGS & PREFIX
    > ##############################
    > #
    > # ℹ️ You have to change the below `SSH_OPTS_AND_ARGS` macro according to your needs! This is where you
    > #   specify the SSH options and arguments (as described by `man ssh` on the host machine) in order to connect to
    > #   the target machine.
    > #
    > # E.g. Here are the details of what is being specified here: 
    > #
    > # - `target-sshmonitor-user@192.168.1.3`: the target user name (`target-sshmonitor-user`) and the
    > #   target machine IP address (`192.168.1.3`).
    > #
    > # - `-i /home/host-sshmonitor-user/.ssh/host_to_target_ssh_monitor_ed25519_key`: the SSH key
    > #   location associated to the target-sshmonitor-user of the target machine.
    > #
    > # - `-o BatchMode=yes`: disable user interaction such as password prompts and host key confirmation
    > #   requests.
    > #
    > # - `-o PasswordAuthentication=no`: avoids password authentication prompt to appear.
    > # 
    > # - `-o ConnectionAttempts=3`: 3 connection(s) attempt(s) to try before exiting (one per second).
    > # 
    > # - `-x`: disables x11 forwarding.
    > #
    > # - Avoid closing and reopening a new SSH connection for each instruction/PV, by adding the three
    > #   following options (`ControlMaster`, `ControlPath` and `ControlPersist`):
    > # 
    > #     - `-o ControlMaster=auto`: Enables the sharing of multiple sessions over a single network
    > #       connection. If set to auto, it creates a master session automatically, but if there is a
    > #       master session already available, subsequent sessions are automatically multiplexed.
    > #
    > #     - `-o ControlPath=~/.ssh/%C`: Specifies the path to the control socket used for connection
    > #       sharing. In this path, %C generates a SHA1 hash (depending on the target information like
    > #       username, hostname, port number) in order to get a short and unique socket name.
    > #
    > #     - `-o ControlPersist=60s`: When used in conjunction with ControlMaster, specifies that the
    > #       master connection should remain open (for 60 seconds here) in the background (waiting for
    > #       future client connections) after the initial client connection has been closed.
    > #
    > epicsEnvSet("SSH_OPTS_AND_ARGS", "target-sshmonitor-user@192.168.1.3 -i /home/host-sshmonitor-user/.ssh/host_to_target_ssh_monitor_ed25519_key -o BatchMode=yes -o PasswordAuthentication=no -o ConnectionAttempts=3 -x -o ControlMaster=auto -o ControlPath=~/.ssh/%C -o ControlPersist=60s")
    >
    > # ℹ️ Specify the below `PREFIX` macro with a unique target name:
    > # 
    > epicsEnvSet("PREFIX", "target1:")
    >
    > ###################################################################################################
    > # LOAD .dbd AND REGISTER "REGISTRARS"
    > #####################################
    > #
    >
    > # ℹ️ Allow to access more EPICS SCAN intervals, which is needed. Replace the below
    > # `${SSHMONITOR}` macro with the one specified in your ../../configure/RELEASE file, if the
    > # macro name isn't `SSHMONITOR` already:
    > dbLoadDatabase("${SSHMONITOR}/dbd/menuScan.dbd")
    >
    > # ℹ️ Replace the below `myTargetMonitoring` string  by your IOC name (specified by your
    > # previous `makeBaseApp.pl` command), e.g. replace the below line by
    > # `dbLoadDatabase("${TOP}/dbd/<your-ioc-name>.dbd")`:
    > dbLoadDatabase("${TOP}/dbd/myTargetMonitoring.dbd")
    >
    > # ℹ️ Replace the below `myTargetMonitoring` string  by your IOC name (specified by your
    > # previous `makeBaseApp.pl` command), e.g. replace the below line by
    > # `<your-ioc-name>_registerRecordDeviceDriver(pdbbase)`:
    > myTargetMonitoring_registerRecordDeviceDriver(pdbbase)
    >
    > ###################################################################################################
    > # CHECK RECORDS NAMES
    > #####################
    > #
    > 
    > # Throw an error if multiple records share the same name 
    > # (but won't prevent the IOC program from starting):
    > var(dbRecordsOnceOnly, 1)
    >
    > ###################################################################################################
    > # CHECK RECORDS NAMES
    > #####################
    > #
    >
    > # ℹ️ You can comment or uncomment any of the below `dbLoadTemplate` lines which are
    > #   loading/importing `.substitutions` files (depending on what you want to monitor):
    > #
    > # ℹ️ Review carefully the content of the `.substitutions` files you want to use before
    > #   loading/importing them!
    > 
    > dbLoadTemplate("${TOP}/db/example_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/processors_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/memory_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/partitions_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/connection_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > #dbLoadTemplate("${TOP}/db/archiver_appliance_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > 
    > # ℹ️ See <https://gitlab.com/sshmonitor/sshmonitor/-/tree/master/sshmonitorApp/db> for the 
    > #   full list of available "default" `.substitutions` files, depending on what you intend to monitor.
    > #   Note that those "default" `.substitutions` files should have been imported by adding the
    > #   below instruction in the `./<your-app-name>App/Db/Makefile` file:
    > #   `DB_INSTALLS += $(wildcard $(SSHMONITOR)/db/*.substitutions)`
    > #
    > # ℹ️ You can also create your own `.substitutions` files in order to monitor the metrics you want
    > #   (if so, do not hesitate to take inspiration from
    > #   https://gitlab.com/sshmonitor/sshmonitor/-/blob/tree/glob/sshmonitorApp/db/example_monitoring.substitutions):
    > 
    > #dbLoadTemplate("${TOP}/db/your_own_specific_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > #dbLoadTemplate("${TOP}/iocBoot/${IOC}/your_other_own_specific_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > 
    > 
    > ###################################################################################################
    > # CHECK CATEGORY NAMES
    > ######################
    > #
    > 
    > system('cat "${SUBSTITUTIONS_FILES}" | grep "CATEGORY = " | tr -d " " | tr "\"" " " | awk "{print $1 $2}" | grep -v "\#" 2>/dev/null | uniq -D | grep . && (echo "ERROR: illegal categories: multiple CATEGORY macros share the same name!"; kill -9 $PPID) || echo "INFO: substitutions files ${SUBSTITUTIONS_FILES} are OK"')
    > 
    > 
    > ###################################################################################################
    > # START IOC PROGRAM
    > ###################
    > #
    > 
    > iocInit

$ chmod +x myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd
```

This is a quite standard {{cmd}} file,
except for the optional `system(...)` command used to avoid a
common {{SSH_Monitor}} error leading to duplicated PVs.

```{seealso}
See [the `.cmd` explanations](../../explanations/cmd_and_template_and_sub_explanations.md#cmd-explanations)
for more details about this command.
```

---

## `.substitutions` creation and configuration

```{seealso}
For a definition of the {{substitutions}} file, 
see its glossary definition: {{substitutions}}.
```

```{seealso}
See [the `.substitutions` explanations](../../explanations/cmd_and_template_and_sub_explanations.md#substitutions-explanations)
for more details about the {{substitutions}} file.
```

In the above {{cmd}} file, some of the "default" {{substitutions}} files of {{SSH_Monitor}} have been used,\
by declaring the below commands:

```{code} bash
    > ...
    > dbLoadTemplate("${TOP}/db/example_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/processors_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/memory_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}, SCAN_MACRO=10 second")
    > dbLoadTemplate("${TOP}/db/partitions_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > dbLoadTemplate("${TOP}/db/connection_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > #dbLoadTemplate("${TOP}/db/archiver_appliance_monitoring.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > ...
```

Those {{substitutions}} files are pre-configured for common/recommended monitoring purposes.
Like memory monitoring, processors monitoring, partitions monitoring, etc.

```{seealso}
See the [recommended monitoring how-to guides](../recommended/index.md)
for more details about those {{substitutions}} files.
```

But you also might want to create and configure your own {{substitutions}} file(s)
(e.g. `myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/target1.substitutions`),
in order to monitor the exact data you want, the way you want.
To that end,
you can look at the "default" [SSH Monitor `.substitutions` files](../../references/substitutions/index.md#substitutions-files)
as references/examples.
In particular,
you can check the [example_monitoring.substitutions](../../references/substitutions/example_monitoring_substitutions.md) file,
which is similar to the one below:

```{code} bash
$ vi myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/target1.substitutions

    > ################
    > # ⚠️  ESCAPES  ⚠️ 
    > ################
    > #
    > # Inside a macro value, some characters need to be escaped, if you don't want them to be
    > # interpreted by the EPICS parser:
    > # - inside double-quotes, escape `$` by `\\\\`
    > # - inside double-quotes, escape `(` by `\\`
    > # - inside double-quotes, escape `)` by `\\`
    > #
    > # E.g.:
    > # STRING_1_INSTRUCTION = "echo 'here are some special chars: \\\\$, \\(, \\), [, ], {, },...'"
    >
    > global
    > {
    >     PREFIX = "${PREFIX_MACRO}"
    >     SSH_OPTS_AND_ARGS = "${SSH_OPTS_AND_ARGS_MACRO}"
    > }
    >
    > file "${TOP}/db/ssh_monitor_core.template"
    > {
    >     {
    >         CATEGORY = "example:" # category name: must be unique across all the 
    >                                       # .substitutions files that are loaded by the .cmd file.
    >                                       # this name will be part of the record's names, 
    >                                       # defined in this scope
    > 
    >         SCAN = "5 minute" # specify at which time interval you want the data to be retrieved
    >                           # in this scope (60 seconds by default if not specified)
    > 
    >         #####################
    >         # INSTRUCTIONS basics
    >         #####################
    >         #
    >         # An "instruction" is a combination of, at least, the 3 following macros:
    >         #
    >         # PV_NAME_<TYPE>_<ID> = "write:your:pv:name"
    >         # <TYPE>_<ID>_INSTRUCTION = "echo 'write your shell instruction'"
    >         # LOAD_<TYPE>_<ID> = ""
    >         #
    >         # Where `<TYPE>` is either:
    >         #     * `SCALAR` (if SCALAR_<ID>_INSTRUCTION returns double-precision floating-point number)
    >         #     * `STRING` (if STRING_<ID>_INSTRUCTION returns sequence of characters)
    >         #
    >         # Where `<ID>` is either
    >         #     * ranging from 1 to 15 (if `SCALAR`)
    >         #     * ranging from 1 to 5 (if `STRING`)
    >         #
    >         # Optional macros can also be defined, e.g. to specify alarms, engineering units,
    >         # instructions length, etc.
    >         # Detailed examples about those macros can be found below.
    >         #
    >         # ⚠️  If the macro `PV_NAME_<TYPE>_<ID>` is empty (or loaded but not defined), then the
    >         #    associated record/PV will be miss-named.
    >         #
    >         # ⚠️  If two macros `PV_NAME_<TYPE>_<ID>` have the same value in the same scope, then one
    >         #    could overwrite the other.
    >         #
    >         # ⚠️  By default, the value of `<TYPE>_<ID>_INSTRUCTION` can be 1024 characters long, but
    >         #    if it's not enough, then you can overwrite the default length with
    >         #    `<TYPE>_<ID>_INSTRUCTION_LENGTH = "1234"`.
    >         #
    >         # ⚠️  Do not forget to load your instruction with `LOAD_<TYPE>_<ID> = ""`.
    > 
    > 
    >         # scalar instruction 1:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_SCALAR_1}"
    >         #
    >         PV_NAME_SCALAR_1 = "scalar1"
    >         SCALAR_1_INSTRUCTION = "echo 1 #LOCAL" #ℹ️ Note that `#LOCAL` specifies that the instruction is 
    >                                                # run on the host rather than the target
    >         LOAD_SCALAR_1 = ""
    >         SCALAR_1_INSTRUCTION_LENGTH = "16" # specify the number of characters used 
    >                                            # in ${SCALAR_1_INSTRUCTION} (default is 1024)
    >                                            # this is optional if length is less than 1024
    >
    >         # scalar instruction 2:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called '${PREFIX}${CATEGORY}${PV_NAME_SCALAR_2}'
    >         #
    >         PV_NAME_SCALAR_2 = "ping_test"
    >         SCALAR_2_INSTRUCTION = "ping -c 3 192.168.1.2 | grep -q '3 packets transmitted, 3 received, 0% packet loss' && echo 0 || echo 1 #LOCAL"
    >         PREC_SCALAR_2 = "0"
    >         LOAD_SCALAR_2 = ""
    >         # if ${SCALAR_2_INSTRUCTION} > 0 then MAJOR alarm:
    >         HIHI_SCALAR_2 = "1"
    >         LOAD_HIHI_SCALAR_2 = ""
    >         HHSV_SCALAR_2 = "MAJOR"
    >         LOAD_HHSV_SCALAR_2 = ""
    >         # if ${SCALAR_2_INSTRUCTION} < 0 then MAJOR alarm:
    >         LOLO_SCALAR_2 = "-1"
    >         LOAD_LOLO_SCALAR_2 = ""
    >         LLSV_SCALAR_2 = "MAJOR"
    >         LOAD_LLSV_SCALAR_2 = ""
    >         # (if ${SCALAR_2_INSTRUCTION} == 0 then no alarm)
    >
    >         # scalar instruction 5:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_SCALAR_5}"
    >         #
    >         PV_NAME_SCALAR_5 = "scalar5"
    >         SCALAR_5_INSTRUCTION = "shuf -i 0-100 -n 1"
    >         LOAD_SCALAR_5 = ""
    >         # specify that engineering unit is %
    >         EGU_SCALAR_5 = "%"
    >         LOAD_EGU_SCALAR_5 = ""
    >         # if ${SCALAR_5_INSTRUCTION} > 80 then MAJOR alarm:
    >         HIHI_SCALAR_5 = "80"
    >         LOAD_HIHI_SCALAR_5 = ""
    >         HHSV_SCALAR_5 = "MAJOR"
    >         LOAD_HHSV_SCALAR_5 = ""
    >         # if ${SCALAR_5_INSTRUCTION} > 60 then MINOR alarm:
    >         HIGH_SCALAR_5 = "60"
    >         LOAD_HIGH_SCALAR_5 = ""
    >         HSV_SCALAR_5 = "MINOR"
    >         LOAD_HSV_SCALAR_5 = ""
    >         # if ${SCALAR_5_INSTRUCTION} < 40 then MINOR alarm:
    >         LOW_SCALAR_5 = "40"
    >         LOAD_LOW_SCALAR_5 = ""
    >         LSV_SCALAR_5 = "MINOR"
    >         LOAD_LSV_SCALAR_5 = ""
    >         # ${SCALAR_5_INSTRUCTION} < 20 then MAJOR alarm:
    >         LOLO_SCALAR_5 = "20"
    >         LOAD_LOLO_SCALAR_5 = ""
    >         LLSV_SCALAR_5 = "MAJOR"
    >         LOAD_LLSV_SCALAR_5 = ""
    >         # (if 60 >= ${SCALAR_5_INSTRUCTION} >= 40 then no alarm)
    > 
    >         # scalar instruction 10 used as a binary value:
    >         #----------------------------------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_SCALAR_10}"
    >         #
    >         PV_NAME_SCALAR_10 = "binary"
    >         SCALAR_10_INSTRUCTION = "echo 1"
    >         LOAD_SCALAR_10 = ""
    >         # if ${SCALAR_10_INSTRUCTION} > 0 then MAJOR alarm:
    >         HIHI_SCALAR_10 = "1"
    >         LOAD_HIHI_SCALAR_10 = ""
    >         HHSV_SCALAR_10 = "MAJOR"
    >         LOAD_HHSV_SCALAR_10 = ""
    >         # if ${SCALAR_10_INSTRUCTION} < 0 then MAJOR alarm:
    >         LOLO_SCALAR_10 = "-1"
    >         LOAD_LOLO_SCALAR_10 = ""
    >         LLSV_SCALAR_10 = "MAJOR"
    >         LOAD_LLSV_SCALAR_10 = ""
    >         # (if ${SCALAR_10_INSTRUCTION} == 0 then no alarm)
    > 
    >         # string instruction 1:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_STRING_1}"
    >         #
    >         PV_NAME_STRING_1 = "string1"
    >         STRING_1_INSTRUCTION = "echo abcdefghij_abcdefghij_abcdefghij_abcdefghij_abcdefghij #DEBUG"
    >         # ℹ️ Note that `#DEBUG` specifies that the instruction should be logged in the IOC Shell
    >         LOAD_STRING_1 = ""
    >         # specify the number of characters used in ${STRING_1_INSTRUCTION} (default is 1024)
    >         STRING_1_INSTRUCTION_LENGTH = "72"
    > 
    >         # string instruction 2:
    >         #----------------------
    >         # the result of this instruction will be stored in
    >         # the record called "${PREFIX}${CATEGORY}${PV_NAME_STRING_2}"
    >         #
    >         PV_NAME_STRING_2 = "local_hostname"
    >         STRING_2_INSTRUCTION = "cat /etc/hostname #LOCAL+DEBUG"
    >         # ℹ️ Note that `#LOCAL+DEBUG` specifies that the instruction is run on the host rather than the target
    >         #   and that the instruction should be logged in the IOC Shell
    >         #   (works also with `#DEBUG+LOCAL`)
    >         LOAD_STRING_2 = ""
    >     }
    > }
```

After creating your {{substitutions}} file, don't forget to load it in the {{cmd}} (e.g. st_target1.cmd):

```{code} bash
$ vi myTargetMonitoringTop/iocBoot/iocMyTargetMonitoring/st_target1.cmd

    > ...
  + > dbLoadTemplate("${TOP}/iocBoot/${IOC}/target1.substitutions", "PREFIX_MACRO=${PREFIX}, SSH_OPTS_AND_ARGS_MACRO=${SSH_OPTS_AND_ARGS}")
    > ...

```

```{tip}
This is a nice little example, but it's not complete.
For more detailed references,
you can look at [SSH Monitor `.substitutions` files](../../references/substitutions/index.md#substitutions-files).
Lots of "common instructions" are already available in those files,
so do not hesitate to use them as examples.
```

```{seealso}
See the [custom shell instructions how-to](./custom_shell_instructions_how_to.md)
for more details about how to design its own {{shell_instruction}}s
and retrieve the data you want to monitor.
```

```{seealso}
See the [alarm records' fields how-to](./epics_records_fields_how_to.md)
for more details about how to set alarm thresholds for any "INSTRUCTION".
```

