Audit: selfh.st survey 2023-2025 read against Felhom; R-989 filed
gates / gates (push) Successful in 6m24s
gates / gates (push) Successful in 6m24s
Read-only. fetch.py + tables.py (stdlib), TABLE.md (45 charts, % of respondents), FELHOM.md (apps, features, reasons, hardware, trends, ten ranked recommendations), METHOD.md (hashes, counts, matching, MIT licence, limits). Survey files are not committed. One register row (R-989) and one STATUS line ask the operator to rule on the recommendations.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
# selfh.st survey 2023–2025 — what it means for Felhom
|
||||
|
||||
Read-only, 2026-10-12. Numbers are **% of the year's respondents** (2023: 1,869 · 2024: 3,655 · 2025: 4,081),
|
||||
from `TABLE.md` section **T*n***; each section cites its year's URL. The crowd is hobby users, not households
|
||||
(`METHOD.md` limits). Catalog = the 62 template folders of `app-catalog-felhom.eu/templates/` at `cb4ececf1f`.
|
||||
|
||||
## 1. Apps (2025 Software charts + Favorite Software)
|
||||
|
||||
`✓ name` = we ship that template · `✗` = we do not · `n/a` = not a server app a household box would run.
|
||||
|
||||
| App | 2025 % | Source | Catalog |
|
||||
|---|---|---|---|
|
||||
| Home Assistant | 62.7 deploy · 14.4 fav | T30, T33 | ✓ home-assistant |
|
||||
| Jellyfin | 60.9 · 12.8 fav | T25, T33 | ✓ jellyfin |
|
||||
| Nextcloud (+ Nextcloud Notes 7.9) | 30.3 · 2.9 fav | T26, T28, T33 | ✓ nextcloud |
|
||||
| Plex | 29.9 · 5.1 fav | T25, T33 | ✓ plex |
|
||||
| Syncthing | 25.9 · 0.8 fav | T26, T33 | ✗ |
|
||||
| Obsidian | 25.5 | T28 | n/a (desktop app; its sync server is a separate project) |
|
||||
| Minecraft (game server) | 25.5 | T32 | ✓ crafty-controller |
|
||||
| Gitea | 20.8 · 0.7 fav | T29, T33 | ✓ gitea |
|
||||
| AudioBookshelf | 20.6 · 2.3 fav | T25, T33 | ✓ audiobookshelf |
|
||||
| Navidrome | 16.1 · 1.0 fav | T25, T33 | ✓ navidrome |
|
||||
| Forgejo | 15.0 | T29 | ✗ |
|
||||
| Synology | 13.5 | T26 | n/a (NAS product) |
|
||||
| FileBrowser / FileBrowser Quantum | 11.8 / 5.1 | T26 | built into every box (not a template; R-917 row text) |
|
||||
| Immich | 10.5 fav | T33 | ✓ immich |
|
||||
| Authelia | 9.6 | T27 | ✗ |
|
||||
| Pocket ID | 7.6 | T27 | ✗ |
|
||||
| BookStack | 7.4 | T28 | ✓ bookstack |
|
||||
| GitLab | 7.4 | T29 | ✗ |
|
||||
| authentik | 6.6 | T27 | ✗ |
|
||||
| Joplin | 6.6 | T28 | ✗ |
|
||||
| Pangolin | 5.9 auth · 7.1 proxy · 0.6 fav | T27, T19, T33 | ✗ (tunnel/proxy; Felhom has its own) |
|
||||
| Kodi | 5.8 | T25 | n/a (player, not a server) |
|
||||
| Trilium · Memos · Wiki.js | 4.5 · 4.1 · 3.8 | T28 | ✗ |
|
||||
| Keycloak · Tinyauth · Zitadel | 3.9 · 2.7 · 1.1 | T27 | ✗ |
|
||||
| Emby | 3.7 · 0.6 fav | T25, T33 | ✓ emby |
|
||||
| Docmost · Outline | 3.5 · 3.2 | T28 | ✓ docmost · ✓ outline |
|
||||
| Vaultwarden | 3.7 fav | T33 | ✓ vaultwarden |
|
||||
| Seafile · AFFiNE · SilverBullet · DokuWiki · Logseq | 2.9 · 2.5 · 2.4 · 2.3 · 2.2 | T26, T28 | ✗ |
|
||||
| SFTPGo · ownCloud · Subsonic · PeerTube · MkDocs · Resilio · Gogs | 2.2 · 2.0 · 2.0 · 1.8 · 1.8 · 1.3 · 0.4 | T25, T26, T28, T29 | ✗ |
|
||||
| Paperless-ngx | 2.1 fav | T33 | ✓ paperless-ngx |
|
||||
| *arr | 2.0 fav | T33 | ✓ radarr, ✓ sonarr (others ✗) |
|
||||
| FreshRSS · Pi-hole · Miniflux · AdGuard Home | 1.3 · 1.2 · 0.7 · 0.5 fav | T33 | ✗ |
|
||||
| Actual Budget · RomM · Mealie · n8n · Homepage · Glance · Karakeep | 1.0 · 1.0 · 0.7 · 0.7 · 0.7 · 0.6 · 0.5 fav | T33 | ✓ actualbudget, romm, mealie, n8n, homepage, glance, karakeep |
|
||||
| Proxmox · Komodo | 0.6 · 0.5 fav | T33 | n/a (host OS / container manager — Felhom's own role) |
|
||||
|
||||
**Catalog gap — above 5 %, not shipped, a server app:** Syncthing 25.9 (T26) · Forgejo 15.0 (T29) ·
|
||||
Authelia 9.6, Pocket ID 7.6, authentik 6.6 (T27, sign-on layers) · GitLab 7.4 (T29) · Joplin server 6.6 (T28) ·
|
||||
Pangolin 5.9 (T27; a tunnel, overlaps Felhom's own). Nothing was added.
|
||||
|
||||
## 2. Our features against what people do
|
||||
|
||||
| Area | Crowd, 2025 | Felhom | Source |
|
||||
|---|---|---|---|
|
||||
| Backup | Onsite 74.8, cloud 51.5, **offsite 25.2, none 13.3** | three automatic nightly tiers (on-drive, second drive, encrypted off-site to a Hetzner Storage Box) + whole-guest copies, local daily and off-site weekly — **a selling point: only 1 in 4 does off-site** | T8; `07-backup-architecture.md` :373-382 |
|
||||
| Backup cloud | Backblaze 15.0, Google Drive 12.6, **Hetzner 9.1 (rising)** | Hetzner Storage Box | T9; `07` :379 |
|
||||
| Remote access | VPN 71.1, reverse proxy 66.6, forwarded ports 17.7 | Cloudflare Tunnel, no open port, no customer VPN; TLS ends at Cloudflare (R-904 deferred) | T18; `01-topology-and-trust.md` :202-210 |
|
||||
| Reverse proxy | NPM 20.6, **Traefik 18.4**, Caddy 14.7, CF Tunnels 13.8 | Traefik behind the tunnel | T19; `01` :113, :143-145 |
|
||||
| Domain | 87.9 have their own | every customer has their own domain | T20; `01` :210 |
|
||||
| OS | Linux 81.3, **Proxmox 44.9**, HA OS 29.4 | Proxmox host + one Debian LXC + Docker | T7; `01` :56-59 |
|
||||
| Containers | 96.3 use them, Docker 89.1 | Docker inside the guest | T10, T13 |
|
||||
| App updates | **manual 45.1**, both 40.3, automatic 14.6 | automatic, watched — **a selling point** | T16; `website/index.html` :156 |
|
||||
| Container manager | 51.5 use one (Portainer 32.7) | the Felhom dashboard does this job | T14, T15 |
|
||||
| NAS | 67.8 run one | NAS as extra storage (FAQ), SMB shares on the box | T5; `website/gyik.html` :77-80 |
|
||||
|
||||
## 3. Reasons for self-hosting
|
||||
|
||||
| Rank 2025 | Reason | 2023 → 2025 | Our pitch names it? |
|
||||
|---|---|---|---|
|
||||
| 1 | Hobby | 95.5 → 94.6 | no — fine: a household that buys care is not tinkering |
|
||||
| 2 | Privacy | 79.7 → 86.3 | **yes** (`index.html` :233-234) |
|
||||
| 3 | Cost | 54.6 → 63.6 | **yes** (:229-230, no subscription) |
|
||||
| 4 | Convenience | – → 63.3 (2024: 55.0) | **yes** (:114-116, :213, :221) |
|
||||
| 5 | Education | 83.0 → 60.6 | no — fine, same reason as Hobby |
|
||||
| 6 | Flexibility | 69.7 → 58.2 | **yes** (:237-242, capacity and control) |
|
||||
| 7 | Business | 12.5 → 12.9 | home page no; `technologiak.html` rack setup only |
|
||||
|
||||
Source T3. The pitch already names the four reasons a buyer can share. Caution: Convenience was added as an
|
||||
option in 2024; some of the 2023 Education/Flexibility votes may have moved to it.
|
||||
|
||||
## 4. Hardware
|
||||
|
||||
**No document states Felhom's supported hardware** (finding). What exists: FAQ advice (N100 mini PC, 16–32 GB,
|
||||
`website/gyik.html` :77-80; an old PC "often yes", :85-88), three example setups (`technologiak.html` :375-447),
|
||||
BYO Proxmox (`documentation/runbooks/day0-install.md` :18-21), and the installer ISO, which is x86_64-only
|
||||
(`scripts/iso/iso-repack.sh` :31; inferred — no document says it).
|
||||
|
||||
| Crowd class (T4) | 2025 % | Felhom |
|
||||
|---|---|---|
|
||||
| Consumer hardware | 57.8 | in (mini PC is the recommendation) |
|
||||
| Low-powered device | 50.0 | **partly out** — x86 N100-class in; ARM boards out (Raspberry Pi OS is 24.0, T7) |
|
||||
| Custom build | 37.8 | in (tower example) |
|
||||
| Cloud VPS | 34.2 | **out** — not offered |
|
||||
| Desktop PC | 31.7 | in, case by case (FAQ :85-88) |
|
||||
| Enterprise hardware | 20.0 | in (rack example, "business") |
|
||||
| Server cluster | 13.7 | **out** — not documented; one host per household |
|
||||
| Laptop | 10.5 | not documented (a tester laptop exists, STATUS) |
|
||||
| Mobile device | 2.3 | **out** |
|
||||
|
||||
## 5. Trends 2023 → 2025 that matter to Felhom (≥ 5 points)
|
||||
|
||||
| Change | Points | Table |
|
||||
|---|---|---|
|
||||
| Cloud VPS as hosting hardware 2.7 → 34.2 (2024 → 2025; not asked 2023) — a 13× jump, may be a change in how the option was offered | +31.5 | T4 |
|
||||
| Home Assistant OS 0.4 → 29.4 (2024 → 2025); Home Assistant use 56.5 → 62.7 | +29.0 | T7, T30 |
|
||||
| Reasons move from learning to practical: Education −22.4, Flexibility −11.5, Cost +9.0, Privacy +6.6 | −22.4 … +9.0 | T3 |
|
||||
| Jellyfin 46.1 → 60.9, Plex 39.5 → 29.9; Immich as favourite 2.2 → 10.5 | +14.8 / −9.6 / +8.3 | T25, T33 |
|
||||
| Backup "None" 6.2 → 13.3 (2024 → 2025) | +7.1 | T8 |
|
||||
|
||||
## 6. Recommendations (ranked; none acted on)
|
||||
|
||||
1. Write one supported-hardware page: x86_64 only, minimum RAM and disk, no ARM, no cloud VPS, BYO Proxmox status — §4, T4.
|
||||
2. Put "off-site, encrypted, automatic backup" in the home page's *why* list, with the 1-in-4 figure — T8 Offsite 25.2.
|
||||
3. Consider Syncthing for the catalog (the largest gap, 25.9) — T26.
|
||||
4. Say in the FAQ what the catalog Home Assistant (container) lacks against Home Assistant OS (add-ons) — T7 HA OS 29.4, T30.
|
||||
5. Name automatic, watched app updates as a reason in the pitch — T16 Automatic 14.6.
|
||||
6. Use the privacy figure as input to R-904 (TLS ends at Cloudflare): privacy is reason 2 and rising — T3 Privacy 86.3, T18 VPN 71.1.
|
||||
7. Consider a Joplin server template for notes (Obsidian users need a separate sync) — T28 Joplin 6.6.
|
||||
8. Consider Forgejo beside Gitea (Forgejo 15.0 is the second git server after Gitea 20.8) — T29.
|
||||
9. Keep single sign-on (Authelia / Pocket ID / authentik) as a later idea; low household value today — T27.
|
||||
10. Do not chase Cloud VPS until the T4 jump is confirmed by the 2026 survey — T4 Cloud VPS 34.2.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Method — selfh.st survey read, 2026-10-12
|
||||
|
||||
**Fetched** 2026-10-12 by `fetch.py` to `~/research-cache/selfhst-survey/` (outside every repo; not committed).
|
||||
|
||||
| Year | Data file | Bytes | SHA-256 | Matches the spec |
|
||||
|---|---|---|---|---|
|
||||
| 2023 | `https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2023-results.json` | 10362 | `714350e5e4b8f3ccbc7ce8c1348122a9b7a4ae8167abaf2c051d3dcc9911f03f` | yes |
|
||||
| 2024 | `…/2024-results.json` | 14290 | `289c5161e5b6b5684ac6c751c16e73dd0dc42490c5c0d24517a6c1f891da4832` | yes |
|
||||
| 2025 | `…/2025-results.json` | 20428 | `e083094ad7f8cb6721f7059c6c73e8779273717bb3d4c1f14dbd744dc0d0229e` | yes |
|
||||
|
||||
**Respondents.** 2025: **4,081**, stated as completed responses on `https://selfh.st/survey/2025-results/`.
|
||||
2023 and 2024 pages give only rounded numbers ("just shy of 1,900", "~3,700" — `https://selfh.st/survey/2023-results/`,
|
||||
`…/2024-results/`). The exact figures used, **1,869** and **3,655**, are the column sum of the first, mandatory,
|
||||
single-choice question (personal / enterprise / both). The same method gives exactly 4,081 for 2025.
|
||||
|
||||
**Shapes.** 2023/2024: top-level `charts`, each with an `id`. 2025: `categories[].charts[]`, no `id`, five
|
||||
categories, 39 charts. Some 2025 charts carry a `commentary` field (not copied).
|
||||
|
||||
**Percentages.** Multi-select charts: % of the year's respondents. Single-choice charts (marked in `TABLE.md`):
|
||||
% of the column sum, because the question went to a subset or not everyone answered. Favourite software is one
|
||||
answer per person but the chart drops small counts, so it is % of respondents.
|
||||
|
||||
**Matching across years.** 2023/2024 by `id` (same titles). 2025 by question meaning, a hand-written map in
|
||||
`tables.py` (`MATCH_2025`); both titles are printed in every section. Answer labels mapped by hand where the
|
||||
wording changed (`ALIAS`, e.g. Yes → Deploy, Generic Linux → Linux, Docker - Individual → Container (Separate),
|
||||
2023 Paperless → Paperless-ngx, 2024 "Trilum" → Trilium); labels that differ only in case are merged and listed.
|
||||
|
||||
**Unmatched (no same question in 2025):** 2023 offsite-backup yes/no (T40), 2023 Kubernetes (T41 — 2024/2025
|
||||
ask about any orchestration), 2023 media-streaming yes/no (T42), 2024 smart-home protocols (T43), 2024
|
||||
ActivityPub yes/no and platforms (T44, T45). 2025-only: Management Platforms, Authentication, Git.
|
||||
|
||||
**Licence.** The results page shows no licence. The data repository `github.com/selfhst/cdn` is **MIT**
|
||||
(GitHub API `license.spdx_id = MIT`; `LICENSE` reads "MIT License, Copyright (c) 2025 selfh.st"). We commit no
|
||||
survey file and no survey prose; only our own counts derived from it, each cited to its URL.
|
||||
|
||||
**Limits.** Hobby users, mostly technical (Technology career 80.6 %, T36), self-selected through the selfh.st
|
||||
newsletter and community, not households buying a service. Multi-select counts: a column passes 100 %. Open-text
|
||||
charts (favourites) omit small answers. **Country:** the JSON files carry no country data, but the **2024 results
|
||||
page has a country table** (HTML only, not in `tables.py`): United States 964, Germany 430 … **Hungary 19**.
|
||||
2023 and 2025 pages have none.
|
||||
@@ -0,0 +1,718 @@
|
||||
# selfh.st self-hosting survey 2023–2025 — tables
|
||||
|
||||
Generated by `tables.py` from the files `fetch.py` downloads. Do not edit by hand.
|
||||
|
||||
| Year | Respondents | Results page | Data file |
|
||||
|---|---|---|---|
|
||||
| 2023 | 1,869 | https://selfh.st/survey/2023-results/ | https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2023-results.json |
|
||||
| 2024 | 3,655 | https://selfh.st/survey/2024-results/ | https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2024-results.json |
|
||||
| 2025 | 4,081 | https://selfh.st/survey/2025-results/ | https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/2025-results.json |
|
||||
|
||||
Values are **% of the year's respondents** (multi-select: a column can pass 100 %).
|
||||
Charts marked *single-choice* show **% of the column sum** instead.
|
||||
`2025 n` is the raw 2025 response count. `–` = not asked that year (or not matched).
|
||||
|
||||
## T1. Primary Purpose
|
||||
|
||||
Titles — 2023: Do you self-host for personal or enterprise purposes? · 2024: Do you self-host for personal or enterprise purposes? · 2025: Primary Purpose
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Personal | 83.4 | 83.1 | 83.7 | 3,417 |
|
||||
| Both | 16.2 | 16.6 | 16.1 | 656 |
|
||||
| Enterprise | 0.4 | 0.4 | 0.2 | 8 |
|
||||
|
||||
## T2. Unique Users
|
||||
|
||||
Titles — 2023: Approximately how many unique users are registered across your applications? · 2024: Approximately how many unique users are registered across your applications? · 2025: Unique Users
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
Note: 2023 buckets 11-15 and 16-20 are kept apart; 2024/2025 ask 11-20.
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| 1 user | 22.1 | 20.9 | 27.6 | 1,123 |
|
||||
| 2 users | 23.1 | 25.5 | 21.0 | 854 |
|
||||
| 3 users | 12.5 | 13.7 | 12.6 | 513 |
|
||||
| 4 users | 10.3 | 10.0 | 9.9 | 401 |
|
||||
| 5 users | 10.7 | 10.3 | 8.5 | 346 |
|
||||
| 6 users | 4.3 | 4.3 | 4.9 | 200 |
|
||||
| 7 users | 1.6 | 1.8 | 4.2 | 171 |
|
||||
| 8 users | 2.2 | 2.3 | 4.2 | 170 |
|
||||
| 9 users | 0.5 | 0.6 | 2.7 | 109 |
|
||||
| 10 users | 6.2 | 2.8 | 2.1 | 86 |
|
||||
| 11-20 users | – | 4.9 | 1.9 | 77 |
|
||||
| 21+ users | 2.1 | 2.8 | 0.5 | 19 |
|
||||
| 11-15 users | 2.6 | – | – | – |
|
||||
| 16-20 users | 2.0 | – | – | – |
|
||||
|
||||
## T3. Reasons for Self-Hosting
|
||||
|
||||
Titles — 2023: Why do you self-host? · 2024: Why do you self-host? · 2025: Reasons for Self-Hosting
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Hobby | 95.5 | 94.8 | 94.6 | 3,862 |
|
||||
| Privacy | 79.7 | 84.0 | 86.3 | 3,520 |
|
||||
| Cost | 54.6 | 56.7 | 63.6 | 2,596 |
|
||||
| Convenience | – | 55.0 | 63.3 | 2,584 |
|
||||
| Education | 83.0 | 63.9 | 60.6 | 2,474 |
|
||||
| Flexibility | 69.7 | 59.8 | 58.2 | 2,374 |
|
||||
| Business | 12.5 | 8.7 | 12.9 | 525 |
|
||||
| Other | 3.7 | 2.2 | 2.6 | 106 |
|
||||
|
||||
## T4. Hardware Options
|
||||
|
||||
Titles — 2023: Select the hardware option(s) used to deploy your self-hosted software. · 2024: Select the hardware option(s) used to deploy your self-hosted software. · 2025: Hardware Options
|
||||
|
||||
Note: 2023 'Custom-built', 'Enterprise', 'Mobile phone' matched to the 2024/2025 wording.
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Consumer hardware | – | 45.8 | 57.8 | 2,358 |
|
||||
| Low-powered device | 52.7 | 54.7 | 50.0 | 2,039 |
|
||||
| Custom build | 43.8 | 35.5 | 37.8 | 1,542 |
|
||||
| Cloud VPS | – | 2.7 | 34.2 | 1,394 |
|
||||
| Desktop PC | 44.6 | 35.9 | 31.7 | 1,295 |
|
||||
| Enterprise hardware | 26.1 | 22.5 | 20.0 | 817 |
|
||||
| Server cluster | 15.8 | 14.7 | 13.7 | 560 |
|
||||
| Laptop | 12.8 | 12.0 | 10.5 | 429 |
|
||||
| Mobile device | 1.8 | 2.5 | 2.3 | 94 |
|
||||
| Other | 7.7 | 1.3 | 1.9 | 78 |
|
||||
|
||||
## T5. Network-Attached Storage
|
||||
|
||||
Titles — 2023: Do you deploy a network-attached storage device? · 2024: Do you deploy a network-attached storage device? · 2025: Network-Attached Storage
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Deploy | 63.2 | 63.0 | 67.8 | 2,768 |
|
||||
| Don't Deploy | 36.8 | 37.0 | 32.2 | 1,313 |
|
||||
|
||||
## T6. Sensitive Data
|
||||
|
||||
Titles — 2023: Do you avoid self-hosting sensitive or important data? · 2024: Do you avoid self-hosting sensitive or important data? · 2025: Sensitive Data
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Don't Avoid | 77.2 | 82.8 | 82.7 | 3,373 |
|
||||
| Avoid | 22.8 | 17.2 | 17.3 | 708 |
|
||||
|
||||
## T7. Operating Systems
|
||||
|
||||
Titles — 2023: Which operating system(s) are installed on your server(s)? · 2024: Which operating system(s) are installed on your server(s)? · 2025: Operating Systems
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Linux | 79.5 | 86.7 | 81.3 | 3,317 |
|
||||
| Proxmox | 38.2 | 43.2 | 44.9 | 1,832 |
|
||||
| Home Assistant OS | – | 0.4 | 29.4 | 1,199 |
|
||||
| Raspberry Pi OS | 29.6 | 27.1 | 24.0 | 979 |
|
||||
| TrueNAS | 15.6 | 14.5 | 19.0 | 774 |
|
||||
| Unraid | 10.3 | 10.7 | 10.1 | 412 |
|
||||
| DiskStation Manager | – | 2.9 | 9.8 | 398 |
|
||||
| Windows Desktop | 11.6 | 9.3 | 9.1 | 373 |
|
||||
| Windows Server | 9.6 | 6.3 | 5.7 | 232 |
|
||||
| BSD | 4.9 | 6.2 | 5.6 | 229 |
|
||||
| Open Media Vault | – | 1.1 | 5.0 | 204 |
|
||||
| macOS | 3.2 | 3.5 | 4.9 | 199 |
|
||||
| ESXi | 5.4 | 3.1 | – | – |
|
||||
| XCP-ng | – | 0.5 | – | – |
|
||||
| Other | 9.4 | 2.0 | 8.3 | 340 |
|
||||
|
||||
## T8. Backup Strategies
|
||||
|
||||
Titles — 2024: Which backup strategies have you deployed for your self-hosted data? · 2025: Backup Strategies
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Onsite | – | 79.2 | 74.8 | 3,054 |
|
||||
| Cloud | – | 52.7 | 51.5 | 2,100 |
|
||||
| Offsite | – | 23.6 | 25.2 | 1,029 |
|
||||
| None | – | 6.2 | 13.3 | 542 |
|
||||
| Other | – | – | 1.2 | 51 |
|
||||
|
||||
## T9. Cloud Storage Providers
|
||||
|
||||
Titles — 2023: Which cloud storage service(s) are you subscribed to for backups? · 2024: Which cloud storage service(s) are you subscribed to for backups? · 2025: Cloud Storage Providers
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Backblaze | 15.4 | 16.2 | 15.0 | 613 |
|
||||
| Google Drive | 11.7 | 13.7 | 12.6 | 514 |
|
||||
| Hetzner | – | 5.1 | 9.1 | 371 |
|
||||
| Microsoft OneDrive | 6.5 | 8.5 | 6.8 | 277 |
|
||||
| iCloud | – | 4.8 | 5.3 | 215 |
|
||||
| Proton Drive | – | 1.3 | 4.0 | 165 |
|
||||
| Dropbox | 2.8 | 3.0 | 2.4 | 96 |
|
||||
| Cloudflare R2 | – | 0.6 | 1.7 | 70 |
|
||||
| BorgBase | – | 2.1 | 1.7 | 68 |
|
||||
| Mega | 1.4 | 1.9 | 1.5 | 63 |
|
||||
| pCloud | – | 0.8 | 1.2 | 51 |
|
||||
| iDrive | – | 0.9 | – | – |
|
||||
| Storj | – | 0.7 | – | – |
|
||||
| Veeam | – | 0.7 | – | – |
|
||||
| Box | – | 0.5 | – | – |
|
||||
| CrashPlan | – | 0.5 | – | – |
|
||||
| Wasabi | – | 0.5 | – | – |
|
||||
| Amazon S3 | 4.5 | – | – | – |
|
||||
| Other | 18.0 | 6.3 | 23.1 | 942 |
|
||||
|
||||
## T10. Container Population
|
||||
|
||||
Titles — 2023: Do you use containers to deploy self-hosted software? · 2024: Do you use containers to deploy self-hosted software? · 2025: Container Population
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Deploy | 94.3 | 95.8 | 96.3 | 3,932 |
|
||||
| Don't Deploy | 5.7 | 4.2 | 3.7 | 149 |
|
||||
|
||||
## T11. Containers vs Bare Metal
|
||||
|
||||
Titles — 2023: Do you prefer container installations to bare metal? · 2024: Do you prefer container installations to bare metal? · 2025: Containers vs Bare Metal
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
Note: 2025 added a third answer (Depends); 2024 and 2025 asked only container users.
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Containers | 79.5 | 88.1 | 69.1 | 2,720 |
|
||||
| Depends | – | – | 26.5 | 1,041 |
|
||||
| Bare Metal | 20.5 | 11.9 | 4.4 | 174 |
|
||||
|
||||
## T12. Container Orchestration
|
||||
|
||||
Titles — 2024: Do you use an orchestration platform to deploy containers? · 2025: Container Orchestration
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| No | – | 75.0 | 76.9 | 3,024 |
|
||||
| Yes | – | 25.0 | 23.1 | 910 |
|
||||
|
||||
## T13. Container Platforms
|
||||
|
||||
Titles — 2023: Which container platform(s) do you use for deployment? · 2024: Which container platform(s) do you use for deployment? · 2025: Container Platforms
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Docker | 89.8 | 89.5 | 89.1 | 3,637 |
|
||||
| LXC | 27.3 | 29.1 | 30.8 | 1,256 |
|
||||
| Podman | 9.8 | 10.1 | 10.9 | 446 |
|
||||
| Other | 4.8 | 2.6 | 3.5 | 142 |
|
||||
|
||||
## T14. Management Software
|
||||
|
||||
Titles — 2023: Do you deploy management software to administer your containers? · 2024: Do you deploy management software to administer your containers? · 2025: Management Software
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Use | 49.7 | 54.6 | 51.5 | 2,022 |
|
||||
| Don't Use | 50.3 | 45.4 | 48.5 | 1,908 |
|
||||
|
||||
## T15. Management Platforms
|
||||
|
||||
Titles — 2025: Management Platforms
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Portainer | – | – | 32.7 | 1,336 |
|
||||
| Dockge | – | – | 10.4 | 425 |
|
||||
| Komodo | – | – | 7.5 | 308 |
|
||||
| Coolify | – | – | 1.5 | 60 |
|
||||
| Other | – | – | 9.4 | 385 |
|
||||
|
||||
## T16. Update Methodology
|
||||
|
||||
Titles — 2023: How do you manage container updates? · 2024: How do you manage container updates? · 2025: Update Methodology
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
Note: 2025 was deployed as multi-select by mistake (column sum 4,113 > 3,930 asked).
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Manual | 58.1 | 54.1 | 45.1 | 1,855 |
|
||||
| Depends / Both | – | 29.2 | 40.3 | 1,658 |
|
||||
| Automatic | 41.9 | 16.7 | 14.6 | 600 |
|
||||
|
||||
## T17. Firewalls
|
||||
|
||||
Titles — 2023: Do you deploy a firewall aside from the router provided by your ISP? · 2024: Do you deploy a firewall aside from the router provided by your ISP? · 2025: Firewalls
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Non-ISP | 55.9 | 51.7 | 58.7 | 2,393 |
|
||||
| ISP | 44.1 | 48.3 | 41.3 | 1,686 |
|
||||
|
||||
## T18. Remote Access
|
||||
|
||||
Titles — 2023: How do you access your self-hosted software remotely? · 2024: How do you access your self-hosted software remotely? · 2025: Remote Access
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| VPN | 68.2 | 77.0 | 71.1 | 2,902 |
|
||||
| Reverse Proxy | 65.9 | 62.9 | 66.6 | 2,716 |
|
||||
| Forwarded Ports | – | 23.0 | 17.7 | 721 |
|
||||
| Only via LAN | 17.5 | 14.2 | 12.5 | 510 |
|
||||
| Other | – | 0.5 | 6.7 | 273 |
|
||||
|
||||
## T19. Reverse Proxies
|
||||
|
||||
Titles — 2023: Which web server(s) do you deploy for proxied services? · 2024: Which web server(s) do you deploy for proxied services? · 2025: Reverse Proxies
|
||||
|
||||
Merged spelling/case variants: 2023 'Nginx' = 'NGINX'
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| NGINX Proxy Manager | – | 31.1 | 20.6 | 842 |
|
||||
| Traefik | 21.3 | 24.1 | 18.4 | 751 |
|
||||
| Caddy | 10.9 | 18.1 | 14.7 | 598 |
|
||||
| Cloudflare Tunnels | 17.1 | 19.3 | 13.8 | 563 |
|
||||
| NGINX | 50.1 | 28.3 | 12.9 | 528 |
|
||||
| Pangolin | – | – | 7.1 | 290 |
|
||||
| HAProxy | 5.4 | 5.0 | 3.1 | 128 |
|
||||
| Apache | 6.6 | 8.5 | 2.4 | 97 |
|
||||
| SWAG | – | 4.2 | 2.3 | 95 |
|
||||
| Zoraxy | – | 0.7 | – | – |
|
||||
| Other | 4.0 | 1.6 | 3.2 | 131 |
|
||||
|
||||
## T20. Registered Domains
|
||||
|
||||
Titles — 2023: Do you utilize a custom domain for your self-hosted applications? · 2024: Do you utilize a custom domain for your self-hosted applications? · 2025: Registered Domains
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Custom Domain | 85.7 | 88.8 | 87.9 | 3,586 |
|
||||
| No Domain | 14.3 | 11.2 | 12.1 | 495 |
|
||||
|
||||
## T21. Domain Registrars
|
||||
|
||||
Titles — 2023: Which registrar(s) do you purchase or lease your custom domains from? · 2024: Which registrar(s) do you purchase or lease your custom domains from? · 2025: Domain Registrars
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Cloudflare | 19.1 | 30.3 | 34.9 | 1,423 |
|
||||
| Namecheap | 13.1 | 18.5 | 16.0 | 651 |
|
||||
| Porkbun | 6.8 | 12.9 | 12.8 | 523 |
|
||||
| OVH | 3.0 | 4.9 | 6.0 | 246 |
|
||||
| GoDaddy | 2.5 | 3.6 | 3.0 | 124 |
|
||||
| DuckDNS | 1.4 | 5.3 | 2.8 | 113 |
|
||||
| Squarespace | – | – | 2.4 | 97 |
|
||||
| Netcup | – | 1.8 | 2.1 | 85 |
|
||||
| IONOS | 1.1 | 2.0 | 2.1 | 84 |
|
||||
| Hetzner | 0.8 | 2.0 | 2.0 | 82 |
|
||||
| Gandi | 2.0 | 2.5 | 1.5 | 62 |
|
||||
| Strato | 1.2 | 1.4 | 1.4 | 57 |
|
||||
| Google Domains | 4.3 | 3.3 | – | – |
|
||||
| Hover | 0.9 | 1.2 | – | – |
|
||||
| INWX | 0.7 | 1.1 | – | – |
|
||||
| NameSilo | 0.8 | 1.0 | – | – |
|
||||
| DreamHost | – | 0.7 | – | – |
|
||||
| Other | 15.3 | 17.8 | 21.1 | 863 |
|
||||
|
||||
## T22. E-mail
|
||||
|
||||
Titles — 2023: Are you self-hosting any components of an e-mail server? · 2024: Are you self-hosting any components of an e-mail server? · 2025: E-mail
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Don't Self-Host | 85.7 | 83.8 | 86.2 | 3,516 |
|
||||
| Self-Host | 14.3 | 16.2 | 13.8 | 562 |
|
||||
|
||||
## T23. E-mail Components
|
||||
|
||||
Titles — 2024: Which e-mail server components are you self-hosting? · 2025: E-mail Components
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Sending | – | 14.6 | 11.8 | 482 |
|
||||
| Receiving | – | 13.4 | 11.5 | 469 |
|
||||
| Client | – | 9.7 | 8.1 | 331 |
|
||||
|
||||
## T24. Database Deployment
|
||||
|
||||
Titles — 2023: How do you deploy databases for self-hosted applications? · 2024: How do you deploy databases for self-hosted applications? · 2025: Database Deployment
|
||||
|
||||
Note: Single-choice in 2023/2024, multi-select in 2025 (sum 5,004): % of respondents.
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Container (Separate) | 62.1 | 70.8 | 84.0 | 3,430 |
|
||||
| Container (Shared) | 15.7 | 15.1 | 21.6 | 883 |
|
||||
| Bare Metal (Shared) | 6.7 | 7.6 | 8.6 | 349 |
|
||||
| Bare Metal (Separate) | 9.3 | 6.5 | 8.4 | 342 |
|
||||
|
||||
## T25. Media Streaming
|
||||
|
||||
Titles — 2023: Which of the platforms below do you deploy to stream media content? · 2024: Which of the platforms below do you deploy to stream media content? · 2025: Media Streaming
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Jellyfin | 46.1 | 53.9 | 60.9 | 2,487 |
|
||||
| Plex | 39.5 | 35.0 | 29.9 | 1,219 |
|
||||
| AudioBookshelf | – | 18.4 | 20.6 | 840 |
|
||||
| Navidrome | – | 13.6 | 16.1 | 656 |
|
||||
| Kodi | 6.5 | 7.6 | 5.8 | 237 |
|
||||
| Emby | 4.1 | 3.6 | 3.7 | 149 |
|
||||
| Subsonic | – | 2.7 | 2.0 | 81 |
|
||||
| PeerTube | – | 2.1 | 1.8 | 74 |
|
||||
| Lightweight Music Server | – | 0.8 | – | – |
|
||||
| Ampache | – | 0.6 | – | – |
|
||||
| Other | 6.7 | 6.0 | 6.6 | 268 |
|
||||
|
||||
## T26. File Storage and Syncing
|
||||
|
||||
Titles — 2024: Which of the platforms below do you deploy for remote file storage, access, or syncing? · 2025: File Storage and Syncing
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Nextcloud | – | 35.8 | 30.3 | 1,236 |
|
||||
| Syncthing | – | 29.5 | 25.9 | 1,058 |
|
||||
| Synology | – | 2.8 | 13.5 | 550 |
|
||||
| FileBrowser | – | 13.5 | 11.8 | 481 |
|
||||
| FileBrowser Quantum | – | – | 5.1 | 207 |
|
||||
| Seafile | – | 3.2 | 2.9 | 118 |
|
||||
| SFTPGo | – | 0.6 | 2.2 | 90 |
|
||||
| ownCloud | – | 2.3 | 2.0 | 83 |
|
||||
| Resilio | – | 0.5 | 1.3 | 53 |
|
||||
| FileStash | – | 0.8 | – | – |
|
||||
| FileRun | – | 0.5 | – | – |
|
||||
| Other | – | 5.3 | 12.0 | 488 |
|
||||
|
||||
## T27. Authentication
|
||||
|
||||
Titles — 2025: Authentication
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Authelia | – | – | 9.6 | 390 |
|
||||
| Pocket ID | – | – | 7.6 | 310 |
|
||||
| authentik | – | – | 6.6 | 268 |
|
||||
| Pangolin | – | – | 5.9 | 241 |
|
||||
| Keycloak | – | – | 3.9 | 158 |
|
||||
| Tinyauth | – | – | 2.7 | 111 |
|
||||
| Zitadel | – | – | 1.1 | 46 |
|
||||
| Other | – | – | 18.1 | 737 |
|
||||
|
||||
## T28. Note-Taking / Documentation
|
||||
|
||||
Titles — 2024: Which of the platforms below do you deploy for documentation or note-taking? · 2025: Note-Taking / Documentation
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Obsidian | – | 18.8 | 25.5 | 1,041 |
|
||||
| Nextcloud Notes | – | 10.4 | 7.9 | 321 |
|
||||
| BookStack | – | 11.1 | 7.4 | 303 |
|
||||
| Joplin | – | 8.2 | 6.6 | 268 |
|
||||
| Trilium | – | 5.1 | 4.5 | 182 |
|
||||
| Memos | – | 4.9 | 4.1 | 167 |
|
||||
| Wiki.js | – | 5.8 | 3.8 | 156 |
|
||||
| Docmost | – | 2.1 | 3.5 | 141 |
|
||||
| Outline | – | 3.1 | 3.2 | 132 |
|
||||
| AFFiNE | – | 1.5 | 2.5 | 101 |
|
||||
| SilverBullet | – | 1.7 | 2.4 | 99 |
|
||||
| DokuWiki | – | 3.5 | 2.3 | 93 |
|
||||
| Logseq | – | 3.1 | 2.2 | 89 |
|
||||
| MkDocs | – | 0.5 | 1.8 | 75 |
|
||||
| HedgeDoc | – | 1.9 | – | – |
|
||||
| StandardNotes | – | 1.0 | – | – |
|
||||
| AppFlowy | – | 0.9 | – | – |
|
||||
| AnyType | – | 0.9 | – | – |
|
||||
| Docusaurus | – | 0.9 | – | – |
|
||||
| TiddlyWiki | – | 0.8 | – | – |
|
||||
| MediaWiki | – | 0.6 | – | – |
|
||||
| SiYuan | – | 0.5 | – | – |
|
||||
| FlatNotes | – | 0.4 | – | – |
|
||||
| XWiki | – | 0.3 | – | – |
|
||||
| Other | – | 3.4 | 13.9 | 568 |
|
||||
|
||||
## T29. Git
|
||||
|
||||
Titles — 2025: Git
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Gitea | – | – | 20.8 | 849 |
|
||||
| Forgejo | – | – | 15.0 | 613 |
|
||||
| GitLab | – | – | 7.4 | 302 |
|
||||
| Gogs | – | – | 0.4 | 18 |
|
||||
| Other | – | – | 2.5 | 101 |
|
||||
|
||||
## T30. Home Automation
|
||||
|
||||
Titles — 2023: Do you deploy any self-hosted software for smart home or home automation purposes? · 2024: Do you deploy any self-hosted software for smart home or home automation purposes? · 2025: Home Automation
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Deploy | 56.5 | 59.8 | 62.7 | 2,548 |
|
||||
| Don't Deploy | 43.5 | 40.2 | 37.3 | 1,516 |
|
||||
|
||||
## T31. Game Servers
|
||||
|
||||
Titles — 2023: Are you self-hosting any game servers? · 2024: Are you self-hosting any game servers? · 2025: Game Servers
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Don't Host | 62.8 | 67.6 | 67.8 | 2,765 |
|
||||
| Host | 37.2 | 32.4 | 32.2 | 1,316 |
|
||||
|
||||
## T32. Games
|
||||
|
||||
Titles — 2024: List the games you're deploying a game server for. · 2025: Games
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Minecraft | – | 24.9 | 25.5 | 1,041 |
|
||||
| Valheim | – | 3.3 | 5.2 | 214 |
|
||||
| Satisfactory | – | 4.3 | 5.2 | 212 |
|
||||
| Factorio | – | 3.2 | 5.1 | 208 |
|
||||
| Terraria | – | 2.1 | 4.0 | 163 |
|
||||
| Palworld | – | 2.1 | 3.2 | 131 |
|
||||
| Foundry VTT | – | 0.4 | 2.6 | 107 |
|
||||
| Project Zomboid | – | 1.1 | 2.1 | 84 |
|
||||
| 7 Days to Die | – | 1.1 | 1.7 | 69 |
|
||||
| ARK | – | 0.9 | 1.7 | 68 |
|
||||
| V Rising | – | 0.8 | 1.6 | 66 |
|
||||
| Counter-Strike | – | 0.9 | 1.5 | 60 |
|
||||
| Space Engineers | – | 0.5 | 1.3 | 53 |
|
||||
| Arma | – | 0.9 | – | – |
|
||||
| Minetest | – | 0.6 | – | – |
|
||||
| Enshrouded | – | 0.5 | – | – |
|
||||
| Assetto Corsa | – | 0.5 | – | – |
|
||||
| Vintage Story | – | 0.3 | – | – |
|
||||
| Other | – | 7.9 | 7.7 | 315 |
|
||||
|
||||
## T33. Favorite Software
|
||||
|
||||
Titles — 2023: What is your favorite self-hosted application? · 2024: What is your favorite self-hosted application? · 2025: Favorite Software
|
||||
|
||||
Note: One answer per person, open text; each year omits small counts, so % is of all respondents. 2023 'Paperless' matched to 'Paperless-ngx'; 2024 'Trilum' read as Trilium.
|
||||
|
||||
Merged spelling/case variants: 2024 'AudioBookshelf' = 'Audiobookshelf'; 2024 'Pi-Hole' = 'Pi-hole'
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Home Assistant | 12.4 | 12.6 | 14.4 | 588 |
|
||||
| Jellyfin | 10.4 | 13.5 | 12.8 | 522 |
|
||||
| Immich | 2.2 | 7.5 | 10.5 | 429 |
|
||||
| Plex | 8.9 | 8.8 | 5.1 | 208 |
|
||||
| Vaultwarden | 7.9 | 5.8 | 3.7 | 152 |
|
||||
| Nextcloud | 7.7 | 4.6 | 2.9 | 118 |
|
||||
| Audiobookshelf | 1.1 | 2.4 | 2.3 | 94 |
|
||||
| Paperless-ngx | 2.0 | 2.5 | 2.1 | 85 |
|
||||
| *arr | 2.4 | 3.0 | 2.0 | 81 |
|
||||
| FreshRSS | 1.2 | 0.9 | 1.3 | 52 |
|
||||
| Pi-hole | 2.4 | 1.2 | 1.2 | 50 |
|
||||
| Actual Budget | – | 0.8 | 1.0 | 42 |
|
||||
| Navidrome | 1.3 | 1.0 | 1.0 | 41 |
|
||||
| RomM | – | – | 1.0 | 41 |
|
||||
| Syncthing | 0.9 | 1.0 | 0.8 | 32 |
|
||||
| Homepage | – | 0.9 | 0.7 | 29 |
|
||||
| Mealie | – | 0.6 | 0.7 | 29 |
|
||||
| n8n | – | 0.4 | 0.7 | 29 |
|
||||
| Gitea | 2.0 | 1.5 | 0.7 | 28 |
|
||||
| Miniflux | 0.9 | 1.0 | 0.7 | 27 |
|
||||
| Emby | – | 0.7 | 0.6 | 26 |
|
||||
| Proxmox | 0.9 | 1.0 | 0.6 | 25 |
|
||||
| Pangolin | – | – | 0.6 | 25 |
|
||||
| Glance | – | – | 0.6 | 24 |
|
||||
| Karakeep | – | – | 0.5 | 22 |
|
||||
| Komodo | – | – | 0.5 | 22 |
|
||||
| AdGuard Home | 0.7 | 0.6 | 0.5 | 20 |
|
||||
| Forgejo | – | 0.9 | – | – |
|
||||
| Authentik | – | 0.5 | – | – |
|
||||
| GitLab | – | 0.5 | – | – |
|
||||
| Matrix | – | 0.5 | – | – |
|
||||
| Firefly III | – | 0.5 | – | – |
|
||||
| Uptime Kuma | – | 0.4 | – | – |
|
||||
| Frigate NVR | – | 0.4 | – | – |
|
||||
| Tiny Tiny RSS | – | 0.4 | – | – |
|
||||
| Trilium | – | 0.4 | – | – |
|
||||
| BookStack | 0.8 | – | – | – |
|
||||
| Portainer | 0.7 | – | – | – |
|
||||
| Grafana | 0.6 | – | – | – |
|
||||
| Authelia | 0.6 | – | – | – |
|
||||
|
||||
## T34. Age Range
|
||||
|
||||
Titles — 2023: What is your age range? · 2024: What is your age range? · 2025: Age Range
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
Note: 2023/2024 bucket 10-19 matched to 2025 12-19.
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| 30-39 years-old | 37.5 | 38.3 | 37.8 | 1,532 |
|
||||
| 20-29 years-old | 34.8 | 31.5 | 29.9 | 1,210 |
|
||||
| 40-49 years-old | 16.7 | 19.5 | 20.8 | 844 |
|
||||
| 50-59 years-old | 4.6 | 6.4 | 7.1 | 287 |
|
||||
| 12-19 years-old | 5.8 | 3.4 | 3.1 | 125 |
|
||||
| 60-69 years-old | 0.6 | 0.9 | 1.3 | 53 |
|
||||
|
||||
## T35. Gender
|
||||
|
||||
Titles — 2023: What is your gender? · 2024: What is your gender? · 2025: Gender
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Male | 96.2 | 95.6 | 95.6 | 3,839 |
|
||||
| Female | 2.5 | 2.4 | 1.9 | 77 |
|
||||
| Non-Binary | – | 1.8 | 1.9 | 77 |
|
||||
| Other | 1.4 | 0.2 | 0.5 | 21 |
|
||||
|
||||
## T36. Career Field
|
||||
|
||||
Titles — 2023: If employed, do you work in a technical field? · 2024: If employed, do you work in a technical field? · 2025: Career Field
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Technology | 79.2 | 81.8 | 80.6 | 3,227 |
|
||||
| Non-Technology | 20.8 | 18.2 | 19.4 | 778 |
|
||||
|
||||
## T37. Mobile OS
|
||||
|
||||
Titles — 2023: What is your primary mobile device's operating system? · 2024: What is your primary mobile device's operating system? · 2025: Mobile OS
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Android | 70.5 | 67.0 | 66.7 | 2,715 |
|
||||
| iOS | 28.1 | 31.6 | 31.8 | 1,295 |
|
||||
| GrapheneOS | – | 0.7 | – | – |
|
||||
| Other | 1.4 | 0.7 | 1.5 | 62 |
|
||||
|
||||
## T38. Donations
|
||||
|
||||
Titles — 2023: Have you donated to a self-hosted project in the last year? · 2024: Have you donated to a self-hosted project in the last year? · 2025: Donations
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Haven't Donated | 61.8 | 60.0 | 59.1 | 2,393 |
|
||||
| Donated | 31.9 | 40.0 | 40.9 | 1,657 |
|
||||
| Prefer not to answer | 6.3 | – | – | – |
|
||||
|
||||
## T39. Education
|
||||
|
||||
Titles — 2023: What is the highest level of education you have completed? · 2024: What is the highest level of education you have completed? · 2025: Education
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Bachelor's Degree | 40.3 | 39.0 | 40.7 | 1,607 |
|
||||
| Master's Degree | 19.7 | 21.6 | 22.6 | 893 |
|
||||
| Some college | 17.3 | 15.9 | 16.6 | 655 |
|
||||
| HS Diploma / GED | 12.3 | 11.7 | 7.4 | 293 |
|
||||
| Associate's Degree | 1.5 | 5.4 | 5.6 | 221 |
|
||||
| Doctorate | 2.1 | 3.2 | 3.1 | 122 |
|
||||
| No formal schooling | 1.8 | 1.8 | 2.8 | 109 |
|
||||
| VoTech Program | 5.0 | 1.3 | 1.2 | 46 |
|
||||
|
||||
# Unmatched charts (asked in 2023 and/or 2024, no same question in 2025)
|
||||
|
||||
## T40. Are you utilizing hosted cloud storage for offsite backups?
|
||||
|
||||
Titles — 2023: Are you utilizing hosted cloud storage for offsite backups?
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| No | 53.9 | – | – | – |
|
||||
| Yes | 46.1 | – | – | – |
|
||||
|
||||
## T41. Do you deploy Kubernetes to manage your self-hosted software?
|
||||
|
||||
Titles — 2023: Do you deploy Kubernetes to manage your self-hosted software?
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| No | 89.6 | – | – | – |
|
||||
| Yes | 10.4 | – | – | – |
|
||||
|
||||
## T42. Do you deploy any self-hosted software for purposes related to media streaming?
|
||||
|
||||
Titles — 2023: Do you deploy any self-hosted software for purposes related to media streaming?
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Yes | 81.6 | – | – | – |
|
||||
| No | 18.4 | – | – | – |
|
||||
|
||||
## T43. Which smart home communication standards and protocols are you utilizing in your self-hosted ecosystem?
|
||||
|
||||
Titles — 2024: Which smart home communication standards and protocols are you utilizing in your self-hosted ecosystem?
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| WiFi | – | 52.1 | – | – |
|
||||
| Zigbee | – | 38.5 | – | – |
|
||||
| MQTT | – | 32.7 | – | – |
|
||||
| Bluetooth | – | 22.7 | – | – |
|
||||
| ESP | – | 19.5 | – | – |
|
||||
| Z-Wave | – | 12.0 | – | – |
|
||||
| Matter | – | 9.9 | – | – |
|
||||
| NFC | – | 7.4 | – | – |
|
||||
| Thread | – | 6.2 | – | – |
|
||||
| Other | – | 0.6 | – | – |
|
||||
|
||||
## T44. Do you deploy any self-hosted ActivityPub platforms?
|
||||
|
||||
Titles — 2024: Do you deploy any self-hosted ActivityPub platforms?
|
||||
|
||||
*Single-choice: % of the column sum.*
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| No | – | 90.2 | – | – |
|
||||
| Yes | – | 9.8 | – | – |
|
||||
|
||||
## T45. Which ActivityPub platform(s) do you deploy?
|
||||
|
||||
Titles — 2024: Which ActivityPub platform(s) do you deploy?
|
||||
|
||||
| label | 2023 | 2024 | 2025 | 2025 n |
|
||||
|---|---|---|---|---|
|
||||
| Mastodon | – | 3.2 | – | – |
|
||||
| PeerTube | – | 1.1 | – | – |
|
||||
| Lemmy | – | 1.0 | – | – |
|
||||
| GoToSocial | – | 0.7 | – | – |
|
||||
| Pixelfed | – | 0.5 | – | – |
|
||||
| Akkoma | – | 0.2 | – | – |
|
||||
| Other | – | 1.3 | – | – |
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Download the selfh.st annual survey results (2023-2025) to a cache outside every repo.
|
||||
|
||||
Prints bytes and SHA-256 for each file. The files are not committed (spec §9.2).
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
BASE = "https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/{}-results.json"
|
||||
CACHE = os.path.expanduser("~/research-cache/selfhst-survey")
|
||||
# Prefixes/suffixes of the hashes the reviewer recorded on 2026-10-12.
|
||||
EXPECTED = {
|
||||
2023: ("714350e5e4b8f3cc", "f03f", 10362),
|
||||
2024: ("289c5161e5b6b568", "4832", 14290),
|
||||
2025: ("e083094ad7f8cb67", "229e", 20428),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(CACHE, exist_ok=True)
|
||||
rc = 0
|
||||
for year, (pre, suf, size) in EXPECTED.items():
|
||||
url = BASE.format(year)
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=30) as r:
|
||||
data = r.read()
|
||||
except Exception as e: # continue with the years that can be read (spec §13)
|
||||
print(f"{year} FETCH FAILED {url}: {e}")
|
||||
rc = 1
|
||||
continue
|
||||
with open(os.path.join(CACHE, f"{year}-results.json"), "wb") as f:
|
||||
f.write(data)
|
||||
h = hashlib.sha256(data).hexdigest()
|
||||
ok = h.startswith(pre) and h.endswith(suf) and len(data) == size
|
||||
print(f"{year} bytes={len(data)} sha256={h} {'MATCH' if ok else 'DIFFERS from spec'}")
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build TABLE.md from the cached selfh.st survey results (2023-2025).
|
||||
|
||||
Reads ~/research-cache/selfhst-survey/<year>-results.json (written by fetch.py).
|
||||
Writes TABLE.md next to this script. Stdlib only.
|
||||
|
||||
Percent rule (spec §1, §8):
|
||||
- multi-select charts: % of the year's respondent count;
|
||||
- single-choice charts (marked "single"): % of the column sum, because the
|
||||
question was asked to a subset or answered by fewer than all respondents.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
CACHE = os.path.expanduser("~/research-cache/selfhst-survey")
|
||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "TABLE.md")
|
||||
YEARS = (2023, 2024, 2025)
|
||||
PAGE = "https://selfh.st/survey/{}-results/"
|
||||
DATA = "https://raw.githubusercontent.com/selfhst/cdn/main/assets/surveys/annual/{}-results.json"
|
||||
|
||||
# Respondent count per year. 2025: the results page states 4,081 completed responses.
|
||||
# 2023/2024: the pages give only rounded numbers ("just shy of 1,900", "~3,700"); the exact
|
||||
# figure is the column sum of the first, mandatory, single-choice question (Primary Purpose),
|
||||
# which is 4,081 in 2025 too - the same method reproduces the stated 2025 number.
|
||||
RESPONDENTS = {2023: 1869, 2024: 3655, 2025: 4081}
|
||||
|
||||
# 2025 title -> the 2023/2024 chart id it asks the same question as. None = 2025 only.
|
||||
MATCH_2025 = {
|
||||
"Primary Purpose": "personal-enterprise",
|
||||
"Unique Users": "unique-users",
|
||||
"Reasons for Self-Hosting": "why-self-host",
|
||||
"Hardware Options": "hardware",
|
||||
"Network-Attached Storage": "deploy-nas",
|
||||
"Sensitive Data": "sensitive-data",
|
||||
"Operating Systems": "operating-system",
|
||||
"Backup Strategies": "backup-strategy",
|
||||
"Cloud Storage Providers": "backup-cloud",
|
||||
"Container Population": "deploy-containers",
|
||||
"Containers vs Bare Metal": "prefer-containers",
|
||||
# 2023 asked about Kubernetes only, 2024/2025 about any orchestration: 2023 kept separate.
|
||||
"Container Orchestration": "orchestration-platform",
|
||||
"Container Platforms": "container-platform",
|
||||
"Management Software": "container-manager",
|
||||
"Management Platforms": None,
|
||||
"Update Methodology": "container-updates",
|
||||
"Firewalls": "deploy-firewall",
|
||||
"Remote Access": "remote-access",
|
||||
"Reverse Proxies": "web-server",
|
||||
"Registered Domains": "custom-domain",
|
||||
"Domain Registrars": "domain-registrar",
|
||||
"E-mail": "email-server",
|
||||
"E-mail Components": "email-components",
|
||||
"Database Deployment": "deploy-database",
|
||||
"Media Streaming": "media-server",
|
||||
"File Storage and Syncing": "file-sync",
|
||||
"Authentication": None,
|
||||
"Note-Taking / Documentation": "documentation-notes",
|
||||
"Git": None,
|
||||
"Home Automation": "smart-home",
|
||||
"Game Servers": "deploy-game",
|
||||
"Games": "game-server",
|
||||
"Favorite Software": "favorite-app",
|
||||
"Age Range": "age-range",
|
||||
"Gender": "gender",
|
||||
"Career Field": "employment-field",
|
||||
"Mobile OS": "mobile-os",
|
||||
"Donations": "donate-project",
|
||||
"Education": "education-level",
|
||||
}
|
||||
# 2023's Kubernetes question differs from the later orchestration question.
|
||||
NOT_MATCHED_2023 = {"orchestration-platform"}
|
||||
|
||||
# Single-choice charts -> % of column sum. Keyed by 2023/2024 id (or 2025 title when 2025-only).
|
||||
SINGLE = {
|
||||
"personal-enterprise", "unique-users", "deploy-nas", "sensitive-data", "offsite-backups",
|
||||
"deploy-containers", "prefer-containers", "orchestration-platform", "container-manager",
|
||||
"container-updates", "deploy-firewall", "custom-domain", "email-server", "stream-media",
|
||||
"smart-home", "deploy-game", "host-activitypub", "age-range", "gender", "employment-field",
|
||||
"mobile-os", "donate-project", "education-level",
|
||||
}
|
||||
|
||||
# Old label -> 2025 label, per chart, where the wording changed but the answer is the same.
|
||||
# Labels that differ only in case are merged automatically (and listed in the output).
|
||||
ALIAS = {
|
||||
"deploy-nas": {"Yes": "Deploy", "No": "Don't Deploy"},
|
||||
"sensitive-data": {"Yes": "Avoid", "No": "Don't Avoid"},
|
||||
"deploy-containers": {"Yes": "Deploy", "No": "Don't Deploy"},
|
||||
"prefer-containers": {"Yes": "Containers", "No": "Bare Metal"},
|
||||
"container-manager": {"Yes": "Use", "No": "Don't Use"},
|
||||
"container-updates": {"Both": "Depends / Both"},
|
||||
"deploy-firewall": {"Yes": "Non-ISP", "No": "ISP"},
|
||||
"custom-domain": {"Yes": "Custom Domain", "No": "No Domain"},
|
||||
"email-server": {"Yes": "Self-Host", "No": "Don't Self-Host"},
|
||||
"smart-home": {"Yes": "Deploy", "No": "Don't Deploy"},
|
||||
"deploy-game": {"Yes": "Host", "No": "Don't Host"},
|
||||
"employment-field": {"Yes": "Technology", "No": "Non-Technology"},
|
||||
"donate-project": {"Yes": "Donated", "No": "Haven't Donated"},
|
||||
"gender": {"Man": "Male", "Woman": "Female"},
|
||||
"education-level": {"Some college, no degree": "Some college"},
|
||||
"age-range": {"10-19 years-old": "12-19 years-old"},
|
||||
"operating-system": {"Generic Linux": "Linux"},
|
||||
"hardware": {"Custom-built": "Custom build", "Enterprise": "Enterprise hardware",
|
||||
"Mobile phone": "Mobile device"},
|
||||
"deploy-database": {"Docker - Individual": "Container (Separate)",
|
||||
"Docker - Shared": "Container (Shared)",
|
||||
"Bare Metal - Individual": "Bare Metal (Separate)",
|
||||
"Bare Metal - Shared": "Bare Metal (Shared)"},
|
||||
"favorite-app": {"Paperless": "Paperless-ngx", "*arr Stack": "*arr", "Trilum": "Trilium"},
|
||||
}
|
||||
# Notes printed under a section heading.
|
||||
NOTES = {
|
||||
"unique-users": "2023 buckets 11-15 and 16-20 are kept apart; 2024/2025 ask 11-20.",
|
||||
"prefer-containers": "2025 added a third answer (Depends); 2024 and 2025 asked only container users.",
|
||||
"container-updates": "2025 was deployed as multi-select by mistake (column sum 4,113 > 3,930 asked).",
|
||||
"age-range": "2023/2024 bucket 10-19 matched to 2025 12-19.",
|
||||
"favorite-app": ("One answer per person, open text; each year omits small counts, so % is of all "
|
||||
"respondents. 2023 'Paperless' matched to 'Paperless-ngx'; 2024 'Trilum' read as Trilium."),
|
||||
"deploy-database": "Single-choice in 2023/2024, multi-select in 2025 (sum 5,004): % of respondents.",
|
||||
"hardware": "2023 'Custom-built', 'Enterprise', 'Mobile phone' matched to the 2024/2025 wording.",
|
||||
}
|
||||
|
||||
|
||||
def charts_of(year):
|
||||
path = os.path.join(CACHE, f"{year}-results.json")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
with open(path, encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
if "charts" in d:
|
||||
return d["charts"]
|
||||
return [c for cat in d["categories"] for c in cat["charts"]]
|
||||
|
||||
|
||||
def series(chart, alias):
|
||||
out, merged = {}, []
|
||||
canon = {}
|
||||
for lab, val in zip(chart["labels"], chart["datasets"][0]["data"]):
|
||||
lab = alias.get(lab, lab)
|
||||
key = lab.lower()
|
||||
if key in canon and canon[key] != lab:
|
||||
merged.append(f"{lab} = {canon[key]}")
|
||||
canon.setdefault(key, lab)
|
||||
out[key] = out.get(key, 0) + val
|
||||
return out, canon, merged
|
||||
|
||||
|
||||
def pct(v, denom):
|
||||
return f"{100.0 * v / denom:.1f}" if denom else "–"
|
||||
|
||||
|
||||
def main():
|
||||
per_year = {y: charts_of(y) for y in YEARS}
|
||||
missing = [y for y in YEARS if per_year[y] is None]
|
||||
by_id = {y: {c["id"]: c for c in (per_year[y] or [])} for y in (2023, 2024)}
|
||||
|
||||
# Build the section list: 2025 order first, then the 2023/2024 charts nobody matched.
|
||||
sections = []
|
||||
used = {2023: set(), 2024: set()}
|
||||
for c25 in per_year[2025] or []:
|
||||
cid = MATCH_2025.get(c25["title"])
|
||||
c23 = by_id[2023].get(cid) if cid and cid not in NOT_MATCHED_2023 else None
|
||||
c24 = by_id[2024].get(cid) if cid else None
|
||||
for y, c in ((2023, c23), (2024, c24)):
|
||||
if c:
|
||||
used[y].add(c["id"])
|
||||
sections.append({"key": cid or c25["title"], "charts": {2023: c23, 2024: c24, 2025: c25}})
|
||||
unmatched = []
|
||||
for cid in list(by_id[2023]) + [i for i in by_id[2024] if i not in by_id[2023]]:
|
||||
c23 = by_id[2023].get(cid) if cid not in used[2023] else None
|
||||
c24 = by_id[2024].get(cid) if cid not in used[2024] else None
|
||||
if c23 or c24:
|
||||
unmatched.append({"key": cid, "charts": {2023: c23, 2024: c24, 2025: None}})
|
||||
|
||||
L = ["# selfh.st self-hosting survey 2023–2025 — tables", "",
|
||||
"Generated by `tables.py` from the files `fetch.py` downloads. Do not edit by hand.", "",
|
||||
"| Year | Respondents | Results page | Data file |", "|---|---|---|---|"]
|
||||
for y in YEARS:
|
||||
L.append(f"| {y} | {RESPONDENTS[y]:,} | {PAGE.format(y)} | {DATA.format(y)} |")
|
||||
L += ["", "Values are **% of the year's respondents** (multi-select: a column can pass 100 %).",
|
||||
"Charts marked *single-choice* show **% of the column sum** instead.",
|
||||
"`2025 n` is the raw 2025 response count. `–` = not asked that year (or not matched).", ""]
|
||||
if missing:
|
||||
L.append(f"**Missing years (download failed): {missing}.**\n")
|
||||
|
||||
def emit(num, sec, section_list_name):
|
||||
ch = sec["charts"]
|
||||
key = sec["key"]
|
||||
single = key in SINGLE
|
||||
alias = ALIAS.get(key, {})
|
||||
titles = [f"{y}: {ch[y]['title']}" for y in YEARS if ch[y]]
|
||||
rows, canon_all, merged_all, denom = {}, {}, [], {}
|
||||
for y in reversed(YEARS): # 2025 first, so its spelling is the shown label
|
||||
if not ch[y]:
|
||||
continue
|
||||
s, canon, merged = series(ch[y], alias)
|
||||
merged_all += merged
|
||||
for k, v in canon.items():
|
||||
if k in canon_all and canon_all[k] != v:
|
||||
merged_all.append(f"{y} '{v}' = '{canon_all[k]}'")
|
||||
canon_all.setdefault(k, v)
|
||||
denom[y] = sum(s.values()) if single else RESPONDENTS[y]
|
||||
for k, v in s.items():
|
||||
rows.setdefault(k, {})[y] = v
|
||||
L.append(f"## T{num}. {ch[2025]['title'] if ch[2025] else ch[2024]['title'] if ch[2024] else ch[2023]['title']}")
|
||||
L.append("")
|
||||
L.append("Titles — " + " · ".join(titles))
|
||||
L.append("")
|
||||
if single:
|
||||
L.append("*Single-choice: % of the column sum.*")
|
||||
L.append("")
|
||||
if key in NOTES:
|
||||
L.append(f"Note: {NOTES[key]}")
|
||||
L.append("")
|
||||
if merged_all:
|
||||
L.append("Merged spelling/case variants: " + "; ".join(sorted(set(merged_all))))
|
||||
L.append("")
|
||||
L.append("| label | 2023 | 2024 | 2025 | 2025 n |")
|
||||
L.append("|---|---|---|---|---|")
|
||||
|
||||
def sortkey(k):
|
||||
r = rows[k]
|
||||
return (k == "other", -r.get(2025, -1), -r.get(2024, -1), -r.get(2023, -1))
|
||||
for k in sorted(rows, key=sortkey):
|
||||
r = rows[k]
|
||||
cells = [pct(r[y], denom[y]) if y in r else "–" for y in YEARS]
|
||||
n = f"{r[2025]:,}" if 2025 in r else "–"
|
||||
L.append(f"| {canon_all[k]} | {' | '.join(cells)} | {n} |")
|
||||
L.append("")
|
||||
|
||||
n = 0
|
||||
for sec in sections:
|
||||
n += 1
|
||||
emit(n, sec, "matched")
|
||||
L += ["# Unmatched charts (asked in 2023 and/or 2024, no same question in 2025)", ""]
|
||||
for sec in unmatched:
|
||||
n += 1
|
||||
emit(n, sec, "unmatched")
|
||||
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(L).rstrip() + "\n")
|
||||
print(f"wrote {OUT}: {n} sections ({len(sections)} from 2025, {len(unmatched)} unmatched)")
|
||||
return 1 if missing else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user