Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34503de68e | ||
|
|
14d519d010 | ||
|
|
fa4509038b | ||
|
|
66a49786c9 |
No files matched your search
@@ -8,9 +8,14 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GITEA_PUBLIC_URL: https://git.zb-server.de
|
||||||
|
GITEA_REPOSITORY: ${{ github.repository }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-go@v7
|
- uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
@@ -33,15 +38,24 @@ jobs:
|
|||||||
-o "$out" ./cmd/netssh
|
-o "$out" ./cmd/netssh
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Package installer
|
||||||
|
run: install -m 755 install.sh dist/install.sh
|
||||||
|
|
||||||
- name: Generate checksums
|
- name: Generate checksums
|
||||||
working-directory: dist
|
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 }}
|
||||||
|
run: bash scripts/release-notes.sh "$RELEASE_TAG" "$GITEA_PUBLIC_URL/$GITEA_REPOSITORY" > dist/release-notes.md
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
|
env:
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ github.ref_name }}"
|
API="$GITEA_PUBLIC_URL/api/v1/repos/$GITEA_REPOSITORY"
|
||||||
API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
||||||
AUTH="Authorization: token ${{ secrets.GITEA_TOKEN }}"
|
AUTH="Authorization: token ${{ secrets.GITEA_TOKEN }}"
|
||||||
|
|
||||||
# Delete existing release for this tag if present
|
# Delete existing release for this tag if present
|
||||||
@@ -51,17 +65,28 @@ jobs:
|
|||||||
curl -sf -X DELETE -H "$AUTH" "$API/releases/$EXISTING"
|
curl -sf -X DELETE -H "$AUTH" "$API/releases/$EXISTING"
|
||||||
fi
|
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 \
|
RELEASE=$(curl -sf -X POST \
|
||||||
-H "$AUTH" \
|
-H "$AUTH" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API/releases" \
|
"$API/releases" \
|
||||||
-d "{
|
--data-binary @dist/release.json)
|
||||||
\"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
|
|
||||||
}")
|
|
||||||
|
|
||||||
RELEASE_ID=$(echo "$RELEASE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
RELEASE_ID=$(echo "$RELEASE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||||
echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
|
echo "release_id=$RELEASE_ID" >> "$GITHUB_OUTPUT"
|
||||||
@@ -69,12 +94,12 @@ jobs:
|
|||||||
- name: Upload assets
|
- name: Upload assets
|
||||||
run: |
|
run: |
|
||||||
RELEASE_ID="${{ steps.create_release.outputs.release_id }}"
|
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")
|
name=$(basename "$file")
|
||||||
echo "↑ $name"
|
echo "↑ $name"
|
||||||
curl -sf -X POST \
|
curl -sf -X POST \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
-H "Content-Type: application/octet-stream" \
|
-H "Content-Type: application/octet-stream" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${RELEASE_ID}/assets?name=${name}" \
|
"$GITEA_PUBLIC_URL/api/v1/repos/$GITEA_REPOSITORY/releases/${RELEASE_ID}/assets?name=${name}" \
|
||||||
--data-binary "@$file"
|
--data-binary "@$file"
|
||||||
done
|
done
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v0.0.16
|
||||||
|
|
||||||
|
- Enable Bash, Zsh, and Fish completion for the configured command alias, such as `j`.
|
||||||
|
- Update or remove generated alias completion files when the command alias changes.
|
||||||
|
|
||||||
|
## v0.0.15
|
||||||
|
|
||||||
|
- Use `https://git.zb-server.de` for release download links and API requests instead of the runner's internal Gitea service URL.
|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -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.
|
are respected. Alias files and installed shell completions are regenerated.
|
||||||
Open a **new terminal** after updating to load the new aliases and hooks.
|
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
|
### Build from source
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -159,6 +169,14 @@ it for new shells. The command alias takes priority over a host alias with the
|
|||||||
same name and works even when no servers are cached. Environment override:
|
same name and works even when no servers are cached. Environment override:
|
||||||
`NETSSH_SHELL_ALIAS=j`.
|
`NETSSH_SHELL_ALIAS=j`.
|
||||||
|
|
||||||
|
The command alias also supports shell completion: `j ser<Tab>` suggests cached
|
||||||
|
server names, just like `netssh ser<Tab>`. The wizard, shell hook, installer, and
|
||||||
|
`netssh completion install` maintain the alias completion files automatically.
|
||||||
|
Changing or disabling the alias removes its old generated completion files.
|
||||||
|
Bash requires bash-completion to load user completion files; for Zsh, include
|
||||||
|
`~/.zfunc` in `fpath` before running `compinit` as described below. Fish loads
|
||||||
|
completion files automatically. Open a new terminal after changing the alias.
|
||||||
|
|
||||||
### API tokens
|
### API tokens
|
||||||
|
|
||||||
NetBox supports two token formats:
|
NetBox supports two token formats:
|
||||||
@@ -506,6 +524,10 @@ Both are non-blocking — the refresh runs as a background process and never del
|
|||||||
|
|
||||||
## Development
|
## 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
|
```sh
|
||||||
go test ./... # includes offline installer/update tests
|
go test ./... # includes offline installer/update tests
|
||||||
go build ./... # build all packages
|
go build ./... # build all packages
|
||||||
|
|||||||
@@ -388,7 +388,17 @@ func completionInstallCmd(root *cobra.Command) *cobra.Command {
|
|||||||
if err := os.WriteFile(file, script, 0o644); err != nil {
|
if err := os.WriteFile(file, script, 0o644); err != nil {
|
||||||
return fmt.Errorf("writing %s: %w", file, err)
|
return fmt.Errorf("writing %s: %w", file, err)
|
||||||
}
|
}
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := hook.SyncCommandCompletion(cfg.Shell.Alias); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Printf("Completion installed → %s\n%s\n", file, note)
|
fmt.Printf("Completion installed → %s\n%s\n", file, note)
|
||||||
|
if cfg.Shell.Alias != "" {
|
||||||
|
fmt.Printf("Completion also enabled for alias %q.\n", cfg.Shell.Alias)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ func TestLocalNotesCLI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestShellInitUpdatesAndRemovesCommandAliasWithoutHosts(t *testing.T) {
|
func TestShellInitUpdatesAndRemovesCommandAliasWithoutHosts(t *testing.T) {
|
||||||
|
t.Setenv("HOME", t.TempDir())
|
||||||
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
||||||
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
||||||
t.Setenv("NETSSH_CACHE_PATH", filepath.Join(t.TempDir(), "hosts.json"))
|
t.Setenv("NETSSH_CACHE_PATH", filepath.Join(t.TempDir(), "hosts.json"))
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ module git.zb-server.de/Sebi/ssh-netbox-wrapper
|
|||||||
|
|
||||||
go 1.26.3
|
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 (
|
require (
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/catppuccin/go v0.3.0 // 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/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/ansi v0.11.6 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // 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/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.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/pflag v1.0.10 // indirect
|
||||||
github.com/spf13/viper v1.21.0 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
|||||||
@@ -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 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
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 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||||
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
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/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 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
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 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
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 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
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 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
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 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
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/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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
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 h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
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 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
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 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
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=
|
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/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 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
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 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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/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 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
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/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 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
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 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
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 h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
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 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
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 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=
|
||||||
@@ -49,7 +49,14 @@ func WriteAliasFiles(entries []AliasEntry) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
writeFishAliasFile(entries) // best-effort — never blocks the caller on failure
|
writeFishAliasFile(entries) // best-effort — never blocks the caller on failure
|
||||||
return nil
|
alias := ""
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.Host == "" {
|
||||||
|
alias = entry.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SyncCommandCompletion(alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeShAliasFile(entries []AliasEntry) error {
|
func writeShAliasFile(entries []AliasEntry) error {
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package hook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config"
|
||||||
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
const completionMarker = "# netssh command alias completion — generated automatically"
|
||||||
|
|
||||||
|
// SyncCommandCompletion installs lazy loaders for the configured command alias.
|
||||||
|
// The loaders obtain the canonical completion from the current netssh binary,
|
||||||
|
// so aliases configured by the wizard work without a separate completion install.
|
||||||
|
func SyncCommandCompletion(alias string) error {
|
||||||
|
if err := config.ValidateCommandAlias(alias); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
statePath := filepath.Join(filepath.Dir(AliasesPath()), "completion_alias")
|
||||||
|
data, err := os.ReadFile(statePath)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
previous := strings.TrimSpace(string(data))
|
||||||
|
if err := config.ValidateCommandAlias(previous); err != nil {
|
||||||
|
return fmt.Errorf("invalid completion alias state: %w", err)
|
||||||
|
}
|
||||||
|
if alias == "" && previous == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, shell := range []string{"bash", "zsh", "fish"} {
|
||||||
|
if previous != "" && previous != alias {
|
||||||
|
path := commandCompletionPath(shell, previous)
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Only delete files owned by netssh; preserve any user replacements.
|
||||||
|
if strings.HasPrefix(string(data), completionMarker+"\n") || strings.HasPrefix(string(data), "#compdef "+previous+"\n"+completionMarker+"\n") {
|
||||||
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if alias == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if shell == "fish" {
|
||||||
|
if _, err := os.Stat(FishConfigDir()); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path := commandCompletionPath(shell, alias)
|
||||||
|
script := commandCompletionScript(shell, alias)
|
||||||
|
existing, err := os.ReadFile(path)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(existing) == script {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := storage.WriteFile(path, []byte(script), 0o644); err != nil {
|
||||||
|
return fmt.Errorf("writing %s alias completion: %w", shell, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if alias == "" {
|
||||||
|
if err := os.Remove(statePath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return storage.WriteFile(statePath, []byte(alias+"\n"), 0o600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandCompletionPath(shell, alias string) string {
|
||||||
|
switch shell {
|
||||||
|
case "bash":
|
||||||
|
return filepath.Join(os.Getenv("HOME"), ".local", "share", "bash-completion", "completions", alias)
|
||||||
|
case "zsh":
|
||||||
|
return filepath.Join(os.Getenv("HOME"), ".zfunc", "_"+alias)
|
||||||
|
default:
|
||||||
|
return filepath.Join(FishConfigDir(), "completions", alias+".fish")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandCompletionScript(shell, alias string) string {
|
||||||
|
switch shell {
|
||||||
|
case "bash":
|
||||||
|
return fmt.Sprintf(`%s
|
||||||
|
source <(netssh completion bash)
|
||||||
|
if [[ $(type -t compopt) == builtin ]]; then
|
||||||
|
complete -o default -F __start_netssh %s
|
||||||
|
else
|
||||||
|
complete -o default -o nospace -F __start_netssh %s
|
||||||
|
fi
|
||||||
|
`, completionMarker, alias, alias)
|
||||||
|
case "zsh":
|
||||||
|
return fmt.Sprintf(`#compdef %s
|
||||||
|
%s
|
||||||
|
source <(netssh completion zsh)
|
||||||
|
compdef _netssh %s
|
||||||
|
_netssh "$@"
|
||||||
|
`, alias, completionMarker, alias)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf(`%s
|
||||||
|
netssh completion fish | source
|
||||||
|
complete --command %s --wraps netssh
|
||||||
|
`, completionMarker, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package hook_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/hook"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func completionEnvironment(t *testing.T) map[string]string {
|
||||||
|
t.Helper()
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "config"))
|
||||||
|
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, "cache"))
|
||||||
|
if err := os.MkdirAll(hook.FishConfigDir(), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return map[string]string{
|
||||||
|
"bash": filepath.Join(home, ".local", "share", "bash-completion", "completions", "j"),
|
||||||
|
"zsh": filepath.Join(home, ".zfunc", "_j"),
|
||||||
|
"fish": filepath.Join(hook.FishConfigDir(), "completions", "j.fish"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandCompletionTracksAliasChanges(t *testing.T) {
|
||||||
|
paths := completionEnvironment(t)
|
||||||
|
if err := hook.WriteAliasFiles([]hook.AliasEntry{{Name: "j"}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, path := range paths {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A manually replaced completion file must survive removal of the old alias.
|
||||||
|
custom := "# user-owned completion\n"
|
||||||
|
if err := os.WriteFile(paths["bash"], []byte(custom), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := hook.WriteAliasFiles([]hook.AliasEntry{{Name: "k"}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for shell, path := range paths {
|
||||||
|
if shell == "bash" {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil || string(data) != custom {
|
||||||
|
t.Fatal("user completion was removed")
|
||||||
|
}
|
||||||
|
} else if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("stale %s completion remains: %v", shell, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := hook.WriteAliasFiles(nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, path := range []string{
|
||||||
|
filepath.Join(os.Getenv("HOME"), ".local", "share", "bash-completion", "completions", "k"),
|
||||||
|
filepath.Join(os.Getenv("HOME"), ".zfunc", "_k"),
|
||||||
|
filepath.Join(hook.FishConfigDir(), "completions", "k.fish"),
|
||||||
|
} {
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("disabled completion remains: %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandAliasCompletesServerNamesAcrossShells(t *testing.T) {
|
||||||
|
for _, shell := range []string{"bash", "zsh", "fish"} {
|
||||||
|
t.Run(shell, func(t *testing.T) {
|
||||||
|
executable, err := exec.LookPath(shell)
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("%s unavailable", shell)
|
||||||
|
}
|
||||||
|
paths := completionEnvironment(t)
|
||||||
|
if err := hook.SyncCommandCompletion("j"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fixtures := t.TempDir()
|
||||||
|
root := &cobra.Command{Use: "netssh"}
|
||||||
|
var canonical bytes.Buffer
|
||||||
|
switch shell {
|
||||||
|
case "bash":
|
||||||
|
err = root.GenBashCompletionV2(&canonical, true)
|
||||||
|
case "zsh":
|
||||||
|
err = root.GenZshCompletion(&canonical)
|
||||||
|
case "fish":
|
||||||
|
err = root.GenFishCompletion(&canonical, true)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(fixtures, shell), canonical.Bytes(), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Exercise Cobra's actual generated shell code with a deterministic backend.
|
||||||
|
backend := `#!/bin/bash
|
||||||
|
case "$1" in
|
||||||
|
completion) cat "$COMPLETION_FIXTURES/$2" ;;
|
||||||
|
__complete|__completeNoDesc) printf 'server01\n:4\n' ;;
|
||||||
|
*) exit 1 ;;
|
||||||
|
esac
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(fixtures, "netssh"), []byte(backend), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("COMPLETION_FIXTURES", fixtures)
|
||||||
|
t.Setenv("ALIAS_COMPLETION", paths[shell])
|
||||||
|
t.Setenv("PATH", fixtures+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||||
|
var script string
|
||||||
|
var args []string
|
||||||
|
switch shell {
|
||||||
|
case "bash":
|
||||||
|
library := ""
|
||||||
|
for _, path := range []string{"/usr/share/bash-completion/bash_completion", "/etc/bash_completion"} {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
library = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if library == "" {
|
||||||
|
t.Skip("bash-completion unavailable")
|
||||||
|
}
|
||||||
|
t.Setenv("BASH_COMPLETION_LIBRARY", library)
|
||||||
|
args = []string{"--noprofile", "--norc", "-c"}
|
||||||
|
script = `source "$BASH_COMPLETION_LIBRARY"
|
||||||
|
shopt -s expand_aliases
|
||||||
|
alias j=netssh
|
||||||
|
_completion_loader j
|
||||||
|
COMP_WORDS=(j ser)
|
||||||
|
COMP_CWORD=1
|
||||||
|
COMP_LINE='j ser'
|
||||||
|
COMP_POINT=5
|
||||||
|
COMP_TYPE=9
|
||||||
|
COMP_KEY=9
|
||||||
|
__start_netssh j ser j
|
||||||
|
printf '%s\n' "${COMPREPLY[@]}"
|
||||||
|
`
|
||||||
|
case "zsh":
|
||||||
|
args = []string{"-f", "-c"}
|
||||||
|
script = `fpath=("$HOME/.zfunc" $fpath)
|
||||||
|
autoload -Uz compinit
|
||||||
|
compinit -u -D
|
||||||
|
alias j=netssh
|
||||||
|
autoload -Uz _j
|
||||||
|
# Capture matches without requiring an interactive ZLE completion widget.
|
||||||
|
_describe() { print -rl -- "${completions[@]}"; }
|
||||||
|
words=(j ser)
|
||||||
|
CURRENT=2
|
||||||
|
_j
|
||||||
|
`
|
||||||
|
case "fish":
|
||||||
|
args = []string{"--no-config", "-c"}
|
||||||
|
script = `alias j netssh
|
||||||
|
set -p fish_complete_path "$XDG_CONFIG_HOME/fish/completions"
|
||||||
|
complete --do-complete 'j ser'
|
||||||
|
`
|
||||||
|
}
|
||||||
|
cmd := exec.Command(executable, append(args, script)...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil || !strings.Contains(string(out), "server01") {
|
||||||
|
t.Fatalf("%s completion: %v\n%s", shell, err, out)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ func TestLocalNotesPersistAndStayIsolated(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
s := &Service{Dir: t.TempDir(), Namespace: "https://netbox.example"}
|
s := &Service{Dir: t.TempDir(), Namespace: "https://netbox.example"}
|
||||||
host := netbox.HostEntry{Name: "web01", Kind: "device"}
|
host := netbox.HostEntry{Name: "web01", Kind: "device"}
|
||||||
n := Note{Host: host, Text: "Wartung\nGrüße 🔧"}
|
n := Note{Host: host, Text: "Maintenance\nReady 🔧"}
|
||||||
if err := s.Write(ctx, n, false); err != nil {
|
if err := s.Write(ctx, n, false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func RunWizard(cfg *config.Config) error {
|
|||||||
strategies := parseStrategies(strategiesRaw)
|
strategies := parseStrategies(strategiesRaw)
|
||||||
tokenVersion := netbox.TokenVersion(token)
|
tokenVersion := netbox.TokenVersion(token)
|
||||||
if tokenVersion == 1 {
|
if tokenVersion == 1 {
|
||||||
fmt.Fprintln(os.Stderr, "\nHinweis: Du verwendest einen Legacy-Token (v1). Erstelle in NetBox einen v2-Token (beginnt mit nbt_) für bessere Kompatibilität.")
|
fmt.Fprintln(os.Stderr, "\nNote: You are using a legacy token (v1). Create a v2 token in NetBox (starting with nbt_) for better compatibility.")
|
||||||
fmt.Fprintln(os.Stderr, " NetBox → Admin → API Tokens → Add Token")
|
fmt.Fprintln(os.Stderr, " NetBox → Admin → API Tokens → Add Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ type Model struct {
|
|||||||
height int
|
height int
|
||||||
recentMode bool // true when showing recent hosts (empty search, initial state)
|
recentMode bool // true when showing recent hosts (empty search, initial state)
|
||||||
filterOpen bool // Ctrl+F toggles
|
filterOpen bool // Ctrl+F toggles
|
||||||
editMode bool // 'e' on selected item
|
editMode bool // Ctrl+E on the selected item
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(client *netbox.Client, c *cache.Cache, defaultUser string, defaultPorts ...int) *Model {
|
func New(client *netbox.Client, c *cache.Cache, defaultUser string, defaultPorts ...int) *Model {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,5 +2,41 @@
|
|||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"config:recommended"
|
"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"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Executable
+26
@@ -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
|
||||||
Reference in new issue
Block a user