[2025-12-08T20:44:49.918Z] "- - -" 0 - 78 223 1 - "-" "-" "-" "-" "172.18.0.2:8080"
I won't go into detail about the line above, since it's not possible
to trace the request using access logs alone.
Envoy also has OpenTelemetry
tracing, which is perfect for understanding sources of latency.
Unfortunatly, it is only available for Application Load Balancers.
Most of the HTTP 499 were happening every 10 minutes, so we managed
to get some of the requests with tcpdump, Wireshark and using http headers
to filter the requests.
This approach helped us reproduce and track down the problem, but it
wasn't a great solution. We clearly needed better tools to catch this
kind of issue the next time it happened.
Therefore, I decided to try out OpenTelemetry
eBPF Instrumentation, also referred to as OBI.
I saw the announcement of Grafana Beyla before it was renamed to OBI,
but I didn't have the time or a strong reason to try it out until now.
Even then, I really liked the idea, and the possibility of using eBPF to
solve this instrumentation problem had been in the back of my mind.
OBI promises zero-code automatic instrumentation for Linux services
using eBPF, so I put together a minimal setup to see how well it
works.
static_resources:
listeners:
- name: go_server_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8000
filter_chains:
- filters:
- name: envoy.filters.network.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
stat_prefix: go_server_tcp
cluster: go_server_cluster
clusters:
- name: go_server_cluster
connect_timeout: 1s
type: LOGICAL_DNS
load_assignment:
cluster_name: go_server_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: target-backend
port_value: 8080package main
import (
"fmt"
"net/http"
)
func main()
http.Handle("/", http.FileServer(http.Dir(".")))
server := http.Server Addr: ":8080"
fmt.Println("Starting server on :8080")
panic(server.ListenAndServe())
services:
autoinstrumenter:
image: otel/ebpf-instrument:main
pid: "service:envoy"
privileged: true
environment:
OTEL_EBPF_TRACE_PRINTER: text
OTEL_EBPF_OPEN_PORT: 8000
envoy:
image: envoyproxy/envoy:v1.33-latest
ports:
- 8000:8000
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
depends_on:
- target-backend
target-backend:
image: golang:1.22-alpine
command: go run /app/backend.go
volumes:
- ./backend.go:/app/backend.go:ro
expose:
- 80802025-12-08 20:44:49.12884449 (305.572 s[305.572 s]) HTTPClient 200 GET /(/) [172.18.0.3 as envoy:36832]->[172.18.0.2 as localhost:8080] contentLen:78B responseLen:0B svc=[envoy generic] traceparent=[00-529458a2be271956134872668dc5ee47-6dba451ec8935e3e[06c7f817e6a5dae2]-01]
2025-12-08 20:44:49.12884449 (1.260901ms[366.65 s]) HTTP 200 GET /(/) [172.18.0.1 as 172.18.0.1:36282]->[172.18.0.3 as envoy:8000] contentLen:78B responseLen:223B svc=[envoy generic] traceparent=[00-529458a2be271956134872668dc5ee47-06c7f817e6a5dae2[0000000000000000]-01]
This is exactly what we needed, with zero-code. The above trace
shows:
2025-12-08 20:44:49.12884449: time of the trace.(1.260901ms[366.65 s]): total response time for the
request, with the actual internal execution time of the request (not
counting the request enqueuing time).HTTP 200 GET /: protocol, response code, HTTP method,
and URL path.[172.18.0.1 as 172.18.0.1:36282]->[172.18.0.3 as envoy:8000]:
source and destination host:port. The initial request originates from my
machine through the gateway (172.18.0.1), hits the Envoy (172.23.0.3),
the proxy then forwards it to the backend application (172.23.0.2).contentLen:78B: HTTP Content-Length. I used curl and
the default request size for it is 78B.responseLen:223B: Size of the response body.svc=[envoy generic]: traced service.traceparent: ids to trace the parent request. We can
see that the Envoy makes a request to the target and this request has
the other one as parent. envoy1:
image: envoyproxy/envoy:v1.33-latest
ports:
- 9000:9000
volumes:
- ./envoy1.yaml:/etc/envoy/envoy.yaml
depends_on:
- envoy2025-12-09 12:28:05.12912285 (2.202041ms[1.524713ms]) HTTP 200 GET /(/) [172.19.0.1 as 172.19.0.1:59030]->[172.19.0.5 as envoy:9000] contentLen:78B responseLen:223B svc=[envoy generic] traceparent=[00-69977bee0c2964b8fe53cdd16f8a9d19-856c9f700e73bf0d[0000000000000000]-01]
2025-12-09 12:28:05.12912285 (1.389336ms[1.389336ms]) HTTPClient 200 GET /(/) [172.19.0.5 as envoy:59806]->[172.19.0.4 as localhost:8000] contentLen:78B responseLen:0B svc=[envoy generic] traceparent=[00-69977bee0c2964b8fe53cdd16f8a9d19-caa7f1ad1c68fa77[856c9f700e73bf0d]-01]
2025-12-09 12:28:05.12912285 (1.5431ms[848.574 s]) HTTP 200 GET /(/) [172.19.0.5 as 172.19.0.5:59806]->[172.19.0.4 as envoy:8000] contentLen:78B responseLen:223B svc=[envoy generic] traceparent=[00-69977bee0c2964b8fe53cdd16f8a9d19-cbca9d64d3d26b40[caa7f1ad1c68fa77]-01]
2025-12-09 12:28:05.12912285 (690.217 s[690.217 s]) HTTPClient 200 GET /(/) [172.19.0.4 as envoy:34256]->[172.19.0.3 as localhost:8080] contentLen:78B responseLen:0B svc=[envoy generic] traceparent=[00-69977bee0c2964b8fe53cdd16f8a9d19-5502f7760ed77b5b[cbca9d64d3d26b40]-01]
2025-12-09 12:28:05.12912285 (267.9 s[238.737 s]) HTTP 200 GET /(/) [172.19.0.4 as 172.19.0.4:34256]->[172.19.0.3 as backend:8080] contentLen:0B responseLen:0B svc=[backend go] traceparent=[00-69977bee0c2964b8fe53cdd16f8a9d19-ac05c7ebe26f2530[5502f7760ed77b5b]-01]
Each log line represents a span belonging to the same trace
(69977bee0c2964b8fe53cdd16f8a9d19). For readability, I
ordered the spans by their traceparent relationship, showing the
request's path as it moves through the system: from the client-facing
Envoy, through the internal Envoy hop, and finally to the Go backend.
You can see both server-side (HTTP) and client-side (HTTPClient) spans
at each hop, along with per-span latency, source and destination
addresses, and response sizes, making it easy to pinpoint where time is
spent along the request chain.
The log lines are helpful, but we need better ways to visualize the
traces and the metrics generated by OBI. I'll share another setup that
more closely reflects what we actually use.
$ incus launch images:debian/trixie server01
Launching server01
$ incus launch images:debian/trixie server02
Launching server02
$ apt install python3 --update -y
$ tee /etc/systemd/system/server.service > /dev/null <<'EOF'
[Unit]
Description=Python HTTP server
After=network.target
[Service]
User=root
Group=root
Type=simple
ExecStart=/usr/bin/python3 -m http.server 8080
Restart=always
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
$ systemctl start server.service
$ python3 -m http.server 8080
services:
autoinstrumenter:
image: otel/ebpf-instrument:main
pid: host
privileged: true
environment:
OTEL_EBPF_CONFIG_PATH: /etc/obi/obi.yml
volumes:
- ./obi.yml:/etc/obi/obi.yml
otel-collector:
image: otel/opentelemetry-collector-contrib:0.98.0
command: ["--config=/etc/otel-collector-config.yml"]
volumes:
- ./otel-collector-config.yml:/etc/otel-collector-config.yml
ports:
- "4318:4318" # Otel Receiver
- "8889:8889" # Prometheus Scrape
depends_on:
- autoinstrumenter
- jaeger
- prometheus
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090" # Prometheus UI
grafana:
image: grafana/grafana
restart: always
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=RandomString123!
volumes:
- ./grafana-ds.yml:/etc/grafana/provisioning/datasources/datasource.yml
ports:
- "3000:3000" # Grafana UI
jaeger:
image: jaegertracing/all-in-one
container_name: jaeger
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # Jaeger OTLP/gRPC Collectorlog_level: INFO
trace_printer: text
discovery:
instrument:
- open_ports: 8080
otel_metrics_export:
endpoint: http://otel-collector:4318
otel_traces_export:
endpoint: http://otel-collector:4318global:
scrape_interval: 5s
scrape_configs:
- job_name: 'otel-collector'
static_configs:
- targets: ['otel-collector:8889']apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: truereceivers:
otlp:
protocols:
http:
endpoint: otel-collector:4318
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
prometheus:
endpoint: 0.0.0.0:8889
namespace: default
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
exporters: [prometheus]autoinstrumenter-1 time=2025-12-30T19:57:17.593Z level=INFO msg="instrumenting process" component=discover.traceAttacher cmd=/usr/bin/python3.13 pid=297514 ino=460310 type=python service=""
autoinstrumenter-1 time=2025-12-30T19:57:18.320Z level=INFO msg="instrumenting process" component=discover.traceAttacher cmd=/usr/bin/python3.13 pid=310288 ino=722998 type=python service=""
autoinstrumenter-1 time=2025-12-30T19:57:18.512Z level=INFO msg="instrumenting process" component=discover.traceAttacher cmd=/usr/bin/python3.13 pid=315183 ino=2888480 type=python service=""$ cat /sys/fs/cgroup/lxc.payload.server01/system.slice/server.service/cgroup.procs
297514
$ cat /sys/fs/cgroup/lxc.payload.server02/system.slice/server.service/cgroup.procs
310288
$ ps -aux grep http.server
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1000000 297514 0.0 0.1 32120 14856 ? Ss 16:03 0:00 /usr/bin/python3 -m http.server 8080
1000000 310288 0.0 0.1 32120 10616 ? Ss 16:09 0:00 /usr/bin/python3 -m http.server 8080
cipriano 315183 0.0 0.1 103476 11480 pts/3 S+ 16:17 0:00 python -m http.server 8080
If we search for the PID in the OpenTelemetry Collector endpoint
where Prometheus metrics are exposed, we can find the attribute values
to filter on.
$ curl http://localhost:8889/metrics rg 297514
default_target_info host_id="148f400ad3ea",host_name="148f400ad3ea",instance="148f400ad3ea:297514",job="python3.13",os_type="linux",service_instance_id="148f400ad3ea:297514",service_name="python3.13",telemetry_sdk_language="python",telemetry_sdk_name="opentelemetry-ebpf-instrumentation",telemetry_sdk_version="main" 1
Now we just need to add the processor to the collector
configuration:
processors: # <--- NEW BLOCK
filter/host_id:
traces:
span:
- 'resource.attributes["service.instance.id"] == "148f400ad3ea:297514"'
service:
pipelines:
traces:
receivers: [otlp]
processors: [filter/host_id] # <--- NEW LINE
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
processors: # <--- NEW BLOCK
- filter/host_id
exporters: [prometheus]server01 container. Below are some screenshots from Jaeger
and Grafana:
netplan apply every 10 minutes because of a bug.
Netplan apply causes interfaces to go down temporarily and this made the
latency go above 500ms which caused the 499s.
Debusine is a tool designed for Debian developers and Operating
System developers in general. This post describes how Debusine stores
and manages files.
Debusine has been designed to run a network of workers that can
perform various tasks that consume and produce artifacts .
The artifact itself is a collection of files structured into an
ontology of artifact types.
This generic architecture should be suited to many sorts of build & CI
problems.
We have implemented artifacts to support building a Debian-like
distribution, but the foundations of Debusine aim to be more general
than that.
For example a package build task takes a debian:source-package as
input and produces some debian:binary-packages and a
debian:package-build-log as output.
This generalized approach is quite different to traditional Debian APT
archive implementations, which typically required having the archive
contents on the filesystem.
Traditionally, most Debian distribution management tasks happen within
bespoke applications that cannot share much common infrastructure.
.changes file.
Looking at the debusine.debian.net instance that we run, we can see
a content-addressing savings of 629 GiB across our (currently) 2 TiB
file store.
This is somewhat inflated by the Debian Archive import, that did not
need to bother to share artifacts between suites.
But it still shows reasonable real-world savings.
| Series: | Class 5 #4.5 |
| Publisher: | Eclipse |
| Copyright: | 2020 |
| ISBN: | 1-7637844-2-8 |
| Format: | Kindle |
| Pages: | 81 |

