1.0.22 • Published 8 days ago

confsync v1.0.22

Weekly downloads
-
License
MIT
Repository
github
Last release
8 days ago

Overview

ConfSync is a simple configuration file management system. It can manage and deploy all your configuration files for you, including revisions. The files are stored in a S3 bucket you specify, and are automatically installed onto your servers via our satellite agent. You control when and how they are deployed, using the CLI or API. After upload to S3, files will auto-install on all your servers in one minute or less (or your pizza is free).

The expectation is that your application will "hot reload" its configuration files when ConfSync updates them. If your app doesn't support this kind of thing, then you can have ConfSync "notify" your app to reload, via a custom signal sent to your process, a localhost web request, or a shell command.

ConfSync can be used as a basic feature flag system, as you can toggle JSON properties on/off as part of config updates. However, this is really rudimentary. If you need professional feature flagging, please check out LaunchDarkly, DevCycle, GrowthBook, or FlagSmith, as they are much more feature-rich.

Features

  • No hosted service or upsell.
  • No persistent or background daemon process.
  • No database whatsoever (uses S3 and only S3).
  • Completely private and on prem, or in your private cloud.
  • Software never calls home (also, there is no home).
  • Unlimited server groups, identified by any environment variables you select.
  • Unlimited config files, with unlimited revisions.
  • Config files may target any or all of your servers, also by env var match.
  • Config files can be in any format.
  • Special features for JSON and JSON5 config files.
  • Optional config overrides per server group.
  • Push and deploy a revision all on the CLI with one command.
  • Zero dependency satellite agent (static binary), runs via cron.
  • Config files can each have custom modes, UIDs and GIDs.
  • Config file updates are written atomically.
  • New file revisions can be partially deployed to specific groups.
  • Deploy can be separate from push (upload).
  • Gradual deploys with custom durations.
  • Custom app notification options for installed files.
  • Simple and easy rollbacks.
  • Show diffs between file revisions.
  • Web hooks for all actions.
  • GitHub Integration.

Table of Contents

The documentation is split up across these files:

Here is the table of contents for this document:

Architecture

ConfSync has two main components:

  1. The ConfSync CLI / API, which only needs to be installed once. It can run anywhere you like, such as an administrative server, or on your local machine. This is the main user interface to the system, and controls pushing files and triggering deployments. It ships as a Node.js package, so you will need to have Node.js preinstalled.
  2. ConfSync Satellite, which is a remote satellite agent that lives on all your servers. This "listens" (polls) for configuration changes in S3, and installs the correct files and revisions on each server. This ships as a single static binary executable with flavors for all popular architectures, and has zero dependencies. It runs via cron.

Glossary

Here are some common terms used in ConfSync:

TermDescription
Target GroupAlso referred to simply as a "group", this is a set of servers you classify by a custom ID and environment variable matching. You can then target server groups for deployments. For example, you can create groups for all of your server environments (Dev, Stage, Prod, etc.), and/or server class (application, database, etc.).
Config FileAlso referred to simply as a "file", this is a single configuration file under management by ConfSync. Specifically, this is a config file definition, not the file content (that comes later). You specify a custom file ID, the destination path on the server where it should (eventually) be installed, which servers it should be installed to (or simply all of them), and optional details like the file mode (permissions), UID, GID, and how to notify your app when the file changes.
RevisionA revision is a single version of a config file. You "push" revisions onto a list in S3, and you can deploy any revision live to any of your target groups. ConfSync keeps all revisions in S3 forever, so you can always rollback to older ones.
PushPushing involves uploading a local file (or changes to a file) up to S3. A push becomes a new revision in the S3 list for each file. You can also deploy the revision (i.e. make it live) at the same time as pushing.
DeployDeploying is setting a specific revision to be "live" in one or more of your server groups. This triggers the satellite agent to actually install the specified file revision onto the appropriate servers. Pushing and deploying can be done separately, or together with one command. Deploying can be instant, or gradual.

Setup

Use npm to install the ConfSync CLI:

$ sudo npm i -g confsync

You may need to be root or use sudo in order to install global commands.

This will add a single confsync command into your PATH.

You only need to install the CLI on one single server (your config management server), or a local machine that has access to your S3 bucket. See ConfSync Satellite for the agent that runs on all your target servers.

Configuration

The ConfSync CLI / API is configured via a single JSON file, which lives wherever the package is installed. You can determine the location of the file by typing:

$ confsync config

The file contains things like the debug log settings, web hooks, and AWS / S3 setup. You can edit the config file manually using your text editor of choice, or you can use command-line syntax like this to quickly set the essentials:

$ confsync config --Storage.AWS.region us-west-1 --Storage.AWS.credentials.accessKeyId YOUR_ACCESS_KEY --Storage.AWS.credentials.secretAccessKey YOUR_SECRET_KEY --Storage.S3.params.Bucket YOUR_S3_BUCKET --Storage.S3.keyPrefix YOUR_S3_PREFIX

Here are descriptions of all the config file properties:

Property PathTypeDescription
log_dirStringThe directory in which to place the ConfSync log file. This can be a relative or absolute path. Relative paths are calculated from the package root directory.
log_filenameStringThe log filename.
log_columnsArrayWhich columns to log. See pixl-logger for details.
debug_levelNumberThis controls how verbose the debug log messages are, from level 1 to 9. Errors and transactions are always logged, regardless of level.
colorBooleanThis controls whether ANSI colors are displayed in the CLI output on the terminal. Set this to false to disable all color.
cmd_suggestBooleanIf set to true, the CLI will emit helpful command suggestions for you.
web_hooksObjectOptionally fire off web hooks for any or all actions. See Web Hooks below.
web_hook_text_templatesObjectThis section controls the text message content sent with web hooks (for things like Slack, Discord, etc.).
Storage.transactionsBooleanEnable or disable storage transactions. Please leave this enabled, as it helps ensure data integrity in S3. See Storage Transactions for details.
Storage.trans_auto_recoverBooleanAutomatically recover after crashes or storage errors. Please leave this enabled, as it helps ensure data integrity in S3. See Storage Transactions for details.
Storage.concurrencyNumberThe maximum number of concurrent threads to use when reading/writing to S3.
Storage.list_page_sizeNumberThe number of items (revisions) per list page. Please do not change this. See Storage Lists if you are curious how this works.
Storage.engineStringThe storage engine to use. This should be set to S3. Support for other engines may be added in the future.
Storage.AWS.regionStringThe AWS region where your S3 bucket lives, e.g. us-west-1.
Storage.AWS.credentials.accessKeyIdStringYour AWS access account key ID. You can omit this if you have AWS authentication handled elsewhere (IAM, EC2, etc.).
Storage.AWS.credentials.secretAccessKeyStringYour AWS account secret key. You can omit this if you have AWS authentication handled elsewhere (IAM, EC2, etc.).
Storage.AWS.connectTimeoutNumberThe timeout for connecting to S3, in milliseconds.
Storage.AWS.socketTimeoutNumberThe idle socket timeout for communicating with S3, in milliseconds.
Storage.AWS.maxAttemptsNumberThe number of retry attempts to make for failed S3 operations (includes exponential backoff).
Storage.S3.keyPrefixStringOptionally prefix all the S3 keys with a directory, such as confsync/. Useful when pointing to a shared S3 bucket.
Storage.S3.fileExtensionsBooleanAdd a .json extension onto all S3 keys. It is highly recommended that you leave this enabled, as it allows your S3 bucket to be backed up / replicated more easily. See S3 File Extensions for details.
Storage.S3.prettyBooleanPretty-print all JSON records in S3. This increases S3 file sizes a bit, but it makes for easier debugging.
Storage.S3.params.BucketStringYour AWS S3 bucket name. Make sure the region matches!

Environment Variables

ConfSync can also be configured via environment variables. These can be declared in your shell environment where you use the CLI, or you can include a dotenv (.env) file in the package root directory. Either way, the variable name syntax is CONFSYNC_key where key is a JSON configuration property path.

For overriding configuration properties via environment variable, you can specify any top-level JSON key from config.json, or a path to a nested property using double-underscore (__) as a path separator. For boolean properties, you can specify 1 for true and 0 for false. Here is an example of some env vars:

CONFSYNC_debug_level=9
CONFSYNC_Storage__AWS__region="us-west-1"
CONFSYNC_Storage__AWS__credentials__accessKeyId="YOUR_AWS_ACCESS_KEY_HERE"
CONFSYNC_Storage__AWS__credentials__secretAccessKey="YOUR_AWS_SECRET_KEY_HERE"
CONFSYNC_Storage__S3__keyPrefix="YOUR_S3_KEY_PREFIX_HERE"
CONFSYNC_Storage__S3__params__Bucket="YOUR_S3_BUCKET_HERE"

Almost every configuration property can be overridden using this environment variable syntax. The only exceptions are things like arrays, e.g. log_columns.

Usage

Type confsync to get help, or confsync list to see a list of all your groups and files:

ConfSync CLI

See the Walkthrough / Tutorial or CLI Reference for more details on CLI usage.

Satellite

ConfSync Satellite is our remote agent that handles installing configuration files on all your servers. Basically, it "listens" (polls) for configuration changes in S3, and installs the correct files and revisions on each server. It ships as a single static binary executable with flavors for all popular architectures, and has zero dependencies.

Satellite does not run persistently in the background, meaning it is not a daemon process nor system service. Rather, it just registers itself with cron on install, and simply run every minute (or any frequently you prefer), checks for changes, installs or updates config files as needed, then exits.

See ConfSync Satellite for full details.

Install Notifications

Not only can ConfSync install your config files, but it can also notify your application when the file changes. Some applications may already have code that polls or otherwise listens for filesystem changes, but if not, ConfSync can help make this easier. It can send a signal to your process, send a request to your web service, or even execute a custom shell command. See below for details.

PID File / Signal

If your application writes out a "PID File" on startup, then ConfSync can read that file to determine your app's PID (Process ID), and send a signal to it. Typically this will be a SIGUSR1 or SIGUSR2, but it can be any signal you want. The idea here is, you can easily add a signal listener to your app, to trigger a config file reload. This eliminates the need to poll or watch the filesystem. To set this up, update your config file with a --pid and --signal like this:

$ confsync update myapp --pid /var/run/myapp.pid --signal SIGUSR2

✅ Success: Configuration file updated: `myapp` (My Great App)

 ┌─────────────────────────────────────────┐
 │ Config ID:  myapp                       │
 │ Title:      My Great App                │
 │ Path:       /opt/myapp/conf/config.json │
 │ Mode:       600                         │
 │ UID:        root                        │
 │ PID File:   /var/run/myapp.pid          │
 │ Signal:     SIGUSR2                     │
 │ Author:     jhuckaby                    │
 │ Created:    2023/10/04 10:44 AM         │
 │ Modified:   2023/10/11 11:07 AM         │
 │ Revisions:  5                           │
 │ Latest Rev: r5                          │
 └─────────────────────────────────────────┘

Now, whenever new revisions of the file are deployed, ConfSync Satellite will attempt to send a signal to your app, by first reading its PID from the /var/run/myapp.pid file, and then sending a SIGUSR2 signal to that process.

process.on('SIGUSR2', function() {
	console.log("Received SIGUSR2 signal!  Reloading config!");
	// reload your config file here
});
import signal
import os

# Define a custom signal handler for SIGUSR2
def sigusr2_handler(signum, frame):
    print("Received SIGUSR2 signal!  Reloading config!")
	# reload your config file here

# Register the custom handler for SIGUSR2
signal.signal(signal.SIGUSR2, sigusr2_handler)
declare(ticks = 1);

// Define a custom signal handler for SIGUSR2
function sigusr2_handler($signo) {
    echo "Received SIGUSR2 signal!  Reloading config!" . PHP_EOL;
	// reload your config file here
}

// Register the custom handler for SIGUSR2
pcntl_signal(SIGUSR2, "sigusr2_handler");
import sun.misc.Signal;
import sun.misc.SignalHandler;

Signal.handle(new Signal("USR2"), new SignalHandler() {
	public void handle(Signal sig) {
		System.out.println("Received SIGUSR2 signal!  Reloading config!");
		// reload your config file here
	}
});
use POSIX;

# Define a custom signal handler for SIGUSR2
sub sigusr2_handler {
    my $signal = shift;
    print "Received SIGUSR2 signal!  Reloading config!\n";
	# reload your config file here
}

# Register the custom handler for SIGUSR2
$SIG{USR2} = \&sigusr2_handler;

To remove the PID / Signal feature from a config file, perform another update and set both arguments to false:

$ confsync update myapp --pid false --signal false

Web Request

If your application has a web server, then ConfSync can send a web request to notify you that a config file has changed. For example, if your web app listens on port 3000, then ConfSync Satellite can send a local web request to http://localhost:3000/api/config/reload, or any URI path of your choice.

Note that ConfSync Satellite runs on each of your servers, so the request can (and should) be on the localhost loopback adapter. Meaning, you can fully lock down your API route, so it only accepts requests from a local IP address (127.0.0.1 or ::1), for security purposes.

To set this up, update your config file with a --webhook URL like this:

$ confsync update myapp --webhook "http://localhost:3000/api/config/reload"

