Solene'% https://dataswamp.org/~solene/ Writing an IPFS publishing service # Introduction I started playing with IPFS because I wanted a different way of publishing data over the Internet. My first experiments with IPFS and a private swarm showed that the technology is useful, but the user experience is bad. It is easy to get an IPFS node running, but hard to make it useful in real world. The issue was not only that the protocol itself is complicated: IPFS is not a file server, it is a content-addressed system where files are identified by a CID, which is a hash of the content. This gives strong integrity guarantees, but it also means most people are puzzled by files addresses looking like `bafybeidbnzlne4dmw4ouep4ppb5zpzoye5t6uj32qpv76ifwm6wnehaygm`. The private IPFS swarm was not really good for the use case I had in mind, I wanted to easily publish content over IPFS and potentially allow friends to do so, but this required to handle some quota management. This is how I ended up writing a small middleware project, because IPFS already handled this with a "remote pinning service" protocol, but there were no open source tool for this. This let users pin IPFS content to a remote server/cluster, then it grew into a service that allow end users to not even use IPFS at all. They can upload files, pin an IPFS CID, publish content through a RSS feed, prevent a CID deletion until a certain date. In this post, I will explain what I built and who can use find it useful. => https://git.coopix.eu/public/ipfs-pinning-middleware Ipfs pinning middleware git project page I also wrote a companion app that allows Nautilus (GNOME file browser) that allows user to right-click on files/directories and publish over IPFS + remote pin in two clicks. => https://git.coopix.eu/public/nautilus-ipfs Nautilus IPFS git project page # IPFS issues The first problem was IPFS itself, it is not easy to understand and the ecosystem UX (kubo, ipfs-desktop) does not help. In the end, I wanted a simple system where users can upload a file, get a link, remove it, etc... The project started as a middleware to allow IPFS users to pin IPFS content to a 24/7 server. Over time, it became a web interface for a small set of users. * upload files / directories * pin a CID * publish links over the https gateway through an RSS feed made of pinned data set to "public" * set an expiration date that will trigger the CID unpinning * set a lock date that prevent the user to unpin the file before the defined date * keep quotas under control for different users (while still accounting space saved by deduplication per users) This is useful for a small group that wants to share files publicly. There are only a couple of proprietary services that offer something similar, and I wanted an open source alternative that could run at home or in a small structure. The server runs as the long-lived pinning node, users do not need to manage their own IPFS daemon, nor do they need to understand the mechanics of IPFS, pinning, CID, gateways... They just push content to the service and the server takes care of preserving it. # Why IPFS at all The reason I kept going with IPFS is that it cool, and it gives a few real advantages that are useful for publishing. The biggest one is content addressing. A CID is a checksum of the content, if you know the URL of a file, you can verify that the content you received matches the expected hash. That is a useful property for trust and integrity. It gives a simple, clear way to check whether the content has changed, which is for instance useful when iterating over something, all previous versions of the files are still accessible for comparison if you know their CID (and they exist somewhere on IPFS). This also helps network load, if a small group of people needs to share a lot of files, or serve a lot of volume, the network can spread that load across multiple peers (only between IPFS users, or if people add IPFS gateways), but this gives more control to users if they want to move their data somewhere else. There is another practical benefit, that is more technical. IPFS lets you use a content-based DNS record (this is called dnslink in IPFS). With dnslink, you can point a subdomain to an IPFS content hash, which makes it possible to use strong CSP headers, which is useful because a strict Content Security Policy (CSP) can help building "static interactive websites" (like cyberchef or omnitools) with security enforced. # Middleware project I did not came up with a nice name for it, as it stands between IPFS nodes (originally) and an "ipfs-cluster" daemon, it's a middleware. Now, for end users, it hides the confusing IPFS parts to focus on the useful stuff. The project does not try to make IPFS simple, it hides it. In practice, this means: * users do not need to run a local node * users do not need to understand pinning * users can upload content through a web interface * a server (or a cluster) takes care of pinning, retention and distributing over an HTTPS gateway The web interface looks like this at the moment of writing: => static/middleware-dashboard.png Middleware web dashboard # Conclusion The project is working worked well enough to be useful, I use it myself for publish content over the web. # Going further I published a few open source projects that are supposed to work 100% within the browser without extra network, they are hosted through my pinning service, with very strict CSP headers that forbid the web browser to connect to another domain. => https://bafybeid6kghacn3v3lzt5pj2dpltigvaivfvuc46cbdqs47abni5ra3z7i.ipfs.coopix.eu/ Omnitools: this does a lot of things, including videos, audio, PDF tools, pictures conversion all within your web browser => https://bafybeie5zi7r3wvkv2kxn6kned5xmrnhqgfamy6tn3kskdxofsy2lzkrlu.ipfs.coopix.eu/ JSON Crack: an interactive JSON editor / visualizer => https://bafybeidavm6tgjjeh7j6mlqpi55zu4iva4ttrok4fibp46c7shnb5q6kia.ipfs.coopix.eu/ Cyberchef: a tool to play with cryptographics functions => https://bafybeibnq23wazx6fy4jjpk65v6jbdmuzfy2y7m2a4nqe3pxuohdenlpzy.ipfs.coopix.eu/ Squoosh: a picture compression tool with left/right difference allowing to explore compression results This illustrates IPFS published content in practice. ]]> gopher://dataswamp.org:70/1/~solene/article-ipfs-remote-pinning-service-with-extra-features gopher://dataswamp.org:70/1/~solene/article-ipfs-remote-pinning-service-with-extra-features Wed, 23 Sep 2026 00:00:00 GMT How to setup your own IPFS private network # Introduction In this blog post, you will learn how to make a private IPFS network that will allow private peers to exchange data between each other. This is in opposition to the default setup in which you use the default configuration connecting to the whole IPFS network. The incentive to have your own network is mostly speed, because this does not have any extra access control, any peer can potentially access to any content you have in your IPFS network. If you do not know about IPFS, I wrote some blog posts, but otherwise IPFS website may give you some clue. It is not easy to grasp, but you can see it as a network service providing content addressed object storage. The base blocks provide features to manage data in the network but also cache it or provide it through a web gateway. => https://ipfs.tech IPFS project official website Kubo is the reference implementation of IPFS. The setup I explain in this article is pretty much useless for most people. Depending on your needs, you will have a better experience with Nextcloud/Seafile for central file sharing, Peergos for encrypted storage, syncthing for directory synchronization between peers, or even magic wormhole for one-shot transfers. I am still not sure which purpose an IPFS private swarm serves correctly, but none of the use cases I just listed, maybe it is useful if you need to keep some dataset synchronized and quickly available in multiple areas, but then an hyperscaler may be more suited. # Setup In this setup, you will have various nodes that can speak to each other (through a VPN, LAN or directly over the Internet) and potentially but not mandatory, a node that is up 24/7. They must all share the same `swarm.key` to create a swarm (a group of nodes, that is private until you get access to the key). The setup itself will allow nodes to publish data and give it access to other nodes for caching, downloading or relaying, but also give the opportunity to publish on a web gateway. # Too Long Didn't Read IPFS daemon will hold data until it reaches its maximum allowed size, then will run a garbage collector to reclaim disk space by removing unused data. Data which was pinned on the server will never be garbage collected. A directory or file is content addressed, this mean is has a unique address derived from its content, which mean the URL of a resource depends on its content, if you update a directory, it will get a new URL because its content changed. IPFS has a mechanism called IPNS which allows to publish a content hash under a fixed hash, which is published to other peers under some conditions, this is the only way to provide an address that never change but in which you can change the content. An IPNS address requires an associated cryptographic key that is managed by kubo, if you want to pin a resource for each people you share data with, you will need a dedicated key for each. Data do not propagate magically over IPFS, if you add data to your node, it remains local until another node pulls in the content, then it will be able to distribute the data too, but there is a high chance it gets garbage collected one day if the node owner do not pin your data. IPFS has no at rest encryption or access control available, it is a method to publish data. # Configuring a node First, you need to install Kubo, make sure to not install kubo-desktop which is unfortunately incompatible with a private swarm. I personally prefer to use it in a container version for sandboxing reasons. Generate the swarm.key with this code: ``` echo -e "/key/swarm/psk/1.0.0/\n/base16/\n$(openssl rand -hex 32)" > swarm.key ``` Download the ipfs-webui which is normally downloaded by kubo from the IPFS network, but as you are not connecting to it, you need to inject the webui in your kubo. On the following URL, download the "car" file that matches your kubo version. => https://github.com/ipfs/ipfs-webui/releases Now, create and init your IPFS daemon using this script, if you do not use podman and prefer to use `ipfs` binary, replace the long podman command by just `ipfs`. The script takes `swarm.key` as the first parameter and the web-ui car file as a second parameter: ``` #!/bin/sh set -xe if ! test -f "$1" then echo "You must give the swarm.key file in parameter" exit 1 fi if ! test -f "$2" then echo "You must give the ipfs-webui@v4.13.0.car file in parameter (version should match your kubo version)" echo "You can download it from https://github.com/ipfs/ipfs-webui/releases" exit 1 fi # execute commands with ipfs run() { podman run --replace --name kubo \ -e LIBP2P_FORCE_PNET=1 -e IPFS_PROFILE=lowpower \ --userns=keep-id \ -v $HOME/.ipfs/:/data/ipfs:Z \ docker.io/ipfs/kubo:release $* } # import webui, requires stdin import_webui() { podman run --replace -i --name kubo \ -e LIBP2P_FORCE_PNET=1 -e IPFS_PROFILE=lowpower \ --userns=keep-id \ -v $HOME/.ipfs/:/data/ipfs:Z \ docker.io/ipfs/kubo:release dag import < "$1" } # make sure the directory exist # you can adjust if you want to store it elsewhere mkdir -p ~/.ipfs # copy the swarm key cp "$1" ~/.ipfs/swarm.key # uncomment this if you do not use the container version which does it automatically #run init # allow server to give other peers address run config Routing.Type dht # allow other peers to relay if a peer is not directly reachable by us but another peer can run config --json Swarm.RelayClient.Enabled true # remove all the default stuff run bootstrap rm --all run config --json Routing.DelegatedRouters '[]' run config --json Bootstrap '[]' # private swarm key = TLS can not be used but it is still encrypted run config --json AutoTLS.Enabled false run config --json AutoConf.Enabled false # allow to use IPNS through pubsub and advertise often run config --json Ipns.UsePubsub true run config Ipns.RecordLifetime 240h run config Ipns.RepublishPeriod 1m run config Ipns.MaxCacheTTL 1m import_webui "$2" echo "Setup successful" echo "You can visit http://localhost:5001/webui/ after starting the server" ``` Now, start the server (use `ipfs daemon` and the environment variables if not using the container): ``` podman run --replace --name kubo --restart=always \ -e LIBP2P_FORCE_PNET=1 \ -e IPFS_PROFILE=lowpower \ -p 8080:8080 -p 127.0.0.1:5001:5001 -p 4001:4001 -p 4001:4001/udp \ --userns=keep-id \ -v $HOME/.ipfs/:/data/ipfs:z docker.io/ipfs/kubo:release ``` On the webui, in the Peers menu, add your other peers. # 24/7 server specific setup The snippet above will work for a server, but you want to change a few things: If you use a container with restricted network, you need to give a reachable IP address to announce to your other peers: ``` ipfs config --json Addresses.Announce '["/ip4/192.168.1.166/tcp/4001"]' ``` You may also want it to be the DHT server to allow peers to discover each others through it, and maybe relay data between peers which could not connect directly: ``` # allow to give other peers address for mesh networking ipfs config Routing.Type dhtserver # allow to relay data between two peers that could not connect to each other ipfs config --json Swarm.RelayService.Enabled true ``` You may also want to limit the amount of storage allowed in your Kubo server before the garbage collector free some space: ``` ipfs config Datastore.StorageMax "50GB" ``` # Networking There are 4 differents ports in use that you need to be aware of: * Port 4001 TCP and port 4001 UDP are used by peers to exchange data between each other * Port 5001 TCP is used to reach the admin API and the webui, do not expose it publicly * Port 8080 TCP is used for the gateway, it allows to expose your IPFS private network content to people able to reach the gateway, without them installing kubo at all # Conclusion In my use case, I have a script regularly pinning a list of IPNS addresses on the server running the HTTPS gateway, so it always download the latest version of the IPNS published resources and make them available through the gateway even if the computers owning the file is offline. This is actually not super useful as I could have done the same thing using Nextcloud, or with a script copying the files to a static HTTPS server. # Going further This infrastructure got more useful after adding a single `ipfs-cluster` service near my gateway running 24/7 allowing the nodes to use it as a remote pinning service. Now, a node can pin a CID on the gateway which make sure the data will be available locally there. ]]> gopher://dataswamp.org:70/1/~solene/article-your-own-ipfs-network gopher://dataswamp.org:70/1/~solene/article-your-own-ipfs-network Fri, 21 Aug 2026 00:00:00 GMT Software to keep photos organized # Introduction I have a lot of photos that I have been carrying since a long time, this is certainly my oldest files that I was able to not lost over 20 years. It has been stored as a hierarchy since then, and it had very poor metadata information, and poor ability to be browsed. It was time to improve on this. My goal was to fix metadata on my pictures, but also put geolocation metadata on them because I really enjoy see a map with thumbnails of memories (just make sure to trim this metadata before sharing). Then I found about "modern" features like face recognitions, which allowed me to easily sort pictures by people, which I found handy when I want to view photos of relatives who are no longer with us. I tried multiple solutions, each with pros and cons, here is an overview of my findings. First, they almost all support the following features, let's say it is the core set of features we want: * support metadata in sidecar files * allows pinning pictures on a map * has face recognition * can do mass edits * duplicate detection * can browse pictures from a map * can sort by year/month # Digikam => https://www.digikam.org/ Project official website As of my experience, it was practical to edit a lot of pictures and reset incorrect metadata, or add geolocation on directories of pictures. But the overall experience was pretty bad, I gave up a few times, I had to dig a lot to figure how to achieve what I wanted correctly. I did not really enjoy using Digikam at all, but to my surprise, it is the most viable and advanced photo library around. There are other software of course, but they all lacked a few features Digikam had. Digikam can generate metadata from filename (date, time), this can be useful when you have a lot of old pictures that have timestamp in the filename, but not in the metadata. # Photoprism => https://www.photoprism.app/ Project official website Photoprism is an open source web app that you can self host. It is rather easy to host and use, although the user interface is not really great, it works well. In addition to face recognition, it has scene / objects recognition. In practice, it gave funky results like a volcano scene for a dish photo. One issue with photoprism is that an instance of it can only have a single set of pictures, you can make multiple users for access control, but they are all admin. Some features have a weird user experience, most notably the face recognition interface. # Immich => https://immich.app/ Project official website This is the best software around for the task, by far, in my opinion. It works great, looks great, it has a great user interface and provides Android and iOS apps for uploading new pictures. Face recognition works well, and I did not have to fight with it to associate names to faces, like it was on Photoprism. One surprising feature I really enjoy is that it daily shows pictures taken the same day of previous years, this is an engaging way to revisit old pictures for me and I check it daily now. Compared to Photoprism, you can have multiple users with their own libraries, and users can give access of some folders or their libraries to other users of the same instance. It comes with a share feature that creates a link that can show a set of pictures and strip all metadata, with optional password and expiration time. Scenes and objects recognition works well, I can search "flower" and get all pictures featuring flowers. This kind of feature adds some CPU use when new pictures are imported, but it is really light in terms of CPU/memory requirements. It even supports animated pictures and videos! # Conclusion For my use case, Immich is exactly what I wanted: an efficient and easy picture management software, that allows me to share some pictures with a URL, and even share my library with my husband. Face recognition is not something I expected on average software, but it works great. Digikam had a steep learning curve, but allowed me to edit a few thousand pictures in a few hours, although Immich supports editing pictures it might not be as convenient. I used Photoprism a bit after looking for an alternative to Digikam, but then I found about Immich and immediately gave up on Photoprism. ]]> gopher://dataswamp.org:70/1/~solene/article-photo-library-management gopher://dataswamp.org:70/1/~solene/article-photo-library-management Thu, 09 Apr 2026 00:00:00 GMT Make your own container base images from trusted sources # Introduction I really like containers, but they are something that is currently very bad from a security point of view: distribution We download container images from container registries, whether it is docker.io, quay.io or ghcr.io, but the upstream project do not sign them, so we can not verify a CI pipeline or the container registry did not mess with the image. There are actually a few upstream actors signing their images: Fedora, Red Hat and universial-blue based distros (Bluefin, Aurora, Bazzite), so if you acquire their public key using for signing from a different channel, you can verify if you got the image originally built. Please do not hesitate to get in touch with me if you know about other major upstream that sign their container images. Nevertheless, we can still create containers ourself from trustable artifacts signed by upstream. Let's take a look at how to proceed with Alpine Linux. # Get the rootfs The first step is to download a few files: * Alpine's linux GPG key * Alpine's "mini root filesystem" build for the architecture you want * The GPG file (extension .asc) for the mini root filesystem you downloaded => https://www.alpinelinux.org/downloads/ Alpine linux download page on the official website The GPG file is at the top of the list, it is better to get it from a different channel to make sure that if the website was hacked, the key was not changed accordingly with all the signed files, in which case you would just trust the key of an attacker and it would validate the artifacts. A simple method is to check the page from webarchive a few days / week before and verify that the GPG file is the same on webarchive and the official website. The GPG key fingerprint I used is 0482 D840 22F5 2DF1 C4E7 CD43 293A CD09 07D9 495A as of the date of publication. # Verify the artifacts You will need to have gpg installed and a initialized keyring (I do not cover this here). Run the following command: ``` gpg --import ncopa.asc gpg --verify alpine-minirootfs-3.23.3-x86_64.tar.gz.asc alpine-minirootfs-3.23.3-x86_64.tar.gz ``` It should answer something like this: ``` gpg: Signature made Wed Jan 28 00:25:36 2026 CET gpg: using RSA key 0482D84022F52DF1C4E7CD43293ACD0907D9495A gpg: Good signature from "Natanael Copa " [unknown] gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 0482 D840 22F5 2DF1 C4E7 CD43 293A CD09 07D9 495A ``` The line "Good signature...." tells you that the file integrity check matches the GPG key you imported. The rest of the message tells you that the key is not trustable. This is actually a GPG thing, you would need to edit your keyring and mark the key as "trustable" or have in your keyring a trusted key that signed this key (this is the web of trust GPG wanted to create), but this will only remove the warning. Of course, you can mark that key trustable if you plan to use it for a long time and you are absolutely sure it is the genuine one. You do not need to verify the checksum using sha256, the GPG check did the same in addition to authenticate the person who produced the checksum. Now you validated the minirootfs authenticity, you can create an Alpine container! # Container creation You can use podman or docker for this: ``` podman import alpine-minirootfs-3.23.3-x86_64.tar.gz alpine:3.23.3-local ``` You are now ready to build more containers based on your own cryptographically verified Alpine container image. # Example of use It is rather easy to build new containers with useful purpose on top of your new base container. Create a Containerfile (or Dockerfile, your mileage may vary): ``` FROM alpine:3.23.3-local RUN apk add nginx CMD ["nginx", "-c", "/app/nginx.conf", "-g", "daemon off;"] ``` Build a container with this command: ``` podman build . -t alpine_local_nginx ``` # Conclusion Without any kind of cryptographic signature mechanism available between upstream and the end user, it is not possible to ensure a container from a third party registry was not tampered with. It is best for security to rebuild the container image, and then rebuild all the containers you need using your base image, rather than blindly trusting registries. One tool to sign container images is cosign. => https://github.com/sigstore/cosign Cosign project GitHub page # Going further This process works for other Linux distributions of course. For instance, for Ubuntu you can download Ubuntu base image, the SHA256SUMS.gpg and SHA256SUMS files, and make sure to get a genuine GPG key to verify the signature. => https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/ Ubuntu official website: ubuntu-base 24.04 releases ]]> gopher://dataswamp.org:70/1/~solene/article-build-your-containers-from-trusted-sources gopher://dataswamp.org:70/1/~solene/article-build-your-containers-from-trusted-sources Thu, 12 Mar 2026 00:00:00 GMT File transfer made easier with Tailscale # Introduction Since I started using Tailscale (using my own headscale server), I've been enjoying it a lot. The file transfer feature is particularly useful with other devices. This blog post explains my small setup to enhance the user experience. # Quick introduction Tailscale is a network service that allows to enroll devices into a mesh VPN based on WireGuard, this mean every peer connects to every peers, this is not really manageable without some lot of work. It also allows automatic DNS assignment, access control, SSH service and lot of features. Tailscale refers to both the service and the client. The service is closed source, but not the client. There is a reimplementation of the server called Headscale that you can use with the tailscale client. => https://tailscale.com/ Tailscale official website => https://headscale.net/ Headscale official website # Automatically receive files When you want to receive a file from Tailscale on your desktop system, you need to manually run `tailscale file get --wait $DEST`, this is rather not practical and annoying to me. I wrote a systemd service that starts the tailscale command at boot, really it is nothing fancy but it is not something available out of the box. In the directory `~/.config/systemd/user/` edit the file `tailscale-receiver.service` with this content: ``` [Unit] Description=tailscale receive file After=network.target [Service] Type=simple ExecStart=/usr/bin/tailscale file get --wait --loop /%h/Documents/ Restart=always RestartSec=5 [Install] WantedBy=default.target ``` The path `/%h/Documents/` will expand to `/$HOME/Documents/` (the first / may be too much, but I keep it just in case), you can modify it to your needs. Enable and start the service with the command: ``` systemctl --user daemon-reload systemctl --enable --now tailscale-receiver.service ``` # Send files from Nautilus When sending files, it is possible to use `tailscale file cp $file $target:` but it is much more convenient to have it directly from the GUI, especially when you do not know all the remotes names. This also makes it easier for family member who may not want to fire up a terminal to send a file. Someone wrote a short python script to add this "Send to" feature to Nautilus => https://github.com/flightmansam/nautilus-sendto-tailscale-python Script flightmansam/nautilus-sendto-tailscale-python Create the directory `~/.local/share/nautilus-python/extensions/` and save the file `nautilus-send-to-tailscale.py` in it. Make sure you have the package "nautilus-python" installed, on Fedora it is `nautilus-python` while on Ubuntu it is `python3-nautilus`, so your mileage may vary. Make sure to restart nautilus, a `killall nautilus` should work but otherwise just logout the user and log back. In Nautilus, in the contextual menu (right click), you should see "Send to Tailscale" and a sub menu should show the hosts. # Conclusion Tailscale is a fantastic technology, having a mesh VPN network allows to secure access to internal services without exposing anything to the Internet. And because it features direct access between peers, it also enables some interesting uses like fast file transfer or VOIP calls without a relay. ]]> gopher://dataswamp.org:70/1/~solene/article-linux-integration-tailscale-file-transfer gopher://dataswamp.org:70/1/~solene/article-linux-integration-tailscale-file-transfer Sun, 08 Mar 2026 00:00:00 GMT Comparison of cloud storage encryption software # Introduction When using a not end-to-end encrypted cloud storage, you may want to store your file encrypted so if the cloud provider (that could be you if you self host a nextcloud or seafile) get hacked, your data will be available to the hacker, this is not great. While there are some encryption software like age or gpg, they are not usable for working transparently with files. A specific class of encryption software exists, they create a logical volume with your files and they are transparently encrypted in the file system. You will learn about cryptomator, gocryptfs, cryfs and rclone. They allow you to have a local directory that is synced with the cloud provider, containing only encrypted files, and a mount point where you access your files. Your files are sent encrypted to the cloud provider, but you can use it as usual (with some overhead). This blog post is a bit "yet another comparison" because all these software also provide a comparison list of challengers. => https://nuetzlich.net/gocryptfs/comparison/ A comparison done by gocryptfs => https://cryptomator.org/comparisons/ A comparison done by cryptomator => https://www.cryfs.org/comparison A comparison done by cryfs # Benchmark My comparison will compare the following attributes and features of each software: * number of files in the encrypted dir always using the same input (837 MB from 4797 files mades of pictures and a git repository) * filename and file tree hierarchy obfuscation within the encrypted dir * size of the encrypted dir compared to the 837 MB of the raw material * cryptography used # Software list Here is the challenger list I decided to evaluate: ## Cryptomator The main software (running on Linux) is open source, they have a client for all major operating system around, including Android and iOS. The android apps is not free (as in beer), the iOS app is free for read-only, the windows / linux / Mac OS program is free. They have an offer for a company-wide system which can be convenient for some users. Cryptomator features a graphical interface, making it easy to use. Encryption suites are good, it uses AES-256-GCM and scrypt, featuring authentication of the encrypted data (which is important as it allows to detect if a file was altered). A salt is used. Hierarchy obfuscation can be sufficient depending on your threat model. The whole structure information is flattened, you can guess the number of directories and their number of files files, and the file sizes, all the names are obfuscated. This is not a huge security flaw, but this is something to consider. => https://docs.cryptomator.org/security/architecture/ Cryptomator implementation details ## gocryptfs This software is written in Go and works on Linux, a C++ Windows version exists, and there is a beta version of Mac OS. => https://nuetzlich.net/gocryptfs/ gocryptfs official website Hierarchy obfuscation is not great, the whole structure information is saved although the names are obfuscated. Cryptography wise, scrypt is used for the key derivation and AES-256-GCM for encryption with authentication. => https://nuetzlich.net/gocryptfs/forward_mode_crypto/ gocryptfs implementation details ## CryFS I first learned about cryfs when using KDE Plasma, there was a graphical widget named "vault" that can drive cryfs to create encrypted directories. This GUI also allow to use gocryptfs but defaults to cryfs. => https://www.cryfs.org/ CryFS official website CryFS is written in C++ but an official rewrite in Rust is ongoing. It works fine on Linux but there are binaries for Mac OS and Windows as well. Encryption suites are good, it uses AES-256-GCM and scrypt, but you can use xchacha20-poly1305 if you do not want AES-GCM. It encrypts files metadata and split all files into small blocks of fixed size, it is the only software in the list that will obfuscate all kind of data (filename, directory name, tree hierarchy, sizes, timestamp) and also protect against an old file replay. => https://www.cryfs.org/howitworks CryFS implementation details ## rclone It can be surprising to see rclone here, it is a file transfer software supporting many cloud provider, but it also features a few "fake" provider that can be combined with any other provider. Thoses fakes remotes can be used to encrypt files, but also aggregate multiple remotes or split files in chunks. We will focus on the "crypt" remote. => https://rclone.org/ Rclone official website rclone is a Go software, it is available everywhere on desktop systems but not on mobile devices. Encryption is done through libNaCl and uses XSalsa20 and Poly1305 which both support authentication, and also use scrypt for key derivation. A salt can be used but it is optional, make sure to enable it. Hierarchy obfuscation is not great, the whole structure information is saved although the names are obfuscated. => https://rclone.org/crypt/ rclone crypt remote implementation details ## Other ecryptfs is almost abandonware, so I did not cover it. => https://lore.kernel.org/ecryptfs/ef98d985-6153-416d-9d5e-9a8a8595461a@app.fastmail.com/ ecryptfs is unmaintained and untested encfs is limited and recommend users to switch to gocryptfs => https://github.com/vgough/encfs?tab=readme-ov-file#about encFS GitHub page: anchor "about" LUKS and Veracrypt are not "cloud friendly" because although you can have a local big file encrypted with it and mount the volume locally, it will be synced as a huge blob on the remote service. # Results From sources directories with 4312 files, 480 directories for a total of 847 MB. * cryptomator ended up with 5280 files, 1345 directories for a total of 855 MB * gocryptfs ended up with 4794 files, 481 directories for a total of 855 MB * cryfs ended up with 57928 files, 4097 directories for a total of 922 MB * rclone ended up with 4311 files, 481 directories for a total of 847 MB Although cryptomater has a bit more files and directories in its encrypted output compared to the original files, the obfuscation is really just all directories being in a single directory with filenames obfuscated. Some extra directories and files are created for cryptomator internal works, which explains the small overhead. I used default settings for cryfs with a blocksize of 16 kB which is quite low and will be a huge overhead for a synchronization software like Nextcloud desktop. Increasing the blocksize is a setting worth considering depending on your file sizes distribution. All files are spread in a binary tree, allowing it to scale to a huge number of files without filesystem performance issue. # Conclusion In my opinion, the best choice from a security point of view would be cryfs. It features full data obfuscation, good encryption, mechanisms that prevent replaying old files or swapping files. The documentation is clear and we can see the design choices are explained with ease and clearly. But to be honest, I would recommend cryptomator to someone who want a nice graphical interface, easy to use software and whose threat model allows some metadata reveal. It is also available everywhere (although not always for free), which is something to consider. Authentication is used by all these software, so you will know if a file was tampered with, although it does not protect against swapping files or replaying an old file, this is certainly not in everyone's threat model. Most people will just want to prevent a data leak to read their data, but the case of a cloud storage provider modifying your encrypted files is less likely. # Going further There is a GUI frontend for gocryptfs and cryfs called SiriKali. => https://mhogomchungu.github.io/sirikali/ SiriKali official project page => https://github.com/mhogomchungu/sirikali SiriKali GitHub project Some self hostable cloud storage provider exists with end-to-end encryption (file are encrypted/decrypted locally and only stored as blob remotely): The two major products I would recommend are Peergos and Seafile. I am a peergos user, it works well and features a Web UI where as seafile encryption is not great as using the web ui requires sharing the password, metadata protection is bad too. => https://peergos.org/ Peergos official website => https://www.seafile.com/en/home/ Seafile official website ]]> gopher://dataswamp.org:70/1/~solene/article-local-encrypted-volume-comparison gopher://dataswamp.org:70/1/~solene/article-local-encrypted-volume-comparison Thu, 19 Feb 2026 00:00:00 GMT Revert fish shell deleting shortcuts behavior # Introduction In a recent change within fish shell, the shortcut to delete last words were replaced by "delete last big chunk" (I don't know exactly how it is called in this case) which is usually the default behavior on Mac OS "command" key vs "alt" key and I guess it is why it was changed like this on fish. Unfortunately, this broke everyone's habit and a standard keyboard do not even offer the new keybinding that received the old behavior. There is an open issue asking to revert this change. => https://github.com/fish-shell/fish-shell/issues/12122 GitHub fish project: Revert alt-backspace behaviour on non-macOS systems #12122 I am using this snippet in `~/.config/fish/config.fish` to restore the previous behavior (the same as in other all other shell, where M-d deletes last word). I build it from the GitHub issue comments, I had to add `$argv` for some reasons. ``` if status is-interactive # Commands to run in interactive sessions can go here # restore delete behavior bind $argv alt-backspace backward-kill-word bind $argv alt-delete kill-word bind $argv ctrl-alt-h backward-kill-word bind $argv ctrl-backspace backward-kill-token bind $argv ctrl-delete kill-token end ``` ]]> gopher://dataswamp.org:70/1/~solene/article-fish-shell-delete-behavior gopher://dataswamp.org:70/1/~solene/article-fish-shell-delete-behavior Sat, 14 Feb 2026 00:00:00 GMT Declaratively manage containers on Linux # Introduction When you have to deal with containers on Linux, there are often two things making you wonder how to deal with effectively: how to keep your containers up to date, and how to easily maintain the configuration of everything running. It turns out podman is offering systemd unit templates to declaratively manage containers, this comes with the fact that podman can run in user mode. This combination gives the opportunity to create files, maintain them in git or deploy them with a configuration management tool like ansible, and keep things separated per user. It is also very convenient when you want to run a program shipped as a container on your desktop. For some reason, this is called "quadlets". => https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html podman-systemd.unit man page In this guide, I will create a kanboard service (a PHP software to run a kanban) under the kanban user. # Setup (simple service) You need to create files that will declare containers and/or networks, this can be done in various places depending on how you want to manage the files, the man page gives all the details, but basically you want to stick with the two following options: * system-wide configuration: `/etc/containers/systemd/users/$(UID)` * user configuration: `~/.config/containers/systemd/` Both will run rootless containers under the user UID, but one keep the files in `/etc/` which may be more suitable for central management. As systemd is used to run the containers, if you want to run a container for a user that is not one where you are logged, you need to always enable it so its related systemd processes / services are running, including the containers, this is done by enabling "linger". ``` useradd -m kanban loginctl enable-linger kanban ``` This will immediately create a session for that user and pop all related services. Now, create a file `/etc/containers/systemd/users/1001/` (1001 being the uid of kanban user) with this content: ``` [Container] Image=docker.io/kanboard/kanboard:latest Network=podman PublishPort=10080:80 Volume=kanboard_data:/var/www/app/data Volume=kanboard_plugins:/var/www/app/plugins Volume=kanboard_ssl:/etc/nginx/ssl [Service] Restart=always [Install] WantedBy=default.target ``` This can exactly map to a very long podman command line that would use the image `docker.io/kanboard/kanboard:latest` in network `podman` and declaring three different container volumes and associated mount points. This generator even allows you to add command line arguments in case an option is not available with systemd format. Because the user already runs, the container will not start yet except if you use `disable-linger` and then `enable-linger` the kanban user, and that would not be ideal to be honest. There is a better way to proceed: `systemctl --user --machine kanban@ daemon-reload` which basically runs `systemctl --user daemon-reload` by the user `kanban` except we do it as root user which is more convenient for automation. Running the container this way will trigger exactly the same processes as if you started it manually with `podman run -v kanboard_data:/var/www/app/data/ [...] docker.io/kanboard/kanboard:latest`. Note that you can skip the `[Install]` section if you do not want to automatically start the container, and prefer to manually start/stop it with "systemctl", this is actually useful if you have the container under your regular user and do not always need it. # Setup (advanced service) If you want to run a more complicated service that need a couple of containers to talk together like a web server, a backend runner and a database, you only need to configure them in the same network. If you need them to start the containers of a group in a specific order, you can add use systemd dependency declaration in `[Install]` section. Podman will run a local DNS resolver that translates the container name into a working hostname, this mean if you have a postgresql container called "db", then you can refer to the postgresql host as "db" from another container within the same network. This works the same way as docker-compose. # Ops ## Getting into a user shell To have a working environment for `journalctl` or `systemctl` commands to work requires to use `machinectl shell kanban@`, otherwise the dbus environment variables will not be initialized. Note that it works too when connecting with ssh, but it is not always ideal if you use it locally. From this shell, you can run commands like `systemctl --user status kanboard.container` for our example or `journalctl --user -f -u kanboard.container`, or run a shell in a container, inspect a volume etc... Using `sudo -u user` or `su - user` will not work. ## Disabling a user If you want to disable the services associated with an user, use this command: ``` loginctl disable-linger username ``` This will immediately close all its sessions and stop services running under that user. ## Automatic updates This is the very first reason I went into using quadlets for local services using containers, I did not want to have to manually run some `podman pull` commands over a list then restart related containers that were running. Podman gives you a systemd services doing all of this for you, this works for containers with the parameter `AutoUpdate=registry` within the section `[Container]`. Enable the timer of this service with: `systemctl --user enable --now podman-auto-update.timer` then you can follow the timer information with `systemctl --user status podman-auto-update.timer` or logs from the update service with `journalctl --user -u podman-auto-update.service`. Make sure to pin your container image to a branch like "stable" or "lts" or "latest" if you want a development version, the update mechanism will obviously do nothing if you pin the image to a specific version or checksum. # Conclusion Quadlets made me switch to podman as it allowed me to deploy and maintain containers with ansible super easily, and also enabled me to separate each services into different users. Prior to this, handling containers on a simple server or desktop was an annoying task to figure what should be running, how to start them and retrieving command lines from the shell history or use a docker/podman compose file. This also comes with all the power from systemd like querying a service status or querying logs with journalctl. # Going further There is a program named "podlet" that allow you to convert some file format into quadlets files, most notably it is useful when getting a `docker-compose.yml` file and transforming it into quadlet files. => https://github.com/containers/podlet/ podlet GitHub page ]]> gopher://dataswamp.org:70/1/~solene/article-podman-containers-with-systemd gopher://dataswamp.org:70/1/~solene/article-podman-containers-with-systemd Tue, 10 Feb 2026 00:00:00 GMT Hardware review: ergonomic mouse Logitech Lift # Introduction In addition to my regular computer mouse, by the end of 2024 I bought a Logitech Lift, a wireless ergonomic vertical mouse. This was the first time I used such mouse, although I am regularly using a track ball, the experience is really different. => https://www.logitech.com/en-gb/shop/p/lift-vertical-ergonomic-mouse.910-006475 Logitech.com : Lift product I wanted to write this article to give some feedback about this device, I enjoy it a lot and I can not really go back to a regular mouse now. # Specifications The mouse works with a single AA / LR6 battery that with a heavy daily use for nine months is still reported as 30% charged. The lift connects using Bluetooth, but Logitech provides a small USB dongle for a perfect "out of the box" experience with any operating system. The dongle can be stored within the mouse when travelling, or when not using it. There is a small button on the bottom of the mouse and 3 LED, this allows the mouse to be switched to different computers: two in Bluetooth, one for the dongle. The first profile is always the dongle. This allows you to connect the mouse to two different computers with Bluetooth and be able to switch between them. This works very well in practice. About the buttons, nothing fancy with the standard two buttons, there are extra "back / next" buttons easily available, one button to cycle the laser resolution / sensitivity. The wheel is excellent, it is precise and easy to use, but if you give it a good kick it will spin a lot without being in free wheel like some other wheels, which is super handy to scroll a huge chunk of text. Due to the mouse design, it is not ambidextrous, but Logitech made a version for left-handed users and right-hander users. # Experience The first week with the mouse was really weird, I was switching back and forth with my old Steel Series mouse because I was less accurate and not used to it. After a week, I became used to holding it, moving it, and it was a real joy and source of fun to go on the computer to use this mouse :) Then, without noticing, I started using it exclusively. A few months later, I realized I did not use the previous mouse for a long time and gave it a try. This was a terrible experience, I was surprised that it was fitting really poorly in my hand, then I disconnected it, and it has been stored in a box since then. It is hard to describe the feeling of this ergonomic mouse, the hand position is really different, but it feels much more enjoyable that I do not consider using a non-ergonomic mouse ever again. I was reluctant to use a wireless mouse at first, but not having to deal with the cable acting as a "spring" is really appreciable. I can definitely play video games with this mouse, except nervous FPS (maybe with some training?). # Conclusion The price tag could be a blocker for many, but at the same time it is an essential peripheral when using your computer. If you feel some pain in your hand when using your computer mouse, maybe give a try to ergonomic mice. ]]> gopher://dataswamp.org:70/1/~solene/article-hardware-review-logitech-lift gopher://dataswamp.org:70/1/~solene/article-hardware-review-logitech-lift Fri, 05 Sep 2025 00:00:00 GMT URL filtering HTTP(S) proxy on Qubes OS # Preamble This article was first published as a community guide on Qubes OS forum. Both are kept in sync. => https://forum.qubes-os.org/t/url-filtering-https-proxy/35846 # Introduction This guide is meant to users who want to allow a qube to reach some websites but not all the Internet, but facing the issue that using the firewall does not work well for DNS names using often changing IPs. ⚠️ This guide is for advanced users who understand what a HTTP(s) proxy is, and how to type commands or edit files in a terminal. The setup will create a `sys-proxy-out` qube that will define a list of allowed domains, and use qvm-connect-tcp to allow client qubes to use it as a proxy. Those qubes could have no netvm, but still reach the filtered websites. I based it on debian 12 xfce, so it's easy to set up and will be supported long term. # Use case * an offline qube that need to reach a particular website * a web browsing qube restricted to a list of websites * mix multiple netvm / VPNs into a single qube # Setup the template * Install debian-12-xfce template * Make a clone of it, let's call it debian-12-xfce-squid * Start the qube and open a terminal * Type `sudo apt install -y squid` * Delete and replace `/etc/squid/squid.conf` with this content (the default file is not suitable at all) ``` acl localnet src 127.0.0.1/32 acl SSL_ports port 443 acl Safe_ports port 80 acl Safe_ports port 443 http_access deny !Safe_ports http_access deny CONNECT !SSL_ports acl permit_list dstdomain '/rw/config/domains.txt' http_access allow localnet permit_list http_port 3128 cache deny all logfile_rotate 0 coredump_dir /var/spool/squid ``` The configuration file only allows the proxy to be used for ports 80 and 443, and disables cache (which would only apply to port 80). Close the template, you are done with it. # Setup an out proxy qube This step could be repeated multiple times, if you want to have multiple proxies with different lists of domains. * Create a new qube, let's call it `sys-proxy-out`, based on the template you configured above (`debian-12-xfce-squid` in the example) * Configure its firewall to allow the destination `*` and port TCP 443, and also `*` and port TCP 80 (this covers basic needs for doing http/https). This is an extra safety to be sure the proxy will not use another port. * Start the qube * Configure the domain list in `/rw/config/domains.txt` with this format: ``` # for a single domain domain.example # for all direct subdomains of qubes.org including qubes.org # this work for doc.qubes-os.org for instance, but not foo.doc.qubes-os.org .qubes-os.org ``` ℹ️ If you change the file, reload with `sudo systemctl reload squid`. ℹ️ If you want to check squid started correctly, type `systemctl status squid`. You should read that it's active, and that there are no error in the log lines. ⚠️ If you have a line with a domain included by another line, squid will not start as it considers it an error! For instance `.qubes.org` includes `doc.qubes-os.org`. ⚠️ As far as I know, it is only possible to allow a hostname or a wildcard of this hostname, so you at least need to know the depth of the hostname. If you want to allow `anything.anylevel.domain.com`, you could use `dstdom_regex` instead of `dstdomain`, but it seems a regular source of configuration problems, and should not be useful for most users. In dom0, using the "Qubes Policy Editor" GUI, create a new file named 50-squid (or edit the file `/etc/qubes/policy.d/50-squid.policy`) and append the configuration lines that you need to adapt from the following example: ``` qubes.ConnectTCP +3128 MyQube @default allow target=sys-proxy-out qubes.ConnectTCP +3128 MyQube2 @default allow target=sys-proxy-out ``` This will allow qubes `MyQube` and `MyQube2` to use the proxy from `sys-proxy-out`. Adapt to your needs here. # How to use the proxy Now the proxy is set up, and `MyQube` is allowed to use it, a few more things are required: * Start qube `MyQube` * Edit `/rw/config/rc.local` to add `qvm-connect-tcp ::3128` * Configure http(s) clients to use `localhost:3128` as a proxy It's possible to define the proxy user wide, this should be picked by all running programs, using this: ``` mkdir -p /home/user/.config/environment.d/ cat </home/user/.config/environment.d/proxy.conf all_proxy=http://127.0.0.1:3128/ EOF ``` # Going further ## Using a disposable qube for the proxy The sys-proxy-out could be a disposable. In order to proceed: * mark sys-proxy-out as a disposable template in its settings * create a new disposable qube using sys-proxy-out as a template * adapt the dom0 rule to have the new disposable qube name in the target field ## Checking logs In the proxy qube, you can check all requests done in `/var/log/squid/access.log`, you can filter with `grep TCP_DENIED` to see denied requests, this can be useful to adapt the domain list. ## Test the proxy ### Check allowed domains are reachable From the http(s) client qube, you can try this command to see if the proxy is working: ``` curl -x http://localhost:3128 https://a_domain_you_allowed/ ``` If the output is not `curl: (56) CONNECT tunnel failed, response 403` then it's working. ### Check non-allowed domains are denied Use the same command as above, but with a domain you did not allow ``` curl -x http://localhost:3128 https://a_domain_you_allowed/ ``` The output should be `curl: (56) CONNECT tunnel failed, response 403`. ### Verify nothing is getting cached In the qube `sys-proxy-out`, inspect `/var/spool/squid/`, it should be empty. If not, please report here, this should not happen. Some logs file exist in `/var/log/squid/`, if you don't want any hints about queried domains, configure squid accordingly. Privacy-specific tweaks are beyond the scope of this guide. ]]> gopher://dataswamp.org:70/1/~solene/article-qubes-os-filtering-out-proxy gopher://dataswamp.org:70/1/~solene/article-qubes-os-filtering-out-proxy Fri, 29 Aug 2025 00:00:00 GMT