@luxmargos/ensure-hosts
v0.1.4
Published
CLI for managing hosts file entries from YAML profiles.
Downloads
794
Readme
@luxmargos/ensure-hosts
Manage local hosts-file entries on macOS, Linux, and Windows from small YAML profiles.
ensure-hosts expands a YAML file into hosts-file records, removes stale records for the same domains when requested, and appends the current records. It is useful for local development domains such as api.myapp.test, admin.myapp.test, and other repeatable project setup.
Why use it?
- Keep project hostnames in versioned YAML instead of editing
/etc/hostsby hand. - Re-run the same command safely when project IPs or domains change.
- Preview changes before writing with
--dry-run. - Remove a profile's entries when you no longer need them.
- Share one base config plus project-specific configs.
Requirements
- Node.js 20 or newer
- Permission to write the system hosts file when not using
--dry-run
Default hosts-file locations:
- Linux/macOS:
/etc/hosts - Windows:
%SystemRoot%\System32\drivers\etc\hosts
Install
npm install -g @luxmargos/ensure-hostsThen check the CLI is available:
ensure-hosts --versionQuick start
Create a config file, for example
hosts.local.yaml. You can use either the subdomains style or the flat style — both produce the same records:Subdomains style — group children under a parent domain:
profile: MYAPP_LOCAL hosts: - domain: myapp.test address: 127.0.0.1 children: - api - adminFlat style — list every domain side by side at the top level:
profile: MYAPP_LOCAL hosts: - domain: myapp.test address: 127.0.0.1 - domain: api.myapp.test address: 127.0.0.1 - domain: admin.myapp.test address: 127.0.0.1Preview what would be written:
ensure-hosts --config ./hosts.local.yaml --dry-runApply the changes:
ensure-hosts --config ./hosts.local.yamlOn macOS and Windows, the CLI can prompt for administrator permission when needed. On Linux, run with
sudoif your user cannot write/etc/hosts:sudo ensure-hosts --config ./hosts.local.yaml
The example above writes records like:
# MYAPP_LOCAL
127.0.0.1 myapp.test
# MYAPP_LOCAL
127.0.0.1 api.myapp.test
# MYAPP_LOCAL
127.0.0.1 admin.myapp.testCommon commands
# Use one config
ensure-hosts --config ./hosts.local.yaml
# Layer multiple configs
ensure-hosts --config ./base.yaml --config ./project.yaml
# Preview the final hosts file without writing
ensure-hosts --config ./hosts.local.yaml --dry-run
# Print the expanded records only
ensure-hosts --config ./hosts.local.yaml --print-records
# Remove entries managed by rewrite:true records
ensure-hosts --config ./hosts.local.yaml --remove
# Remove every domain listed by the config, including rewrite:false records
ensure-hosts --config ./hosts.local.yaml --remove-forceConfiguration reference
A config file must be .yaml or .yml and contain:
profile: PROFILE_NAME
hosts:
- domain: some.domain.test
address: 192.168.1.111
rewrite: true
skipSelf: false
children:
- sitea
- domain: siteb
address: ::1Fields:
| Field | Required? | Description |
| --- | --- | --- |
| profile | Yes | Label written as a comment above generated entries, for example # MYAPP_LOCAL. |
| hosts | Yes | Array of host entries. |
| domain | Yes | Domain name. Inside children, this can be a relative subdomain such as api. |
| address | For records you want written | IPv4 or IPv6 address. Children inherit the parent address unless they set their own. |
| rewrite | No | Whether existing entries for the same domain may be cleaned before appending. Defaults to true. Children inherit the parent value. |
| skipSelf | No | Skip writing this node while still processing its children. Defaults to false. |
| children | No | Nested subdomains as strings or full objects. |
Subdomains
Child strings inherit the parent address and rewrite value:
profile: LOCAL
hosts:
- domain: example.test
address: 127.0.0.1
children:
- api
- adminThis writes:
example.testapi.example.testadmin.example.test
If a child already contains the full parent domain, it is not duplicated. For example, api.example.test stays api.example.test.
As a flat list
If you'd rather spell every domain out instead of using children:, you can list them side by side at the top level. The result is the same as the nested form above:
profile: LOCAL
hosts:
- domain: example.test
address: 127.0.0.1
- domain: api.example.test
address: 127.0.0.1
- domain: admin.example.test
address: 127.0.0.1This is handy when you're building the config from a script, or when you simply prefer reading a flat list. address, rewrite, and skipSelf can still be set on any entry.
skipSelf example
Use skipSelf: true when you only want child records:
profile: LOCAL
hosts:
- domain: example.test
address: 127.0.0.1
skipSelf: true
children:
- api
- adminThis writes api.example.test and admin.example.test, but not example.test.
How rewriting works
By default, rewrite is true.
With rewrite: true, ensure-hosts:
- Removes existing hosts entries for the same domain.
- Removes stale adjacent
# PROFILE_NAMEcomments left by previous runs. - Appends the current generated entry when an address is available.
With rewrite: false, ensure-hosts:
- Leaves existing entries for the same domain untouched.
- Appends the generated entry only if that domain is not already present.
Entries without an effective address are not written. If their effective rewrite value is true, matching existing entries can still be cleaned.
Generated entries use one # PROFILE_NAME comment per contiguous managed block by default. Use --repeat-profile-comments when you want the profile comment repeated before every generated hosts line.
Remove mode
Use remove mode when you want to uninstall entries from a profile instead of ensuring them.
ensure-hosts --config ./hosts.local.yaml --remove
ensure-hosts --config ./hosts.local.yaml --remove-force
ensure-hosts --config ./hosts.local.yaml --remove --dry-run--removeremoves domains whose effectiverewritevalue istrue. Domains markedrewrite: falseare left untouched.--remove-forceremoves every domain listed by the config, includingrewrite: falsedomains.
--remove and --remove-force cannot be used together, and neither can be combined with --print-records.
Environment variables
The CLI automatically loads .env from the current working directory. Missing .env files are ignored by default.
Use --env-file <path> if your dotenv file is not named .env. The option is repeatable and files are loaded in order:
ensure-hosts --env-file .env --env-file .env.local --config ./hosts.yamlBy default, later dotenv files overwrite existing values, including shell environment variables and values from earlier dotenv files. Use respect mode to keep existing values:
ensure-hosts --env-override respect --env-file .env --env-file .env.local --config ./hosts.yamlYou can also set the mode with ENSURE_HOSTS_ENV_OVERRIDE=overwrite or ENSURE_HOSTS_ENV_OVERRIDE=respect. CLI options take precedence over environment variables.
Missing dotenv files are skipped by default. Use --env-file-missing error or ENSURE_HOSTS_ENV_FILE_MISSING=error to fail when an expected dotenv file does not exist.
You can provide config paths with ENSURE_HOSTS_CONFIG instead of --config:
ENSURE_HOSTS_CONFIG=./hosts.local.yaml,./another.yamlYou can also override the hosts file path, which is useful for tests or custom workflows:
ENSURE_HOSTS_HOSTS_FILE=./tmp-hostsTo disable macOS/Windows elevation prompts:
ENSURE_HOSTS_NO_ELEVATE=trueYAML config files support Docker Compose-like variable interpolation after dotenv files are loaded:
profile: "${PROFILE_NAME:-LOCAL}"
hosts:
- domain: "${APP_DOMAIN}"
address: "${APP_ADDRESS-127.0.0.1}"Supported forms:
${VAR_NAME}expands to the environment value. If missing, it expands to an empty string and prints an[ensure-hosts]warning.${VAR_NAME:-default}usesdefaultwhen the variable is unset or empty.${VAR_NAME-default}usesdefaultonly when the variable is unset.
CLI options
--config <path> YAML/YML config file path (repeatable)
--env-file <path> dotenv file path (default: .env, repeatable)
--env-override <mode> dotenv collision mode: overwrite|respect (default: overwrite)
--env-file-missing <mode> missing dotenv mode: ignore|error (default: ignore)
--hosts-file <path> override hosts file path
--dry-run print rewritten hosts content without writing
--print-records print expanded records and exit
--repeat-profile-comments repeat profile comment before every generated host line
--remove remove rewrite:true domains (respects rewrite:false)
--remove-force remove all listed domains, including rewrite:false
--no-elevate disable macOS/Windows privilege prompt
--help show help
--version show versionNotes:
- On Linux,
--no-elevateis effectively a no-op because Linux does not use an in-process elevation prompt. Run the command withsudowhen needed. - Use
--hosts-fileorENSURE_HOSTS_HOSTS_FILEto test against a temporary file instead of the real hosts file.
Development
Install dependencies:
npm installRun checks:
npm test
npm run typecheck
npm run buildThe repository also includes a Docker-based Linux test harness (Dockerfile.linux-test and compose.linux-test.yaml). These files are for testing only, not production.
# Run the unit test suite in a Linux container
docker compose -f compose.linux-test.yaml run --rm test
# Typecheck and build
docker compose -f compose.linux-test.yaml run --rm typecheck
docker compose -f compose.linux-test.yaml run --rm build
# Exercise CLI scenarios
docker compose -f compose.linux-test.yaml run --rm dry-run
docker compose -f compose.linux-test.yaml run --rm print-records
docker compose -f compose.linux-test.yaml run --rm root-write
docker compose -f compose.linux-test.yaml run --rm etc-hosts-write
docker compose -f compose.linux-test.yaml run --rm non-root-fail
docker compose -f compose.linux-test.yaml run --rm removeThe root-write, non-root-fail, and remove services use --hosts-file /tmp/test-hosts, so the real /etc/hosts is not modified. The etc-hosts-write service writes to the container's ephemeral /etc/hosts, not your host machine.
License
MIT
