Configuring Heedy#

If you are an admin of your heedy instance, you can perform all server maintenance and apply configuration changes directly from the “Server Config” UI. You have the option of performing a full database backup when restarting on each configuration change made from the web UI, so if the server fails to restart for any reason, any changes you made will be automatically reverted.

Installing Plugins#

You can install a plugin using the web UI by uploading a zip file containing the plugin folder. Plugin Upload UI

You will be prompted to restart heedy to activate the plugin. It is highly recommended that you check the box for a database backup whenever installing new plugins. That way, if there are issues, heedy can revert the entire update, including any changes to the database.

Manually Installing Plugins#

You can also manually upload a plugin by extracting the zip file, and placing the plugin folder in the plugins subdirectory of your database folder.

That is, suppose your heedy database folder is at ./myheedy. Then installing the plugin myplugin can be achieved as follows using bash:

unzip myplugin-1.0.0.zip
mkdir ./myheedy/plugins
mv myplugin ./myheedy/plugins/

You will then need to enable the plugin by modifying your heedy.conf vim ./myheedy/heedy.conf to add myplugin to the active plugins array:

active_plugins = ["fitbit", "notebook", "myplugin"];

You will need to restart heedy for the changes to take effect.

Default Configuration#

Your heedy.conf is simply overriding the configuration options defined in the plugins you installed, as well as the core built-in configuration. You can modify most of these options by setting their values in your heedy.conf.

The built-in configuration, shown here, contains a lot of heedy’s internals, including the configurations for the plugins that are built into heedy by default. For this reason, unless you know what you are doing, it is recommended that you leave most options at their default values.

/*
    This file represents the default configuration for a heedy server.

    You can override any of these options in your server's heedy.conf,
    or in your plugin's configuration file.

*/

// By default, listen on all interfaces. 
// Set to "localhost:1324" to only listen on localhost
addr=":1324"

// The external URL of the heedy instance. If blank, set to addr
url=""

// Backend API socket address, used by plugins to interact with heedy
api = "unix:heedy.sock"

// The list of users who are given administrative permissions. 
// The user created when setting up heedy is automatically added here
admin_users = []

// These are the builtin plugins that are active by default.
active_plugins = ["notifications","timeseries","python","kv"]

// The log levels (debug,info,warn,error)
log_level = "info"
// The folder in which to write log files. If stdout, writes to stdout
log_dir = "stdout"

// Forbid the following usernames from being created
forbidden_users = ["admin","heedy","public","users"]

// The SQL app string to use to connect to the database, in the form:
//  <sql type>://<app string>
// By default, heedy uses an sqlite3 database saved in the data subfolder
sql = "sqlite3://heedy.db?_journal=WAL&_busy_timeout=5000"

// frontend gives the javascript module which implements the main UI
frontend = "heedy/main.mjs"
// preload is a list of modules which will be required for the frontend to load
preload = ["main.mjs","dist/vue.mjs","util.mjs","dist/date-fns.mjs","worker.mjs",
    "dist/codemirror.mjs","dist/draggable.mjs","dist/markdown-it.mjs"]

// The number of bytes to allow in a REST request body. 
// NOTE: This does not apply to datapoint inserts in timeseries, 
// which are allowed to be of arbitrary size 
// TODO: currently it DOES apply to datapoint inserts.
request_body_byte_limit = 4e+6

// Whether or not to permit the public to connect to websockets.
// Note that even if true, they will only have public-level access to events.
// This allows public not to take websocket resources from users
allow_public_websocket = false

// Event websockets will send a heartbeat message (ping/pong) if this time elapses
// with no other messages received. If 0, auto heartbeat is disabled.
websocket_heartbeat = "15m"

// If writing a message to an event websocket takes longer than this, it will be closed.
// It will also be closed if a heartbeat ping takes longer than this for a round-trip
websocket_write_timeout = "5s"

// The timeout between asking a plugin nicely to shut down and killing it.
run_timeout = "10s"

// The size in bytes of the HTTP logger for logging in verbose mode.
// The log will be truncated after this many bytes of the request/response
verbose_log_buffer = 1024

// The number of backups to keep when updating things. Once there are more backups,
// older ones are deleted.
max_backup_count = 3

// Runtypes that come compiled into heedy's core. The builtin runtype refers to
// built-in code that is run on the given key. The exec runtype allows plugins
// to run arbitrary executables as follows:
//      plugin "myplugin" {
//          run "myexecutable" {
//              type="exec"
//              cmd=["./myexecutable","--arg1"]
//          }
//      }
runtype "builtin" {
    config_schema = {
        "key": {"type": "string"},
        "required": ["key"]
    }
}
runtype "exec" {
    config_schema = {
        "cmd": {"type": "array", "items": {"type": "string"}, "minItems": 1},
        "api": {"type": "string"},
        "required": ["cmd"]
    }
}

// -----------------------------------------------------------------------------
// NOTIFICATIONS
// 

plugin "notifications" {
    version= version
    description= "Allow plugins and apps to notify a user of their status"
    frontend= "notifications/main.mjs"

    run "backend" {
        type = "builtin"
        key = "notifications"
    }

    routes = {
        "/api/notifications": "run:backend"
    }
}


// -----------------------------------------------------------------------------
// DASHBOARD
// 

plugin "dashboard" {
    version= version
    description= join("Dashboards are a builtin object that handles",
                        " display and visualization of info from various sources")
    frontend= "dashboard/main.mjs"

    run "backend" {
        type = "builtin"
        key = "dashboard"
    }

    config_schema = {
        "types": {
            "type": "object",
            "description": "The definitions of all dashboard types",
            "default": {},
            "additionalProperties": { 
                "type": "object", 
                "properties": {
                    "api": {
                        "type":"string"
                    },
                    "query_schema": {
                        "$ref": "http://json-schema.org/draft-07/schema",
                        "default": {}
                    },
                    "frontend_schema": {
                        "$ref": "http://json-schema.org/draft-07/schema",
                        "default": {}
                    }
                },
                "required": ["api"]
            
            }
        }
    }

    types = {
        "dataset": {
            "api": "run:timeseries.backend/dashboard"
        }
    }

}

