# Build the configuration

A form that writes the config.yaml a normal run takes and the workflow step the Action takes, from the binary's own list of settings.

Source: https://docs.jmrp.io/ghchronicle/configuration/builder/

Answer what your deployment is, and this writes the three things that run it:
the `config.yaml` a normal run reads, the command that runs it, and the step a
workflow gives the composite Action. All three come from one set of answers,
and none of them asks for a credential.

## What the form allows and the parser refuses

A form is a form: a destination is a checkbox and the keys it cannot resolve
without are not, so a few clicks are enough to write a file that dies at
start-up. The page warns about the ones its own generated list of settings can
see, above the outputs, and names the rest here. Refusing the rest in the form
would mean keeping a second copy of a parser rule on this page, which is the
drift the whole arrangement exists to avoid.

- A destination switched on with a required key left empty is refused by name,
  with an example: `sinks.loki: url is required, for example
  http://loki:3100`. The form warns about this one.
- A credential field answered with the credential instead of the name of an
  environment variable is left out of the file. The form warns about this one.
- A file with nothing in it is refused: `the file is empty`.
- No account at all is refused: `targets: set at least one of user, orgs or
  repos`.
- `heartbeat: 0` is refused: it must be positive, and leaving the key out is
  how the loop's tick is derived instead.
- A cadence that is not a duration is refused where it is used:
  `every.families.repo: time: invalid duration "soon"`.
- `sinks.elasticsearch.api_key` filled in beside `sinks.elasticsearch.username`
  is refused: `sinks.elasticsearch: set either api_key or username and
  password, not both`.
- Writing `-` as `sinks.sql.path` while `sinks.stdout` is on is accepted and
  gives two writers one stream; pick one of them.

> **What the form does with a credential**
>
> Where a setting is a credential, the form asks for the NAME of an environment
> variable and writes `${NAME}` into the file. That is the expansion the binary
> performs at start-up, and it is what lets the file be committed. An answer
> that is not a variable name is not written at all: the form says so and
> leaves the key out, rather than folding a pasted token into a reference to a
> variable that will never exist. One field is not covered by that,
> `sinks.otlp.headers`, which is free text because a header is not always a
> credential: what you type there is written as it stands, so put a `${VAR}`
> reference in it. The Action's token is a repository secret, which is what the
> step references.

## The form

This page is a form that writes a configuration. What follows is the inventory it offers, which is generated from the binary's own types, and the three outputs it starts from.

- **The account and its API**

  - `github`: a block of settings
  - `github.token`: string, required, a credential, like `${GITHUB_TOKEN}`
  - `github.base_url`: string, like `https://github.example.com/api/v3`
  - `github.web_url`: string, like `https://github.example.com`
  - `github.timeout`: string, defaults to `30s`, like `30s`
  - `github.reserve_rate`: int, defaults to `500`, like `500`

- **What to collect**

  - `targets`: a block of settings
  - `targets.user`: string, like `your-github-login`
  - `targets.orgs`: list, like `some-org, another-org`
  - `targets.repos`: list, like `someone/one-repo`
  - `targets.exclude`: list, like `someone/experiment-*`
  - `targets.include_forks`: bool, defaults to `false`, like `false`
  - `targets.include_archived`: bool, defaults to `false`, like `false`
  - `targets.include_private`: bool, defaults to `true`, like `true`

