{"id":17356826,"url":"https://github.com/joonas-fi/joonas-sys","last_synced_at":"2025-10-25T16:31:57.470Z","repository":{"id":64535994,"uuid":"335602102","full_name":"joonas-fi/joonas-sys","owner":"joonas-fi","description":"My personal system installation (Ubuntu + programs \u0026 conf I use) as code.","archived":false,"fork":false,"pushed_at":"2025-01-20T10:36:30.000Z","size":11334,"stargazers_count":8,"open_issues_count":28,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-28T12:21:19.248Z","etag":null,"topics":["configuration-as-code","immutable","operating-system"],"latest_commit_sha":null,"homepage":"https://joonas.fi/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joonas-fi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-03T11:26:56.000Z","updated_at":"2025-01-20T10:36:32.000Z","dependencies_parsed_at":"2024-06-20T01:36:15.890Z","dependency_job_id":"84cb0db4-44ed-4d98-952c-668817328968","html_url":"https://github.com/joonas-fi/joonas-sys","commit_stats":{"total_commits":461,"total_committers":2,"mean_commits":230.5,"dds":0.3882863340563991,"last_synced_commit":"bb3c4ee05bc5b4f85c1e17c21acfcc9b2c5984a1"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joonas-fi%2Fjoonas-sys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joonas-fi%2Fjoonas-sys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joonas-fi%2Fjoonas-sys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joonas-fi%2Fjoonas-sys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joonas-fi","download_url":"https://codeload.github.com/joonas-fi/joonas-sys/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248983808,"owners_count":21193649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration-as-code","immutable","operating-system"],"created_at":"2024-10-15T18:58:56.873Z","updated_at":"2025-10-25T16:31:52.436Z","avatar_url":"https://github.com/joonas-fi.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"⬆️ For table of contents, click the above icon\n\n![](docs/logo.webp)\n\nMy personal system installation (Ubuntu + programs \u0026 conf I use) as code. You might be\nfamiliar with \"dotfiles\" - this is a bit further. :) (Because I'm a giant nerd.)\n\nYou can also think of this as my research into how an OS and applications should be configured to maximize:\n\n- usability\n- stability\n\t* nothing breaks during normal daily use. I get to opt in to when things update.\n- state separation\n\t* I know exactly where files that I care about are. They're under `/sysroot`.\n\tAll other files in their traditional places like `/home/\u003cusername\u003e` are not interesting.\n\tYou just have to declare total bankrupty on how current sw operates and writes their files because there\n\tis no useful state separation.\n- security\n\t* mainly by compartmentalization but also by [memory safety](docs/memory-safety-roadmap.md).\n- simplicity and understandability via minimalism\n\t* move as much as possible to containers (containers have well-defined boundaries)\n\t* not needing update and uninstall during OS normal operation. Remember, we install from scratch\n\t  and don't update. There's tremendous amount of complexity in updating software: Microsoft's MSI,\n\t  Linux's initramfs hooks, boot partition versioned kernels and initrd's... It all just gets\n\t  simpler if software doesn't need update/remove/versioning capabilities.\nThis is not a generic operating system (\"distro\") that could help you - it's my personalized system,\ni.e. difference between an OS and an OS installation after one has set it up for her liking. But I'm\nsharing this to share ideas \u0026 for other people to get inspired!\n\nIn summary, running these tools contain:\n\n- An image for system partition with up-to-date Ubuntu installation + apps I use + config I use\n- Tool for updating bootloader to point to the new system image\n\n\nFurther documentation\n---------------------\n\n- [Rationale - why this approach?](docs/rationale.md)\n\t* This brings important context into why I'm specifically doing things that may seem hard!\n- [Manual steps to do after updates](docs/manual-steps.md)\n- [Installing on a new system](docs/installing-on-a-new-system.md)\n- [Boot process](docs/boot-process.md)\n\t* Includes troubleshooting on boot issues\n\n\nHow does it work\n----------------\n\nSummary:\n\n- My system image (OS + installed apps) is immutable\n- Despite immutability, testing new software is easy (just `$ apt install \u003cprogram\u003e` like always)\n\t* Short-term root state changes (like installing a new program) are redirected to a \"diff\" tree\n\t  using [overlayfs](https://wiki.archlinux.org/index.php/Overlay_filesystem) so I don't\n\t  accidentally lose short-term data after reboot.\n\t* Short-term data is wiped out weekly on purpose.\n\t* It is easy for me to audit that I'm not accidentally throwing away important data because I can\n\t  inspect the diff tree.\n* No automatic updates to software, but move to a fresh install of up-to-date system weekly. Ahh,\n  that fresh car smell - weekly!\n* All configuration comes from this repo (\"system state as code\").\n\n\n### Filesystem layout\n\nPartitions:\n\n1. Boot partition (more [details](docs/boot-process.md))\n2. Root partition\n\nThe root partition physically **doesn't** (at root) look like your\n[typical Linux filesystem hierarchy](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard)\nbut instead one is projected at early boot process with [chroot](https://en.wikipedia.org/wiki/Chroot)-like\nmechanism (actually implemented with [OverlayFS](https://en.wikipedia.org/wiki/OverlayFS)) from\nselected *checkout* in OS-checkout dir.\n\ntl;dr:\n\n- the typical filesystem hierarchy lives at `/sysroot/OS-checkout/$CHECKOUT`\n- any writes on top of the typical filesystem hierarchy are written to `/sysroot/OS-diff/$CHECKOUT`\n\nThis makes my root partition quite beautifully organized:\n\n```console\n$ tree -L 2 /sysroot/\n/sysroot/\n├── apps\n│   ├── Desktop\n│   ├── docker\n│   ├── flatpak\n│   ├── flatpak-appdata\n│   ├── git config\n│   ├── mcfly\n│   ├── netplan\n│   ├── OS-checkout\n│   ├── OS-diff\n│   ├── OS-repo\n│   ├── ssh-client\n│   ├── ssh-server\n│   ├── SYSTEM\n│   ├── tailscale-state\n│   ├── wireguard-tunnels\n│   ├── work\n│   └── zoxide\n├── lost+found\n└── swapfile\n```\n\n![](docs/system-files-layout.drawio.png)\n\nThe above is managed by:\n\n```shell\n$ jsys ostree pull\n```\n\n\u003e [!TIP]\n\u003e The command outputs enough help to guide with the rest.\n\n\n### No updates to the system\n\nI disable updates in my OS and the programs (Firefox etc.), but I run these steps weekly:\n\n1. Checkout a freshly-installed kernel+drivers+programs+settings\n2. (Optionally) Test the new checkout's system in a VM\n3. Point the bootloader to the new checkout\n4. Reboot into the new active system\n\nAs a result:\n\n- Updates won't ever break my running system all of a sudden\n\t* Because the system-under-use is never updated\n\t* Updates are important for security, but I achieve the same by just starting each week with a\n\t  freshly installed system containing newest packages\n\t* Semantically my software never gets updated. Software is much simpler if you don't ever have\n\t  to worry about update logic (or removal for that matter). Crud doesn't just add up. You have\n\t  to get good at identifying \u0026 managing state though!\n- I get to decide the exact time when I apply all the updates in an atomic manner. The final straw\n  to migrating to Linux was Windows 10 shutting down my computer \u0026 VMs when I was not looking. Never\n  again.\n- If updates break anything, I can rollback by booting into the previous week's system that worked\n\n\n### Installation, configuration, repository file layout\n\nThe most interesting location is [install-steps/](install-steps/).\nThe scripts are run in order and most often use the package manager to install software and/or run custom commands.\n\n(Notice the distinct lack of \"configuration management tools\" like Ansible/Chef.. turns out you can\nmake do with simple shell scripts if you don't need the \"convergence\" properties configuration management\ntooling. I.e. installing a system once is far easier than making changes to ever-living system.)\n\nSecond most interesting directory tree is [overrides/](overrides/), which contains all customized\nfiles I want to be present in the image:\n\n- Some software runs with its default configuration (no overrides needed)\n- For some software I want to override their files (or provide additional ones)\n\t* [Firefox is one such example](overrides/usr/lib/firefox/) (disable auto-updates \u0026 customize settings)\n\t* [overrides/home/joonas/.config/](overrides/home/joonas/.config/) pretty much is my \"dotfiles\"\n\nThe `Dockerfile` is mainly about getting this to build anywhere (think: build this Debian-based image\nfrom e.g. Arch Linux) with minimal dependencies.\n\n`bin/bootstrap-and-install.sh` mainly bootstraps Debian installation environment and pivots the chroot\ninside to our system tree that we're building and calls `install.sh` where the actual installation\ncan begin now that we have a working package manager.\n\n\n### Handling state\n\nThere are roughly three categories of data:\n\n| Type of file | Source of data | Stored in | Example |\n|--------------|--------|-----------|---------|\n| Static file installed by an application | OS package manager | System image | Executable or config file that you didn't change |\n| File that only needs to be changed rarely | This repository | System image | Application's config file that you customized, like [Firefox customizations](overrides/usr/lib/firefox/browser/defaults/preferences/user.js) |\n| Persistent data, state that changes often | User generated | Under `/sysroot` | Application's state directories (example: [Docker](overrides/var/lib/docker)) \u0026 files, work files, photographs you took etc. |\n\nThere's one special case: secret files - like your SSH private keys or other sensitive data, which\nbasically is rarely-changing data (therefore could be stored in repo), but for security reasons\nshouldn't be stored in the repo.\nIn this case I can make the file\n[/home/joonas/.ssh/id_rsa](https://github.com/joonas-fi/joonas-sys/blob/ab68d9e47612ffb8984c37343e21f091e1599445/overrides/home/joonas/.ssh/id_rsa)\nbe a symlink (\"redirect\") to `/persist/ssh/id_rsa`, so:\n\n- I can manage the state outside of the repo\n- and not have to configure the software to look for the file in my special location.\n\n\n### Distribution\n\nI.e. how the updates are distributed to all my computers.\n\nThe versioning is handled by [OSTree](https://ostreedev.github.io/ostree/introduction/) which could\nbe summarized as \"git for operating system binaries\".\n[Flatpak is also built on top of OSTree](https://docs.flatpak.org/en/latest/under-the-hood.html).\n\n```mermaid\nflowchart TD\n    subgraph \"Builder\"\n        builder[$ jsys build]\n        push[$ rclone]\n    end\n    subgraph \"S3 bucket ☁️\"\n        remote[OSTree remote]\n    end\n    subgraph \"computer\"\n        local[OSTree local repo]\n        jsysflash[$ jsys flash]\n        checkouts[\"/sysroot/OS-checkout/$VERSION\"]\n    end\n\n    push -. take result of .-\u003e builder\n    push -- push/sync --\u003e remote\n    remote -- $ ostree pull --\u003e local\n    local -. checkout a version into use .-\u003e jsysflash\n    jsysflash -- write --\u003e checkouts\n\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoonas-fi%2Fjoonas-sys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoonas-fi%2Fjoonas-sys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoonas-fi%2Fjoonas-sys/lists"}