December 2025
Really trying to get my head around vim variables and schenanegins
ok so g: is for vim variables?
like
like when we set variables in nvim setings! like
The vim equivalent of setting these variables would be
Ahhhh set, this is where my confusion comes from between let which is for setting options! (see :h :set)
Getting confused again.... :let g:loaded_netw = 1 is setting the value of the global variable loaded_netw. Likewise
God... ok
g:colors_nameis a global variableletaccesses variablesg:andv:are types of variablesvim.optis an optionsetaccessesoptions
Ok, asked gemini to do me a table to help clear this up!
Command Cheat Sheet (Get & Set)
And now for buffers etc
And globals
Lua Globals (_G) vs. Vim Globals (vim.g)
Summary of the "Big Three"
- vim.opt: Switches/knobs for the Editor (Line numbers, tabs).
- vim.g: Settings for Plugins (Theme names, enable/disable flags).
- _G: Logic for Functions (Your custom code that needs to run everywhere).
Ok so.... In a lua console
Then can do EITHER :lua print(pls()) or :lua print(_G.pls()) as _G is the global namespace!
Then, the equivalent in vim is
notice that
print()does not appear, as it goes to message history?vim.notifyWILL print however
This STILL doesn't make sense to me - but I guess that the lua print() is only really for debugging? In general an API show either log to a file or notify?
I found the docs here where it says lua print() redirects its output to the nvim message area?
Ok well the various types of printing in vim and lua are frying my brain a bit - especially since print() in lua claims to print to the message area but... does not? well gemini (tried) to explain
vim.notify() is fairly modern so that does make sense honestly. its the others
Ok NEVER MIND - it was because i was in the lua console - i assume print() is found to a different messages area or something
xdg-open over SSH (socat forwarding)
The complete flow (step by step)
-
Local machine: systemd starts
open-forward.serviceon login- Runs:
socat UNIX-LISTEN:/tmp/open-forward.sock,fork EXEC:"xargs xdg-open" - Creates socket at
/tmp/open-forward.sock, waiting for connections
- Runs:
-
You SSH to remote:
ssh user@remote- SSH reads
~/.ssh/config, seesRemoteForward /tmp/open-forward-%r.sock /tmp/open-forward.sock %rexpands to remote username (e.g.,joel)- SSH creates
/tmp/open-forward-joel.sockon remote, tunneled back to local socket
- SSH reads
-
On remote: something calls
xdg-open https://example.com- Shell finds
~/.local/bin/xdg-openfirst (due to PATH ordering) - Our wrapper script runs instead of system xdg-open
- Shell finds
-
Wrapper script checks:
[ -S "/tmp/open-forward-$USER.sock" ]- Socket exists? → forward the URL through it
- No socket? → fallback to system
/run/current-system/sw/bin/xdg-open
-
URL flows through tunnel:
printf '%s' "https://example.com" | socat - UNIX-CONNECT:/tmp/open-forward-joel.sock- Data goes: remote socket → SSH tunnel → local socket
-
Local socat receives URL:
- Pipes it to
xargs xdg-open - Your local browser opens the URL
- Pipes it to
SSH config tokens
When you run ssh joel@remote-server:
%r=joel(remote username - who you're logging in AS)%u= your local username (on machine running ssh)%h=remote-server(remote hostname)
Since RemoteForward creates the socket on the REMOTE machine, %r matches $USER there.
Concrete example: which socket name is used where?
Setup:
- Machine A (local laptop): logged in as
joelyboy - Machine B (remote server): will SSH in as
test-user
Why the username matters (multi-user scenario)
socat basics
socat creates a bidirectional channel between two "addresses". Syntax: socat ADDRESS1 ADDRESS2
Common address types:
UNIX-LISTEN:/path- Create a listening Unix socket at pathUNIX-CONNECT:/path- Connect to existing Unix socket-- stdin/stdoutEXEC:"command"- Execute command, pipe data to it
Testing locally (no SSH needed)
Terminal 1 - start listener:
Terminal 2 - send URL:
Inspecting sockets
Architecture diagram
Fixing DNS for work VPN
DNS servers werent being set by the VPN (which they should be) - claudes summary:
Problem
VPN connected successfully but all internet connectivity lost:
Root cause: Split responsibility between routing and DNS resolution.
The VPN correctly became the default route for IP traffic:
But resolvectl status showed tun0 had no DNS servers:
DNS queries still went to Virgin Media (194.168.4.100) via wlp98s0, but those packets got routed through the VPN tunnel where Virgin's DNS was unreachable.
Fix
Assign DNS servers to the VPN connection:
Result
Now DNS queries route through tun0 to reachable public DNS servers.
Why OpenVPN didn't push DNS
The work OpenVPN server isn't configured to push DNS settings (push "dhcp-option DNS x.x.x.x"). Client-side config works around this.
Nixos @ symbol
Can use the @ symbol to reference object args (kind of like args: {x: number, y: number} in js)
Vimscript functions
So weird weird details of how functions work in vim
system()is a FUNCTION:echois a COMMAND
and functions CANNOT be called directly from the command line!
Hence why :system(...) does not work! need :echo or :call to invoke it
Even weirder... even inside a vimscript function this still applies? which seems strange to me but oh well... So like doing : to invoke a command the same applies in vimscript in will complain if you try and do
will yield
as you need echo or call!
DBus (Interacting with system services)
DBus is a message bus system that allows applications to talk to one another. You can interact with it using busctl.
1. List available services on the user bus
2. Introspect a service (see methods/properties)
3. Call a method (Send a notification)
4. Get a property
DBus Concepts & Troubleshooting
1. ZSH Issue: Quotes & Expansion
- The Signature:
{sv}is interpreted as brace expansion. You must use single quotes:'susssasa{sv}i'. - The Message:
!inside double quotes triggers history expansion in interactive ZSH. Use single quotes:'Sent via DBus!'.
2. The Hierarchy: Service vs Object vs Interface
It helps to think of it like an Object-Oriented generic URL:
Service -> Object -> Interface -> Method
3. "Are you guessing the path?"
No. While they often match the service name (replacing . with /), they don't have to. You find them using tree:
4. "Is that a socket path?" No. It looks like a file path, but it is a logical name used purely within DBus to organize objects inside a service. It does not exist on your hard drive.
Pretty cool i can see the notify method in dbus directly here!
Systemd Learning
1. Visibility of units
When running systemctl --user status, it defaults to showing the active service tree (specifically default.target).
- Sockets often sit quietly in the background waiting for a connection. Unless they are actively processing something or triggered an error, they might not show up in the main status tree.
- To see sockets specifically:
systemctl --user list-sockets(this shows what ports/files systemd is listening on). - To see everything systemd knows about (loaded units):
systemctl --user list-units
2. The @ symbol (Template Units)
The @ symbol denotes a Template Unit.
- Normal Unit:
my-service.service-> One config, one instance. - Template Unit:
my-socket@.service-> One config, many possible instances.
When you have Accept=yes in a socket file (my-socket.socket), systemd does this magic:
- It listens on port 8888.
- Connection comes in from IP
1.2.3.4. - Systemd looks for a service named
my-socket@.service. - It instantiates it as
my-socket@0-127.0.0.1:8888-1.2.3.4:5678.service(or similar unique ID). - It runs that specific instance for that specific connection.
This is how SSH works (sshd@.service) handling multiple users at once!
3. What is a "Unit"?
A Unit is just the generic object name for "thing systemd manages". Everything in systemd is a unit. The suffix tells you what type of unit it is:
.service: A process/script (The Worker).socket: A network port/file (The Phone Line).timer: A schedule (The Alarm Clock).target: A group of other units (like "Multi-User Mode" or "Graphical Mode").mount: A filesystem mount point
They link together via dependencies:
- Timer
Wants-> Service (starts it when time is up) - Socket
Activates-> Service (starts it when call comes in) - Target
Wants-> Services (starts bunch of them at boot)
Jollof notes
Ok (above is gemini)
So if i enabled the my-socket.socket and then I've opened 2 zshs with this
then I can see below
My socket has 2 connections! It's activated the template and I can see my-socket@1.... and my-socket@2
Systemd Naming & Implicit Links
- Implicit by Name (Magic Naming): By default,
my-name.socketactivatesmy-name.service(ormy-name@.serviceifAccept=yes). - Explicit Override: Use
Service=other-service.servicein the[Socket]section to break the naming convention and trigger a different unit.
Accept=yes vs Accept=no
- Accept=yes: Systemd accepts the connection itself and spawns a new instance of the template service (
@.service) for each incoming connection. This is how SSH handles multiple simultaneous users. - Accept=no (Default): Systemd passes the listening socket to a single service instance. The service is responsible for calling
accept()(like a web server or database).
Systemd Hierarchy: Slices, Scopes, and Services
The Concept: Think of it like a file system for processes.
- Cgroups (Control Groups): The actual directories in the kernel that track processes.
- Slices (
.slice): The "folders". They organize things hierarchically and control resources (e.g., "System gets 80% CPU, User gets 20%"). - Services (
.service) & Scopes (.scope): The "files". These contain the actual running processes.
The Tree:
- Root (
-.slice): The top of the tree. user.slice: Holds all user sessions.user-1000.slice: Your specific user (UID 1000).user@1000.service: The systemd manager instance for you.app.slice: Applications started by the GUI or systemd (likefirefox,my-socket).session.slice: Core session stuff (likedbus,pipewire).
Interactive Commands:
-
Visualize the Tree:
systemd-cglsShows the entire hierarchy recursively. -
Monitor Resources (The "Top" for Cgroups):
systemd-cgtopShows CPU/Memory usage per Slice/Service. -
Inspect Your User Tree:
systemctl --user statusShows what your specific systemd instance is managing right now. -
Creating a Temporary Scope (Fun Test): Run a command in a specific slice to limit its resources.
systemd-run --user --scope -p MemoryMax=10M --unit=my-test bash(This starts a bash shell that systemd kills if it uses >10MB RAM).
Treesitter
Why no matter how many times do i google this i forget it ffs - TOGGLING TREESITTER TREEEEEEE......
Qmk compiling
I haven't compiled my QMK keyboard in an age so here's the command again
I think i will keep space as special key on my keychron as right alt is a bit hard to access under right thumb. But I will move keymaps to matching ./modules/nixos-keyd.nix
- To setup
- Finding my keyboard
-
compiling:
qmk compile -kb keychron/q4/iso -km joelyboy -
unplug keyboard and re-plug in pressing the flash button underneath the space bar
Will need another keyboard at this stage
- then
qmk compile -kb keychron/q4/iso -km joelyboy

