Project challenges / verified progress
Beacon: turn a program into a service

The engineering notebook

Load targets from config

How does a service know what to watch before it starts watching?

Loading statusStage 2 of 10

  • Workspace not ready
  • Agent not ready
Focus25:00
A small focus ritual

0 focus sessions completed. Every fourth session offers a longer break. Start each phase when you are ready.

Study time never unlocks verified lesson progress.

Loading...

Loading verified progress...

Loading GitHub account...
Phase 1 — Time and input

Step 01 of 06

Learn the concept

Configuration is where a service changes without a rebuild. It is also where bad input becomes a production outage before main has finished. A monitor should reject htps:// at startup, not discover it on the first incident call.

CONFIG PRECEDENCE STACKFlagshighest priorityoperator nowEnvironmentprocess defaultssupervisorYAML filetarget listreviewedCode defaultssafe fallbacklast
Precedence is a stack, not a debate. If two sources set `interval`, the topmost source wins and the resolved value is the only value the rest of the program sees.
Step 01

The ideas this is made of

Flags are excellent for one run

Command-line flags are visible in shell history, easy to document and hard to mistake for ambient state. They are ideal for -config=targets.yaml or -interval=30s during development. They are less pleasant for long target lists because quoting arrays on a command line is a small punishment invented by terminals.

Environment variables belong to the process

Environment variables travel well through systemd, containers and CI. They are strings, so every value needs parsing and error messages. They are also easy to inherit accidentally. Use them for coarse settings such as BEACON_INTERVAL=1m, not for secrets in this course and not for a hundred URLs.

Config files need schemas even when YAML does not

YAML accepts many shapes. Your service should not. A targets list with name and url fields is a contract, and startup validation enforces it before scheduling begins. The error should say targets[2].url: unsupported scheme ftp, not invalid config. One helps; one sulks.

Resolved config is the only config

After loading defaults, file, environment and flags, collapse them into one Config value. Every later package receives that value, not access to flags or os.Getenv. That keeps precedence in one place and makes tests boring in the best possible way.

Validate a tiny config
package main

import (
	"fmt"
	"net/url"
)

type Target struct{ Name, URL string }

func main() {
	t := Target{Name: "docs", URL: "https://example.com"}
	u, err := url.ParseRequestURI(t.URL)
	if err != nil || u.Scheme != "https" {
		fmt.Println("bad target")
		return
	}
	fmt.Println(t.Name, u.Host)
}

The important move is parsing before use. Beacon's full loader will read YAML and merge sources, but every target still passes through the same URL validation gate before the scheduler sees it.

Where each setting belongs

SourceBest forBad fit

Flag

Local override

Long lists

Environment

Supervisor setting

Structured data

YAML file

Targets and names

One-off debug

Default

Safe baseline

Site-specific values

What these are called on the job

  • Precedence — The rule deciding which source wins when two set the same field.

  • Fail fast — Stop during startup with a useful error rather than running with invalid state.

  • Schema — The shape and meaning of accepted configuration, even when the file format is loose.