Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ Planned. Each v1 issue adds its part.
server, on Android and on an iPhone (issue #24), with a table for
the results.
- `docs/releasing.md` - how releases are made and checked.
- `docs_test.go` (at the root of the module) checks that no shell
block in a Markdown file at the root or in `docs/` ends a command line
with a `#` comment: zsh does not treat it as a comment (#92).
- `test/e2e/` - the end-to-end test with `sshd` in a container (DESIGN.md
section 17). Build tag `e2e`. The image of the container is
`test/e2e/testdata/sshd`.
Expand All @@ -73,8 +76,10 @@ Planned. Each v1 issue adds its part.
```sh
go test -race ./...
go vet ./...
golangci-lint run --build-tags e2e # the tag adds the files of test/e2e
go test -race -tags e2e ./test/e2e/... # needs Docker, ssh and git
# the tag adds the files of test/e2e
golangci-lint run --build-tags e2e
# needs Docker, ssh and git
go test -race -tags e2e ./test/e2e/...
```

The last one is the end-to-end test (DESIGN.md section 17). It builds
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,25 @@ root on the broker host on the agent machine
useradd --system --home-dir /var/lib/nodkey --shell /usr/sbin/nologin nodkey
install -d -m 0755 /etc/nodkey
install -d -m 0700 /etc/nodkey/credentials /run/nodkey-setup
nodkey keygen --out /run/nodkey-setup/broker-tls-key # prints the pin of the broker
# prints the pin of the broker
nodkey keygen --out /run/nodkey-setup/broker-tls-key
systemd-creds encrypt --with-key=host+tpm2 --name=broker-tls-key \
/run/nodkey-setup/broker-tls-key /etc/nodkey/credentials/broker-tls-key.cred
rm /run/nodkey-setup/broker-tls-key
systemd-ask-password -n "Telegram bot token:" |
systemd-creds encrypt --with-key=host+tpm2 --name=telegram-token - /etc/nodkey/credentials/telegram-token.cred
systemd-ask-password -n "bws access token:" |
systemd-creds encrypt --with-key=host+tpm2 --name=bws-token - /etc/nodkey/credentials/bws-token.cred
install -m 0644 /dev/null /etc/nodkey/known_hosts # empty: no SSH keys yet
# empty: no SSH keys yet
install -m 0644 /dev/null /etc/nodkey/known_hosts
```

4. **On the agent machine**, make the TLS key of the client:

```sh
install -d -m 0700 ~/.config/nodkey
nodkey keygen --out ~/.config/nodkey/client.key # prints the pin of the client
# prints the pin of the client
nodkey keygen --out ~/.config/nodkey/client.key
```

Then write `~/.config/nodkey/client.toml`, with mode `0600`:
Expand Down
9 changes: 6 additions & 3 deletions docs/manual-ntfy-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ on the iPhone.
On the ntfy server:

```sh
ntfy user add nodkey-test-broker # asks for a password
# asks for a password
ntfy user add nodkey-test-broker
ntfy user add nodkey-test-phone
ntfy access nodkey-test-broker nodkey-test-requests write-only
ntfy access nodkey-test-broker nodkey-test-answers read-only
Expand All @@ -78,10 +79,12 @@ In terminal 3:
```sh
T=~/.config/nodkey-test
install -d -m 0700 "$T"
read -rs TOKEN # paste the token, then press Enter; it is not shown
# paste the token, then press Enter; it is not shown
read -rs TOKEN
(umask 077; printf '%s\n' "$TOKEN" > "$T/ntfy-token")
unset TOKEN
ls -l "$T/ntfy-token" # must show -rw-------
# must show -rw-------
ls -l "$T/ntfy-token"
```

Check the access rules, without a token:
Expand Down
15 changes: 10 additions & 5 deletions docs/manual-telegram-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ In terminal 3:
```sh
T=~/.config/nodkey-test
install -d -m 0700 "$T"
read -rs TOKEN # paste the token, then press Enter; it is not shown
# paste the token, then press Enter; it is not shown
read -rs TOKEN
(umask 077; printf '%s\n' "$TOKEN" > "$T/telegram-token")
unset TOKEN
ls -l "$T/telegram-token" # must show -rw-------
# must show -rw-------
ls -l "$T/telegram-token"
```

### 3. A helper for the Bot API
Expand Down Expand Up @@ -93,7 +95,8 @@ tg getUpdates '{"allowed_updates":["message"]}' | jq '.result[].message.from.id'
Keep the number in a variable:

```sh
CHAT=123456789 # your user ID; the private chat with the bot has the same ID
# your user ID; the private chat with the bot has the same ID
CHAT=123456789
```

From now on, never call `getUpdates` while the broker runs, except in
Expand All @@ -104,7 +107,8 @@ From now on, never call `getUpdates` while the broker runs, except in
In terminal 3, in the nodkey source:

```sh
git rev-parse HEAD # write this commit into the results
# write this commit into the results
git rev-parse HEAD
go build -o "$T/nodkey" ./cmd/nodkey
install -d -m 0700 "$T/bin" "$T/state" "$T/home" "$T/home/.config" "$T/home/.config/nodkey"
"$T/nodkey" keygen --out "$T/broker.key"
Expand Down Expand Up @@ -389,7 +393,8 @@ seconds were too short for it. So first give each request 3 minutes.

```sh
sed -i 's/^timeout = "60s"$/timeout = "180s"/' "$T/broker.toml"
grep '^timeout' "$T/broker.toml" # must show: timeout = "180s"
# must show: timeout = "180s"
grep '^timeout' "$T/broker.toml"
```

2. Restart the broker, so that it reads the new timeout. First stop it
Expand Down
18 changes: 12 additions & 6 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ without it, such as for 0.1.0. Then take the file `nodkey` out of the
tarball:

```sh
VERSION=0.2.0 # the version you downloaded
# the version you downloaded
VERSION=0.2.0
tar -xzf "nodkey_${VERSION}_linux_amd64.tar.gz" nodkey
```

Expand Down Expand Up @@ -400,7 +401,8 @@ of your own. Only your taps count.
the broker:

```sh
read -rs TOKEN # paste the token, then press Enter; it is not shown
# paste the token, then press Enter; it is not shown
read -rs TOKEN
printf 'url = "https://api.telegram.org/bot%s/getUpdates"\n' "$TOKEN" |
curl -sS --config - -H 'Content-Type: application/json' \
-d '{"allowed_updates":["message","my_chat_member"]}'
Expand Down Expand Up @@ -469,7 +471,8 @@ the broker reads.
([access control](https://docs.ntfy.sh/config/#access-control)):

```sh
ntfy user add nodkey-broker # asks for a password
# asks for a password
ntfy user add nodkey-broker
ntfy user add phone
ntfy access nodkey-broker nodkey-requests write-only
ntfy access nodkey-broker nodkey-answers read-only
Expand Down Expand Up @@ -904,7 +907,8 @@ access to it: such an account could read them too.
bws. For the store `vault`:

```sh
umask 077 # new files private; run `umask 022` before you copy the unit (section 8)
# new files private; run `umask 022` before you copy the unit (section 8)
umask 077
install -d -o nodkey -g nodkey -m 0700 /var/lib/nodkey /var/lib/nodkey/stores /var/lib/nodkey/stores/vault
# Only for Bitwarden's EU cloud or your own server:
sudo -u nodkey env -i -C /var/lib/nodkey/stores/vault HOME=/var/lib/nodkey/stores/vault \
Expand Down Expand Up @@ -1721,8 +1725,10 @@ export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/nodkey/agent.sock"
Check it:

```sh
ssh-add -l # the keys that this client may use, with their names
ssh deploy@web-1 true # a message comes for the login
# the keys that this client may use, with their names
ssh-add -l
# a message comes for the login
ssh deploy@web-1 true
```

`ssh`, `git` and Ansible work through it with no changes. Some things to
Expand Down
130 changes: 130 additions & 0 deletions docs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package nodkey

import (
"os"
"path/filepath"
"strings"
"testing"
)

// TestDocsShellLinesHaveNoTrailingComment checks the shell blocks of the
// docs (issue #92). Interactive zsh does not treat "#" as the start of a
// comment unless the option interactivecomments is on, and it is off by
// default. A reader who pastes "cmd # note" into zsh passes "#" and the
// words after it to cmd. So a note goes on its own line above the
// command. zsh may print "command not found: #" for such a line, but no
// command gets extra words.
func TestDocsShellLinesHaveNoTrailingComment(t *testing.T) {
var files []string
for _, pattern := range []string{"*.md", "docs/*.md"} {
found, err := filepath.Glob(pattern)
if err != nil {
t.Fatal(err)
}
files = append(files, found...)
}
if len(files) < 3 {
t.Fatalf("found only %d Markdown files: run the test from the root of the module", len(files))
}
for _, f := range files {
data, err := os.ReadFile(f) //nolint:gosec // G304: a Markdown file of the repository, from a fixed glob.
if err != nil {
t.Fatal(err)
}
for _, bad := range trailingShellComments(string(data)) {
t.Errorf("%s:%d: a shell line ends in a # comment; put the note on its own line above the command (issue #92): %s",
f, bad.line, bad.text)
}
}
}

// TestTrailingShellComments checks the finder itself, so that the test
// above cannot pass because the finder sees nothing.
func TestTrailingShellComments(t *testing.T) {
doc := strings.Join([]string{
"text # not code",
"```sh",
"# a note on its own line",
"read -rs TOKEN # paste the token",
`echo "a # in quotes" 'and # here'`,
"curl https://example.com/#anchor",
"echo $# ${#x}",
"```",
"```toml",
"key = 1 # TOML comment",
"```",
" ```bash",
" VERSION=0.2.0 # the version",
" ```",
"```",
"plain block # not shell",
"```",
}, "\n")
got := trailingShellComments(doc)
want := []int{4, 13}
if len(got) != len(want) {
t.Fatalf("found %v, want lines %v", got, want)
}
for i := range want {
if got[i].line != want[i] {
t.Errorf("finding %d is on line %d, want %d", i, got[i].line, want[i])
}
}
}

type shellLine struct {
line int
text string
}

// trailingShellComments returns the lines of the sh, bash, shell, zsh and
// console blocks of a Markdown text that have a "#" comment after a
// command.
func trailingShellComments(doc string) []shellLine {
var out []shellLine
inBlock, isShell := false, false
for i, line := range strings.Split(doc, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "```") {
if inBlock {
inBlock, isShell = false, false
continue
}
inBlock = true
switch strings.TrimSpace(strings.TrimPrefix(trimmed, "```")) {
case "sh", "bash", "shell", "zsh", "console":
isShell = true
}
continue
}
if isShell && hasTrailingComment(trimmed) {
out = append(out, shellLine{line: i + 1, text: trimmed})
}
}
return out
}

// hasTrailingComment reports whether a shell line has a word that starts
// with "#" after some other word, outside quotes.
func hasTrailingComment(line string) bool {
var quote rune
prev := ' '
seenWord := false
for _, r := range line {
switch {
case quote != 0:
if r == quote {
quote = 0
}
case r == '\'' || r == '"':
quote = r
seenWord = true
case r == '#' && (prev == ' ' || prev == '\t'):
return seenWord
case r != ' ' && r != '\t':
seenWord = true
}
prev = r
}
return false
}
Loading