FreeBSD: preferring ee (avoiding vi) for csh/tcsh and sh
https://gist.github.com/grahamperrin/be1bc6ac40bfef0693b0ab5cef050f3e
ee(1)
https://man.freebsd.org/cgi/man.cgi?query=ee&sektion=1&manpath=freebsd-release
FreeBSD: preferring ee (avoiding vi) for csh/tcsh and sh
https://gist.github.com/grahamperrin/be1bc6ac40bfef0693b0ab5cef050f3e
ee(1)
https://man.freebsd.org/cgi/man.cgi?query=ee&sektion=1&manpath=freebsd-release
Wrote a blogpost about simple (I mean with a shell and a text editor) #X11 configuration.
Covered topics:
1) #Trackball configuration for left hand. Also remapping of some buttons to have scrolling and middle button (not exists out of the box).
2) Theming: #GTK2 #GTK3 #QT , installing cursor(s), fonts and icons.
3) #Xrandr for multimonitor configuration
4) #Xserver settings for #HighDPI
5) #XDG utils and #Emacs as a system file manager
6) #XDM login window
https://eugene-andrienko.com/en/it/2025/07/24/x11-configuration-simple.html
Had a play with #FreeBSD in live mode from a USB stick on an older laptop tonight just to see if the hardware was supported.
Boy am I out of practice at configuring stuff on the command line! I've become spoilt by GUIs in recent years.
Didn't help with the bug in 15.0 where the WiFi firmware file was incorrectly named and couldn't be loaded for the Intel 8260. Got there eventually, but it seemed to be very slow with iwlwifi. More experimentation required!
RE: https://mastodon.bsd.cafe/@grahamperrin/116127306722990113
Imagine: a FreeBSD developer breaking all six ground rules of the FreeBSD Community Code of Conduct and imagining that their abuse will have no consequences.
Imagine: the same person later imagining that they're qualified to provide an uninvited lecture about conduct.
Normally, I'm discreet about such things.
Unfortunately, some people don't know when to be quiet. Imagine that person.
sh | shh
If this file exists, please do NOT follow the widespread advice to overwrite it:
/usr/local/etc/pkg/repos/FreeBSD.conf
Bug reports for the src and doc trees, and the wiki:
― <https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291501>
― <https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=291502>
I need a Debian VM for the tech edits on #n4sa2e. Switched to the Mac, so I need to set up a new one.
Reminded once again that you can 95% set up a working Linux box by hitting ENTER in the installer. The only things you type are your username, passwords, and one TAB to say "yes, fry my disk." The advanced options are there, but you must look for them.
My #freebsd people: I love y'all, but THIS is an installer that welcomes new users.
Using pkgbasify to repair a broken installation of FreeBSD 14.3-RELEASE
https://gist.github.com/grahamperrin/9570092c127bd43777961b6f016e75ba
Non-systemd operating systems like the BSDs, Devuan, etc...
Do we think there will be some kind of work around in place for Gnome 50?
Has KDE articulated a plan either in favour or not for systemd-only in the future similarly to Gnome?
Xfce stated a direction?
I'm going to guess that Cosmic will always be only systemd.
FreeBSD package repository name changes
https://reddit.com/r/freebsd/comments/1n40dbd/freebsd_repository_name_changes/
FreeBSD Closes the Laptop Gap — Year One Update
We’ve published a Year One update on the Foundation’s ongoing work to improve laptop and desktop support in FreeBSD. The article highlights progress in hardware enablement, driver development, installer changes, and the roadmap for continued improvements in 2026.
Read the update:
https://freebsdfoundation.org/blog/freebsd-closes-the-laptop-gap-year-one-project-update/
GhostBSD is going to abandon Xorg after 15 years. Eric Turgeon explains why GhostBSD switched from Xorg to XLibre instead of Wayland.
Full details here: https://ostechnix.com/ghostbsd-switches-to-xlibre-over-wayland/
pkgbase: all packages deleted without deleting FreeBSD.
pkg-unregister(8) is our friend.
pkg version 2.5.0 is something special.
A software reason to be jolly for a festive season.
Fa la la la la, la la, la, la.
https://www.reddit.com/r/freebsd/comments/1pbup22/comment/nvqe3kf/
Help, please:
https://lists.freebsd.org/archives/freebsd-current/2025-September/008892.html
I want to use uclcmd with a file that is initially empty:
/usr/local/etc/pkg/repos/FreeBSD.conf
FreeBSD: pkgbase: major upgrades
― a rough guide, for alpha testing on AMD64.
From <https://redd.it/1nlo8lp>:
❮ An obvious response to the roughness: it's more complicated than legacy freebsd-update(8) (for FreeBSD 14.3-RELEASE). True; this is for alpha testing.
Please read the preamble before anything else. If you have any question, please don't hesitate to ask. ❯
Infrastructure Modernization Commissioned by the Sovereign Tech Agency
We’ve published an update on the infrastructure program commissioned by the Sovereign Tech Agency and managed by the FreeBSD Foundation.
Read the full update:
https://freebsdfoundation.org/blog/infrastructure-modernization-commissioned-by-the-sovereign-tech-agency/
#FreeBSD #OpenSource #Infrastructure
@ax6761 bonjour! Ici Graham P, rapporteur du bug 287569.
Royaume-Uni, deux points:
1/ il y a quelques mois, FreeBSD developers ont fait beaucoup topnotch invisible changes, without which it would have been presque impossible to not only make a "PR" (not to be confused with une pull request) mais aussi trouvez un travail-around
2/ the pictured error peut etre moins likely to occur if requirements de la systeme sont documented
3/ mon "BR" (bug report, not to be confused with une probleme report) pour documentation n'etait pas acceptable.
Oh la la. Deux ou trois. J'suis un poet et I don't know it.
<https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=287722>
― Website: system requirements: memory/RAM: UFS and ZFS
Exactly one year ago, on 30th December 2024, I laid the foundation of FediMeteo.
I took a VM, installed FreeBSD, and set up the first jail to support Italy. The goal was to create a tool for my own use, support a few countries, and announce it.
Unexpectedly, the enthusiasm was incredible. That pushed me to keep going, support more countries and cities, and turn it into what it is today.
FediMeteo now supports 38 countries and 2,937 cities, with more than 7,700 followers in the Fediverse alone, not counting the many people who follow via RSS feeds or visit the web pages.
If you are curious to read the story and some technical details, you can find it here:
https://it-notes.dragas.net/2025/02/26/fedimeteo-how-a-tiny-freebsd-vps-became-a-global-weather-service-for-thousands/
Today is also Tuesday, a #ThankYouTuesday, so I want to say thanks to:
* OpenMeteo - @openmeteo - for providing accurate, high quality data, without which FediMeteo would be far less useful
* @grunfink - creator of snac, who made all of this possible using very few resources, on a 4 euro per month VM
* FreeBSD, which thanks to the efficiency of the OS and its jail implementation made it possible to run this service in a stable and efficient way with minimal effort
* FediFollows - @FediFollows - that periodically spreads the word about cities, countries, and the enthusiasm around the project
*All of you*, who suggested, encouraged, corrected, and celebrated this project
And forward toward supporting more countries and other interesting features already in the works.
Happy birthday, FediMeteo! 🎉
#FediMeteo #HappyBirthday #Meteo #Weather #FreeBSD #snac #snac2 #OpenMeteo #Fediverse
A continuation of the same idea
I had already explored the same problem with snac and nginx in two previous posts: Improving snac Performance with Nginx Proxy Cache (https://it-notes.dragas.net/2025/01/29/improving-snac-performance-with-nginx-proxy-cache/) and Caching snac Proxied Media with Nginx (https://it-notes.dragas.net/2025/02/08/caching-snac-proxied-media-with-nginx/). In both cases, the idea was that the reverse proxy should absorb repeated public requests instead of letting them consume snac resources.Why there is almost no media
Before talking about HAProxy, it is worth mentioning one of the most important optimizations, which is not in the proxy configuration at all.The homepage is static because it can be static
The main homepage follows the same logic.Many countries, one entry point
FediMeteo is made of many country instances. Each one runs in its own jail and listens on its own internal address and port. From the outside, however, they all live under the same domain structure:fedimeteo.comAnd many more.
www.fedimeteo.com
it.fedimeteo.com
uk.fedimeteo.com
jp.fedimeteo.com
us.fedimeteo.com
usa.fedimeteo.com
can.fedimeteo.com
canada.fedimeteo.com
fedimeteo.com backend_fedimeteoThe frontend then needs only one rule:
www.fedimeteo.com backend_fedimeteo
it.fedimeteo.com backend_it
uk.fedimeteo.com backend_uk
jp.fedimeteo.com backend_jp
us.fedimeteo.com backend_us
usa.fedimeteo.com backend_us
can.fedimeteo.com backend_ca
canada.fedimeteo.com backend_ca
use_backend %[req.hdr(host),field(1,:),lower,map(/usr/local/etc/fedimeteo.map,backend_fedimeteo)]This reads the
Host header, removes the port if present, lowercases the result, and looks it up in /usr/local/etc/fedimeteo.map. If nothing matches, it falls back to the main FediMeteo backend.Backends as small compartments
The country backends are deliberately plain:backend backend_itOne backend, one jail, one snac instance. This is exactly the same organizational principle as the rest of the project. If I need to reason about Italy, I look at the Italian jail. If I need to reason about the United Kingdom, I look at the UK jail. If one day I need to move a country elsewhere, the separation is already there.
mode http
http-reuse safe
server srv1 10.0.0.2:8001 maxconn 30
backend backend_uk
mode http
http-reuse safe
server srv1 10.0.0.7:8001 maxconn 30
backend backend_jp
mode http
http-reuse safe
server srv1 10.0.0.32:8001 maxconn 30
maxconn 30 value is not a magic number. It is a ceiling. I want each small backend to have a visible limit in front of it. If something starts hammering a country instance, I prefer the pressure to appear at the HAProxy layer instead of becoming unlimited concurrent work inside snac.http-reuse safe lets HAProxy reuse backend connections where appropriate. This is another small reduction in unnecessary work. Opening connections repeatedly is not the biggest problem in the world, but avoiding it is still better, especially when many small services sit behind the same proxy.The front door
The HTTPS frontend listens on IPv4 and IPv6 and offers both HTTP/2 and HTTP/1.1:frontend https_inTLS defaults are set globally:
bind :::443 v4v6 ssl crt /usr/local/etc/certs/ alpn h2,http/1.1
mode http
option http-keep-alive
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256Port 80 only redirects to HTTPS, except for Let's Encrypt challenges:
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
acl letsencrypt-acl path_beg /.well-known/acme-challenge/In the HTTPS frontend I also set the usual forwarding headers:
http-request redirect scheme https code 301 unless letsencrypt-acl
use_backend letsencrypt-backend if letsencrypt-acl
http-request set-header X-Real-IP %[src]And I add HSTS:
http-request set-header X-Forwarded-Proto https
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"None of this is unusual, and that is fine. The interesting parts of an infrastructure are not always the parts that should be unusual.
Two caches, because the requests are different
The HAProxy configuration defines two caches:cache mediacacheI keep media and ActivityPub JSON separate because they are not the same kind of traffic.
total-max-size 128
max-object-size 10000000
max-age 3600
process-vary on
max-secondary-entries 12
cache jsoncache
total-max-size 16
max-object-size 1000000
max-age 60
process-vary on
max-secondary-entries 12
Recognizing media
For media, the ACL is based on file extensions:acl is_media path_end -i .jpg .jpeg .png .gif .webp .svg .ico .mp4 .webm .mp3 .ogg .wav .flac .mov .avi .mkv .m4vThen I store the result in a transaction variable:
http-request set-var(txn.is_media) bool(true) if is_mediaThe cache lookup is straightforward:
http-request cache-use mediacache if { var(txn.is_media) -m bool true }
And on the response side:http-response set-header Cache-Control "max-age=3600, public" if { var(txn.is_media) -m bool true }
http-response del-header Set-Cookie if { var(txn.is_media) -m bool true }
http-response del-header Vary if { var(txn.is_media) -m bool true }
http-response cache-store mediacache if { var(txn.is_media) -m bool true }
The Cache-Control header makes the intent explicit. Set-Cookie is removed because a public media object should not carry session information. Vary is removed because I do not want the same avatar to fragment into many cache entries because of harmless header differences.http-request del-header Authorization if { var(txn.is_media) -m bool true }
http-request del-header Cookie if { var(txn.is_media) -m bool true }
I would not do this globally. I do it after deciding that the request is media. Scope is what makes these rules safe.ActivityPub JSON microcaching
The ActivityPub side starts from theAccept header:acl is_ap_json req.hdr(Accept),lower -m sub application/activity+jsonThis part matters because ActivityPub uses content negotiation. The same path may return HTML to a browser and JSON to a remote instance. If the proxy pretends that a URL is always one thing, it will eventually cache the wrong representation.
acl is_ap_ldjson req.hdr(Accept),lower -m sub application/ld+json
acl is_outbox path_end /outbox
acl is_get method GET
acl has_auth req.hdr(Authorization) -m found
acl has_cookie req.hdr(Cookie) -m found
http-request set-var(txn.is_activitypub) bool(true) if is_get !is_outbox is_ap_json !has_auth !has_cookieThere are several decisions here, all important.
http-request set-var(txn.is_activitypub) bool(true) if is_get !is_outbox is_ap_ldjson !has_auth !has_cookie
GET, because I am not caching deliveries or anything that changes state. It must not be /outbox, because outbox collections are not the traffic I want to cache here. It must not have Authorization, and it must not have cookies, because authenticated or user-specific requests do not belong in a shared public cache.http-request cache-use jsoncache if { var(txn.is_activitypub) -m bool true }
http-response set-header Cache-Control "max-age=60, public" if { var(txn.is_activitypub) -m bool true }
http-response cache-store jsoncache if { var(txn.is_activitypub) -m bool true }
Sixty seconds is short, but useful. Federation often creates small clusters of identical requests. A remote server fetches an actor, another fetches the same actor, something asks for the same object, something retries. I do not need to cache these responses for hours. I only need HAProxy to answer the second and third identical request during the same small burst.Static media paths
There is also a rule for static paths:acl is_short_path path_reg ^/[^/]+/s/This comes from the same observation that led me to cache snac media with nginx. snac uses static media paths, and those paths often represent the kind of public, repeatable traffic that should not consume backend threads if the proxy can serve it. I call them "short", not because they are, but because the first time I saw them, I thought the 's' stood for "short", not "static". The name just stuck.
http-request cache-use mediacache if is_short_path
Vary, but not without limits
process-vary onI want HAProxy to process
max-secondary-entries 12
Vary, because content negotiation is real, especially when ActivityPub is involved. But I also want variation to be bounded. If every slightly different header creates another cache entry, the cache becomes a complicated way to miss.Vary before storing the response. A shared avatar does not need to vary by Accept. For ActivityPub JSON, I am more careful because the representation matters.Seeing whether it works
During rollout, I like to expose a very small diagnostic header:http-response set-header X-Cache-Status HIT if !{ srv_id -m found }
http-response set-header X-Cache-Status MISS if { srv_id -m found }
This is intentionally simple. If HAProxy selected a backend server, I call it a miss. If no backend server was selected, the response came from cache, so I call it a hit. It is not a complete observability system, but it is enough to answer the first question I usually have after changing a cache rule.curl -I https://it.fedimeteo.com/path/to/avatar.pngThe second request should be a hit.
curl -I https://it.fedimeteo.com/path/to/avatar.png
Accept header:curl -I \And I also want to verify that cookies and authorization prevent public caching:
-H 'Accept: application/activity+json' \
https://it.fedimeteo.com/some/activitypub/object
curl -I \A cache that works should be visible. A cache that is invisible can be correct, but it can also be silently wrong. I prefer to know.
-H 'Cookie: test=value' \
-H 'Accept: application/activity+json' \
https://it.fedimeteo.com/some/activitypub/object
curl -I \
-H 'Authorization: Bearer fake' \
-H 'Accept: application/activity+json' \
https://it.fedimeteo.com/some/activitypub/object
Compression and operational paths
HAProxy also handles gzip compression:filter compressionThis keeps another common responsibility at the edge. The country instances can stay focused on snac and the forecast data, while HAProxy deals with client-facing compression for HTML, JSON, and ActivityPub responses.
compression algo gzip
compression type text/css text/html text/javascript application/javascript text/plain text/xml application/json application/activity+json
frontend prometheusAnd I keep internal operational paths, such as statistics and Grafana, handled before the hostname map. These are small details, but ordering matters. Special paths should be explicit and early. The hostname map is for FediMeteo routing, not for every internal tool I happen to expose behind the same proxy.
bind 127.0.0.1:8405
mode http
http-request use-service prometheus-exporter
no log
What this changes in practice
The nice thing about this configuration is that none of its parts is particularly surprising.Caveats
This configuration is not a universal HAProxy recipe for ActivityPub services.Conclusion
FediMeteo started as a small idea and became larger than I expected, but I still want it to feel small in the right ways. Small does not mean fragile. Small means understandable. It means that each part has a reason to exist, and that unnecessary work is removed before it becomes a problem.