apt install lxc libvirt-clients debootstrap
veth kernel module is loaded by adding the following to
/etc/modules-load.d/lxc-local.conf:
veth
and then loading it manually for now:
modprobe veth
Enable IPv4 forwarding by putting this in /etc/sysctl.d/lxc-local.conf:
net.ipv4.ip_forward=1
and applying it:
sysctl -p /etc/sysctl.d/lxc-local.conf
Restart the LXC network bridge:
systemctl restart lxc-net.service
Ensure that container traffic is not blocked by the host firewall, for example by adding
the following to /etc/network/iptables.up.rules:
-A FORWARD -d 10.0.3.0/24 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -s 10.0.3.0/24 -j ACCEPT
-A INPUT -d 224.0.0.251 -s 10.0.3.1 -j ACCEPT
-A INPUT -d 239.255.255.250 -s 10.0.3.1 -j ACCEPT
-A INPUT -d 10.0.3.255 -s 10.0.3.1 -j ACCEPT
-A INPUT -d 10.0.3.1 -s 10.0.3.0/24 -j ACCEPT
and applying the rules:
iptables-apply
lxc-create -n foo --template=download -- --list
and then create a Debian forky container using:
lxc-create -n forky -t download -- -d debian -r forky -a amd64
Start and stop the container like this:
lxc-start -n forky
lxc-stop -n forky
lxc-attach -n forky
Inside the container, you can change the root password by typing:
passwd
and install some essential packages:
apt install openssh-server vim
To find the container's IP address (for example, so that you can ssh to it from the host):
lxc-ls --fancy
git-buildpackage, 2006).
Every Debian maintainer can (and should!) release their package from git reliably and in a standard form (dgit push, 2013; tag2upload, 2025). This is not only more principled, but also more convenient, and with better UX, than pre-dgit tooling like dput.
Indeed a Debian maintainer can now often release their changes to Debian, from git, using only git branches (so no tarballs). Releasing to Debian can be simply pushing a signed tag (tag2upload, 2025).
A Debian maintainer can maintain a stack of changes to upstream source code in git (gbp pq 2009). They can even maintain such a delta series as a rebasing git branch, directly buildable, and use normal git rebase style operations to edit their changes, (git-dpm, 2010; git-debrebase, 2018)
An authorised Debian developer can do a modest update to any package in Debian, even one maintained by someone else, working entirely in git in a standard and convenient way (dgit, 2013).
Debian contributors can share their work-in-progress on git forges and collaborate using merge requests, git based code review, and so on. (Alioth, 2003; Salsa, 2018.)
Core engineering principle
The Debian git transition project is based on one core engineering principle:
Every Debian Source Package can be losslessly converted to and from git.
In order to transition away from Debian Source Packages, we need to gateway between the old dsc approach, and the new git approach.
This gateway obviously needs to be bidirectional: source packages uploaded with legacy tooling like dput need to be imported into a canonical git representation; and of course git branches prepared by developers need to be converted to source packages for the benefit of legacy downstream systems (such as the Debian Archive and apt source).
This bidirectional gateway is implemented in src:dgit, and is allowing us to gradually replace dsc-based parts of the Debian system with git-based ones.
Correspondence between dsc and git
A faithful bidirectional gateway must define an invariant:
The canonical git tree, corresponding to a .dsc, is the tree resulting from dpkg-source -x.
This canonical form is sometimes called the dgit view . It s sometimes not the same as the maintainer s git branch, because many maintainers are still working with patches-unapplied git branches. More on this below.
(For 3.0 (quilt) .dscs, the canonical git tree doesn t include the quilt .pc directory.)
Patches-applied vs patches-unapplied
The canonical git format is patches applied . That is:
If Debian has modified the upstream source code, a normal git clone of the canonical branch gives the modified source tree, ready for reading and building.
Many Debian maintainers keep their packages in a different git branch format, where the changes made by Debian, to the upstream source code, are in actual patch files in a debian/patches/ subdirectory.
Patches-applied has a number of important advantages over patches-unapplied:
git commit. Many Debian insiders working with patches-unapplied are still using quilt(1), a footgun-rich contraption for working with patch files!
gbp pq), no need to commit patch files, etc. One can always edit every file and commit it with git commit.
3.0 (quilt) source format, the patch files files in debian/patches/ must somehow be kept up to date. Nowadays though, tools like git-debrebase and git-dpm (and dgit for NMUs) make it very easy to work with patches-applied git branches. git-debrebase can deal very ergonomically even with big patch stacks.
(For smaller packages which usually have no patches, plain git merge with an upstream git branch, and a much simpler dsc format, sidesteps the problem entirely.)
Prioritising Debian s users (and other outsiders)
We want everyone to be able to share and modify the software that they interact with. That means we should make source code truly accessible, on the user s terms.
Many of Debian s processes assume everyone is an insider. It s okay that there are Debian insiders and that people feel part of something that they worked hard to become involved with. But lack of perspective can lead to software which fails to uphold our values.
Our source code practices in particular, our determination to share properly (and systematically) are a key part of what makes Debian worthwhile at all. Like Debian s installer, we want our source code to be useable by Debian outsiders.
This is why we have chosen to privilege a git branch format which is more familiar to the world at large, even if it s less popular in Debian.
Consequences, some of which are annoying
The requirement that the conversion be bidirectional, lossless, and context-free can be inconvenient.
For example, we cannot support .gitattributes which modify files during git checkin and checkout. .gitattributes cause the meaning of a git tree to depend on the context, in possibly arbitrary ways, so the conversion from git to source package wouldn t be stable. And, worse, some source packages might not to be representable in git at all.
Another example: Maintainers often have existing git branches for their packages, generated with pre-dgit tooling which is less careful and less principled than ours. That can result in discrepancies between git and dsc, which need to be resolved before a proper git-based upload can succeed.
That some maintainers use patches-unapplied, and some patches-unapplied, means that there has to be some kind of conversion to a standard git representation. Choosing the less-popular patches-applied format as the canonical form, means that many packages need their git representation converted. It also means that user- and outsider-facing branches from browse,git .dgit.d.o and dgit clone are not always compatible with maintainer branches on Salsa. User-contributed changes need cherry-picking rather than merging, or conversion back to the maintainer format. The good news is that dgit can automate much of this, and the manual parts are usually easy git operations.
Distributing the source code as git
Our source code management should be normal, modern, and based on git. That means the Debian Archive is obsolete and needs to be replaced with a set of git repositories.
The replacement repository for source code formally released to Debian is *.dgit.debian.org. This contains all the git objects for every git-based upload since 2013, including the signed tag for each released package version.
The plan is that it will contain a git view of every uploaded Debian package, by centrally importing all legacy uploads into git.
Tracking the relevant git data, when changes are made in the legacy Archive
Currently, many critical source code management tasks are done by changes to the legacy Debian Archive, which works entirely with dsc files (and the associated tarballs etc). The contents of the Archive are therefore still an important source of truth. But, the Archive s architecture means it cannot sensibly directly contain git data.
To track changes made in the Archive, we added the Dgit: field to the .dsc of a git-based upload (2013). This declares which git commit this package was converted from. and where those git objects can be obtained.
Thus, given a Debian Source Package from a git-based upload, it is possible for the new git tooling to obtain the equivalent git objects. If the user is going to work in git, there is no need for any tarballs to be downloaded: the git data could be obtained from the depository using the git protocol.
The signed tags, available from the git depository, have standardised metdata which gives traceability back to the uploading Debian contributor.
Why *.dgit.debian.org is not Salsa
We need a git depository - a formal, reliable and permanent git repository of source code actually released to Debian.
Git forges like Gitlab can be very convenient. But Gitlab is not sufficiently secure, and too full of bugs, to be the principal and only archive of all our source code. (The open core business model of the Gitlab corporation, and the constant-churn development approach, are critical underlying problems.)
Our git depository lacks forge features like Merge Requests. But:
*.dgit.debian.org formal git depository.
Roadmap
In progress
Right now we are quite focused on tag2upload.
We are working hard on eliminating the remaining issues that we feel need to be addressed before declaring the service out of beta.
Future Technology
Whole-archive dsc importer
Currently, the git depository only has git data for git-based package updates (tag2upload and dgit push). Legacy dput-based uploads are not currently present there. This means that the git-based and legacy uploads must be resolved client-side, by dgit clone.
We will want to start importing legacy uploads to git.
Then downstreams and users will be able to get the source code for any package simply with git clone, even if the maintainer is using legacy upload tools like dput.
Support for git-based uploads to security.debian.org
Security patching is a task which would particularly benefit from better and more formal use of git. git-based approaches to applying and backporting security patches are much more convenient than messing about with actual patch files.
Currently, one can use git to help prepare a security upload, but it often involves starting with a dsc import (which lacks the proper git history) or figuring out a package maintainer s unstandardised git usage conventions on Salsa.
And it is not possible to properly perform the security release as git.
Internal Debian consumers switch to getting source from git
Buildds, QA work such as lintian checks, and so on, could be simpler if they don t need to deal with source packages.
And since git is actually the canonical form, we want them to use it directly.
Problems for the distant future
For decades, Debian has been built around source packages. Replacing them is a long and complex process. Certainly source packages are going to continue to be supported for the foreseeable future.
There are no doubt going to be unanticipated problems. There are also foreseeable issues: for example, perhaps there are packages that work very badly when represented in git. We think we can rise to these challenges as they come up.
Mindshare and adoption - please help!
We and our users are very pleased with our technology. It is convenient and highly dependable.
dgit in particular is superb, even if we say so ourselves. As technologists, we have been very focused on building good software, but it seems we have fallen short in the marketing department.
A rant about publishing the source code
git is the preferred form for modification.
Our upstreams are overwhelmingly using git. We are overwhelmingly using git. It is a scandal that for many packages, Debian does not properly, formally and officially publish the git history.
Properly publishing the source code as git means publishing it in a way that means that anyone can automatically and reliably obtain and build the exact source code corresponding to the binaries. The test is: could you use that to build a derivative?
Putting a package in git on Salsa is often a good idea, but it is not sufficient. No standard branch structure git on Salsa is enforced, nor should it be (so it can t be automatically and reliably obtained), the tree is not in a standard form (so it can t be automatically built), and is not necessarily identical to the source package. So Vcs-Git fields, and git from Salsa, will never be sufficient to make a derivative.
Debian is not publishing the source code!
The time has come for proper publication of source code by Debian to no longer be a minority sport. Every maintainer of a package whose upstream is using git (which is nearly all packages nowadays) should be basing their work on upstream git, and properly publishing that via tag2upload or dgit.
And it s not even difficult! The modern git-based tooling provides a far superior upload experience.
A common misunderstanding
dgit push is not an alternative to gbp pq or quilt. Nor is tag2upload. These upload tools complement your existing git workflow. They replace and improve source package building/signing and the subsequent dput. If you are using one of the usual git layouts on salsa, and your package is in good shape, you can adopt tag2upload and/or dgit push right away.
git-debrebase is distinct and does provides an alternative way to manage your git packaging, do your upstream rebases, etc.
Documentation
Debian s documentation all needs to be updated, including particularly instructions for packaging, to recommend use of git-first workflows. Debian should not be importing git-using upstreams release tarballs into git. (Debian outsiders who discover this practice are typically horrified.) We should use only upstream git, work only in git, and properly release (and publish) in git form.
We, the git transition team, are experts in the technology, and can provide good suggestions. But we do not have the bandwidth to also engage in the massive campaigns of education and documentation updates that are necessary especially given that (as with any programme for change) many people will be sceptical or even hostile.
So we would greatly appreciate help with writing and outreach.
Personnel
We consider ourselves the Debian git transition team.
Currently we are:
Diziet and spwhitton.
In January 2024 I wrote about the insanity of the Magnificent Seven dominating the MSCI World Index, and I wondered how long the number can continue to go up? It has continued to surge upward at an accelerating pace, which makes me worry that a crash is likely closer. As a software professional, I decided to analyze whether using stop-loss orders could reliably automate avoiding deep drawdowns.
As everyone with some savings in the stock market (hopefully) knows, the stock market eventually experiences crashes. It is just a matter of when and how deep the crash will be. Staying on the sidelines for years is not a good investment strategy, as inflation will erode the value of your savings. Assuming the current true inflation rate is around 7%, a restaurant dinner that costs 20 euros today will cost 24.50 euros in three years. Savings of 1000 euros today would drop in purchasing power from 50 dinners to only 40 dinners in three years.
Hence, if you intend to retain the value of your hard-earned savings, they need to be invested in something that grows in value. Most people try to beat inflation by buying shares in stable companies, directly or via broad market ETFs. These historically grow faster than inflation during normal years, but likely drop in value during recessions.
CACHE_DIR.mkdir(parents=True, exist_ok=True)
end_date = datetime.today().strftime("%Y-%m-%d")
cache_file = CACHE_DIR / f" TICKER - START_DATE -- end_date .parquet"
if cache_file.is_file():
dataframe = pandas.read_parquet(cache_file)
print(f"Loaded price data from cache: cache_file ")
else:
dataframe = yfinance.download(
TICKER,
start=START_DATE,
end=end_date,
progress=False,
auto_adjust=False
)
dataframe.to_parquet(cache_file)
print(f"Fetched new price data from Yahoo Finance and cached to: cache_file ")print("First 5 rows of the raw data:")
print(df.head())
print("Last 5 rows of the raw data:")
print(df.tail())First 5 rows of the raw data
Price Adj Close Close High Low Open Volume
Ticker BNP.PA BNP.PA BNP.PA BNP.PA BNP.PA BNP.PA
Date
2014-01-02 29.956285 55.540001 56.910000 55.349998 56.700001 316552
2014-01-03 30.031801 55.680000 55.990002 55.290001 55.580002 210044
2014-01-06 30.080338 55.770000 56.230000 55.529999 55.560001 185142
2014-01-07 30.943321 57.369999 57.619999 55.790001 55.880001 370397
2014-01-08 31.385597 58.189999 59.209999 57.750000 57.790001 489940
Last 5 rows of the raw data
Price Adj Close Close High Low Open Volume
Ticker BNP.PA BNP.PA BNP.PA BNP.PA BNP.PA BNP.PA
Date
2025-12-11 78.669998 78.669998 78.919998 76.900002 76.919998 357918
2025-12-12 78.089996 78.089996 80.269997 78.089996 79.470001 280477
2025-12-15 79.080002 79.080002 79.449997 78.559998 78.559998 233852
2025-12-16 78.860001 78.860001 79.980003 78.809998 79.430000 283057
2025-12-17 80.080002 80.080002 80.150002 79.080002 79.199997 262818df["RSI"] = compute_rsi(df["price"], period=14) baseline_series = [
"time": ts, "value": val
for ts, val in df_plot[["timestamp", BASELINE_LABEL]].itertuples(index=False)
]
baseline_json = json.dumps(baseline_series)
template = jinja2.Template("template.html")
rendered_html = template.render(
title=title,
heading=heading,
description=description_html,
...
baseline_json=baseline_json,
...
)
with open("report.html", "w", encoding="utf-8") as f:
f.write(rendered_html)
print("Report generated!") variable in Jinja syntax gets replaced with the actual JSON:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> title </title>
...
</head>
<body>
<h1> heading </h1>
<div id="chart"></div>
<script>
// Ensure the DOM is ready before we initialise the chart
document.addEventListener('DOMContentLoaded', () =>
// Parse the JSON data passed from Python
const baselineData = baseline_json safe ;
const strategyData = strategy_json safe ;
const markersData = markers_json safe ;
// Create the chart
const chart = LightweightCharts.createChart(document.getElementById('chart'),
width: document.getElementById('chart').clientWidth,
height: 500,
layout:
background: color: "#222" ,
textColor: "#ccc"
,
grid:
vertLines: color: "#555" ,
horzLines: color: "#555"
);
// Add baseline series
const baselineSeries = chart.addLineSeries(
title: ' baseline_label ',
lastValueVisible: false,
priceLineVisible: false,
priceLineWidth: 1
);
baselineSeries.setData(baselineData);
baselineSeries.priceScale().applyOptions(
entireTextOnly: true
);
// Add strategy series
const strategySeries = chart.addLineSeries(
title: ' strategy_label ',
lastValueVisible: false,
priceLineVisible: false,
color: '#FF6D00'
);
strategySeries.setData(strategyData);
// Add buy/sell markers to the strategy series
strategySeries.setMarkers(markersData);
// Fit the chart to show the full data range (full zoom)
chart.timeScale().fitContent();
)
</script>
</body>
</html>
A new release of my mixed collection of things package dang package arrived
at CRAN earlier today. The dang package regroups
a few functions of mine that had no other home as for example
lsos() from a
StackOverflow question from 2009 (!!), the overbought/oversold
price band plotter from an older blog post, the market monitor
blogged about as well as the checkCRANStatus() function
tweeted about by Tim
Taylor. And more so take a look.
This release retires two functions: the social media site nobody ever
visits anymore shut down its API too, so no way to mute posts by a given
handle. Similarly, the (never official) ability by Google to supply
financial data is no more, so the function to access data this way is
gone too. But we also have two new ones: one that helps with CRAN entries for ORCiD ids, and
another little helper to re-order microbenchmark results by
summary column (defaulting to the median). Other than the usual updates
to continuous integrations, as well as a switch to Authors@R which will
result in CRAN nagging me less
about this, and another argument update.
The detailed NEWS entry follows.
Via my CRANberries, there is a comparison to the previous release. For questions or comments use the the issue tracker at the GitHub repo.Changes in version 0.0.17 (2025-12-18)
- Added new funtion
reorderMicrobenchmarkResultswith aliasrmr- Use
toloweron email argument tocheckCRANStatus- Added new function
cranORCIDsbootstrapped from two emails by Kurt Hornik- Switched to using Authors@R in DESCRIPTION and added ORCIDs where available
- Switched to
r-ciaction with included bootstrap step; updated updated the checkout action (twice); added (commented-out) log accessor- Removed
googleFinanceDataas the (unofficial) API access point no longer works- Removed
muteTweetersbecause the API was turned off
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can sponsor me at GitHub.
This post is an unpublished review for Unique security and privacy threats of large language models a comprehensive surveyMuch has been written about large language models (LLMs) being a risk to user security and privacy, including the issue that, being trained with datasets whose provenance and licensing are not always clear, they can be tricked into producing bits of data that should not be divulgated. I took on reading this article as means to gain a better understanding of this area. The article completely fulfilled my expectations. This is a review article, which is not a common format for me to follow: instead of digging deep into a given topic, including an experiment or some way of proofing the authors claims, a review article will contain a brief explanation and taxonomy of the issues at hand, and a large number of references covering the field. And, at 36 pages and 151 references, that s exactly what we get. The article is roughly split in two parts: The first three sections present the issue of security and privacy threats as seen by the authors, as well as the taxonomy within which the review will be performed, and sections 4 through 7 cover the different moments in the life cycle of a LLM model (at pre-training, during fine-tuning, when deploying systems that will interact with end-users, and when deploying LLM-based agents), detailing their relevant publications. For each of said moments, the authors first explore the nature of the relevant risks, then present relevant attacks, and finally close outlining countermeasures to said attacks. The text is accompanied all throughout its development with tables, pipeline diagrams and attack examples that visually guide the reader. While the examples presented are sometimes a bit simplistic, they are a welcome guide and aid to follow the explanations; the explanations for each of the attack models are necessarily not very deep, and I was often left wondering I correctly understood a given topic, or wanting to dig deeper but being this a review article, it is absolutely understandable. The authors present an easy to read prose, and this article covers an important spot in understanding this large, important, and emerging area of LLM-related study.
Welcome to post 56 in the R4 series.
The recent post #54
reviewed a number of earlier posts on r-ci, our small (but very
versatile) runner for continunous integration (CI) with R. The post also
introduced the notion of using a container in the matrix of jobs
defined and running in parallel. The initial motivation was the (still
ongoing, and still puzzling) variation in run-times of GitHub Actions.
So when running CI and relying on r2u for the fast, easy,
reliable: pick all three! provision of CRAN packages as Ubuntu binaries,
a small amount of time is spent prepping a basic Ubuntu instance with
the necessary setup. This can be as fast as maybe 20 to 30 seconds, but
it can also stretch to almost two minutes when GitHub is busier or out
of sorts for other reasons. When the CI job itself is short, that is a
nuisance. We presented relying on a pre-made r2u4ci container that adds
just a few commands to the standard r2u container to be complete for CI.
And with that setup CI runs tend to be reliably faster.
This situation is still evolving. I have not converted any of my
existing CI scripts (apart from a test instance or two), but I keep
monitoring the situation. However, this also offered another
perspective: why not rely on a different container for a
different CI aspect? When discussing the CI approach with Jeff the other day (and helping add CI to his
mmap repo), it occurred to me we could also use on of
the Rocker containers for
R-devel. A minimal change to the underlying run.sh script
later, this was accomplished. An example is provided as both a test and
an illustration in the repo for package
RcppInt64 in its script ci.yaml:
strategy:
matrix:
include:
- name: container, os: ubuntu-latest, container: rocker/r2u4ci
- name: r-devel, os: ubuntu-latest, container: rocker/drd
- name: macos, os: macos-latest
- name: ubuntu, os: ubuntu-latest
runs-on: $ matrix.os
container: $ matrix.container drd container from Rocker. The CI runner script now
checks for a possible Rdevel binary as provided inside
drd (along with alias RD) and uses it when
present. And that is all that there is: no other change on the user
side; tests now run under R-devel. You can see some of the initial runs
at the rcppint64
repo actions log. Another example is now also at Jeff s
mmap repo.
It should be noted that this relies on R-devel running packages made
with R-release. Every few years this breaks when R needs to break its
binary API. If and when that happens this option will be costlier as the
R-devel instance will then have to (re-)install its R package
dependencies. This can be accomodated easily as a step in the yaml file.
And under normal circumstances it is not needed.
Having easy access to recent builds of R-devel (the container
refreshes weekly on a schedule) with the convenience of r2u gives another option
for package testing. I may continue to test locally with R-devel as my
primary option, and most likely keep my CI small and lean (usually just
one R-relase run on Ubuntu) but having another option at GitHub Actions
is also a good thing.
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.
io/fs.FS
structure that can replace embed.FS in most contexts.1
package embed import ( "archive/zip" "bytes" _ "embed" "fmt" "io/fs" "sync" ) //go:embed data/embed.zip var embeddedZip []byte var dataOnce = sync.OnceValue(func() *zip.Reader r, err := zip.NewReader(bytes.NewReader(embeddedZip), int64(len(embeddedZip))) if err != nil panic(fmt.Sprintf("cannot read embedded archive: %s", err)) return r ) func Data() fs.FS return dataOnce()
embed.zip archive with a rule in a Makefile. We specify the
files to embed as dependencies to ensure changes are detected.
common/embed/data/embed.zip: console/data/frontend console/data/docs common/embed/data/embed.zip: orchestrator/clickhouse/data/protocols.csv common/embed/data/embed.zip: orchestrator/clickhouse/data/icmp.csv common/embed/data/embed.zip: orchestrator/clickhouse/data/asns.csv common/embed/data/embed.zip: mkdir -p common/embed/data && zip --quiet --recurse-paths --filesync $@ $^
$@ is the rule target, while $^ expands to all
the dependencies, modified or not.

