Skip to content

Linux Server Sink

varTrack writes config values as a file on a remote Linux server over SSH (SFTP). Useful for servers that read configuration from flat files (.env, .properties, JSON, YAML) and can't use a central datastore.


Configuration

datasources: [{
  linux_server: {
    tag:      ""                         // optional — resolves to "linux_server-{tag}"
    host:     "server.example.com"
    port:     22                         // optional, default 22
    username: "deploy"

    // One of password or ssh_key:
    ssh_key: {private_key: {file: "/run/secrets/id_rsa"}}
    // password: {file: "/run/secrets/ssh-password"}

    base_directory: "/etc/app"
    file_paths:     ["app.env"]          // the file written; relative paths are under base_directory
    env_as_path:    false                // true: one directory per environment

    known_hosts_file: "/etc/vartrack/known_hosts"   // optional
  }
}]

The SSH key is read from a file, so give private_key as {file: "/path"}. password takes any credential form: a string, {file: …} or {ref: …} from Vault.


Where the file goes

Setting production is written to
default /etc/app/app_production.env
env_as_path: true /etc/app/production/app.env
rule destination_template: "/etc/app/{env}.env" /etc/app/production.env

destination_template on the rule takes precedence over base_directory and file_paths. Missing directories are created.

The file's extension sets its format: .env writes key=value lines; .json, .yaml, .properties and .ini are written in their own format. Keys are written as they come out of the config file, sorted; .env, .properties and .ini files start with a header line:

# /etc/app/production/app.env — from a YAML file with database.host, feature.dark_mode, max_connections
# vartrack managed — env=production tenant=default
database.host=mongo.prod.internal
feature.dark_mode=true
max_connections=50

Host keys

linux_server: {
  host:       "app-01.example.com"
  username:   "deploy"
  password:   {file: "/run/secrets/ssh-password"}
  file_paths: ["/etc/myapp/app.env"]

  host_key_verification: "strict"     // "strict" (default), "accept-new", "off"
  known_hosts_file:      "/etc/vartrack/known_hosts"
}
Mode Behaviour
strict The host's key must already be in known_hosts_file (or the orchestrator's system known hosts)
accept-new Needs known_hosts_file. A host seen for the first time is accepted and its key is written to that file before the first command runs, so the next connection checks it; a changed key is refused
off Any key is accepted and nothing is recorded. Every connection logs a warning: a man in the middle would not be detected. Not recommended

Writing the file

linux_server: {
  host:       "app-01.example.com"
  username:   "deploy"
  password:   {file: "/run/secrets/ssh-password"}
  file_paths: ["/etc/myapp/app.env"]

  file_mode:           "0640"
  file_owner:          "app:app"          // chown; usually needs use_sudo
  create_directories:  true               // default; false fails when the directory is missing
  backup_before_write: true
  backup_suffix:       ".bak"             // default
  validation_command:          "nginx -t -c {file}"
  validate_after_rename:       false          // default; true checks the file in place instead (see below)
  rollback_on_validation_fail: true           // only with validate_after_rename
  post_write_commands:         ["systemctl reload nginx"]
  continue_on_command_error:   false
}

In order: missing directories are created, the previous file is copied to {file}{backup_suffix} when backup_before_write is on, and the new content is written to a temp file next to the target (.<name>.vt-<id>.tmp, in the same directory so the rename stays on one filesystem). The temp file gets the mode and owner of the file it replaces, then file_mode and file_owner are applied. validation_command runs on the temp file — {file} is the temp file, {target} the path it is going to — and when it fails the sync fails and the target is untouched. Then one rename puts the new file in place, so a service reading (or reloading on change) sees the old file or the new one, never half of either. Last, post_write_commands run in order: a failing one fails the sync unless continue_on_command_error is on.

A write that dies between its steps leaves the old file and at most a temp file, which the next write of that path removes.

Over SFTP the rename is OpenSSH's posix-rename; a server without it is refused with an error naming use_sftp: false, which replaces the file with mv -f over a shell. varTrack does not fall back to remove-then-rename, which has a moment with no file.

A validation_command that needs the file at its final path (an include path, a path inside the file) can set validate_after_rename: true: the file is replaced first and validated in place, and rollback_on_validation_fail then puts the previous file back when it fails. A reader can see the new file before it is validated in that mode.

Transfer, sudo and connection

Field Effect
use_sftp Files move over SFTP (default); false moves them through shell commands, for servers without SFTP
use_sudo, sudo_password File operations and commands run with sudo; the file is uploaded to /tmp first and installed from there. sudo_password takes any credential form
connection_timeout, command_timeout Limits for connecting and for each command
max_retries, retry_backoff Connection attempts after the first, and the wait between them; a changed host key is never retried
enable_compression SSH compression

Files are written as the SSH user (or root with use_sudo); without file_mode they get that user's default permissions.


Drift detection

The watcher asks the orchestrator each poll; the orchestrator reads the remote file over SSH and compares its keys with what it last synced from Git — a changed or missing key is drift (Drift Detection).

rules: [{
  platform:   "github"
  datasource: "linux_server"
  file_name: "app-settings.yaml"
  branch_map: {main: "production"}
  self_heal:  true
}]