- **Where the points go**

  - `sinks`: a block of settings
  - `sinks.influxdb`: a block of settings
  - `sinks.influxdb.url`: string, required, like `http://localhost:8181`
  - `sinks.influxdb.token`: string, a credential, like `${INFLUX_TOKEN}`
  - `sinks.influxdb.org`: string, defaults to `default`, like `default`
  - `sinks.influxdb.bucket`: string, required, like `github`
  - `sinks.influxdb.batch`: int, like `5000`
  - `sinks.influxdb.exclude`: list, defaults to `gh_job_log`, like `gh_job_log`
  - `sinks.influxdb.dedupe`: bool, defaults to `true`, like `true`
  - `sinks.prometheus`: a block of settings
  - `sinks.prometheus.listen`: string, defaults to `:9605`, like `127.0.0.1:9605`
  - `sinks.prometheus.path`: string, defaults to `/metrics`, like `/metrics`
  - `sinks.prometheus.no_prime`: bool, defaults to `false`, like `false`
  - `sinks.otlp`: a block of settings
  - `sinks.otlp.endpoint`: string, required, like `http://collector:4318/v1/metrics`
  - `sinks.otlp.headers`: map, a credential, like `Authorization: Bearer ${OTLP_TOKEN}`
  - `sinks.otlp.service`: string, like `ghchronicle`
  - `sinks.otlp.raw`: bool, defaults to `false`, like `false`
  - `sinks.otlp.batch`: int, like `2000`
  - `sinks.otlp.repeat`: string, like `1m`
  - `sinks.loki`: a block of settings
  - `sinks.loki.url`: string, required, like `http://loki:3100/loki/api/v1/push`
  - `sinks.loki.tenant_id`: string, like `tenant-one`
  - `sinks.loki.labels`: map, like `job: ghchronicle`
  - `sinks.loki.batch`: int, like `1000`
  - `sinks.loki.max_age`: string, like `1h`
  - `sinks.file`: a block of settings
  - `sinks.file.path`: string, required, like `/var/log/ghchronicle/points.lp`
  - `sinks.file.format`: string, one of `influx`, `json`
  - `sinks.file.max_bytes`: int, like `67108864`
  - `sinks.file.keep`: int, like `5`
  - `sinks.stdout`: bool, defaults to `false`, like `false`
  - `sinks.stdout_format`: string, one of `influx`, `json`
  - `sinks.telegraf`: a block of settings
  - `sinks.telegraf.url`: string, required, like `http://telegraf:8186/telegraf`
  - `sinks.telegraf.username`: string, like `telegraf`
  - `sinks.telegraf.password`: string, a credential, like `${TELEGRAF_PASSWORD}`
  - `sinks.telegraf.batch`: int, like `5000`
  - `sinks.telegraf.dedupe`: bool, defaults to `true`, like `true`
  - `sinks.graphite`: a block of settings
  - `sinks.graphite.addr`: string, required, like `graphite:2003`
  - `sinks.graphite.prefix`: string, defaults to `github`, like `github`
  - `sinks.graphite.batch`: int, like `1000`
  - `sinks.graphite.dedupe`: bool, defaults to `true`, like `true`
  - `sinks.sql`: a block of settings
  - `sinks.sql.dialect`: string, defaults to `postgres`, one of `postgres`
  - `sinks.sql.path`: string, required, like `/var/lib/ghchronicle/points.sql`
  - `sinks.sql.max_bytes`: int, like `67108864`
  - `sinks.sql.keep`: int, like `5`
  - `sinks.sql.dedupe`: bool, defaults to `true`, like `true`
  - `sinks.postgres`: a block of settings
  - `sinks.postgres.dsn`: string, required, a credential, like `${DATABASE_URL}`
  - `sinks.postgres.batch`: int, like `1000`
  - `sinks.postgres.dedupe`: bool, like `true`
  - `sinks.elasticsearch`: a block of settings
  - `sinks.elasticsearch.url`: string, required, like `http://elasticsearch:9200`
  - `sinks.elasticsearch.prefix`: string, defaults to `ghchronicle`, like `ghchronicle`
  - `sinks.elasticsearch.username`: string, like `elastic`
  - `sinks.elasticsearch.password`: string, a credential, like `${ES_PASSWORD}`
  - `sinks.elasticsearch.api_key`: string, a credential, like `${ES_API_KEY}`
  - `sinks.elasticsearch.batch`: int, like `1000`
  - `sinks.elasticsearch.dedupe`: bool, defaults to `true`, like `true`
  - `sinks.dedupe_file`: string, defaults to `ghchronicle-state-written.bin`, like `/var/lib/ghchronicle/state-written.bin`
  - `sinks.dedupe_horizon`: string, defaults to `720h`, like `720h`