embed.zip.$ unzip -p common/embed/data/embed.zip wc -c numfmt --to=iec 7.3M $ ll common/embed/data/embed.zip -rw-r--r-- 1 bernat users 2.9M Dec 7 17:17 common/embed/data/embed.zip
goos: linux goarch: amd64 pkg: akvorado/common/embed cpu: AMD Ryzen 5 5600X 6-Core Processor BenchmarkData/compressed-12 2262 526553 ns/op 610 B/op 10 allocs/op BenchmarkData/uncompressed-12 9482 123175 ns/op 0 B/op 0 allocs/op
io.ReaderAt or io.Seeker
interfaces, unlike directly embedded files. This prevents some features, like
serving partial files or detecting MIME types when serving files over HTTP.
ReadDir() and ReadFile() methods.
archive/zip module does not support it.
apt-get install guix they use the official Guix guix-install.sh approach. Read more about that effort in the announcement of Debian with Guix.
The second step was to reconsider my approach to generate the Guix images. The earlier design had several stages. First, Debian+Guix containers were created. Then from those containers, a pure Guix container was created. Finally, using the pure Guix container another pure Guix container was created. The idea behind that GCC-like approach was to get to reproducible images that were created from an image that had no Debian left on it. However, I never managed to finish this. Partially because I hadn t realized that every time you build a Guix container image from Guix, you effectively go back in time. When using Guix version X to build a container with Guix on it, it will not put Guix version X into the container but will put whatever version of Guix is available in its package archive, which will be an earlier version, such as version X-N. I had hope to overcome this somehow (running a guix pull in newly generated images may work), but never finished this before Guix was removed from Debian.
So what could a better design look like?
For efficiency, I had already started experimenting with generating the final images directly from the Debian+Guix images, and after reproducibility bugs were fixed I was able to get to reproducible images. However, I was still concerned that the Debian container could taint the process somehow, and was also concerned about the implied dependency on non-free software in Debian.
I ve been using comparative rebuilds using similar distributions to confirm artifact reproducibility for my software projects, comparing builds on Trisquel 11 with Ubuntu 22.04, and AlmaLinux 9 with RockyLinux 9 for example. This works surprisingly well. Including one freedom-respecting distribution like Trisquel will detect if any non-free software has bearing on artifacts. Using different architectures, such as amd64 vs arm64 also help with deeper supply-chain concerns.
My conclusion was that I wanted containers with the same Guix commit for both Trisquel and Ubuntu. Given the similarity with Debian, adapting and launching the Guix on Trisquel/Debian project was straight forward. So we now have Trisquel 11/12 and Ubuntu 22.04/24.04 images with the same Guix on them.
Do you see where the debian-with-guix and guix-on-dpkg projects are leading to?
We are now ready to look at the modernized Guix Container Images project. The tags are the same as before:
registry.gitlab.com/debdistutils/guix/container:latest
registry.gitlab.com/debdistutils/guix/container:slim
registry.gitlab.com/debdistutils/guix/container:extra
registry.gitlab.com/debdistutils/guix/container:gash
The method to create them is different. Now there is a build job that uses the earlier Guix+Trisquel container (for amd64) or Guix+Debian (for arm64, pending Trisquel arm64 containers). The build job create the final containers directly. Next a Ubuntu reproduce job is launched that runs the same commands, failing if it cannot generate the bit-by-bit identical container. Then single-arch images are tested (installing/building GNU hello and building libksba), and then pushed to the GitLab registry, adding multi-arch images in the process. Then the final multi-arch containers are tested by building Guile-GnuTLS and, on success, uploaded to the Docker Hub.
How would you use them? A small way to start the container is like this:
jas@kaka:~$ podman run -it --privileged --entrypoint=/bin/sh registry.gitlab.com/debdistutils/guix/container:latest
sh-5.2# env HOME=/ guix describe # https://issues.guix.gnu.org/74949
guix 21ce6b3
repository URL: https://git.guix.gnu.org/guix.git
branch: master
commit: 21ce6b392ace4c4d22543abc41bd7c22596cd6d2
sh-5.2#
The need for --entrypoint=/bin/sh is because Guix s pack command sets up the entry point differently than most other containers. This could probably be fixed if people want that, and there may be open bug reports about this.
The need for --privileged is more problematic, but is discussed upstream. The above example works fine without it, but running anything more elaborate with guix-daemon installing packages will trigger a fatal error. Speaking of that, here is a snippet of commands that allow you to install Guix packages in the container.
cp -rL /gnu/store/*profile/etc/* /etc/
echo 'root:x:0:0:root:/:/bin/sh' > /etc/passwd
echo 'root:x:0:' > /etc/group
groupadd --system guixbuild
for i in $(seq -w 1 10); do useradd -g guixbuild -G guixbuild -d /var/empty -s $(command -v nologin) -c "Guix build user $i" --system guixbuilder$i; done
env LANG=C.UTF-8 guix-daemon --build-users-group=guixbuild &
guix archive --authorize < /share/guix/ci.guix.gnu.org.pub
guix archive --authorize < /share/guix/bordeaux.guix.gnu.org.pub
guix install hello
GUIX_PROFILE="/var/guix/profiles/per-user/root/guix-profile"
. "$GUIX_PROFILE/etc/profile"
hello
This could be simplified, but we chose to not hard-code in our containers because some of these are things that probably shouldn t be papered over but fixed properly somehow. In some execution environments, you may need to pass --disable-chroot to guix-daemon.
To use the containers to build something in a GitLab pipeline, here is an example snippet:
test-amd64-latest-wget-configure-make-libksba:
image: registry.gitlab.com/debdistutils/guix/container:latest
before_script:
- cp -rL /gnu/store/*profile/etc/* /etc/
- echo 'root:x:0:0:root:/:/bin/sh' > /etc/passwd
- echo 'root:x:0:' > /etc/group
- groupadd --system guixbuild
- for i in $(seq -w 1 10); do useradd -g guixbuild -G guixbuild -d /var/empty -s $(command -v nologin) -c "Guix build user $i" --system guixbuilder$i; done
- export HOME=/
- env LANG=C.UTF-8 guix-daemon --build-users-group=guixbuild &
- guix archive --authorize < /share/guix/ci.guix.gnu.org.pub
- guix archive --authorize < /share/guix/bordeaux.guix.gnu.org.pub
- guix describe
- guix install libgpg-error
- GUIX_PROFILE="//.guix-profile"
- . "$GUIX_PROFILE/etc/profile"
script:
- wget https://www.gnupg.org/ftp/gcrypt/libksba/libksba-1.6.7.tar.bz2
- tar xfa libksba-1.6.7.tar.bz2
- cd libksba-1.6.7
- ./configure
- make V=1
- make check VERBOSE=t V=1
More help on the project page for the Guix Container Images.
That s it for tonight folks, and remember, Happy Hacking!
$Name would be turned $Name~wikipesija.org
after the import.2 We also created empty user accounts that would take
ownership of the edits to be imported so that we could use the standard account
management tools on them later on. MediaWiki supports importing contributions
without a local account to attribute them to, but it doesn't seem to be possible
to convert an imported actor3 to a regular user later on which we wanted
to keep as a possibility, even with the minor downside of creating a few hundred
users that'll likely never get touched again later.
We also made specific decisions to add the username suffix to everyone, not to
just those names that'd conflicted with existing SUL accounts, and to deal with
renaming users that wanted their contributions linked to an existing SUL account
only after the import. This both reduced complexity and thus risk from the
import phase, which already had much more unknowns compared to the rest of the
process, but also were much better options ethically as well: suffixing all names
meant we would not imply that those people chose to be Wikimedians with those
specific usernames (when in reality it was us choosing to import those edits to
the Wikimedia universe), and doing renames using the standard MediaWiki account
management tooling meant that it produced the normal public log entries that
all other MediaWiki administrative actions create.
With all of the edits imported, the only major thing remaining was doing those merges
I mentioned earlier to attribute imported edits to people's existing SUL accounts.
Thankfully, the local account -based system makes it actually pretty simple. Usually
CentralAuth prevents renaming individual local accounts that are attached to a global
account, but that check can be bypassed with a maintenance script or a privileged
enough account. Renaming the user automatically detached it from the previous global
account, after which an another maintenance script could be used to attach the
user to the correct global account.
wikipesija.org was the domain where the old third-party wiki was hosted on, and
~ was used as a separator character in usernames during the
SUL finalization in the early
2010s so using it here felt appropriate as well.
Welcome to the report for November 2025 from the Reproducible Builds project!
These monthly reports outline what we ve been up to over the past month, highlighting items of news from elsewhere in the increasingly-important area of software supply-chain security. As always, if you are interested in contributing to the Reproducible Builds project, please see the Contribute page on our website.
In this report:
On Friday 8th November, Chris Lamb gave a talk called 10 years of Reproducible Builds at SeaGL in Seattle, WA.
Founded in 2013, SeaGL is a free, grassroots technical summit dedicated to spreading awareness and knowledge about free source software, hardware and culture. Chris talk:
[ ] introduces the concept of reproducible builds, its technical underpinnings and its potentially transformative impact on software security and transparency. It is aimed at developers, security professionals and policy-makers who are concerned with enhancing trust and accountability in our software. It also provides a history of the Reproducible Builds project, which is approximately ten years old. How are we getting on? What have we got left to do? Aren t all the builds reproducible now?
In Debian this month, Jochen Sprickerhof created a merge request to replace the use of reprotest in Debian s Salsa Continuous Integration (CI) pipeline with debrebuild. Jochen cites the advantages as being threefold: firstly, that only one extra build needed ; it uses the same sbuild and ccache tooling as the normal build ; and works for any Debian release . The merge request was merged by Emmanuel Arias and is now active.
kpcyrd posted to our mailing list announcing the initial release of repro-threshold, which implements an APT transport that defines a threshold of at least X of my N trusted rebuilders need to confirm they reproduced the binary before installing Debian packages. Configuration can be done through a config file, or through a curses-like user interface.
Holger then merged two commits by Jochen Sprickerhof in order to address a fakeroot-related reproducibility issue in the debian-installer, and J rg Jaspert deployed a patch by Ivo De Decker for a bug originally filed by Holger in February 2025 related to some Debian packages not being archived on snapshot.debian.org.
Elsewhere, Roland Clobus performed some analysis on the live Debian trixie images, which he determined were not reproducible. However, in a follow-up post, Roland happily reports that the issues have been handled. In addition, 145 reviews of Debian packages were added, 12 were updated and 15 were removed this month adding to our knowledge about identified issues.
Lastly, Jochen Sprickerhof filed a bug announcing their intention to binary NMU a very large number of the R programming language after a reproducibility-related toolchain bug was fixed.
Bernhard M. Wiedemann posted another openSUSE monthly update for their work there.
Julien Malka and Arnout Engelen launched the new hash collection
server for NixOS. Aside from improved reporting to help focus reproducible builds
efforts within NixOS, it collects build hashes as individually-signed attestations
from independent builders, laying the groundwork for further tooling.
307 was uploaded to Debian unstable (as well as version 309). These changes included further attempts to automatically attempt to deploy to PyPI by liaising with the PyPI developers/maintainers (with this experimental feature). [ ][ ][ ]
In addition, reprotest versions 0.7.31 and 0.7.32 were uploaded to Debian unstable by Holger Levsen, who also made the following changes:
debian/watch file, as Lintian now flags this as error for native Debian packages. [ ][ ]Standards-Version to 4.7.2, with no changes needed. [ ]Rules-Requires-Root header as it is no longer required.. [ ]
Once again, there were a number of improvements made to our website this month including:
SOURCE_DATE_EPOCH page to fix the Lisp example syntax. [ ]
CONFIG_MODULE_HASHES patchset for the Linux kernel, which aims to enable reproducible kernel packages for Linux distributions .
.rpms.
Via our mailing list, Martin Monperrus let us know about their recently-published page on the Software Supply Chain Security of Web3. The abstract of their paper is as follows:
Web3 applications, built on blockchain technology, manage billions of dollars in digital assets through decentralized applications (dApps) and smart contracts. These systems rely on complex, software supply chains that introduce significant security vulnerabilities. This paper examines the software supply chain security challenges unique to the Web3 ecosystem, where traditional Web2 software supply chain problems intersect with the immutable and high-stakes nature of blockchain technology. We analyze the threat landscape and propose mitigation strategies to strengthen the security posture of Web3 systems.Their paper lists reproducible builds as one of the mitigating strategies. A PDF of the full text is available to download.
SARndbox (race)clamav (rust toolchain)contrast/identity/loupe/mousai (need glib-macros update)cosmic (cosmic* HashMap)dealers-choice (nocheck)falcon (python-falcon date)FreeDoko (date)gnutls (FTBFS-CPU)gods-deluxe (jar mtimes)Kinect (date)libplasma6 (qmlcachegen race)llvm (rocm-omp date)rnp (FTBFS-2041)rocsolver (FTBFS-j1)switcheroo (FTBFS-j1)vdrift (date)ibus (parallelism)qmlcachegen (with Ulf Hermann)python-gffutils.python-biom-format.python-requests-cache.python-tld.smart-open.vanguards.pycifrw.golang-github-apptainer-container-library-client.python-ofxhome.python-lupa.mu-editor.python-spdx-tools.python-django-waffle.biosquid.dateparser.parsinsert.rdf2rml.python-et-xmlfile.deblur.ytcc.pgpainless.trillian.pywavelets.jsonpath-ng.presto.python-pyutil.python-os-apply-config.pydata-sphinx-theme.python-ciso8601.python-pymummer.qcat.tkgate.tkgate.ruby-gnuplot.python-nixio.python-altair.python-graphene.python-phabricator.python-slimmer.python-kafka.python-sshsig.python-babelgladeextractor.python-genson.flawfinder.crasm.insilicoseq.pychopper.pycparser.whipper.vt.pyxnat.golang-github-kshedden-statmodel.nim-hts.golang-github-emicklei-dot.golang-gonum-v1-plot.beangulp.virulencefinder.ansible-lint.entropybroker.namecheap.spopt.pyasn.python-pyvcf.python-pysaml2.#reproducible-builds on irc.oftc.net.
rb-general@lists.reproducible-builds.org
registry.gitlab.com/debdistutils/guix/guix-on-dpkg:trisquel11-guix
registry.gitlab.com/debdistutils/guix/guix-on-dpkg:trisquel12-guix
registry.gitlab.com/debdistutils/guix/guix-on-dpkg:ubuntu22.04-guix
registry.gitlab.com/debdistutils/guix/guix-on-dpkg:ubuntu24.04-guix
Or you prefer guix-on-dpkg on Docker Hub:
docker.io/jas4711/guix-on-dpkg:trisquel11-guix
docker.io/jas4711/guix-on-dpkg:trisquel12-guix
docker.io/jas4711/guix-on-dpkg:ubuntu22.04-guix
docker.io/jas4711/guix-on-dpkg:ubuntu24.04-guix
You may use them as follows. See the guix-on-dpkg README for how to start guix-daemon and installing packages.
jas@kaka:~$ podman run -it --hostname guix --rm registry.gitlab.com/debdistutils/guix/guix-on-dpkg:trisquel11-guix
root@guix:/# head -1 /etc/os-release
NAME="Trisquel GNU/Linux"
root@guix:/# guix describe
guix 136fc8b
repository URL: https://gitlab.com/debdistutils/guix/mirror.git
branch: master
commit: 136fc8bfe91a64d28b6c54cf8f5930ffe787c16e
root@guix:/#
You may now be asking yourself: why? Fear not, gentle reader, because having two container images of roughly similar software is a great tool for attempting to build software artifacts reproducible, and comparing the result to spot differences. Obviously.
I have been using this pattern to get reproducible tarball artifacts of several software releases for around a year and half, since libntlm 1.8.
Let s walk through how to setup a CI/CD pipeline that will build a piece of software, in four different jobs for Trisquel 11/12 and Ubuntu 22.04/24.04. I am in the process of learning Codeberg/Forgejo CI/CD, so I am still using GitLab CI/CD here, but the concepts should be the same regardless of platform. Let s start by defining a job skeleton:
.guile-gnutls: &guile-gnutls
before_script:
- /root/.config/guix/current/bin/guix-daemon --version
- env LC_ALL=C.UTF-8 /root/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild $GUIX_DAEMON_ARGS &
- GUIX_PROFILE=/root/.config/guix/current; . "$GUIX_PROFILE/etc/profile"
- type guix
- guix --version
- guix describe
- time guix install --verbosity=0 wget gcc-toolchain autoconf automake libtool gnutls guile pkg-config
- time apt-get update
- time apt-get install -y make git texinfo
- GUIX_PROFILE="/root/.guix-profile"; . "$GUIX_PROFILE/etc/profile"
script:
- git clone https://codeberg.org/guile-gnutls/guile-gnutls.git
- cd guile-gnutls
- git checkout v5.0.1
- ./bootstrap
- ./configure
- make V=1
- make V=1 check VERBOSE=t
- make V=1 dist
after_script:
- mkdir -pv out/$CI_JOB_NAME_SLUG/src
- mv -v guile-gnutls/*-src.tar.* out/$CI_JOB_NAME_SLUG/src/
- mv -v guile-gnutls/*.tar.* out/$CI_JOB_NAME_SLUG/
artifacts:
paths:
- out/**
This installs some packages, clones guile-gnutls (it could be any project, that s just an example), build it and return tarball artifacts. The artifacts are the git-archive and make dist tarballs.
Let s instantiate the skeleton into four jobs, running the Trisquel 11/12 jobs on amd64 and the Ubuntu 22.04/24.04 jobs on arm64 for fun.
guile-gnutls-trisquel11-amd64:
tags: [ saas-linux-medium-amd64 ]
image: registry.gitlab.com/debdistutils/guix/guix-on-dpkg:trisquel11-guix
extends: .guile-gnutls
guile-gnutls-ubuntu22.04-arm64:
tags: [ saas-linux-medium-arm64 ]
image: registry.gitlab.com/debdistutils/guix/guix-on-dpkg:ubuntu22.04-guix
extends: .guile-gnutls
guile-gnutls-trisquel12-amd64:
tags: [ saas-linux-medium-amd64 ]
image: registry.gitlab.com/debdistutils/guix/guix-on-dpkg:trisquel12-guix
extends: .guile-gnutls
guile-gnutls-ubuntu24.04-arm64:
tags: [ saas-linux-medium-arm64 ]
image: registry.gitlab.com/debdistutils/guix/guix-on-dpkg:ubuntu24.04-guix
extends: .guile-gnutls
Running this pipeline will result in artifacts that you want to confirm for reproducibility. Let s add a pipeline job to do the comparison:
guile-gnutls-compare:
image: alpine:latest
needs: [ guile-gnutls-trisquel11-amd64,
guile-gnutls-trisquel12-amd64,
guile-gnutls-ubuntu22.04-arm64,
guile-gnutls-ubuntu24.04-arm64 ]
script:
- cd out
- sha256sum */*.tar.* */*/*.tar.* sort grep -- -src.tar.
- sha256sum */*.tar.* */*/*.tar.* sort grep -v -- -src.tar.
- sha256sum */*.tar.* */*/*.tar.* sort uniq -c -w64 sort -rn
- sha256sum */*.tar.* */*/*.tar.* grep -- -src.tar. sort uniq -c -w64 grep -v '^ 1 '
- sha256sum */*.tar.* */*/*.tar.* grep -v -- -src.tar. sort uniq -c -w64 grep -v '^ 1 '
# Confirm modern git-archive tarball reproducibility
- cmp guile-gnutls-trisquel12-amd64/src/*.tar.gz guile-gnutls-ubuntu24-04-arm64/src/*.tar.gz
# Confirm old git-archive (export-subst but long git describe) tarball reproducibility
- cmp guile-gnutls-trisquel11-amd64/src/*.tar.gz guile-gnutls-ubuntu22-04-arm64/src/*.tar.gz
# Confirm 'make dist' generated tarball reproducibility
- cmp guile-gnutls-trisquel11-amd64/*.tar.gz guile-gnutls-ubuntu22-04-arm64/*.tar.gz
- cmp guile-gnutls-trisquel12-amd64/*.tar.gz guile-gnutls-ubuntu24-04-arm64/*.tar.gz
artifacts:
when: always
paths:
- ./out/**
Look how beautiful, almost like ASCII art! The commands print SHA256 checksums of the artifacts, sorted in a couple of ways, and then proceeds to compare relevant artifacts. What would the output of such a run be, you may wonder? You can look for yourself in the guix-on-dpkg pipeline but here is the gist of it:
$ cd out
$ sha256sum */*.tar.* */*/*.tar.* sort grep -- -src.tar.
79bc24143ba083819b36822eacb8f9e15a15a543e1257c53d30204e9ffec7aca guile-gnutls-trisquel11-amd64/src/guile-gnutls-v5.0.1-src.tar.gz
79bc24143ba083819b36822eacb8f9e15a15a543e1257c53d30204e9ffec7aca guile-gnutls-ubuntu22-04-arm64/src/guile-gnutls-v5.0.1-src.tar.gz
b190047cee068f6b22a5e8d49ca49a2425ad4593901b9ac8940f8842ba7f164f guile-gnutls-trisquel12-amd64/src/guile-gnutls-v5.0.1-src.tar.gz
b190047cee068f6b22a5e8d49ca49a2425ad4593901b9ac8940f8842ba7f164f guile-gnutls-ubuntu24-04-arm64/src/guile-gnutls-v5.0.1-src.tar.gz
$ sha256sum */*.tar.* */*/*.tar.* sort grep -v -- -src.tar.
1e8d107ad534b85f30e432d5c98bf599aab5d8db5f996c2530aabe91f203018a guile-gnutls-trisquel11-amd64/guile-gnutls-5.0.1.tar.gz
1e8d107ad534b85f30e432d5c98bf599aab5d8db5f996c2530aabe91f203018a guile-gnutls-ubuntu22-04-arm64/guile-gnutls-5.0.1.tar.gz
bc2df2d868f141bca5f3625aa146aa0f24871f6dcf0b48ff497eba3bb5219b84 guile-gnutls-trisquel12-amd64/guile-gnutls-5.0.1.tar.gz
bc2df2d868f141bca5f3625aa146aa0f24871f6dcf0b48ff497eba3bb5219b84 guile-gnutls-ubuntu24-04-arm64/guile-gnutls-5.0.1.tar.gz
$ sha256sum */*.tar.* */*/*.tar.* sort uniq -c -w64 sort -rn
2 bc2df2d868f141bca5f3625aa146aa0f24871f6dcf0b48ff497eba3bb5219b84 guile-gnutls-trisquel12-amd64/guile-gnutls-5.0.1.tar.gz
2 b190047cee068f6b22a5e8d49ca49a2425ad4593901b9ac8940f8842ba7f164f guile-gnutls-trisquel12-amd64/src/guile-gnutls-v5.0.1-src.tar.gz
2 79bc24143ba083819b36822eacb8f9e15a15a543e1257c53d30204e9ffec7aca guile-gnutls-trisquel11-amd64/src/guile-gnutls-v5.0.1-src.tar.gz
2 1e8d107ad534b85f30e432d5c98bf599aab5d8db5f996c2530aabe91f203018a guile-gnutls-trisquel11-amd64/guile-gnutls-5.0.1.tar.gz
$ sha256sum */*.tar.* */*/*.tar.* grep -- -src.tar. sort uniq -c -w64 grep -v '^ 1 '
2 79bc24143ba083819b36822eacb8f9e15a15a543e1257c53d30204e9ffec7aca guile-gnutls-trisquel11-amd64/src/guile-gnutls-v5.0.1-src.tar.gz
2 b190047cee068f6b22a5e8d49ca49a2425ad4593901b9ac8940f8842ba7f164f guile-gnutls-trisquel12-amd64/src/guile-gnutls-v5.0.1-src.tar.gz
$ sha256sum */*.tar.* */*/*.tar.* grep -v -- -src.tar. sort uniq -c -w64 grep -v '^ 1 '
2 1e8d107ad534b85f30e432d5c98bf599aab5d8db5f996c2530aabe91f203018a guile-gnutls-trisquel11-amd64/guile-gnutls-5.0.1.tar.gz
2 bc2df2d868f141bca5f3625aa146aa0f24871f6dcf0b48ff497eba3bb5219b84 guile-gnutls-trisquel12-amd64/guile-gnutls-5.0.1.tar.gz
$ cmp guile-gnutls-trisquel12-amd64/src/*.tar.gz guile-gnutls-ubuntu24-04-arm64/src/*.tar.gz
$ cmp guile-gnutls-trisquel11-amd64/src/*.tar.gz guile-gnutls-ubuntu22-04-arm64/src/*.tar.gz
$ cmp guile-gnutls-trisquel11-amd64/*.tar.gz guile-gnutls-ubuntu22-04-arm64/*.tar.gz
$ cmp guile-gnutls-trisquel12-amd64/*.tar.gz guile-gnutls-ubuntu24-04-arm64/*.tar.gz
That s it for today, but stay tuned for more updates on using Guix in containers, and remember; Happy Hacking!
I am a huge fan of Git, as I have witnessed how it has made software development so much more productive compared to the pre-2010s era. I wish all Debian source code were in Git to reap the full benefits.
Git is not perfect, as it requires significant effort to learn properly, and the ecosystem is complex with even more things to learn ranging from cryptographic signatures and commit hooks to Git-assisted code review best practices, forge websites, and CI systems.
Sure, there is still room to optimize its use, but Git certainly has proven itself and is now the industry standard. Thus, some readers might be surprised to learn that Debian development in 2025 is not actually based on Git. In Debian, the version control is done by the Debian archive itself. Each commit is a new upload to the archive, and the commit message is the debian/changelog entry. The commit log is available at snapshots.debian.org.
In practice, most Debian Developers (people who have the credentials to upload to the Debian archive) do use Git and host their packaging source code on salsa.debian.org the GitLab instance of Debian. This is, however, based on each DD s personal preferences. The Debian project does not have any policy requiring that packages be hosted on salsa.debian.org or be in version control at all.
unstable area is equivalent to the main development branch).Vcs-Git that advertises which version control repository the maintainer uses. However, newcomers to Debian are surprised to notice that not all packages are hosted on salsa.debian.org but at various random places with their own account and code submission systems, and there is nothing enforcing or even warning if the code there is out of sync with what was uploaded to Debian. Any Debian Developer can at any time upload a new package with whatever changes, bypassing the Git repository, even when the package advertised a Git repository. All PGP signed commits, Git tags and other information in the Git repository are just extras currently, as the Debian archive does not enforce or validate anything about them.
This also makes contributing to multiple packages in parallel hard. One can t just go on salsa.debian.org and fork a bunch of repositories and submit Merge Requests. Currently, the only reliable way is to download source packages from Debian unstable, develop patches on top of them, and send the final version as a plain patch file by email to the Debian bug tracker. To my knowledge, no system exists to facilitate working with the patches in the bug tracker, such as rebasing patches 6 months later to detect if they or equivalent changes were applied or if sending refreshed versions is needed.
To newcomers in Debian, it is even more surprising that there are packages that are on salsa.debian.org but have the Merge Requests feature disabled. This is often because the maintainer does not want to receive notification emails about new Merge Requests, but rather just emails from bugs.debian.org. This may sound arrogant, but keep in mind that these developers put in the effort to set up their Mutt/Emacs workflow for the existing Debian process, and extending it to work with GitLab notifications is not trivial. There are also purists who want to do everything via the command-line (without having to open a browser, run JavaScript and maintain a live Internet connection), and tools like glab are not convenient enough for the full workflow.
The recommended first step in contributing to a package is to use the built-in Fork feature on Salsa. This serves two purposes. Primarily, it allows any contributor to publish their Git branches and submit them as Merge Requests. Additionally, the mere existence of a list of Forks enables contributors to discover each other, and in rare cases when the original package is not accepting improvements, collaboration could arise among the contributors and potentially lead to permanent forks in the general meaning. Forking is a fundamental part of the dynamics in open source that helps drive quality and agreement. The ability to fork ultimately serves as the last line of defense of users rights. Git supports this by making both temporary and permanent forks easy to create and maintain.Further, it states:
Debian packaging work should be reasonably transparent and public to allow contributors to participate. A maintainer should push their pending changes to Salsa at regular intervals, so that a potential contributor can discover if a particular change has already been made or a bug has been fixed in version control, and thus avoid duplicate work. Debian maintainers should make reasonable efforts to publish planned changes as Merge Requests on Salsa and solicit feedback and reviews. While pushing changes directly on the main Git branch is the fastest workflow, second only to uploading all changes directly to Debian repositories, it is not an inclusive way to develop software. Even packages that are maintained by a single maintainer should at least occasionally publish Merge Requests to allow new contributors to step up and participate.I think these are key aspects leading to transparency and true open source collaboration. Even though this talks about Salsa which is based on GitLab the concepts are universal and will work also on other forges, like Forgejo or GitHub. The point is that sharing work-in-progress on a real-time platform, with CI and other supporting features, empowers and motivates people to iterate on code collaboratively. As an example of an anti-pattern, Oracle MySQL publishes the source code for all their releases and is license-compliant, but as they don t publish their Git commits in real-time, it does not feel like a real open source project. Non-Oracle employees are not motivated to participate as second-class developers who are kept in the dark. Debian should embrace git and sharing work in real-time, embodying a true open source spirit.
git-buildpackage support so it can integrate into salsa.debian.org without turning off Debian packaging security features. The git-buildpackage tool itself also needs various improvements, such as making contributing to multiple different packages with various levels of diligence in debian/gbp.conf maintenance less error-prone.
Eventually, if it starts looking like all Debian packages might get hosted on salsa.debian.org, I would also start building a review.debian.org website to facilitate code review aspects that are unique to Debian, such as tracking Merge Requests across GitLab projects in ways GitLab can t do, highlighting which submissions need review most urgently, feeding code reviews and approvals into the contributors.debian.org database for better attribution, and so forth.
Details on this vision will be in a later blog post, so subscribe to updates!
guix tool and a reasonable fresh guix pull.
Supported architectures include amd64 and arm64. The multi-arch container is called:
registry.gitlab.com/debdistutils/guix/debian-with-guix-container:stable
It may also be accessed via debian-with-guix at Docker Hub as:
docker.io/jas4711/debian-with-guix:stable
The container images may be used like this:
$ podman run --privileged -it --hostname guix --rm registry.gitlab.com/debdistutils/guix/debian-with-guix-container:stable
root@guix:/# hello
bash: hello: command not found
root@guix:/# guix describe
guix c9eb69d
repository URL: https://gitlab.com/debdistutils/guix/mirror.git
branch: master
commit: c9eb69ddbf05e77300b59f49f4bb5aa50cae0892
root@guix:/# LC_ALL=C.UTF-8 /root/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild &
[1] 21
root@guix:/# GUIX_PROFILE=/root/.config/guix/current; . "$GUIX_PROFILE/etc/profile"
root@guix:/# guix describe
Generation 2 Nov 28 2025 10:14:11 (current)
guix c9eb69d
repository URL: https://gitlab.com/debdistutils/guix/mirror.git
branch: master
commit: c9eb69ddbf05e77300b59f49f4bb5aa50cae0892
root@guix:/# guix install --verbosity=0 hello
accepted connection from pid 55, user root
The following package will be installed:
hello 2.12.2
hint: Consider setting the necessary environment variables by running:
GUIX_PROFILE="/root/.guix-profile"
. "$GUIX_PROFILE/etc/profile"
Alternately, see guix package --search-paths -p "/root/.guix-profile"'.
root@guix:/# GUIX_PROFILE="/root/.guix-profile"
root@guix:/# . "$GUIX_PROFILE/etc/profile"
root@guix:/# hello
Hello, world!
root@guix:/#
Below is an example GitLab pipeline job that demonstrate how to run guix install to install additional dependencies, and then download and build a package that pick up the installed package from the system.
test-wget-configure-make-libksba-amd64:
image: registry.gitlab.com/debdistutils/guix/debian-with-guix-container:stable
before_script:
- env LC_ALL=C.UTF-8 /root/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild $GUIX_DAEMON_ARG &
- GUIX_PROFILE=/root/.config/guix/current; . "$GUIX_PROFILE/etc/profile"
- guix describe
- guix install libgpg-error
- GUIX_PROFILE="/root/.guix-profile"; . "$GUIX_PROFILE/etc/profile"
- apt-get install --update -y --no-install-recommends build-essential wget ca-certificates bzip2
script:
- wget https://www.gnupg.org/ftp/gcrypt/libksba/libksba-1.6.7.tar.bz2
- tar xfa libksba-1.6.7.tar.bz2
- cd libksba-1.6.7
- ./configure
- make V=1
- make check VERBOSE=t V=1
The images were initially created for use in GitLab CI/CD Pipelines but should work for any use.
The images are built in a GitLab CI/CD pipeline, see .gitlab-ci.yml.
The containers are derived from official Debian stable images with Guix installed and a successful run of guix pull, built using buildah invoked from build.sh using image/Containerfile that runs image/setup.sh.
The pipeline also push images to the GitLab container registry, and then also to Docker Hub.
Guix binaries are downloaded from the Guix binary tarballs project because of upstream download site availability and bandwidth concerns.
Enjoy these images! Hopefully they can help you overcome the loss of Guix in Debian which made it a mere apt-get install guix away before.
There are several things that may be improved further. An alternative to using podman --privileged is to use --security-opt seccomp=unconfined --cap-add=CAP_SYS_ADMIN,CAP_NET_ADMIN which may be slightly more fine-grained.
For ppc64el support I ran into an error message that I wasn t able to resolve:
guix pull: error: while setting up the build environment: cannot set host name: Operation not permitted
For riscv64, I can t even find a Guix riscv64 binary tarball for download, is there one anywhere?
For arm64 containers, it seems that you need to start guix-daemon with --disable-chroot to get something to work, at least on GitLab.com s shared runners, otherwise you will get this error message:
guix install: error: clone: Invalid argument
Building the images themselves also require disabling some security functionality, and I was not able to build images with buildah without providing --cap-add=CAP_SYS_ADMIN,CAP_NET_ADMIN otherwise there were errors like this:
guix pull: error: cloning builder process: Operation not permitted
guix pull: error: clone: Operation not permitted
guix pull: error: while setting up the build environment: cannot set loopback interface flags: Operation not permitted
Finally on amd64 it seems --security-opt seccomp=unconfined is necessary, otherwise there is an error message like this, even if you use --disable-chroot:
guix pull: error: while setting up the child process: in phase setPersonality: cannot set personality: Function not implemented
This particular error is discussed upstream, but I think generally that these error suggest that guix-daemon could use more optional use of features: if some particular feature is not available, gracefully fall back to another mode of operation, instead of exiting with an error. Of course, it should never fall back to an insecure mode of operation, unless the user requests that.
Happy Hacking!
Next.