Minecraft: Discord chat bridge, the first Ansible Vault, and a patch that never applied - #35
Merged
Conversation
PATCH_DEFINITIONS names a directory, and in directory mode mc-image-helper enumerates *.json only, parsing each file with a plain JSON ObjectMapper as a bare patch definition rather than a patch set. patches/spigot.yaml matched none of those: wrong extension, wrong format, and wrapped in a `patches:` key. The mismatch is silent. The directory listing simply does not match the file, so an empty patch set is processed, `mc-image-helper patch` exits 0, and the "Applying patch definitions from /patches" line still appears in the log. The patch has therefore never been applied, and spigot.yml's commands.log has stayed true -- which is what the compose comment relies on being false to keep SimpleLogin's "/login <password>" out of logs/latest.log in the clear. Convert the definition to JSON in the shape directory mode expects, remove the superseded file from the host so it cannot be mistaken for a working patch, and move the rationale into the task, since JSON cannot carry comments. Add check-json to pre-commit: an unresolvable path or malformed definition is a startup-blocking failure (PatchOperationException propagates out of mc-image-helper under set -e), so a syntax error is worth catching at commit time rather than at container start. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016sBzgNzdYA8b3YrXR3DJrA
…ault Install DiscordSRV from Modrinth alongside SimpleLogin. Release 1.30.5 is the only one tagged for game version 26.2 and lists the paper loader, so it resolves against the pinned VERSION without a "?" escape. The bot token never enters a config file. DiscordSRV resolves its token from the DISCORDSRV_TOKEN environment variable before falling back to BotToken in config.yml, so Ansible writes it into .env from the vault and it stays out of the data volume and out of /discord debug uploads. It is deliberately not CFG_-prefixed, so REPLACE_ENV_VARIABLE_PREFIX will not interpolate it into any file under /data. The channel mapping is not a secret and goes in a patch definition, applied on every start like the spigot one so it cannot drift out of the repo. The Discord console channel is explicitly disabled rather than left at its dummy default: running server commands from Discord would be a second, weaker path to the console beside SSH. This is the repo's first secret that has to live on a host rather than on the runner, so it introduces Ansible Vault. Values are encrypted individually with encrypt_string rather than by encrypting the whole file. Both survive the Lint job, which has no vault password, but a whole-file vault makes ansible-lint give up on the file -- it runs with a dummy password and logs "Ignored exception from JinjaRule / VariableNamingRule ... Decryption failed", reporting a pass without linting it. Inline values keep the rest of the file checked, keep names greppable and diffs reviewable, and avoid ansible-vault edit and its editor swap files. The cost is that rekey does not work on them, so they are kept together in one file. vault_password_file stays out of ansible.cfg. Ansible resolves it whether or not a run touches vaulted data and treats a missing file as fatal, which is why 9a65f59 removed it; CI passes a path via ANSIBLE_VAULT_PASSWORD_FILE instead, and local runs pass --vault-password-file. There is no ANSIBLE_VAULT_PASSWORD variable in ansible-core, only a path, so CI writes the secret to a file in RUNNER_TEMP, which the runner empties at the end of every job. The .env task gets no_log: CI runs with --diff, this repository is public, and GitHub only masks the literal secret values it issued -- a token decrypted from the vault is not one of them. group_vars is excluded from yamlfix. It rewrites the block scalar (| to |-, reindented); that still decrypts, but a formatter has no business rewriting ciphertext when the failure mode is a broken production deploy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016sBzgNzdYA8b3YrXR3DJrA
Signed-off-by: Bradley Reynolds <bradley.reynolds@tailstory.dev>
Signed-off-by: Bradley Reynolds <bradley.reynolds@tailstory.dev>
The DiscordSRV change had grown docs/ansible.md from 79 lines to 180. Drop the Discord bridge section entirely. docs/ansible.md documents Ansible -- running it, CI deploys, bootstrapping, the settings that live outside the repo. How a Minecraft plugin is configured is application config and does not belong in it, and the first-time setup steps only restated DiscordSRV's own documentation, which covers gateway intents and the bot invite better and will not drift out of date the way a copy here would. Cut the Secrets section to the rules and leave out the derivations: where secrets live, how to add one, no_log, the vault_password_file landmine, and the rotation caveat. Shorten the vault notes under Running locally and CI deploys, and fold the ANSIBLE_VAULT_PASSWORD bullet into the ansible Environment bullet that already explains the main-only rationale. One fact from the deleted section was worth keeping because it is specific to this repo rather than to DiscordSRV: the patch only lands on the second container start, and a repeat converge will not trigger one. That moves into the comment above the task that installs the patch. 107 lines, down from 180. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016sBzgNzdYA8b3YrXR3DJrA
shenanigansd
deleted the
claude/discordsrv-ansible-integration-mpmoyb
branch
September 3, 2026 04:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a Discord ↔ Minecraft chat bridge with DiscordSRV, installed from Modrinth like every other plugin. Along the way it fixes the patch mechanism, which turned out never to have worked, and introduces the repo's first Ansible Vault.
patches/spigot.yamlhas never been appliedWorth reviewing first, since it is a live issue rather than part of the feature.
PATCH_DEFINITIONS: "/patches"is directory mode, andPatchCommand.loadPatchSet()enumerates it with:Three independent reasons the existing file is skipped: it is named
.yaml;patchSetMapperis a plain JSON-onlyObjectMapper; and directory mode reads each file as a barePatchDefinition, so thepatches:wrapper only works whenPATCH_DEFINITIONSnames a single file (itzg/docker-minecraft-server#2834, open since 2024).The mismatch is silent — the listing simply does not match, an empty patch set is processed,
mc-image-helper patchexits 0, andApplying patch definitions from /patchesstill appears in the log. Sospigot.yml'scommands.loghas stayedtrue, which is what the compose comment relies on beingfalseto keep SimpleLogin's/login <password>out oflogs/latest.login the clear.Converted to JSON in the shape directory mode expects, with the stale file removed from the host and
check-jsonadded to pre-commit. Worth confirming the damage and rotating existing logs:DiscordSRV
The bot token never enters a config file. DiscordSRV resolves it from the
DISCORDSRV_TOKENenvironment variable before falling back toBotTokeninconfig.yml, so Ansible writes it into.envfrom the vault and it stays out of the data volume and out of/discord debuguploads. It is deliberately notCFG_-prefixed, soREPLACE_ENV_VARIABLE_PREFIXcannot interpolate it into any file under/data.The channel mapping is not a secret and lives in a patch definition, re-applied on every start like the spigot one so it cannot drift out of the repo.
DiscordConsoleChannelIdis explicitly set to""rather than left at its dummy default — running server commands from Discord would be a second, weaker path to the console beside SSH.Discord-side setup is DiscordSRV's own documentation and is not restated here.
First Ansible Vault
This is the first secret that has to live on a host rather than on the runner. Values are encrypted per value with
encrypt_stringrather than by encrypting the whole file, which keeps the rest of the file lintable and its diffs reviewable; the cost is thatrekeydoes not work on inline values, so they stay together in one file.vault_password_filestays out ofansible.cfg. Ansible resolves it on every run whether or not that run touches vaulted data and treats a missing file as fatal — which is why 9a65f59 removed it. CI passes a path viaANSIBLE_VAULT_PASSWORD_FILE; local runs pass--vault-password-file. There is noANSIBLE_VAULT_PASSWORDvariable in ansible-core, only a path, so CI writes the secret to a file in$RUNNER_TEMP, which the runner empties at the end of every job.The
.envtask getsno_log: CI runs with--diff, this repository is public, and GitHub only masks the literal secret values it issued — a token decrypted from the vault is not one of them.group_varsis excluded from yamlfix. It rewrites the block scalar (|→|-, reindented); that still decrypts, but a formatter has no business rewriting ciphertext when the failure mode is a broken production deploy.Before merging
ANSIBLE_VAULT_PASSWORDto theansibleGitHub Environment (not a repository secret). Without it the next unattended Sunday run fails.After deploying
Merging does not deploy; dispatch
Deploy Ansible. Then restart the container once:plugins/DiscordSRV/config.ymldoes not exist until DiscordSRV has enabled once, and patches are applied before the server starts, so the first converge logsUnable to patch ... it is not an existing fileand does nothing. A repeat dispatch will not fix it on its own — the image digest is pinned, so Compose recreates nothing.Verification
prek run --all-files,ansible-lintandansible-playbook --syntax-checkall pass with no vault password available, which is the condition theLintjob runs under. zizmor passes offline under--persona=auditorwith nozizmor.ymlchange — the new secret goes through a step-levelenv:block, never inline inrun:.Two things to watch on the first patched start, neither of which I could verify without deploying: the patcher decodes the whole of
config.ymland re-encodes it with Jackson'sYAMLMapper, destroying comments and re-resolving every scalar (YAML 1.1 readsyes/no/on/offas booleans), and an op whose JSONPath stops resolving becomes a startup-blocking failure rather than a no-op.🤖 Generated with Claude Code
https://claude.ai/code/session_016sBzgNzdYA8b3YrXR3DJrA
Generated by Claude Code