- **How often**

  - `every`: a block of settings
  - `every.default`: string, like `15m`
  - `every.groups`: map, like `ci: 1m`
  - `every.families`: map, like `deps: 24h`

- **The run's own log**

  - `log`: a block of settings
  - `log.level`: string, defaults to `info`, one of `debug`, `info`, `warn`, `error`
  - `log.format`: string, defaults to `text`, one of `text`, `json`
  - `log.file`: string, like `/var/log/ghchronicle/ghchronicle.log`
  - `log.max_bytes`: int, like `67108864`
  - `log.keep`: int, like `5`

- **The run**

  - `heartbeat`: string, like `15s`
  - `groups`: list, like `audience, account, repos`
  - `state_file`: string, defaults to `ghchronicle-state.json`, like `/var/lib/ghchronicle/state.json`
  - `backfill`: a block of settings
  - `backfill.since`: string, like `2y`
  - `migrate`: string, defaults to `auto`, one of `auto`, `warn`
  - `grafana`: a block of settings
  - `grafana.url`: string, like `http://localhost:3000`
  - `grafana.token`: string, a credential, like `${GRAFANA_TOKEN}`
  - `grafana.user`: string, like `admin`
  - `grafana.password`: string, a credential, like `${GRAFANA_PASSWORD}`
  - `grafana.folder`: string, like `GitHub`
  - `grafana.publish_on_start`: bool, like `false`
  - `grafana.dashboard_uid`: string, like `my-existing-dashboard`
  - `grafana.datasource`: a block of settings
  - `grafana.datasource.url`: string, like `http://influxdb:8181`
  - `grafana.datasource.uid`: string, like `ae3x9k2`
  - `grafana.datasource.sslmode`: string, like `require`
  - `grafana.datasource.loki_uid`: string, like `be7m1q4`

- **config.yaml**

  Save this beside the binary, or at the path you give to -config, and run it with the command below.

  ```yaml
  github:
    token: ${GITHUB_TOKEN}
  targets:
    user: octocat
  sinks:
    stdout: true
  ```

- **The command that runs it**

  ```sh
  ghchronicle -config config.yaml -once
  ```

- **Workflow step**

  These answers need settings no input carries, so the step reads the file above. Commit it at that path.

  ```yaml
  - uses: jmrplens/ghchronicle@v2
    with:
      token: ${{ secrets.GHCHRONICLE_TOKEN }}
      config: .github/ghchronicle.yaml
  ```

## What to do with each output

There is not one command, which is why the form writes it. A configuration that
names a destination is run with `-once`. A configuration that names none is
refused by `-once`, with the parser asking for a sink you deliberately did not
want, so it is run with `-card-only` and a path for the card instead. The
command above changes with the answers, and so does the step.

The step goes in the `steps:` of a workflow job. Answers no input can carry make
the step read the file, and the file has to be committed at the path the step
names; answers the four inputs can carry are written into the step itself, and
then there is no file at all. A step with no file that names no destination is a
card run, `mode: card` with a `card:` path, because that is the one thing the
Action turns into `-card-only`.

The two outputs default the opposite way on one setting, and the step is written
so it says which one you are getting. `include-private` is off unless you ask
for it, because a card that counts private repositories names them in a public
README; `targets.include_private` is on unless you say otherwise, because the
token already reaches them. So a file-less step always writes the input out
rather than leaving it to a default that means the opposite of the file beside
it. See [GitHub Actions](https://docs.jmrp.io/ghchronicle/install/actions/) for the workflow around
it, and [the token](https://docs.jmrp.io/ghchronicle/start/token/) for what the secret needs to be
able to do.

## The rest

- [The file](https://docs.jmrp.io/ghchronicle/configuration/): what each block is for, and what
  happens when a value is wrong.
- [Targets](https://docs.jmrp.io/ghchronicle/configuration/targets/): which repositories.
- [Cadences](https://docs.jmrp.io/ghchronicle/configuration/cadences/): every family, its group and
  its built-in interval.
- [Choosing a store](https://docs.jmrp.io/ghchronicle/sinks/): one page per destination.
