diff --git a/.gitignore b/.gitignore index 2ae8b6c..adfc721 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules dist bin/.*.ts tools/.*.ts +docs/_build diff --git a/docs/api-reference.rst b/docs/api-reference.rst new file mode 100644 index 0000000..282e4aa --- /dev/null +++ b/docs/api-reference.rst @@ -0,0 +1,529 @@ +API Reference +============= + +GenieACS expoeses a rich RESTful API through its NBI component. This document +serves as a reference for the available APIs. + +This API makes use of MongoDB's query language in some of its endpoints. Refer +to MongoDB's documentation for details. + +.. note:: + + The examples below use ``curl`` command for simplicity and ease of testing. + Query parameters are URL-encoded, but the original pre-encoding values are + shown for reference. These examples assume genieacs-nbi is running locally + and listening on the default NBI port (7557). + +.. warning:: + + A common pitfll is not properly percent-encoding special characters in the + device ID or query in the URL. + +Endpoints +--------- + +GET /\/?query=\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Search for records in the database (e.g. devices, tasks, presets, files). +Returns a JSON representation of all items in the given collection that match +the search criteria. + +*collection*: The data collection to search. Could be one of: tasks, devices, +presets, objects. + +*query*: Search query. Refer to MongoDB queries for reference. + +Examples +^^^^^^^^ + +- Find a device by its ID: + +.. code:: javascript + + query = {"_id": "202BC1-BM632w-0000000"} + +.. code:: bash + + curl -i 'http://localhost:7557/devices/?query=%7B%22_id%22%3A%22202BC1-BM632w-0000000%22%7D' + +- Find a device by its MAC address: + +.. code:: javascript + + query = { + "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress": "20:2B:C1:E0:06:65" + } + +.. code:: bash + + curl -i 'http://localhost:7557/devices/?query=%7B%22InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.MACAddress%22%3A%2220:2B:C1:E0:06:65%22%7D' + +- Search for devices that have not initiated an inform in the last 7 days. + +.. code:: javascript + + query = { + "_lastInform": { + "$lt" : "2017-12-11 13:16:23 +0000" + } + } + +.. code:: bash + + curl -i 'http://localhost:7557/devices/?query=%7B%22_lastInform%22%3A%7B%22%24lt%22%3A%222017-12-11%2013%3A16%3A23%20%2B0000%22%7D%7D' + +- Show pending tasks for a given device: + +.. code:: javascript + + query = {"device": "202BC1-BM632w-0000000"} + +.. code:: bash + + curl -i 'http://localhost:7557/tasks/?query=%7B%22device%22%3A%22202BC1-BM632w-0000000%22%7D' + +- Return specific parameters for a given device: + +.. code:: javascript + + query = {"_id": "202BC1-BM632w-0000000"} + +.. code:: bash + + curl -i 'http://localhost:7557/devices?query=%7B%22_id%22%3A%22202BC1-BM632w-0000000%22%7D&projection=InternetGatewayDevice.DeviceInfo.ModelName,InternetGatewayDevice.DeviceInfo.Manufacturer' + +The ``projection`` URL param is a comma-separated list of the parameters to receive. + +POST /devices/\/tasks?[connection_request] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Enqueue task(s) and optionally trigger a connection request to the device. +Refer to :ref:`tasks` section for information about the task object format. +Returns status code 200 if the tasks have been successfully executed, and 202 +if the tasks have been queued to be executed at the next inform. + +*device_id*: The ID of the device. + +*connection_request*: Indicates that a connection request will be triggered to +execute the tasks immediatly. Otherwise, the tasks will be queued and be +processed at the next inform. + +The response body is the task object as it is inserted in the database. The +object will include ``_id`` property which you can use to look up the task +later. + +Examples +^^^^^^^^ + +- Refresh all device parameters now: + +.. code:: bash + + curl -i 'http://localhost:7557/devices/202BC1-BM632w-0000000/tasks?connection_request' \ + -X POST \ + --data '{"name": "refreshObject", "objectName": ""}' + +- Change WiFi SSID and password: + +.. code:: javascript + + { + "name": "setParameterValues", + "parameterValues": [ + ["InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", "GenieACS", "xsd:string"], + ["InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey", "hello world", "xsd:string"] + ] + } + +.. code:: bash + + curl -i 'http://localhost:7557/devices/202BC1-BM632w-0000000/tasks?connection_request' \ + -X POST \ + --data '{"name":"setParameterValues", "parameterValues": [["InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID", "GenieACS", "xsd:string"],["InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey", "hello world", "xsd:string"]]}' + +POST /tasks/\/retry +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Retry a faulty task at the next inform. + +*task_id*: The ID of the task as returned by 'GET /tasks' request. + +Example +^^^^^^^ + +.. code:: bash + + curl -i 'http://localhost:7557/tasks/5403908ef28ea3a25c138adc/retry' -X POST + +DELETE /tasks/\ +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Delete the given task. + +*task_id*: The ID of the task as returned by 'GET /tasks' request. + +Example +^^^^^^^ + +.. code:: bash + + curl -i 'http://localhost:7557/tasks/5403908ef28ea3a25c138adc' -X DELETE + +DELETE /faults/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Delete the given fault. + +*fault_id*: The ID of the fault as returned by 'GET /faults' request. The ID +format is "\:\". + +Example +^^^^^^^ + +.. code:: bash + + curl -i 'http://localhost:7557/faults/202BC1-BM632w-0000000:default' -X DELETE + +DELETE /devices/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Delete the given device from the database. + +Example +^^^^^^^ + +.. code:: bash + + curl -X DELETE -i 'http://localhost:7557/devices/202BC1-BM632w-000001' + +.. note:: + + Note that the device will be registered again when/if it contacts the ACS + again (e.g. on the next periodic inform). + +POST /devices/\/tags/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Assign a tag to a device. Has no effect if such tag already exists. + +*device_id*: The ID of the device. + +*tag*: The tag to be assigned. + +Example +^^^^^^^ + +Assign the tag "testing" to a device: + +.. code:: bash + + curl -i 'http://localhost:7557/devices/202BC1-BM632w-0000000/tags/testing' -X POST + +DELETE /devices/\/tags/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Remove a tag from a device. + +*device_id*: The ID of the device. + +*tag*: The tag to be removed. + +Example +^^^^^^^ + +Remove the tag "testing" from a device: + +.. code:: bash + + curl -i 'http://localhost:7557/devices/202BC1-BM632w-0000000/tags/testing' -X DELETE + +PUT /presets/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create or update a preset. Returns status code 200 if the preset has been +added/updated successfully. The body of the request is a JSON representation of +the preset. Refer to :ref:`presets` section below for details about its format. + +*preset_name*: The name of the preset. + +Example +^^^^^^^ + +Create a preset to set 5 minutes inform interval for all devices tagged with +"test": + +.. code:: javascript + + query = { + "weight": 0, + "precondition": "{\"_tags\": \"test\"}" + "configurations": [ + { + "type": "value", + "name": "InternetGatewayDevice.ManagementServer.PeriodicInformEnable", + "value": "true" + }, + { + "type": "value", + "name": "InternetGatewayDevice.ManagementServer.PeriodicInformInterval", + "value": "300" + } + ] + } + +.. code:: bash + + curl -i 'http://localhost:7557/presets/inform' \ + -X PUT \ + --data '{"weight": 0, "precondition": "{\"_tags\": \"test\"}", "configurations": [{"type": "value", "name": "InternetGatewayDevice.ManagementServer.PeriodicInformEnable", "value": "true"}, {"type": "value", "name": "InternetGatewayDevice.ManagementServer.PeriodicInformInterval", "value": "300"}]}' + +DELETE /presets/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/presets/inform' -X DELETE + +PUT /files/\ +~~~~~~~~~~~~~~~~~~~~~~~~ + +Upload a new file or overwrite an existing one. Returns status code 200 if the +file has been added/updated successfully. The file content should be sent as +the request body. + +*file_name*: The name of the uploaded file. + +The following file metadata may be sent as request headers: + +- ``fileType``: For firmware images it should be "1 Firmware Upgrade Image". + Other common types are "2 Web Content" and "3 Vendor Configuration File". + +- ``oui``: The OUI of the device model that this file belogs to. + +- ``productClass``: The product class of the device. + +- ``version``: In case of firmware images, this refer to the firmware version. + +Example +^^^^^^^ + +Upload a firmware image file: + +.. code:: bash + + curl -i 'http://localhost:7557/files/new_firmware_v1.0.bin' \ + -X PUT \ + --data-binary @"./new_firmware_v1.0.bin" \ + --header "fileType: 1 Firmware Upgrade Image" \ + --header "oui: 123456" \ + --header "productClass: ABC" \ + --header "version: 1.0" + +DELETE /files/\ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Delete a previously uploaded file: + +.. code:: bash + + curl -i 'http://localhost:7557/files/new_firmware_v1.0.bin' -X DELETE + +GET /files/ +~~~~~~~~~~~ + +Gets all previously uploaded files. + +GET /files/?query={"filename":"\"} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Find files using a query. + +.. _tasks: + +Tasks +----- + +Find the different availabe tasks and their object structure. + +``getParameterValues`` +~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: javascript + + query = { + "name": "getParameterValues", + "parameterNames": [ + "InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnectionNumberOfEntries", + "InternetGatewayDevice.Time.NTPServer1", "InternetGatewayDevice.Time.Status" + ] + } + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-96318REF-SR360NA0A4%252D0003196/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name": "getParameterValues", "parameterNames": ["InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnectionNumberOfEntries", "InternetGatewayDevice.Time.NTPServer1", "InternetGatewayDevice.Time.Status"] }' + +You may request a single or multiple parameters at once. + +After the task has been executed successfully you can then fetch the CPE object +and read the parameters from the JSON object. + +.. code:: javascript + + query = {"_id": "00236a-96318REF-SR360NA0A4%2D0003196"} + +.. code:: bash + + curl -i 'http://localhost:7557/devices/?query=%7B%22_id%22%3A%2200236a-96318REF-SR360NA0A4%252D0003196%22%7D' + +``refreshObject`` +~~~~~~~~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-SR552n-SR552NA084%252D0003269/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name": "refreshObject", "objectName": "InternetGatewayDevice.WANDevice.1.WANConnectionDevice"}' + +``setParameterValues`` +~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-SR552n-SR552NA084%252D0003269/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name": "setParameterValues", "parameterValues": [["InternetGatewayDevice.ManagementServer.UpgradesManaged",false]]}' + +Multiple values can be set at once by adding multiple arrays to the +parameterValues key. For example: + +.. code:: javascript + + { + name: "setParameterValues", + parameterValues: [["InternetGatewayDevice.ManagementServer.UpgradesManaged", false], ["InternetGatewayDevice.Time.Enable", true], ["InternetGatewayDevice.Time.NTPServer1", "pool.ntp.org"]] + } + +``addObject`` +~~~~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-SR552n-SR552NA084%252D0003269/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name":"addObject","objectName":"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection"}' + +``reboot`` +~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-SR552n-SR552NA084%252D0003269/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name": "reboot"}' + +``factoryReset`` +~~~~~~~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-SR552n-SR552NA084%252D0003269/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name": "factoryReset"}' + +``download`` +~~~~~~~~~~~~ + +.. code:: bash + + curl -i 'http://localhost:7557/devices/00236a-SR552n-SR552NA084%252D0003269/tasks?timeout=3000&connection_request' \ + -X POST \ + --data '{"name": "download", "file": "mipsbe-6-42-lite.xml"}' + +.. _presets: + +Presets +------- + +Presets assign a set of configuration or a Provision script to devices based on +a precondition (search filter), schedule (cron expression), and events. + +Precondition +~~~~~~~~~~~~ + +The ``precondition`` property is a JSON string representation of the search +filter to test if the preset applies to a given device. Examples preconditions +are: + +- ``{"param": "value"}`` +- ``{"param": value", "param2": {"$ne": "value2"}}`` + +Other operators that can be used are ``$gt``, ``$lt``, ``$gte`` and ``$lte``. + +Configuration +~~~~~~~~~~~~~ + +The configuration property is an array containing the different configurations +to be applied to a device, as shown below: + +.. code:: javascript + + [ + { + "type": "value", + "name": "InternetGatewayDevice.ManagementServer.PeriodicInformEnable", + "value": "true" + }, + { + "type": "value", + "name": "InternetGatewayDevice.ManagementServer.PeriodicInformInterval", + "value": "300" + }, + { + "type": "delete_object", + "name": "object_parent", + "object": "object_name" + }, + { + "type": "add_object", + "name": "object_parent", + "object": "object_name" + }, + { + "type": "provision", + "name": "YourProvisionName" + }, + ] + +The configuration type ``provision`` triggers a Provision script. In the +example above, the provision named "YourProvisionName" will be executed. + +Provisions +---------- + +Create a provision +~~~~~~~~~~~~~~~~~~ + +The Provision's JavaScript code is the body of the HTTP PUT request. + +.. code:: bash + + curl -X PUT -i 'http://localhost:7557/provisions/mynewprovision' --data 'log("Provision started at " + now);' + +Delete a provision +~~~~~~~~~~~~~~~~~~ + +.. code:: bash + + curl -X DELETE -i 'http://localhost:7557/provisions/mynewprovision' + +Get provisions +~~~~~~~~~~~~~~ + +Get all provisions: + +.. code:: bash + + curl -X GET -i 'http://localhost:7557/provisions/' diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..88b2d8e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,65 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import json + +# -- Project information ----------------------------------------------------- + +project = 'GenieACS Documentation' +copyright = '2019, GenieACS Inc.' +author = 'GenieACS Inc.' + +# The full version, including alpha/beta/rc tags +release = json.load(open("../package.json"))["version"] + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_logo = "logo.svg" +html_theme_options = { + "logo_only": True +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +master_doc = 'index' + +highlight_language = 'javascript' \ No newline at end of file diff --git a/docs/cpe-authentication.rst b/docs/cpe-authentication.rst new file mode 100644 index 0000000..852934b --- /dev/null +++ b/docs/cpe-authentication.rst @@ -0,0 +1,6 @@ +.. _cpe-authentication: + +CPE Authentication +================== + +TODO diff --git a/docs/environment-variables.rst b/docs/environment-variables.rst new file mode 100644 index 0000000..e76ce10 --- /dev/null +++ b/docs/environment-variables.rst @@ -0,0 +1,6 @@ +.. _environment-variables: + +Environment Variables +===================== + +TODO \ No newline at end of file diff --git a/docs/ext-sample.js b/docs/ext-sample.js new file mode 100644 index 0000000..9386014 --- /dev/null +++ b/docs/ext-sample.js @@ -0,0 +1,34 @@ +// This is an exmaple GenieACS extension to get the current latitude/longitude +// of the International Space Station. Why, you ask? Because why not. +// To install, copy this file to config/ext/iss.js. + +"use strict"; + +const http = require("http"); + +let cache = null; +let cacheExpire = 0; + +function latlong(args, callback) { + if (Date.now() < cacheExpire) + return callback(null, cache); + + http.get("http://api.open-notify.org/iss-now.json", (res) => { + if (res.statusCode !== 200) + return callback(new Error(`Request failed (status code: ${res.statusCode})`)); + + let rawData = ""; + res.on("data", (chunk) => rawData += chunk); + + res.on("end", () => { + let pos = JSON.parse(rawData)["iss_position"]; + cache = [+pos["latitude"], +pos["longitude"]]; + cacheExpire = Date.now() + 10000; + callback(null, cache); + }); + }).on("error", (err) => { + callback(err); + }); +} + +exports.latlong = latlong; diff --git a/docs/extensions.rst b/docs/extensions.rst new file mode 100644 index 0000000..7ec23c9 --- /dev/null +++ b/docs/extensions.rst @@ -0,0 +1,33 @@ +.. _extensions: + +Extensions +========== + +Given that :ref:`provisions` and :ref:`virtual-parameters` are executed in a +sandbox environment, it is not possible to interact with external sources or +execute any action that requires OS, file system, or network access. Extensions +exist to bridge that gap. + +Extensions are fully-privileged Node.js modules and as such have access to +standard Node libraries and 3rd party packages. Functions exposed by the +extension can be called from Provision scripts using the ``ext()`` function. A +typical use case for extensions is fetching credentials from a database to have +that pushed to the device during provisioning. + +By default, the extension JS code must be placed under ``config/ext`` +directory. You may need to create that directory if it doesn't already exist. + +The example extension below fetches data from an external REST API and returns +that to the caller: + +.. literalinclude:: ext-sample.js + :language: javascript + +To call this extension from a Provision or a Virtual Parameter script: + +.. code:: javascript + + // The arguments "arg1" and "arg2" are passed to the latlong. Though they are + // unused in this particular example. + const res = ext("ext-sample", "latlong", "arg1", "arg2"); + log(JSON.stringify(res)); diff --git a/docs/https.rst b/docs/https.rst new file mode 100644 index 0000000..50500e3 --- /dev/null +++ b/docs/https.rst @@ -0,0 +1,6 @@ +.. _https: + +HTTPS +===== + +TODO diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..cf1c75e --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,55 @@ +.. GenieACS documentation master file, created by + sphinx-quickstart on Wed Jun 5 13:47:06 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to GenieACS's documentation! +==================================== + +.. toctree:: + :caption: Table of Contents + +.. raw:: latex + + \part{Installation} + +.. toctree:: + :maxdepth: 1 + :caption: Installation + + installation-guide + environment-variables + +.. raw:: latex + + \part{Administration} + +.. toctree:: + :maxdepth: 1 + :caption: Administration + + provisions + virtual-parameters + +.. raw:: latex + + \part{Integration} + +.. toctree:: + :maxdepth: 1 + :caption: Integration + + extensions + api-reference + +.. raw:: latex + + \part{Security} + +.. toctree:: + :maxdepth: 1 + :caption: Security + + https + cpe-authentication + roles-and-permissions diff --git a/docs/installation-guide.rst b/docs/installation-guide.rst new file mode 100644 index 0000000..0a5de04 --- /dev/null +++ b/docs/installation-guide.rst @@ -0,0 +1,230 @@ +Installation Guide +================== + +This guide is for installing GenieACS on a single server on any Linux distro +that uses *systemd* as its init system. + +The various GenieACS services are independant of each other and may be +installed on different servers. You may also run multiple instances of each in +a load-balancing/failover setup. + +.. attention:: + + For production deployments make sure to configure TLS and change + ``UI_JWT_SECRET`` to a unique and secure string. Refer to :ref:`https` + section for how to enable TLS to encrypt traffic. + +Prerequisites +------------- + +.. topic:: Node.js + + GenieACS requires Node.js 10.x and up. Refer to https://nodejs.org/ for + instructions. + +.. topic:: MongoDB + + GenieACS requires MongoDB 3.6 and up. Refer to https://www.mongodb.com/ for + instructions. + +Install GenieACS +------------------- + +.. topic:: Installing from NPM: + + .. parsed-literal:: + + sudo npm install -g genieacs@\ |release| + +.. topic:: Installing from source + + If you prefer installing from source, such as when running a GenieACS copy + with custom patches, refer to README.md file in the source package. Adjust + the next steps below accordingly. + +Configure systemd +----------------- + +.. topic:: Create a system user to run GenieACS daemons + + .. code:: bash + + sudo useradd --system --no-create-home --user-group genieacs + +.. topic:: Create directory to save extensions and environment file + + We'll use :file:`/opt/genieacs/ext/` directory to store extension scripts (if any). + + .. code:: bash + + mkdir /opt/genieacs + mkdir /opt/genieacs/ext + chown genieacs:genieacs /opt/genieacs/ext + + Create the file :file:`/opt/genieacs/genieacs.env` to hold our configuration + options which we pass to GenieACS as environment variables. See + :ref:`environment-variables` section for a list of all available + configuration options. + + .. code:: bash + + GENIEACS_CWMP_ACCESS_LOG_FILE=/var/log/genieacs/genieacs-cmwp-access.log + GENIEACS_NBI_ACCESS_LOG_FILE=/var/log/genieacs/genieacs-nbi-access.log + GENIEACS_FS_ACCESS_LOG_FILE=/var/log/genieacs/genieacs-fs-access.log + GENIEACS_UI_ACCESS_LOG_FILE=/var/log/genieacs/genieacs-ui-access.log + GENIEACS_DEBUG_FILE=/var/log/genieacs/genieacs-debug.yaml + GENIEACS_UI_JWT_SECRET=secret + + Set file ownership and permissions: + + .. code:: bash + + sudo chown genieacs:genieacs /opt/genieacs/genieacs.env + sudo chmod 600 /opt/genieacs/genieacs.env + +.. topic:: Create logs directory + + .. code:: bash + + mkdir /var/log/genieacs + chown genieacs:genieacs /var/log/genieacs + +.. topic:: Create systemd unit files + + Create a systemd unit file for each of the four GenieACS services. Note that + we're using EnvironmentFile directive to read the environment variables from + the file we created earlier. + + Each service has two streams of logs: access log and process log. Access logs + are configured here to be dumped in a log file under + :file:`/var/log/genieacs/` while process logs go to *journald*. Use + ``journalctl`` command to view process logs. + + 1. Run the following command to create ``genieacs-cwmp`` service: + + .. code:: bash + + sudo systemctl edit --force genieacs-cwmp + + Then paste the following in the editor and save: + + .. code:: cfg + + [Unit] + Description=GenieACS CWMP + After=network.target + + [Service] + User=genieacs + EnvironmentFile=/opt/genieacs/genieacs.env + ExecStart=genieacs-cwmp + KillMode=process + + [Install] + WantedBy=default.target + + 2. Run the following command to create ``genieacs-nbi`` service: + + .. code:: bash + + sudo systemctl edit --force genieacs-nbi + + Then paste the following in the editor and save: + + .. code:: cfg + + [Unit] + Description=GenieACS NBI + After=network.target + + [Service] + User=genieacs + EnvironmentFile=/opt/genieacs/genieacs.env + ExecStart=genieacs-nbi + KillMode=process + + [Install] + WantedBy=default.target + + 3. Run the following command to create ``genieacs-fs`` service: + + .. code:: bash + + sudo systemctl edit --force genieacs-fs + + Then paste the following in the editor and save: + + .. code:: cfg + + [Unit] + Description=GenieACS FS + After=network.target + + [Service] + User=genieacs + EnvironmentFile=/opt/genieacs/genieacs.env + ExecStart=genieacs-fs + KillMode=process + + [Install] + WantedBy=default.target + + 4. Run the following command to create ``genieacs-ui`` service: + + .. code:: bash + + sudo systemctl edit --force genieacs-ui + + Then paste the following in the editor and save: + + .. code:: cfg + + [Unit] + Description=GenieACS UI + After=network.target + + [Service] + User=genieacs + EnvironmentFile=/opt/genieacs/genieacs.env + ExecStart=genieacs-ui + KillMode=process + + [Install] + WantedBy=default.target + +.. topic:: Configure log file rotation using logrotate + + Save the following under :file:`/etc/logrotate.d/genieacs` + + .. code:: + + /var/log/genieacs/*.{log,yaml} { + daily + rotate 30 + compress + delaycompress + dateext + } + +.. topic:: Enable and start services + + .. code:: bash + + sudo systemctl enable genieacs-cwmp + sudo systemctl start genieacs-cwmp + sudo systemctl status genieacs-cwmp + + sudo systemctl enable genieacs-nbi + sudo systemctl start genieacs-nbi + sudo systemctl status genieacs-nbi + + sudo systemctl enable genieacs-fs + sudo systemctl start genieacs-fs + sudo systemctl status genieacs-fs + + sudo systemctl enable genieacs-ui + sudo systemctl start genieacs-ui + sudo systemctl status genieacs-ui + + Review the status message for each to verify that the services are running + successfully. diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 0000000..5b61e48 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/provisions.rst b/docs/provisions.rst new file mode 100644 index 0000000..06f6216 --- /dev/null +++ b/docs/provisions.rst @@ -0,0 +1,271 @@ +.. _provisions: + +Provisions +========== + +A Provision is a piece of JavaScript code that is executed on the server on a +per-device basis. It enables implementing complex provisioning scenarios and +other operations such as automated firmware upgrade rollout. Apart from a few +special functions, the script is essentially a standard ES6 code executed in +strict mode. + +Provisions are mapped to devices using presets. Note that the added performance +overhead when using Provisions as opposed to simple preset configuration +entries is relatively small. Anything that can be done via preset +configurations can be done using a Provision script. In fact, the now +deprecated configuration format is still supported primarily for backward +compatibility and it is recommended to use Provision scripts for all +configuration. + +When assigning a Provision script to a preset, you may pass arguments to the +script. The arguments can be accessed from the script through the global +``args`` variable. + +.. note:: + + Provision scripts may get executed multiple times in a given session. + Although all data model-mutating operations are idempotent, a script as a + whole may not be. It is, therefore, necessary to repeatedly run the script + until there are no more side effects and a stable state is reached. + +Built-in functions +------------------ + +``declare(path, timestamps, values)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This function is for declaring parameter values to be set, as well as specify +constraints on how recent you'd like the parameter value (or other attributes) +to have been refreshed from the device. If the given timestamp is lower than +the timestamp of the last refresh from the device, then this function will +return the last known value. Otherwise, the value will be fetched from the +device before being returned to the caller. + +The timestamp argument is an object where the key is the attribute name (e.g. +``value``, ``object``, ``writable``, ``path``) and the value is an integer +representing a Unix timestamp. + +The values argument is an object similar to the timestamp argument but its +property values being the parameter values to be set. + +The possible attributes in 'timestamps' and 'values' arguments are: + +- ``value``: a [, ] pair + +This attribute is not available for objects or object instances. If the value +is not a [, ] array then it'll assumed to be a value without a +type and therefore the type will be inferred from the parameter's type. + +- ``writable``: boolean + +The meaning of this attribute can vary depending on the type of the parameter. +In the case of regular parameters, it indicates if its value is writable. In +the case of objects, it's whether or not it's possible to add new object +instances. In the case of object instances, it indicates whether or not this +instance can be deleted. + +- ``object``: boolean + +True if this is an object or object instance, false otherwise. + +- ``path``: string + +This attribute is special in that it's not a parameter attribute per se, but it +refers to the presence of parameters matching the given path. For example, +given the following wildcard path: + +``InternetGatewayDevice.LANDevice.1.Hosts.Host.*.MACAddress`` + +Using a recent timestamp for path in ``declare()`` will result in a sync with +the device to rediscover all Host instances (``Host.*``). The path attribute +can also be used to create or delete object instances as described in +:ref:`path-format` section. + +The return value of ``declare()`` is an iterator to access parameters that +match the given path. Each item in the iterator has the attribute 'path' in +addition to any other attribute given in the ``declare()`` call. The iterator +object itself has convenience attribute accessors which come in handy when +you're expecting a single parameter (e.g. when path does not contain wildcards +or aliases). + +.. code:: javascript + + // Example: Setting the SSID as the last 6 characters of the serial number + let serial = declare("Device.DeviceInfo.SerialNumber", {value: 1}); + declare("Device.LANDevice.1.WLANConfiguration.1.SSID", null, {value: serial.value[0]}); + +``clear(path, timestamp)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This function invalidates the database copy of parameters (and their child +parameters) that match the given path and have a last refresh timestamp that is +less than the given timestamp. The most obvious use for this function is to +invalidate the database copy of the entire data model after the device has been +factory reset: + +.. code:: javascript + + // Example: Clear cached device data model Note + // Make sure to apply only on "0 BOOTSTRAP" event + clear("Device", Date.now()); + clear("InternetGatewayDevice", Date.now()); + +``commit()`` +~~~~~~~~~~~~ + +This function commits the pending declarations and performs any necessary sync +with the device. It's usually not required to call this function as it called +implicitly at the end of the script and when accessing any property of the +promise-like object returned by the ``declare()`` function. Calling this +explicitly is only necessary if you want to control the order in which +parameters are configured. + +``ext(file, function, arg1, arg2, ...)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Execute an extension script and return the result. The first argument is the +script filename while second argument is the function name within that script. +Any remaining arguments will be passed to that function. See :ref:`extensions` +for more details. + +.. _path-format: + +Path format +----------- + +A parameter path may contain a wildcard (``*``) or an alias filter +(``[name:value]``). A wildcard segment in a parameter path will apply the +declared configuration to zero or more parameters that match the given path +where the wildcard segment can be anything. + +An alias filter is like a wildcard, but additionally performs filtering on the +child parameters based on the key-value pairs provided. For example, the +following path: + +``Device.WANDevice.1.WANConnectionDevice.1.WANIPConnection.[AddressingType:DHCP].ExternalIPAddress`` + +will return a list of ExternalIPAddress parameters (0 or more) where the +sibling parameter AddressingType is assigned the value "DHCP". + +This can be useful when the exact instance numbers may be different from one +device to another. It is possible to use more than one key-value pair in the +alias filter. It's also possible to use multiple filters or use a combination +of filters and wildcards. + +Creating/deleting object instances +---------------------------------- + +Given the declarative nature of provisions, we cannot explicitly tell the +device to create or delete an instance under a given object. Instead, we +specify the number of instances we want there to be, and based on that GenieACS +will determine whether or not it needs to create or delete instances. For +example, the following declaration will ensure we have one and only one +WANIPConnection object: + +.. code:: javascript + + // Example: Ensure we have one and only one WANIPConnection object + declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.*", null, {path: 1}); + +Note the wildcard at the end of the parameter path. + +It is also possible to use alias filters as the last path segment which will +ensure that the declared number of instances is satisfied given the alias +filter: + +.. code:: javascript + + // Ensure that *all* other instances are deleted + declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[]", null, {path: 0}); + + // Add the two entries we care about + declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[SourceIPAddress:192.168.1.0,SourceNetMask:255.255.255.0]", {path: now}, {path: 1}); + declare("InternetGatewayDevice.X_BROADCOM_COM_IPAddrAccCtrl.X_BROADCOM_COM_IPAddrAccCtrlListCfg.[SourceIPAddress:172.16.12.0,SourceNetMask:255.255.0.0]", {path: now}, {path: 1}); + +Special GenieACS parameters +--------------------------- + +In addition to the parameters exposed in the device's data model through +TR-069, GenieACS has its own set of special parameters: + +``DeviceID`` +~~~~~~~~~~~~ + +This parameter sub-tree includes the following read-only parameters: + +- ``DeviceID.ID`` +- ``DeviceID.SerialNumber`` +- ``DeviceID.ProductClass`` +- ``DeviceID.OUI`` +- ``DeviceID.Manufacturer`` + +``Tags`` +~~~~~~~~ + +The ``Tags`` root parameter is used to expose device tags in the data model. +Tags appear as child parameters that are writable and have boolean value. +Setting a tag to ``false`` will delete that tag, and setting the value of a +non-existing tag parameter to ``true`` will create it. + +.. code:: javascript + + // Example: Remove "tag1", add "tag2", and read "tag3" + declare("Tags.tag1", null, {value: false}); + declare("Tags.tag2", null, {value: true}); + let tag3 = declare("Tags.tag3", {value: 1}); + +``Reboot`` +~~~~~~~~~~ + +The ``Reboot`` root parameter hold the timestamp of the last reboot command. +The parameter value is writable and declaring a timestamp value that is larger +than the current value will trigger a reboot. + +.. code:: javascript + + // Example: Reboot the device only if it hasn't been rebooted in the past 300 seconds + declare("Reboot", null, {value: Date.now() - (300 * 1000)}); + +``FactoryReset`` +~~~~~~~~~~~~~~~~ + +Works like ``Reboot`` parameter but for factory reset. + +.. code:: javascript + + // Example: Default the device to factory settings + declare("FactoryReset", null, {value: Date.now()}); + +``Downloads`` +~~~~~~~~~~~~~ + +The ``Downloads`` sub-tree holds information about the last download +command(s). A download command is represented as an instance (e.g. +``Downloads.1``) containing parameters such as ``Download`` (timestamp), +``LastFileType``, ``LastFileName``. The parameters ``FileType``, ``FileName``, +``TargetFileName`` and ``Download`` are writable and can be used to trigger a +new download. + +.. code:: javascript + + declare("Downloads.[FileType:1 Firmware Upgrade Image]", {path: 1}, {path: 1}); + declare("Downloads.[FileType:1 Firmware Upgrade Image].FileName", {value: 1}, {value: "firmware-2017.01.tar"}); + declare("Downloads.[FileType:1 Firmware Upgrade Image].Download", {value: 1}, {value: Date.now()}); + +Common file types are: + +- ``1 Firmware Upgrade Image`` +- ``2 Web Content`` +- ``3 Vendor Configuration File`` +- ``4 Tone File`` +- ``5 Ringer File`` + +.. warning:: + + Pushing a file to the device is often a service-interrupting operation. It's + recommended to only trigger it on certain events such as ``1 BOOT`` or during + a predetermined maintenance window). + +After the CPE had finished downloading and applying the config file, it will +send a ``7 TRANSFER COMPLETE`` event. You may use that to trigger a reboot +after the firmware image or configuration file had been applied. diff --git a/docs/roles-and-permissions.rst b/docs/roles-and-permissions.rst new file mode 100644 index 0000000..706eb61 --- /dev/null +++ b/docs/roles-and-permissions.rst @@ -0,0 +1,6 @@ +.. _roles-and-permissions: + +Roles and Permissions +===================== + +TODO diff --git a/docs/virtual-parameters.rst b/docs/virtual-parameters.rst new file mode 100644 index 0000000..beebb20 --- /dev/null +++ b/docs/virtual-parameters.rst @@ -0,0 +1,105 @@ +.. _virtual-parameters: + +Virtual Parameters +================== + +Virtual parameters are user-defined parameters whose values are generated using +a custom Javascript code. Virtual parameters behave just like regular +parameters and appear in the data model under ``VirtualParameters.`` path. +Virtual parameter names cannot contain a period (``.``). + +The execution environment for virtual parameters is almost identical to that of +provisions. See :ref:`provisions` for more details and examples. The only +differences between the scripts of provisions and virtual parameters are: + +- You can't pass custom arguments to virtual parameter scripts. Instead, the + variable ``args`` will hold the current vparam timestamps and values as well + as the declared timestamps and values. Like this: + +.. code:: javascript + + // [, , ] + [{path: 1559840000000, value: 1559840000000}, {value: ["cur val", "xsd:string"]}, {path: 1559849387191, value: 1559849387191}, {value: ["new val", "xsd:string"]}] + +- Virtual parameter scripts must return an object containing the attributes of + this parameter. + +.. note:: + + Just like a regular parameter, creating a virtual parameter does not + automatically add it to the parameter list for a device. It needs to fetched + (manually or via a preset) before you can see it in the data model. + +Examples +-------- + +Unified MAC parameter across different device models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: javascript + + // Example: Unified MAC parameter across different device models + let m = "00:00:00:00:00:00"; + let d = declare("Device.WANDevice.*.WANConnectionDevice.*.WANIPConnection.*.MACAddress", {value: Date.now()}); + let igd = declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANPPPConnection.*.MACAddress", {value: Date.now()}); + + if (d.size) { + for (let p of d) { + if (p.value[0]) { + m = p.value[0]; + break; + } + } + } + else if (igd.size) { + for (let p of igd) { + if (p.value[0]) { + m = p.value[0]; + break; + } + } + } + + return {writable: false, value: [m, "xsd:string"]}; + +Expose an external value as a virtual parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: javascript + + // Example: Expose an external value as a virtual parameter + let serial = declare("DeviceID.SerialNumber", {value: 1}); + if (values.value) { + ext("example-ext", "set", serial.value[0], values.value[0]); + return {writable: true, value: [values.value[0], "xsd:string"]}; + } + else { + let v = ext("example-ext", "get", serial.value[0]); + return {writable: true, value: [v, "xsd:string"]}; + } + +Create an editable virtual parameter for WPA passphrase +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: javascript + + // Example: Create an editable virtual parameter for WPA passphrase + let m = ""; + if (VALUES && VALUES.value) { + m = VALUES.value[0]; + declare("Device.WiFi.AccessPoint.1.Security.KeyPassphrase", null, {value: m}); + declare("InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", null, {value: m}); + } + else { + let d = declare("Device.WiFi.AccessPoint.1.Security.KeyPassphrase", {value: Date.now()}); + let igd = declare("InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase", {value: Date.now()}); + + if (d.size) { + m = d.value[0]; + } + else if (igd.size) { + m = igd.value[0]; + } + } + + return {writable: true, value: [m, "xsd:string"]};