diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 8299a55..1a2f53d 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -11,6 +11,8 @@ jobs: steps: - uses: actions/checkout@v7 + with: + fetch-depth: 0 - uses: actions/setup-go@v7 with: @@ -33,14 +35,24 @@ jobs: -o "$out" ./cmd/netssh done + - name: Package installer + run: install -m 755 install.sh dist/install.sh + - name: Generate checksums working-directory: dist - run: sha256sum netssh_* > checksums.txt + run: sha256sum netssh_* install.sh > checksums.txt + + - name: Generate release notes + env: + RELEASE_TAG: ${{ github.ref_name }} + REPOSITORY_URL: ${{ github.server_url }}/${{ github.repository }} + run: bash scripts/release-notes.sh "$RELEASE_TAG" "$REPOSITORY_URL" > dist/release-notes.md - name: Create release id: create_release + env: + TAG: ${{ github.ref_name }} run: | - TAG="${{ github.ref_name }}" API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" AUTH="Authorization: token ${{ secrets.GITEA_TOKEN }}" @@ -51,17 +63,28 @@ jobs: curl -sf -X DELETE -H "$AUTH" "$API/releases/$EXISTING" fi + # Serialize Markdown as JSON without hand-escaping quotes or newlines. + python3 - <<'PY_RELEASE' > dist/release.json + import json + import os + import sys + from pathlib import Path + + tag = os.environ["TAG"] + json.dump({ + "tag_name": tag, + "name": tag, + "body": Path("dist/release-notes.md").read_text(), + "draft": False, + "prerelease": False, + }, sys.stdout) + PY_RELEASE + RELEASE=$(curl -sf -X POST \ -H "$AUTH" \ -H "Content-Type: application/json" \ "$API/releases" \ - -d "{ - \"tag_name\": \"$TAG\", - \"name\": \"$TAG\", - \"body\": \"## Installation\n\n\`\`\`sh\ncurl -fsSL https://git.zb-server.de/Sebi/ssh-netbox-wrapper/raw/branch/main/install.sh | bash\n\`\`\`\", - \"draft\": false, - \"prerelease\": false - }") + --data-binary @dist/release.json) RELEASE_ID=$(echo "$RELEASE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT" @@ -69,7 +92,7 @@ jobs: - name: Upload assets run: | RELEASE_ID="${{ steps.create_release.outputs.release_id }}" - for file in dist/netssh_* dist/checksums.txt; do + for file in dist/netssh_* dist/install.sh dist/checksums.txt; do name=$(basename "$file") echo "↑ $name" curl -sf -X POST \ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..92b6c1f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog + +## v0.0.14 + +- Enable Renovate updates for indirect Go modules as well as direct dependencies and CI actions. +- Enable Go version updates and keep Go module metadata tidy after dependency updates. +- Correct direct and indirect dependency declarations in `go.mod`. +- Include `install.sh` as a downloadable release asset. +- Include the installer in `checksums.txt` alongside the platform binaries. +- Link the installation command in release notes directly to the release's installer asset. +- Generate a change list from commits since the previous version tag for every release. + +## v0.0.13 + +- Translate the remaining German setup message and test example into English. +- Correct the connection editor comment to reference `Ctrl+E`. + +## v0.0.12 + +- Support updating an existing installation by running the installer again. +- Remove the installer's Python dependency. +- Require valid SHA-256 checksums before replacing the binary. +- Check required programs before downloading and list missing dependencies. +- Check for `sudo` before downloading when the installation directory requires elevated permissions. +- Consolidate legacy shell hooks and aliases in Bash, Zsh, and Fish profiles. +- Back up changed profiles and preserve symlinks and file permissions. +- Regenerate aliases and completions during installation and updates. +- Add a configurable command alias, such as `j`, to the setup wizard. +- Refresh or remove the generated command alias when its configuration changes. + +## v0.0.11 + +- Add multiline local server notes and optional NetBox comments in the TUI. +- Add CLI commands to show, replace, and append server notes. +- Keep local notes separate from the host cache, with private permissions and atomic writes. +- Use NetBox ETags for comment conflict protection when supported by the server. +- Correct authentication for NetBox v2 tokens. +- Fix environment variable overrides for configuration keys. +- Preserve usage history when refreshing cached hosts. +- Discard stale TUI search results and use `Ctrl+E` for connection editing. +- Validate port overrides and preserve an explicit port 22. +- Resolve stale and live TUI selections through the configured resolver chain. +- Reject incomplete full cache refreshes and avoid marking filtered refreshes as full refreshes. +- Stop interpreting remote command flags as SSH options when applying defaults. diff --git a/README.md b/README.md index cb55b39..ad3b4f6 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,16 @@ and permissions are preserved. `XDG_CONFIG_HOME`, `XDG_CACHE_HOME` and `ZDOTDIR` are respected. Alias files and installed shell completions are regenerated. Open a **new terminal** after updating to load the new aliases and hooks. +### Release downloads and change history + +Each new release includes `install.sh`, binaries for Linux and macOS (amd64 and +arm64), and `checksums.txt` covering all of these files. The release notes include +an installation command pointing directly to that release's `install.sh` asset +and a list of changes since the previous version tag. + +See [CHANGELOG.md](CHANGELOG.md) for the change history. The installer attached to +a release installs or updates to the latest published binary release. + ### Build from source ```sh @@ -506,6 +516,10 @@ Both are non-blocking — the refresh runs as a background process and never del ## Development +Renovate monitors the release workflow's actions, the Go version in `go.mod`, and +all direct and indirect Go module dependencies. Go dependency updates also run +`go mod tidy` to keep `go.mod` and `go.sum` consistent. + ```sh go test ./... # includes offline installer/update tests go build ./... # build all packages diff --git a/go.mod b/go.mod index e9947bb..a5c4400 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,20 @@ module git.zb-server.de/Sebi/ssh-netbox-wrapper go 1.26.3 +require ( + github.com/charmbracelet/bubbles v1.0.0 + github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/huh v1.0.0 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/spf13/cobra v1.10.2 + github.com/spf13/viper v1.21.0 +) + require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.3.0 // indirect - github.com/charmbracelet/bubbles v1.0.0 // indirect - github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/colorprofile v0.4.1 // indirect - github.com/charmbracelet/huh v1.0.0 // indirect - github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect @@ -38,9 +43,7 @@ require ( github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect diff --git a/go.sum b/go.sum index 98bb8c0..ca2b3cb 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= +github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= @@ -18,10 +22,20 @@ github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= +github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= +github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= @@ -29,16 +43,30 @@ github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEX github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -57,8 +85,12 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= @@ -77,12 +109,16 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= @@ -90,3 +126,7 @@ golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/release_notes_test.go b/release_notes_test.go new file mode 100644 index 0000000..ebb6bb8 --- /dev/null +++ b/release_notes_test.go @@ -0,0 +1,57 @@ +package netssh_test + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func TestReleaseNotesUseReleaseAssetAndVersionRange(t *testing.T) { + script, err := filepath.Abs("scripts/release-notes.sh") + if err != nil { + t.Fatal(err) + } + repo := t.TempDir() + git := func(args ...string) { + t.Helper() + cmd := exec.Command("git", args...) + cmd.Dir = repo + cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=Release Test", "GIT_AUTHOR_EMAIL=release@example.com", "GIT_COMMITTER_NAME=Release Test", "GIT_COMMITTER_EMAIL=release@example.com", "GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null") + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git %v: %v\n%s", args, err, out) + } + } + git("init") + git("-c", "commit.gpgsign=false", "commit", "--allow-empty", "-m", "Initial version") + git("tag", "v0.0.1") + render := func(tag string) string { + t.Helper() + cmd := exec.Command("bash", script, tag, "https://git.example.com/team/netssh/") + cmd.Dir = repo + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("render: %v\n%s", err, out) + } + return string(out) + } + initial := render("v0.0.1") + if !strings.Contains(initial, "Initial release:") || !strings.Contains(initial, "Initial version") { + t.Fatalf("first release: %s", initial) + } + git("-c", "commit.gpgsign=false", "commit", "--allow-empty", "-m", `Add installer and "quoted" release notes`) + git("tag", "v0.0.2") + git("-c", "commit.gpgsign=false", "commit", "--allow-empty", "-m", "Unreleased changes") + notes := render("v0.0.2") + for _, want := range []string{"https://git.example.com/team/netssh/releases/download/v0.0.2/install.sh", "Changes since `v0.0.1`:", `Add installer and "quoted" release notes`, "checksums.txt"} { + if !strings.Contains(notes, want) { + t.Errorf("missing %q:\n%s", want, notes) + } + } + for _, excluded := range []string{"Initial version", "Unreleased changes", "raw/branch/main"} { + if strings.Contains(notes, excluded) { + t.Errorf("unexpected %q:\n%s", excluded, notes) + } + } +} diff --git a/renovate.json b/renovate.json index 5db72dd..66fcfbe 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,41 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" + ], + "dependencyDashboard": true, + "postUpdateOptions": [ + "gomodTidy" + ], + "packageRules": [ + { + "description": "Keep GitHub Actions in the Gitea release workflow up to date", + "matchManagers": [ + "github-actions" + ], + "enabled": true + }, + { + "description": "Include indirect Go modules in dependency updates", + "matchManagers": [ + "gomod" + ], + "matchDepTypes": [ + "indirect" + ], + "enabled": true + }, + { + "description": "Update the Go version declared in go.mod", + "matchManagers": [ + "gomod" + ], + "matchDepNames": [ + "go" + ], + "matchDepTypes": [ + "golang" + ], + "rangeStrategy": "bump" + } ] } diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 0000000..2455c81 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 2 ]]; then + printf 'Usage: %s TAG REPOSITORY_URL\n' "$0" >&2 + exit 1 +fi + +TAG=$1 +REPOSITORY_URL=${2%/} +git rev-parse --verify "$TAG^{commit}" >/dev/null +# Start at the parent so the release being built cannot match itself. +PREVIOUS_TAG=$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$TAG^" 2>/dev/null || true) + +printf '## Installation / Update\n\n' +printf '```sh\ncurl -fsSL %s/releases/download/%s/install.sh | bash\n```\n\n' "$REPOSITORY_URL" "$TAG" +printf 'The installer is attached to this release. It checks dependencies and verifies binary SHA-256 checksums before installation. Python is not required.\n\n' +printf 'Download `checksums.txt` to verify the attached binaries and `install.sh`.\n\n' +printf '## Changes\n\n' +if [[ -n "$PREVIOUS_TAG" ]]; then + printf 'Changes since `%s`:\n\n' "$PREVIOUS_TAG" + git log --no-merges --format='- %s (%h)' "$PREVIOUS_TAG..$TAG" +else + printf 'Initial release:\n\n' + git log --no-merges --format='- %s (%h)' "$TAG" +fi