Short-lived tokens don't belong in the same file as long-lived credentials

You protect ~/.bashrc.secret with chmod 444 so nothing accidentally overwrites your API keys and passwords. Then your token-refresh script needs to write a new session cookie somewhere — and hits Permission denied.

The fix isn't adding a second writable secrets file. It's stop persisting short-lived tokens entirely.

Refactor the refresh script to output TOKEN HASH on stdout and let the caller capture it:

# refresh_token — reads credentials, prints session token to stdout
CREDENTIALS=$(bash ~/bin/refresh_token 2>/dev/null)
TOKEN=$(echo "$CREDENTIALS" | awk '{print $1}')
HASH=$(echo "$CREDENTIALS" | awk '{print $2}')

No file writes. No second secrets layer. The token lives in the process environment for the duration of the operation and disappears when it's done.

This works because session tokens are cheap to re-obtain — one HTTP call with stored credentials. The only reason to persist them is avoiding that call, which is almost never worth the complexity of managing a writable token store alongside a read-only credential store.

The rule: if you can regenerate it from long-lived credentials in under a second, don't persist it.

One Telegram Bot + Two Machines = Silent Message Loss

Running the same Telegram bot relay as a systemd service on multiple machines means both instances poll the same bot token. Telegram delivers each update to one consumer only — so messages vanish at random, or land on the wrong host. Both sides think they're healthy; neither is.

The fix: one bot per machine, one channel per bot. Each channel becomes a dedicated console for exactly one host. No coordination needed, no distributed locks, no split-brain.

To manage multiple bot tokens in a shared dotfiles repo, key them by hostname:

# dotfiles — all tokens in one file, encrypted as usual
export TG_TOKEN_homeserver="token_aaa"
export TG_TOKEN_vps="token_bbb"

# relay picks its own token at runtime
export TG_TOKEN=$(eval echo \$TG_TOKEN_$(hostname))

Every machine runs the same service file unchanged — hostname resolves to the right token automatically. Adding a third machine is just a new bot, a new channel, and one more TG_TOKEN_<hostname> line.

TG Channel: homeserver  →  bot-A  →  machine 1
TG Channel: vps         →  bot-B  →  machine 2
TG Channel: pi          →  bot-C  →  machine 3

`exec bash` vs `bash`: don't nest your shells

After changing your hostname with hostnamectl set-hostname, the current terminal still shows the old name. You want a fresh shell — but bash and exec bash behave differently.

Running bash spawns a child process. Your old shell is still alive underneath, and $SHLVL increments by one. Environment changes (like the new hostname) are picked up, but you've added a layer:

$ echo $SHLVL
1
$ bash
$ echo $SHLVL
2

Nested shells can cause subtle issues — exit only drops you back to the parent, aliases and env vars may differ between layers, and deep nesting is easy to accidentally accumulate.

exec bash replaces the current process in-place (same PID). The old shell is gone, a new one takes over, and $SHLVL stays the same:

$ echo $SHLVL
1
$ exec bash
$ echo $SHLVL
1

This is the clean way to reload your shell config after changing .bashrc, updating $HOSTNAME, or any other env-level change. No nesting, no leftover state.

Telegram Bot Custom Commands: setMyCommands and the empty-array delete

Telegram bots can show a command menu when users type / in a chat. Registering commands is a single API call — no approval, no webhook setup, no special bot-side handling.

curl -s -X POST "https://api.telegram.org/bot<TOKEN>/setMyCommands" \
  -H "Content-Type: application/json" \
  -d '{"commands": [
    {"command": "status", "description": "Show active host"},
    {"command": "switch", "description": "Switch active host"}
  ]}'

From the bot's perspective, /status or /switch desktop arrives as plain text in message.text — you parse it like any other message.

Scope-aware deletion

deleteMyCommands supports optional scope and language_code parameters so you can clear commands for specific contexts (private chats, groups, a single chat, a specific language). But there's no API to delete individual commands.

The elegant part: setMyCommands always does a full overwrite. Sending an empty array clears everything:

# These are equivalent:
curl -s -X POST "https://api.telegram.org/bot<TOKEN>/setMyCommands" \
  -d '{"commands": []}'

curl -s -X POST "https://api.telegram.org/bot<TOKEN>/deleteMyCommands"

To remove a single command, just setMyCommands with the updated list minus the one you want gone. The overwrite-everything semantics means there's no "add" or "remove" — only "set the complete list." deleteMyCommands is a convenience method, not a necessity.

Tmux Windows Replace Terminal Tabs (And Then Some)

If you're still using terminal tabs, tmux windows do the same job with extras you didn't know you needed.

The hierarchy: Session > Window > Pane. A session holds multiple windows, each window can be split into panes. The bottom status bar shows all windows at a glance:

[main] 0:bash* 1:vim- 2:server

* marks the active window, - marks the previous one. You always know where you are.

Key bindings to internalize:

Action Key
New window Ctrl+b c
Next window Ctrl+b n
Previous window Ctrl+b p
Jump by number Ctrl+b 0~9
List all windows Ctrl+b w
Rename window Ctrl+b ,

What tmux gives you over plain terminal tabs:

  • Survives disconnects — SSH drops? Reconnect with tmux attach, everything is still there. Terminal tabs are gone the moment the connection dies.
  • Panes — Split any window horizontally or vertically without opening another tab.
  • Scriptable — Create and arrange windows/panes from a script or config.

The one real risk: if the machine itself reboots, the session is gone. tmux-resurrect and tmux-continuum can save/restore layouts, but in practice most people use tmux to survive network hiccups, not server reboots. A reboot means re-opening a session, which takes seconds.