// The dashboard object is built in - its implementation comes as part of the 
// dashboard plugin. Note that the builtin dashboard object cannot be disabled, 
// even if the plugin itself is inactive. This is because an object type 
// is globally defined in the configuration.
type "dashboard" {

    // meta_schema gives the schema required for dashboard metadata 
    // (in the "meta" field of the dashboard object)
    meta_schema = {}

    routes = {
        "/dashboard": "run:dashboard.backend/object"
        "/dashboard/*": "run:dashboard.backend/object"
    }
}

// -----------------------------------------------------------------------------
// TIMESERIES
// 

plugin "timeseries" {
    version= version
    description= join("A builtin plugin that handles",
                        " time-series data and visualization")
    frontend= "timeseries/main.mjs"
    preload=["dist/json-schema.mjs","dist/downsample.mjs","dist/v-calendar.mjs","timeseries/worker.mjs"]

    run "backend" {
        type = "builtin"
        key = "timeseries"
    }

    routes = {
        "/api/timeseries/*": "run:backend"
    }

    config_schema = {
        "batch_size": {
            "type": "integer",
            "description": "Size of an average batch of datapoints in the database",
            "default": 1024
        },
        "max_batch_size": {
            "type": "integer",
            "description": "Maximum size of a batch allowed before splitting",
            "default": 2047
        },
        "batch_compression_level": {
            "type": "integer",
            "description": join("Compression level to use when writing batches to database.",
                         "-1 means no compression - but uncompressed databases are incompatible with compressed"),
            "default": 2
        },
        "compress_query_response": {
            "type": "boolean",
            "description": "Whether or not to compress timeseries responses if supported",
            "default": true
        }
    }

    user_settings_schema = {
        "visualizations": {
            "type": "array",
            "description": "List of javascript functions that customize the visualization of datasets",
            "items": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of the visualization"
                    },
                    "enabled": {
                        "type": "boolean",
                        "description": "Whether or not to enable the visualization",
                        "default": true
                    },
                    "code": {
                        "type": "string",
                        "description": "JavaScript function that returns a visualization object"
                    },
                    "test_query": {
                        "type": "object",
                        "description": "The default query to use when testing the visualization",
                    }
                },
                "required": ["name", "code", "enabled"]
            },
            "default": []
        }
    }

}

// The timeseries object is built in - its implementation comes as part of the 
// timeseries plugin. Note that the builtin timeseries object cannot be disabled, 
// even if the plugin itself is inactive. This is because an object type 
// is globally defined in the configuration.
type "timeseries" {

    // meta_schema gives the schema required for timeseries metadata 
    // (in the "meta" field of the timeseries object)
    meta_schema = {
        "schema": {
            "$ref": "http://json-schema.org/draft-07/schema",
            "default": {}
        },
        "required": ["schema"]
    }

    routes = {
        "/timeseries": "run:timeseries.backend/object"
        "/timeseries/*": "run:timeseries.backend/object"
        "/data": "run:timeseries.backend/object"
        "/data/*": "run:timeseries.backend/object"
        // "/actions": "run:timeseries.backend/object"
        // "/actions/*": "run:timeseries.backend/object"
        // "/act": "run:timeseries.backend/object"
    }

    // These are the scopes defined specifically for timeseries
    scope = {
        // "act": "Allows intervention"
    }

}

// -----------------------------------------------------------------------------
// KV
// 

plugin "kv" {
    version = version
    description = "Key-value storage for apps and plugins"

    run "backend" {
        type = "builtin"
        key = "kv"
    }

    routes = {
        "/api/kv/*": "run:backend"
    }
}

// -----------------------------------------------------------------------------
// PYTHON
// 

plugin "python" {
    version= version
    description= "Support for running python-based plugins"

    run "backend" {
        type = "builtin"
        key = "python"
    }

    config_schema = {
        "path": {
            "type": "string",
            "description": "Path to the python interpreter to use",
            "default": ""
        },
        "pip_args": {
            "type":"array",
            "items": {"type": "string"},
            "description": join(
                    "Command-line arguments to pass to pip (pip install {args}",
                    " mypackage or pip install {args} -r requirements.txt)"),
            "default": []
        },
        "venv_args": {
            "type":"array",
            "items": {"type": "string"},
            "description": join(
                    "Command-line arguments to pass to venv",
                    " (python -m venv venv/myplugin {args})"),
            "default": []
        },
        "per_plugin_venv": {
            "type": "boolean",
            "description": join(
                "Should a venv be created for each python plugin?",
                "Or should it just use the configured python?"
            ),
            "default": true
        },
        "validate_python": {
            "type": "boolean",
            "description": "Should the interpreter at path be checked for validity?",
            "default": true
        },
        "validate_venv": {
            "type": "boolean",
            "description": "Should the interpreter in each plugin venv be checked for validity?",
            "default": true
        },
    }
}

// The python runtype allows running a python file using the Python interpreter
// configured in heedy. Furthermore, it also makes sure any dependecies
// in a requirements.txt are installed before running the file.
runtype "python" {
    config_schema = {
        "path": {
            "type": "string"
        },
        "args": {
            "type": "array",
            "items": {"type": "string"},
            "default": []
        },
        "api": {"type": "string"},
        "required": ["path"]
    }
    api = "run:python.backend/runtypes/python"
}