✅ Success: Configuration file updated: `myapp` (My Great App)

 ┌─────────────────────────────────────────────────────┐
 │ Config ID:  myapp                                   │
 │ Title:      My Great App                            │
 │ Path:       /opt/myapp/conf/config.json             │
 │ Mode:       600                                     │
 │ UID:        root                                    │
 │ Web Hook:   http://localhost:3000/api/config/reload │
 │ Author:     jhuckaby                                │
 │ Created:    2023/10/04 10:44 AM                     │
 │ Modified:   2023/10/11 11:43 AM                     │
 │ Revisions:  5                                       │
 │ Latest Rev: r5                                      │
 └─────────────────────────────────────────────────────┘

ConfSync Satellite will now notify your app via web request, each time the /opt/myapp/conf/config.json file is updated. The request itself will be a HTTP POST, and the payload will be a JSON document that describes the file that was just installed or updated. Example request body (pretty-printed):

{
	"title": "My Great App",
	"id": "myapp",
	"username": "jhuckaby",
	"path": "/opt/myapp/conf/config.json",
	"web_hook": "http://localhost:3000/api/config/reload",
	"modified": 1697049843.871,
	"created": 1696441493.515,
	"live": {
		"dev": {
			"rev": "r5",
			"start": 1696993875.524,
			"duration": 600
		},
		"prod": {
			"rev": "r5",
			"start": 1696993875.524,
			"duration": 600
		}
	},
	"mode": "600",
	"uid": "root",
	"rev": "r5"
}

The User-Agent header for these requests will be set to ConfSync Satellite v#.#.# (where #.#.# will be 1.0.0 or higher).

To remove the Web Request feature from a config file, set the --webhook switch to false:

$ confsync update myapp --webhook false

Shell Exec

ConfSync can optionally run a custom shell command whenever a file is installed on one of your servers. This can come in handy if you want your application to be completely restarted when its config file is updated. Note that this feature is inherently dangerous (i.e. storing shell commands in S3 that can be executed on all your servers), so it is disabled by default in ConfSync Satellite, and must be explicitly enabled when you install it on each of your servers.

Once you have enabled the feature, you can add a shell command to your config files by calling update with a special --exec switch. Example:

$ confsync update myapp --exec "systemctl restart myapp"

✅ Success: Configuration file updated: `myapp` (My Great App)

 ┌─────────────────────────────────────────┐
 │ Config ID:  myapp                       │
 │ Title:      My Great App                │
 │ Path:       /opt/myapp/conf/config.json │
 │ Mode:       600                         │
 │ UID:        root                        │
 │ Shell Exec: systemctl restart myapp     │
 │ Author:     jhuckaby                    │
 │ Created:    2023/10/04 10:44 AM         │
 │ Modified:   2023/10/11 3:30 PM          │
 │ Revisions:  5                           │
 │ Latest Rev: r5                          │
 └─────────────────────────────────────────┘

This will instruct ConfSync Satellite to execute the shell command systemctl restart myapp each time the /opt/myapp/conf/config.json file is updated.

To remove the Shell Exec feature from a config file, set the --exec switch to false:

$ confsync update myapp --exec false

Web Hooks

ConfSync supports the concept of "web hooks", meaning it can fire off a web request to a custom URL in response to certain actions. These can be configured both in the CLI / API, and in ConfSync Satellite. For the CLI / API, each action triggers a single web request. For Satellite, web requests are sent from all of your servers where the install occurred.

To setup web hooks in the CLI / API, edit your config.json file and populate the web_hooks object thusly:

"web_hooks": {
	"deploy": "https://hooks.slack.com/services/TO8ZZFDBQ/BC6ZZNG14/pJKFjZZI"
}

You can alternatively add hooks using the config command:

$ confsync config --web_hooks.deploy "https://hooks.slack.com/services/TO8ZZFDBQ/BC6ZZNG14/pJKFjZZI"

This example adds a web hook for the deploy action specifically, and targets a custom Slack Bot. The web request itself will be a HTTP POST, and the payload will be a JSON document that describes the action that took place. Example request body (pretty-printed):

{
	"id": "myapp",
	"username": "jhuckaby",
	"groups": [ "dev", "prod" ],
	"rev": "r1",
	"text": "Configuration file `myapp` revision `r1` deployed by `jhuckaby` to groups: **dev,prod**",
	"code": "deploy",
	"msg": "myapp",
	"content": "Configuration file `myapp` revision `r1` deployed by `jhuckaby` to groups: **dev,prod**"
}

Each action will include an appropriate Markdown-formatted summary, stored in both text and content properties, made to be compatible with most chat apps (Slack, Discord, etc.). To customize these text templates, see the web_hook_text_templates property in your config.json file.

