Meeting Saturday, 19 September 2026
Well I've done it now... I jumped off the deep end of the pool into Opencode AI for help resolving this issue.
My systems kept having issues with missing journal files and I didn't know what the cause was nor could find a reason. I decided to ask Opencode running on my laptop why this happened and it found the error immediately - and more importantly it resolved it.
Below I let Opencode write the article detailing the issue, solution, and take the credit. I just had to ask the question from my own terminal. I don't feel bad about this because it resolve an issue with seemingly a reason unknown. Note that edited the color scheme to make this readable.
Fixing journald on a machine that regenerates its machine-id every boot
— a systemd drop-in, a merge script, and how opencode helped find the answer
If you live on the edge — privacy-hardening a laptop, cloning bare-metal setups, or deliberately regenerating /etc/machine-id on every boot — you've probably seen this one. You hit the terminal, run plain journalctl, and get:
No journal files were found.
(1 of 5 files failed to open: No such file or directory)
Yet /var/log/journal/ is clearly full of data. Logs exist. The boots happened. The daemon just refuses to show them. This post walks through the root cause and the fix we landed on — and it's a genuinely nice example of where an AI-driven terminal assistant earns its keep.
Story credit first: I handed this symptom to opencode during a troubleshooting session. It traced the "No journal files were found" symptom back to a machine-id / journal-directory mismatch that I'd long suspected but never fully pinned down, then proposed the exact mechanism described below. I ran the commands it composed, verified each step, and the fix has survived subsequent reboots. The design flaws it caught (the enable-order deadlock, the active-user-journal case) are real ones — this is what happens when you argue with your tooling instead of just accepting the first suggestion.
Everything that should read the journal reports that nothing exists:
journalctl (as a normal user) → No journal files were found.
After a reboot, the same machine boots fine, systemd reports healthy, but the log trails are empty.
Meanwhile ls /var/log/journal/ shows one or more subdirectories, and sudo journalctl --directory=/var/log/journal/<something> often does find data.
The clue that localizes the whole thing: the journal on disk lives under a directory that is named after a machine-id that no longer matches the one the kernel and systemd are currently using.
When systemd-journald starts, it derives its persistent storage directory from the current machine identifier:
/var/log/journal/$MACHINE_ID
If you only read the journal as root, you may never notice the drift:
$ cat /etc/machine-id
b3f2d6c10c924f4d8db39926d3f1b3a2
$ sudo ls /var/log/journal/
b3f2d6c10c924f4d8db39926d3f1b3a2/
That all lines up. But now add to the picture a policy that regenerates /etc/machine-id on every boot — a common privacy choice, since the machine-id is a stable, queryable fingerprint of the box. On my setup that job was owned by a small oneshot service that was enabled at multi-user.target:
# regenerate-machine-id.service (the OLD way)
[Unit]
Description=Change Machine ID
After=local-fs.target
ConditionPathExists=/etc
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'chmod 644 /etc/machine-id /var/lib/dbus/machine-id'
ExecStart=/bin/bash -c 'rm -f /etc/machine-id /var/lib/dbus/machine-id && dbus-uuidgen --ensure=/etc/machine-id'
ExecStart=/bin/bash -c 'systemd-machine-id-setup'
ExecStart=/bin/bash -c 'chmod 444 /etc/machine-id /var/lib/dbus/machine-id'
[Install]
WantedBy=multi-user.target
Here's the trap. Boot order goes:
systemd-journald.service starts early — before multi-user.target fire.
The daemon reads /etc/machine-id = N1, and creates/pins /var/log/journal/N1.
Later, at multi-user.target, regenerate-machine-id.service runs and swaps the id to N2.
A few seconds into the boot, /etc/machine-id = N2, but journald is still writing to /var/log/journal/N1.
Now journalctl — which re-reads the current machine-id from disk and looks for that exact directory name — finds nothing. The daemon's view (N1) and the world's view (N2) disagree for the entire rest of the session, until the next reboot recreates the same dance.
The point: regenerating the machine-id is fine. Regenerating it after journald has already committed to a directory name is what breaks the journal. The fix is to change when the regen happens, and to heal the history that accumulated under the old ids.
Instead of a separately-enabled service, the regen becomes an ExecStartPre hook on systemd-journald.service itself. That guarantees it runs before the daemon computes its directory — so journald always starts on a fresh, consistent id — and the merge step carries the pre-regen history forward.
/etc/systemd/system/systemd-journald.service.d/order-regen.conf
[Service]
ExecStartPre=-/usr/local/bin/regenerate-machine-id.sh
The leading - means systemd treats the pre-command as best-effort: if the script fails for any reason, journald still starts. The drop-in is merged into the stock unit automatically — there's nothing to enable, no symlinks, no WantedBy. Just a file that lives in the standard drop-in directory.
And the script itself:
##___________________________________________________##
/usr/local/bin/regenerate-machine-id.sh
#!/bin/bash
mkdir -p /var/log/journal/_stale
trace=/var/log/journal/_stale/regen-trace-$(date +%s).log
exec >>"$trace" 2>&1
set -x
rm -f /etc/machine-id /var/lib/dbus/machine-id
dbus-uuidgen --ensure=/etc/machine-id
new=$(cat /etc/machine-id)
jdir="/var/log/journal/$new"
mkdir -p "$jdir"
chown root:systemd-journal "$jdir"
chmod 2775 "$jdir"
for d in /var/log/journal/*/; do
d="${d%/}"
[ "$d" = "$jdir" ] && continue
prev="${d##*/}"
for f in "$d"/*.journal "$d"/*.journal~ "$d"/*.journal.*; do
[ -e "$f" ] || continue
b=$(basename "$f")
case "$b" in
user-*@*|system@*) ;; # rotated journals: merge
user-*) mkdir -p /var/log/journal/_stale; mv -f -- "$f" "/var/log/journal/_stale/$b.$prev"; continue ;; # active user journal: bound to old machine-id, side-archive
*) ;; # system.journal & friends: merge
esac
mv -f -- "$f" "$jdir/"
done
rmdir "$d" 2>/dev/null || true
done
##___________________________________________________##
Lines / Purpose
rm -f /etc/machine-id… && dbus-uuidgen --ensure
Regenerate the id (this also covers /var/lib/dbus/machine-id).
new=$(cat /etc/machine-id)
Read the id we just generated — the directory journald will use this boot.
mkdir / jdir; chown root:systemd-journal; chmod 2775
Pre-create the journal directory with the exact ownership/perms systemd-journald expects (empty dirs created by mkdir alone get the wrong owner and the daemon complains loudly).
Loop over old /var/log/journal/*/
Reconcile history from previous boot ids into the new directory.
user-*@* / system@*
Rotated journals (system@xxxx.journal, user-*@*.journal) are content-addressed and id-independent — safe to merge wholesale.
user-*.journal (no @)
The active per-user journal is bound to the running session's metadata and the old machine-id; it must not be silently merged. It's side-archived as _stale/<name>.<old-id> before the daemon touches it.
mv -f catch-all
Everything else (primarily system.journal) is plain-old binary log data; move it into place under the new id.
rmdir
Sweep the emptied old directories; non-empty ones fail harmlessly.
exec >>"$trace" 2>&1; set -x
Every run leaves a timestamped, fully verbose trace under /var/log/journal/_stale/ — invaluable when this thing runs silently before logging is up.
The drop-in and script are distro-agnostic: it's the same systemd, same dbus-uuidgen, plain bash everywhere. The rollout checklist on the other box:
Copy the script +x.
Copy the drop-in.
Disable the old service — this is the part that's easy to miss.
systemctl daemon-reload.
# from the working box:
sudo scp /usr/local/bin/regenerate-machine-id.sh laptop_name:
sudo scp /etc/systemd/system/systemd-journald.service.d/order-regen.conf laptop_name:
# on the target:
sudo chmod +x regenerate-machine-id.sh
sudo mv regenerate-machine-id.sh /usr/local/bin/
sudo mkdir -p /etc/systemd/system/systemd-journald.service.d/
sudo mv order-regen.conf /etc/systemd/system/systemd-journald.service.d/
sudo systemctl disable regenerate-machine-id.service
sudo systemctl daemon-reload
Why step 3 is non-negotiable: if the old regenerate-machine-id.service is still enabled at multi-user.target, we're back to the original bug within the same boot — the drop-in regenerates N1 and merges, journald pins N1, then the old service fires again, silently swapping to N2 behind journald's back. Result: "No journal files were found" on the very next journalctl, re-introduced on every boot. Remove one or the other; you never want both.
The fix is specifically for environments that already chose the per-boot-regen behavior:
If your machine-id is stable, do not install this — it forces a fresh identity every boot, on top of a journald restart.
Expect DHCP observers to see a new client id, /etc/machine-id consumers (subscription software, DMID-based licensing, container bind-mounts) to churn, and any tools that fingerprint the box to see a "new machine" each boot.
If you only want a one-time reset, just sudo rm /etc/machine-id /var/lib/dbus/machine-id && sudo systemd-machine-id-setup — no drop-in needed.
This is, honestly, a case where the AI didn't just assemble a plausible answer — it caught the design bugs that make other write-ups fail. The naïve fix (a script that regenerates the id and hopes for the best) breaks again within minutes of boot, because the regen was still ordered after journald. The refined one puts the regen inside journald's startup, merges the old journals, and side-archives the active user journals that are intrinsically bound to the dead id.
That sequence — presenting the symptom, having opencode reason through boot ordering and journal storage semantics, then reviewing and running what it proposed — is the part worth copying, more than the files themselves. The files are worth having too, though. After several subsequent boots, build logs are intact and journalctl --since=yesterday finally works again.
Diagnosed and solved with opencode
Recently (2026) we learned MS Windows is tagging user data to allow tracking down to the machine identifier (GUID)...
Well we can't have that on Linux, right? Wrong. Linux has a supposedly innocuous locally-used-only system ID known as "machine-id". To be sure
To change this on your systems on every shutdown/reboot, create a service file to generate a new machine-id. WARNING: YMMV and you could see bad things happen, but so far I've noticed Zero ill effects on my systems.
To change the /etc/machine-id and /var/lib/dbus/machine-id on Linux Mint, you need to create a systemd service file. Follow the steps below to set it up.
Open a terminal.
Use a text editor to create a new service file named change-machine-id.service in the /etc/systemd/system/ directory. You can use vi or any other text editor:
sudo vi /etc/systemd/system/change-machine-id.service
## or if you prefer, edit with nano ##
Add the following content to the file:
[Unit]
Description=Change Machine ID
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'rm -f /etc/machine-id /var/lib/dbus/machine-id && dbus-uuidgen --ensure=/etc/machine-id'
[Install]
WantedBy=multi-user.target
After saving the service file, you need to enable and start the service:
Enable the service to run at boot:
sudo systemctl enable change-machine-id.service
Start the service immediately:
sudo systemctl start change-machine-id.service
Reboot and check that your machine-id changed.
References: https://man.archlinux.org/man/machine-id.5.en
https://poweradm.com/reset-machine-linux/
Enjoy!!
If you don't already know this by now, there's a great Open Source tool called ytdownloader.
ytdownloader is a "GKT3 frontend for yt-dlp (the active branch of youtube-dl) with focus on best audio and video. Uses ffmpeg for joining audio & video."
This tool is based on youtube-dl and will enable downloading videos (or just the audio) from various sites. Sure it's has youtube in the name, but it does loads more. Check the developer's ytdownloader github page here.
Here's a command alias I use to grab videos at a certain resolution along with their description.
alias ytd='yt-dlp -ciw -S res:1080,ext:mp4:m4a -S vcodec:avc1 --write-thumbnail --convert-thumbnails png -o "%
Just add the alias listed below to your ~/.bashrc, ~/.bash_aliases, or ~/.zshrc and either logout or source the file after writing it to use it immediately.
Source the file you ask?
Sure you can use a search engine for that, but here you go.
Open the terminal
Type "source ~/.bashrc"
Press Enter
Then use your new alias
Try it now!
ytd https://youtu.be/J8nGqkUJMxU
And there you go - look at the directory listing for the files:
20140704 - How to use aliases in BASH [J8nGqkUJMxU].mp4
20140704 - How to use aliases in BASH [J8nGqkUJMxU].description
And if you don't like command line tutorials, why are you using Linux? Seriously?
Alas, there is a GUI front-end for yt-dlp called ytdownloader.
Grab from the ArchLinux AUR on Arch Linux based distributions with:
yay -S ytdownloader
paru -S ytdownloader
Enjoy!!
I recommend this excellent lightweight ARM-based laptop for any Linux geek just wanting to have a sharp tool in their backpack. I've had mine since June 2020 and it is still going strong. I ordered the NVME M.2 SATA adapter, but have only installed inside without an NVME drive mainly due to power consumption requirements. This thing runs nearly 9 hours off the mains. It ships with Manjaro Linux (Arch Linux based) right out of the box on the 64GB EMMC. This won't break any speed records, but with a fixed 4GB of memory and the lower-power ARM processor, it really does fill a niche.
Specs:
CPU: 64-Bit Dual-Core ARM 1.8GHz Cortex A72 and Quad-Core ARM 1.4GHz Cortex A53
GPU: Quad-Core MALI T-860
RAM: 4 GB LPDDR4 Dual Channel System DRAM Memory
Flash: 64 GB eMMC 5.0
Wireless: WiFi 802.11AC + Bluetooth 5.0
One USB 3.0 and one USB 2.0 Type-A Host Ports
USB 3.0 Type-C ports with alt-mode display out (DP 1.2) and 15W 5V 3A charge.
MicroSD Card Slot: 1
Headphone Jack: 1
Microphone: Built-in
Keyboard: Full Size ANSI(US) type Keyboard
Touch-pad: Large Multi-Touch Touchpad
Power: Input: 100~240V, Output: 5V3A
Battery: Lithium Polymer Battery (9600mAH)
Display: 14.1″ IPS LCD (1920 x 1080)
Front Camera: 2.0 Megapixels
Power Supply included, comes with both US and EU plugs
Dimension: 329mm x 220mm x 12mm (WxDxH)
Weight: 1.26 kg (2.78 lbs)
Warranty: 30 days