From 9e43c2bbc724b6aedda47e222495bd23d4ab0bce Mon Sep 17 00:00:00 2001 From: Wilko Date: Sun, 18 Oct 2026 22:52:59 +0200 Subject: [PATCH] Say which docs describe main, add a CHANGELOG and a release docs check README and docs describe main (Docker :dev), while :latest is the last release. A note at the top of README and docs now says so and links the README of the latest release (2.4.0) and the new CHANGELOG.md, which lists everything since 2.4.0 under "Unreleased". Sections that exist only in main get a marker line *(unreleased)* below the heading, so anchors stay stable. Before publishing, the release workflow runs scripts/check-release-docs.sh for the tag: no *(unreleased)* markers left, a CHANGELOG section for the version, and the README note pointing to it. release_docs_test.go keeps the note and CHANGELOG consistent on every CI run. CHANGELOG.md describes the release steps. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 15 ++++++++ CHANGELOG.md | 44 ++++++++++++++++++++++ README.md | 4 ++ docs/environment_variables.md | 4 ++ docs/installation.md | 2 + release_docs_test.go | 70 +++++++++++++++++++++++++++++++++++ scripts/check-release-docs.sh | 30 +++++++++++++++ 7 files changed, 169 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 release_docs_test.go create mode 100755 scripts/check-release-docs.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7ac441..91accea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,8 +10,23 @@ on: workflow_dispatch: jobs: + docs: + name: Check docs for release + # A manual run on a branch has no version to check against. + if: github.ref_type == 'tag' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Check README, docs and CHANGELOG + run: ./scripts/check-release-docs.sh "${{ github.ref_name }}" + docker: name: Publish Docker :release + needs: docs + # Publish only if the docs check passed, or did not run (manual run on a branch). + if: ${{ !cancelled() && (needs.docs.result == 'success' || needs.docs.result == 'skipped') }} runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..44d8dc5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog + +All notable changes are listed here. `Unreleased` is what `main` and the Docker image `:dev` contain; each version heading matches a release tag and the Docker image `:` (the newest one is also `:latest`). + +## Unreleased + +### Breaking + +- `/gen_keys` and `/remove_keys` accept `POST` only. `GET` now returns `405`. The dashboard already uses `POST`; scripts or bookmarks that call these endpoints with `GET` must switch. ([#170](https://github.com/wimaha/TeslaBleHttpProxy/pull/170)) + +### Security + +- State-changing requests a browser sends from another website are rejected with `403`, so a page you visit can no longer change your keys or send commands to the car. evcc, curl and Home Assistant send neither `Origin` nor `Sec-Fetch-Site` and are not affected. ([#170](https://github.com/wimaha/TeslaBleHttpProxy/pull/170)) +- Dashboard and log viewer escape all output (`html/template`). ([#170](https://github.com/wimaha/TeslaBleHttpProxy/pull/170)) + +### Added + +- `trustedOrigins` environment variable for reverse proxies over plain HTTP that rewrite the `Host` header, and for web pages on other origins that call the API. See [environment variables](docs/environment_variables.md#trustedorigins). ([#170](https://github.com/wimaha/TeslaBleHttpProxy/pull/170)) +- Docker image `:dev`, built from `main` on every push. ([#171](https://github.com/wimaha/TeslaBleHttpProxy/pull/171)) +- README examples for `set_charge_limit` and `set_sentry_mode`. ([#169](https://github.com/wimaha/TeslaBleHttpProxy/pull/169)) + +### Fixed + +- A client that disconnects while a response is written no longer stops the proxy. ([#170](https://github.com/wimaha/TeslaBleHttpProxy/pull/170)) + +### Changed + +- Building from source requires Go 1.26 or newer. ([#172](https://github.com/wimaha/TeslaBleHttpProxy/pull/172)) +- Tests run against a simulated vehicle, so commands, connection handling and the HTTP API are covered without a car. ([#169](https://github.com/wimaha/TeslaBleHttpProxy/pull/169)) + +## 2.4.0 - 2026-10-17 + +Changes up to and including 2.4.0 are described in the [GitHub releases](https://github.com/wimaha/TeslaBleHttpProxy/releases). + +## Releasing + +Before you push a release tag: + +1. Rename `## Unreleased` to `## - ` and add a new, empty `## Unreleased` above it. +2. In README and `docs/`, replace every marker line `*(unreleased)*` with `*(since )*`. +3. Raise the version in the note at the top of the README to `` (text and link). +4. Commit, then tag `` (without `v`, like `2.4.0`). + +The release workflow checks these steps and stops before publishing `:latest` if one is missing. diff --git a/README.md b/README.md index 91d6c54..57edeea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # TeslaBleHttpProxy +> **Note:** This README describes the current development state (`main`, Docker `:dev`). For the latest release **2.4.0** (Docker `:latest`) see the [README of 2.4.0](https://github.com/wimaha/TeslaBleHttpProxy/blob/2.4.0/README.md). What changed since then is listed in the [CHANGELOG](CHANGELOG.md). Sections marked *(unreleased)* are only in `:dev`. + TeslaBleHttpProxy is a program written in Go that receives HTTP requests and forwards them via Bluetooth to a Tesla vehicle. The program can, for example, be easily used together with [evcc](https://github.com/evcc-io/evcc). The program stores the received requests in a queue and processes them one by one. This ensures that only one Bluetooth connection to the vehicle is established at a time. @@ -287,6 +289,8 @@ This is a Tesla vehicle constraint, not a limitation of TeslaBleHttpProxy. ### Dashboard buttons return 403 Forbidden +*(unreleased)* + The proxy rejects state-changing requests that a browser sends from another website, so a page you visit cannot change your keys or send commands to the car. If you open the dashboard through a reverse proxy over plain HTTP that rewrites the `Host` header, your own requests can look cross-site too. Set the [`trustedOrigins`](docs/environment_variables.md#trustedorigins) environment variable to the address you open in the browser, e.g. `trustedOrigins=http://nas.local:8080`. Over HTTPS this is not needed. The same applies to custom web pages or browser tools that call the API with `POST` from another origin: they get `403` unless their origin is listed in `trustedOrigins`. diff --git a/docs/environment_variables.md b/docs/environment_variables.md index daae937..a9694fc 100644 --- a/docs/environment_variables.md +++ b/docs/environment_variables.md @@ -1,5 +1,7 @@ # Environment variables +> **Note:** This page describes the current development state (`main`, Docker `:dev`). Sections marked *(unreleased)* are not in the latest release yet, see the [CHANGELOG](../CHANGELOG.md). + You can optionally set environment variables to override the default behavior. ## logLevel @@ -24,6 +26,8 @@ This is the address and port to listen for HTTP requests. (Default: :8080) ## trustedOrigins +*(unreleased)* + Comma-separated list of origins (`scheme://host[:port]`) that may send state-changing requests (dashboard buttons, commands) from a browser. Requests from evcc, curl or Home Assistant integrations are not affected. (Default: empty) You need it in two cases, because the proxy otherwise answers `403 Forbidden`: diff --git a/docs/installation.md b/docs/installation.md index 45fdd26..90cc576 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,5 +1,7 @@ # Installation +> **Note:** This page describes the current development state (`main`, Docker `:dev`). Sections marked *(unreleased)* are not in the latest release yet, see the [CHANGELOG](../CHANGELOG.md). + ## Step 1: Install Raspberry Pi OS Setup your raspberry Pi. You will find an installer here: https://www.raspberrypi.com/software/ diff --git a/release_docs_test.go b/release_docs_test.go new file mode 100644 index 0000000..46c01f8 --- /dev/null +++ b/release_docs_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "os" + "regexp" + "strings" + "testing" +) + +// The README and docs describe main; these checks keep the pointers to the latest release +// consistent between releases. scripts/check-release-docs.sh does the stricter check on a tag. + +func readFile(t *testing.T, name string) string { + t.Helper() + data, err := os.ReadFile(name) + if err != nil { + t.Fatal(err) + } + return string(data) +} + +// latestRelease returns the first version section after "## Unreleased" in CHANGELOG.md. +func latestRelease(t *testing.T) string { + t.Helper() + var headings []string + for _, line := range strings.Split(readFile(t, "CHANGELOG.md"), "\n") { + if heading, ok := strings.CutPrefix(line, "## "); ok { + headings = append(headings, heading) + } + } + if len(headings) < 2 || headings[0] != "Unreleased" { + t.Fatalf("CHANGELOG.md must start with \"## Unreleased\" followed by the latest release, got %q", headings) + } + version, _, _ := strings.Cut(headings[1], " ") + return version +} + +func TestReadmeNotePointsToLatestRelease(t *testing.T) { + version := latestRelease(t) + head := strings.Join(strings.SplitN(readFile(t, "README.md"), "\n", 6)[:5], "\n") + for _, want := range []string{"**" + version + "**", "/blob/" + version + "/README.md", "(CHANGELOG.md)"} { + if !strings.Contains(head, want) { + t.Errorf("the note at the top of README.md must contain %q (latest release in CHANGELOG.md is %s)", want, version) + } + } +} + +func TestDocsHaveDevelopmentNote(t *testing.T) { + for _, name := range []string{"docs/environment_variables.md", "docs/installation.md"} { + if !strings.Contains(readFile(t, name), "(../CHANGELOG.md)") { + t.Errorf("%s needs the note that it describes main, with a link to the CHANGELOG", name) + } + } +} + +// Markers must be a line of their own, so the release check finds them and heading anchors +// (like docs/environment_variables.md#trustedorigins) stay stable across releases. +func TestReleaseMarkersAreOwnLines(t *testing.T) { + marker := regexp.MustCompile(`(?i)\*\((unreleased|since [0-9.]+)\)\*`) + for _, name := range []string{"README.md", "docs/environment_variables.md", "docs/installation.md"} { + for i, line := range strings.Split(readFile(t, name), "\n") { + if strings.HasPrefix(line, "> **Note:**") || !marker.MatchString(line) { + continue + } + if trimmed := strings.TrimSpace(line); trimmed != "*(unreleased)*" && !regexp.MustCompile(`^\*\(since [0-9.]+\)\*$`).MatchString(trimmed) { + t.Errorf("%s:%d: put the marker on a line of its own, below the heading: %q", name, i+1, line) + } + } + } +} diff --git a/scripts/check-release-docs.sh b/scripts/check-release-docs.sh new file mode 100755 index 0000000..858fdf6 --- /dev/null +++ b/scripts/check-release-docs.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# Checks that README, docs and CHANGELOG are ready for release . +# Run by the release workflow before publishing; see "Releasing" in CHANGELOG.md. +set -u + +version="${1:?usage: $0 }" +failed=0 + +markers=$(grep -n -x '\*(unreleased)\*' README.md docs/*.md) +if [ -n "$markers" ]; then + echo "Replace these markers with *(since $version)*:" + echo "$markers" + failed=1 +fi + +if ! grep -q -E "^## $(printf '%s' "$version" | sed 's/\./\\./g')( |$)" CHANGELOG.md; then + echo "CHANGELOG.md has no section '## $version'." + failed=1 +fi + +if ! head -n 5 README.md | grep -F "/blob/$version/README.md" | grep -q -F "**$version**"; then + echo "The note at the top of README.md does not point to release $version." + failed=1 +fi + +if [ "$failed" -ne 0 ]; then + echo "Docs are not ready for release $version." + exit 1 +fi +echo "Docs are ready for release $version."