There are actually several actions you can hook, including a special "universal" hook which fires for all of them:

Web Hook IDDescription
addGroupFires when a new group is added.
updateGroupFires when an existing group is updated.
deleteGroupFires when a group is deleted.
addConfigFileFires when a new config file is added.
updateConfigFileFires when an existing config file is updated.
deleteConfigFileFires when a config file is deleted.
pushFires when a new file revision is pushed to S3.
deployFires when a file revision is deployed live.
universalFires when any action occurs.--

For samples of each web hook JSON payload, see the Web Hook Samples directory.

The User-Agent header for these requests will be set to ConfSync v#.#.# (where #.#.# will be 1.0.0 or higher).

Logging

ConfSync keeps its own log file, which contains all errors, transactions and debug messages. By default, this log file is created in a logs subdirectory under the package root, and is named confsync.log. Here is an example log snippet:

[1697078909.542][2023-10-11 19:48:29][joemax.local][30908][ConfSync][debug][3][ConfSync v1.0.0 starting up][["/usr/local/bin/node","/Users/jhuckaby/git/confsync/cli.js","group","delete","prod"]]
[1697078910.136][2023-10-11 19:48:30][joemax.local][30908][ConfSync][transaction][deleteGroup][prod][{"id":"prod","username":"jhuckaby","text":"Configuration target group deleted: `prod`"}]

The top-level debug_level property in your config.json controls how verbose the debug log entries are. A debug_level level of 1 is the most quiet, and only contains transactions and errors. A level of 5 is a fair bit louder, and level 9 is the loudest. Use these higher levels for troubleshooting issues.

You can customize the location and filename of the log file by including top-level log_dir and log_filename properties in your config.json file.

You can also optionally customize the log "columns" that are written out. By default, the following columns are written for each row:

Log ColumnDescription
hires_epochThis is a high-resolution Epoch timestamp.
dateThis is a human-readable date/time stamp in the format: YYYY-MM-DD HH:MI:SS (in the local server timezone).
hostnameThis is the hostname of the server or PC/Mac running the ConfSync CLI / API.
pidThis is the Process ID (PID) of the ConfSync process running on the server.
componentThis is the name of the current component, or simply ConfSync for generic messages.
codeThis is the error code, transaction code, or debug log level of the message, from 1 to 9.
msgThis is the log message text itself.
dataAny additional data that accompanies the message will be in this column, in JSON format.

To customize the log columns, include a top-level log_columns property in your config.json file, and set it to an array of strings, where each string specifies the column. Example:

"log_columns": ["hires_epoch", "date", "hostname", "pid", "component", "code", "msg", "data"]

Upgrading

To upgrade ConfSync, first backup your config and logs using the following command (or else NPM will wipe them out!):

confsync uninstall

Then repeat the original NPM install command:

sudo npm i -g confsync

This will install the latest version, and it will restore your previously backed up config.json file and logs. Note that the command may need to be executed as root or with sudo.

Estimated S3 Costs

ConfSync Satellite does query S3 once per minute per server (by default), to poll for config file changes. Luckily, this poll operation only involves a single S3 GET, and AWS charges $0.00044 USD per 1,000 GET requests (in us-west-1, as of this writing). So here is how that will affect your monthly AWS bill:

# of ServersTotal Cost Per Month
1$0.01 USD
10$0.19 USD
100$1.90 USD
1,000$19.00 USD
10,000$190.00 USD

You can further reduce these costs by configuring Satellite to only poll every other minute (half cost), or every 5 minutes (20% cost). See ConfSync Satellite for details.

You may be tempted to configure Satellite's crontab to skip polling on weekends, or after work hours. Trust me: don't do this. Those are the times when you will need ConfSync the most, e.g. to perform an emergency midnight rollback of a botched config file, or fix some weekend panic crisis.

See Also

License

The MIT License (MIT)

Copyright (c) 2023 - 2024 Joseph Huckaby

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1.0.22

8 days ago

1.0.21

12 days ago

1.0.20

12 days ago

1.0.19

13 days ago

1.0.18

29 days ago

1.0.17

5 months ago

1.0.16

5 months ago

1.0.15

5 months ago

1.0.14

5 months ago

1.0.13

5 months ago

1.0.12

5 months ago

1.0.11

5 months ago

1.0.10

5 months ago

1.0.9

5 months ago

1.0.8

6 months ago

1.0.7

7 months ago

1.0.6

7 months ago

1.0.5

7 months ago

1.0.4

7 months ago

1.0.3

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago