├── app-build ├── incus-osd └── update-application-tags.py ├── incus-osd ├── tests │ ├── .gitignore │ └── incusos_tests │ │ ├── tests_incusos_api_system_resources.py │ │ ├── tests_incusos_api.py │ │ ├── tests_incusos_api_services.py │ │ ├── tests_upgrade.py │ │ ├── incus_test_vm │ │ └── util.py │ │ └── tests_incusos_api_system_logging.py ├── api │ ├── doc.go │ ├── seed │ │ ├── doc.go │ │ ├── network.go │ │ ├── provider.go │ │ ├── incus.go │ │ ├── applications.go │ │ ├── migration_manager.go │ │ ├── operations_center.go │ │ └── install.go │ ├── images │ │ ├── doc.go │ │ ├── index.go │ │ ├── update_file.go │ │ ├── update.go │ │ ├── changelog.go │ │ ├── update_file_architecture.go │ │ ├── update_severity.go │ │ └── update_file_component.go │ ├── debug_tui.go │ ├── system_reset.go │ ├── application.go │ ├── system_provider.go │ ├── service_usbip.go │ ├── service_tailscale.go │ ├── system_logging.go │ ├── service_iscsi.go │ ├── service_linstor.go │ ├── service_nvme.go │ ├── service_ovn.go │ ├── weekday.go │ ├── service_ceph.go │ └── service_multipath.go ├── internal │ ├── zfs │ │ └── doc.go │ ├── rest │ │ ├── doc.go │ │ ├── response │ │ │ ├── doc.go │ │ │ ├── util.go │ │ │ └── swagger.go │ │ ├── wrapper.go │ │ ├── api_system_reset.go │ │ ├── api_system_resources.go │ │ └── api_debug_tui.go │ ├── state │ │ ├── doc.go │ │ ├── state_internal_test.go │ │ └── file.go │ ├── proxy │ │ └── doc.go │ ├── util │ │ ├── doc.go │ │ └── luks.go │ ├── nftables │ │ └── doc.go │ ├── reset │ │ └── doc.go │ ├── storage │ │ ├── doc.go │ │ └── storage_test.go │ ├── backup │ │ └── doc.go │ ├── applications │ │ ├── doc.go │ │ ├── app_debug.go │ │ ├── struct.go │ │ └── load.go │ ├── keyring │ │ ├── doc.go │ │ └── keys.go │ ├── services │ │ ├── doc.go │ │ ├── struct.go │ │ └── load.go │ ├── tui │ │ ├── doc.go │ │ └── modal.go │ ├── recovery │ │ └── doc.go │ ├── seed │ │ ├── doc.go │ │ ├── seed_test.go │ │ ├── incus.go │ │ ├── provider.go │ │ ├── applications.go │ │ ├── migration_manager.go │ │ ├── operations_center.go │ │ ├── parse.go │ │ ├── applications_test.go │ │ └── install.go │ ├── manifests │ │ └── doc.go │ ├── install │ │ └── doc.go │ ├── providers │ │ ├── doc.go │ │ ├── errors.go │ │ └── load.go │ ├── systemd │ │ ├── doc.go │ │ ├── daemon.go │ │ ├── sysusers.go │ │ ├── hostnamectl.go │ │ ├── paths.go │ │ ├── system.go │ │ └── netlogd.go │ └── secureboot │ │ ├── doc.go │ │ └── fuse.go ├── cli │ ├── doc.go │ ├── utils_windows.go │ ├── utils_notwindows.go │ ├── load.go │ ├── cli_services.go │ └── cli_root.go └── cmd │ ├── image-customizer │ └── html │ │ ├── img │ │ └── favicon.ico │ │ └── css │ │ └── local.css │ ├── image-publisher │ ├── signing.go │ ├── utils.go │ ├── main.go │ ├── main_prune.go │ └── index.go │ ├── flasher-tool │ └── utils.go │ ├── generate-manifests │ └── main.go │ └── measure-pcrs │ └── main.go ├── mkosi.images ├── base │ ├── mkosi.conf │ ├── mkosi.extra │ │ └── usr │ │ │ └── lib │ │ │ ├── systemd │ │ │ ├── logind.conf │ │ │ ├── resolved.conf.d │ │ │ │ ├── 00-disable-llmnr.conf │ │ │ │ └── 00-disable-fallback-dns.conf │ │ │ ├── journald.conf.d │ │ │ │ └── 00-persistent-journal.conf │ │ │ ├── system │ │ │ │ ├── apparmor.service.d │ │ │ │ │ └── override.conf │ │ │ │ ├── prometheus-node-exporter.service.d │ │ │ │ │ └── override.conf │ │ │ │ ├── kpx.service │ │ │ │ ├── tailscale.service │ │ │ │ ├── print-incus-osd-journal-errors.service │ │ │ │ ├── incus-agent.service │ │ │ │ ├── incus-osd.service │ │ │ │ └── swtpm.service │ │ │ ├── system-preset │ │ │ │ └── 00-incus-os.preset │ │ │ └── incus-agent-setup │ │ │ ├── sysctl.d │ │ │ └── 90-incusos.conf │ │ │ ├── repart.d │ │ │ ├── 00-esp.conf │ │ │ ├── 01-seed-data.conf │ │ │ ├── 11-usr-verity.conf │ │ │ ├── 50-local-data.conf │ │ │ ├── 30-swap.conf │ │ │ ├── 20-usr-verity-sig.conf │ │ │ ├── 40-root.conf │ │ │ ├── 22-usr.conf │ │ │ ├── 10-usr-verity-sig.conf │ │ │ ├── 21-usr-verity.conf │ │ │ └── 12-usr.conf │ │ │ ├── sysusers.d │ │ │ └── incus-os.conf │ │ │ ├── tmpfiles.d │ │ │ ├── 04-java.conf │ │ │ ├── 10-prometheus-node-exporter.conf │ │ │ ├── 90-certificates.conf │ │ │ ├── 04-services.conf │ │ │ └── 04-apparmor.conf │ │ │ ├── udev │ │ │ └── rules.d │ │ │ │ ├── incus-agent.rules │ │ │ │ └── 99-partlabel.rules │ │ │ └── sysupdate.d │ │ │ ├── 12-usr.transfer │ │ │ ├── 11-usr-verity.transfer │ │ │ ├── 10-usr-verity-sig.transfer │ │ │ └── 20-uki.transfer │ ├── mkosi.conf.d │ │ ├── 00-distro.conf │ │ ├── 00-output.conf │ │ ├── 04-apparmor.conf │ │ ├── 04-services.conf │ │ ├── 99-remove-symlinks.conf │ │ ├── 02-basic-config.conf │ │ ├── 01-kernel-drbd.conf │ │ ├── 01-kernel-zfs.conf │ │ ├── 90-certificates.conf │ │ ├── 99-cleanup.conf │ │ ├── 10-disable-shell.conf │ │ ├── 01-kernel.conf │ │ ├── 90-certificates.sh │ │ ├── 04-apparmor.sh │ │ ├── 04-services.sh │ │ ├── 99-remove-symlinks.sh │ │ ├── 99-cleanup.sh │ │ ├── 01-kernel-zfs.sh │ │ └── 03-core-packages.conf │ └── mkosi.postinst.d │ │ └── 00-set-os-release.sh.chroot ├── incus-ceph │ ├── install.sh │ └── mkosi.conf ├── operations-center │ ├── mkosi.conf │ └── mkosi.extra │ │ └── usr │ │ └── lib │ │ └── systemd │ │ └── system │ │ └── operations-center.service ├── incus │ ├── mkosi.conf │ └── install.sh ├── incus-linstor │ ├── mkosi.conf │ └── install.sh ├── migration-manager │ ├── mkosi.conf │ └── mkosi.extra │ │ └── usr │ │ └── lib │ │ └── systemd │ │ └── system │ │ └── migration-manager.service └── debug │ └── mkosi.conf ├── mkosi.version ├── scripts ├── lint │ ├── golangci.sh │ └── licenses.sh ├── test │ └── switch-secure-boot-signing-key.sh ├── spawn-image └── inject-secure-boot-vars.sh ├── mkosi.packages └── initrd-tmpfs-root │ ├── debian │ ├── source │ │ └── format │ ├── rules │ ├── changelog │ ├── control │ ├── initrd-tmpfs-root.links │ ├── initrd-tmpfs-root.install │ └── copyright │ ├── 00-device-timeout.conf │ ├── systemd-fsck.conf │ ├── 99-add-cdrom-partitions.rules │ ├── systemd-repart.conf │ ├── initrd-show-devices.service │ ├── initrd-boot-message.service.in │ ├── initrd-swtpm.service │ ├── initrd-tmpfs-root.service │ ├── initrd-boot-message.sh │ └── initrd-show-devices.sh ├── test └── metadata.tar.xz ├── mkosi.extra └── efi │ └── loader │ └── loader.conf ├── doc ├── .sphinx │ ├── _static │ │ ├── tag.png │ │ └── favicon.ico │ ├── .markdownlint │ │ ├── exceptions.txt │ │ ├── style.rb │ │ ├── doc-lint.sh │ │ └── rules.rb │ ├── requirements.txt │ └── spellingcheck.yaml ├── images │ ├── incusos-started.png │ ├── vsphere-install.png │ ├── vsphere-network.png │ ├── vsphere-installed.png │ ├── vsphere-tpm-list.png │ ├── vsphere-tpm-ready.png │ ├── vsphere-upload-vm.png │ ├── incus-cli-vm-install.png │ ├── physical-installed.png │ ├── proxmox-import-iso.png │ ├── proxmox-vm-install.png │ ├── vsphere-create-vm1.png │ ├── vsphere-create-vm2.png │ ├── vsphere-create-vm3.png │ ├── vsphere-create-vm4.png │ ├── vsphere-create-vm5.png │ ├── vsphere-create-vm6.png │ ├── vsphere-create-vm7.png │ ├── vsphere-create-vm8.png │ ├── vsphere-detach-iso.png │ ├── vsphere-tpm-backup.png │ ├── vsphere-tpm-create.png │ ├── vsphere-upload-keys.png │ ├── incus-webui-import-iso.png │ ├── incus-webui-instances.png │ ├── incus-webui-vm-install.png │ ├── libvirt-cli-vm-install.png │ ├── libvirt-ui-vm-install.png │ ├── virtualbox-vm-install.png │ ├── libvirt-ui-vm-boot-order.png │ ├── physical-secureboot-keys.png │ ├── proxmox-vm-configure-iso.png │ ├── libvirt-ui-vm-configure-iso.png │ ├── libvirt-ui-vm-configure-tpm.png │ ├── proxmox-vm-incusos-running.png │ ├── proxmox-vm-install-complete.png │ ├── virtualbox-vm-configure-iso.png │ ├── incus-cli-vm-incusos-running.png │ ├── incus-cli-vm-install-complete.png │ ├── incus-webui-vm-configure-iso.png │ ├── incus-webui-vm-configure-tpm.png │ ├── incus-webui-vm-incusos-running.png │ ├── libvirt-cli-vm-incusos-running.png │ ├── libvirt-ui-vm-incusos-running.png │ ├── libvirt-ui-vm-install-complete.png │ ├── libvirt-ui-vm-no-install-media.png │ ├── libvirt-ui-vm-reattach-cdrom.png │ ├── physical-secureboot-overview.png │ ├── virtualbox-vm-first-boot-error.png │ ├── virtualbox-vm-incusos-running.png │ ├── virtualbox-vm-install-complete.png │ ├── incus-webui-vm-install-complete.png │ ├── libvirt-cli-vm-install-complete.png │ ├── libvirt-cli-vm-no-install-media.png │ ├── libvirt-ui-vm-configure-customize.png │ ├── libvirt-ui-vm-configure-secure-boot.png │ ├── incus-webui-vm-configure-secure-boot.png │ ├── libvirt-ui-vm-configure-virtual-disk.png │ ├── proxmox-vm-configure-secure-boot-tpm.png │ ├── virtualbox-vm-clearing-secure-boot-keys.png │ └── virtualbox-vm-configure-secure-boot-tpm.png ├── reference │ ├── applications │ │ ├── incus-ceph.md │ │ ├── incus-linstor.md │ │ ├── debug.md │ │ ├── non-primary.md │ │ ├── migration-manager.md │ │ ├── operations-center.md │ │ ├── incus.md │ │ └── shared-api.md │ ├── system │ │ ├── power.md │ │ ├── resources.md │ │ ├── logging.md │ │ ├── providers.md │ │ └── security.md │ ├── services │ │ ├── lvm.md │ │ ├── multipath.md │ │ ├── usbip.md │ │ ├── iscsi.md │ │ ├── nvme.md │ │ ├── ceph.md │ │ ├── shared-api.md │ │ ├── linstor.md │ │ ├── tailscale.md │ │ └── ovn.md │ ├── services.md │ ├── system.md │ ├── applications.md │ ├── api.md │ └── partitioning-scheme.md ├── getting-started.md ├── tutorials.md ├── reference.md ├── getting-started │ ├── requirements.md │ ├── installation.md │ └── installation │ │ └── virtual-proxmox.md ├── support.md └── .wordlist.txt ├── mkosi.repart ├── 01-seed-data.conf ├── 00-esp.conf ├── 10-usr-verity-sig.conf ├── 11-usr-verity.conf └── 12-usr.conf ├── .github ├── FUNDING.yml ├── SUPPORT.md └── workflows │ ├── commits.yml │ └── daily.yml ├── mkosi.sandbox └── etc │ └── apt │ ├── sources.list.d │ ├── linbit-linstor.sources │ ├── zabbly-ovn-stable.sources │ ├── zabbly-incus-stable.sources │ ├── zabbly-kernel-stable.sources │ └── linbit-drbd9.sources │ └── keyrings │ └── linbit.asc ├── .gitignore ├── mkosi.conf └── README.md /app-build/incus-osd: -------------------------------------------------------------------------------- 1 | ../incus-osd -------------------------------------------------------------------------------- /incus-osd/tests/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Output] 2 | Output=base 3 | -------------------------------------------------------------------------------- /mkosi.version: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | date -u +%Y%m%d%H%M 4 | -------------------------------------------------------------------------------- /scripts/lint/golangci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | golangci-lint run 3 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/logind.conf: -------------------------------------------------------------------------------- 1 | HandleLidSwitch=ignore 2 | -------------------------------------------------------------------------------- /test/metadata.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/test/metadata.tar.xz -------------------------------------------------------------------------------- /incus-osd/api/doc.go: -------------------------------------------------------------------------------- 1 | // Package api is used for user facing API structs. 2 | package api 3 | -------------------------------------------------------------------------------- /mkosi.extra/efi/loader/loader.conf: -------------------------------------------------------------------------------- 1 | secure-boot-enroll force 2 | timeout 3 3 | editor no 4 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/sysctl.d/90-incusos.conf: -------------------------------------------------------------------------------- 1 | net.ipv4.conf.all.arp_ignore=1 2 | -------------------------------------------------------------------------------- /doc/.sphinx/_static/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/.sphinx/_static/tag.png -------------------------------------------------------------------------------- /incus-osd/internal/zfs/doc.go: -------------------------------------------------------------------------------- 1 | // Package zfs is used to manage the local ZFS pool. 2 | package zfs 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/00-distro.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | WithRecommends=false 3 | WithDocs=false 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/00-output.conf: -------------------------------------------------------------------------------- 1 | [Output] 2 | Format=directory 3 | ManifestFormat=json 4 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/00-device-timeout.conf: -------------------------------------------------------------------------------- 1 | [Manager] 2 | DefaultDeviceTimeoutSec=600 3 | -------------------------------------------------------------------------------- /doc/images/incusos-started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incusos-started.png -------------------------------------------------------------------------------- /doc/images/vsphere-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-install.png -------------------------------------------------------------------------------- /doc/images/vsphere-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-network.png -------------------------------------------------------------------------------- /incus-osd/internal/rest/doc.go: -------------------------------------------------------------------------------- 1 | // Package rest holds the REST API server and endpoints. 2 | package rest 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/04-apparmor.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | BuildScripts=mkosi.conf.d/04-apparmor.sh 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/04-services.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | BuildScripts=mkosi.conf.d/04-services.sh 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/00-esp.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=esp 3 | CopyBlocks=auto 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/sysusers.d/incus-os.conf: -------------------------------------------------------------------------------- 1 | g tss - - 2 | u tss - - 3 | m tss tss 4 | -------------------------------------------------------------------------------- /doc/.sphinx/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/.sphinx/_static/favicon.ico -------------------------------------------------------------------------------- /doc/images/vsphere-installed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-installed.png -------------------------------------------------------------------------------- /doc/images/vsphere-tpm-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-tpm-list.png -------------------------------------------------------------------------------- /doc/images/vsphere-tpm-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-tpm-ready.png -------------------------------------------------------------------------------- /doc/images/vsphere-upload-vm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-upload-vm.png -------------------------------------------------------------------------------- /incus-osd/api/seed/doc.go: -------------------------------------------------------------------------------- 1 | // Package seed contains the API files used for image seed files. 2 | package seed 3 | -------------------------------------------------------------------------------- /incus-osd/internal/state/doc.go: -------------------------------------------------------------------------------- 1 | // Package state is used to manage persistent data on disk. 2 | package state 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/resolved.conf.d/00-disable-llmnr.conf: -------------------------------------------------------------------------------- 1 | [Resolve] 2 | LLMNR=no 3 | -------------------------------------------------------------------------------- /doc/images/incus-cli-vm-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-cli-vm-install.png -------------------------------------------------------------------------------- /doc/images/physical-installed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/physical-installed.png -------------------------------------------------------------------------------- /doc/images/proxmox-import-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/proxmox-import-iso.png -------------------------------------------------------------------------------- /doc/images/proxmox-vm-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/proxmox-vm-install.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm1.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm2.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm3.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm4.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm5.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm6.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm7.png -------------------------------------------------------------------------------- /doc/images/vsphere-create-vm8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-create-vm8.png -------------------------------------------------------------------------------- /doc/images/vsphere-detach-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-detach-iso.png -------------------------------------------------------------------------------- /doc/images/vsphere-tpm-backup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-tpm-backup.png -------------------------------------------------------------------------------- /doc/images/vsphere-tpm-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-tpm-create.png -------------------------------------------------------------------------------- /doc/images/vsphere-upload-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/vsphere-upload-keys.png -------------------------------------------------------------------------------- /incus-osd/cli/doc.go: -------------------------------------------------------------------------------- 1 | // Package cli provides cobra commands for inclusion in downstream projects. 2 | package cli 3 | -------------------------------------------------------------------------------- /doc/.sphinx/.markdownlint/exceptions.txt: -------------------------------------------------------------------------------- 1 | .tmp/doc/contributing.md:5: MD002 First header should be a top level header 2 | -------------------------------------------------------------------------------- /doc/images/incus-webui-import-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-import-iso.png -------------------------------------------------------------------------------- /doc/images/incus-webui-instances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-instances.png -------------------------------------------------------------------------------- /doc/images/incus-webui-vm-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-vm-install.png -------------------------------------------------------------------------------- /doc/images/libvirt-cli-vm-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-cli-vm-install.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-install.png -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-install.png -------------------------------------------------------------------------------- /incus-osd/internal/proxy/doc.go: -------------------------------------------------------------------------------- 1 | // Package proxy is used for configuring system-wide proxy settings. 2 | package proxy 3 | -------------------------------------------------------------------------------- /incus-osd/internal/util/doc.go: -------------------------------------------------------------------------------- 1 | // Package util contains code used by more than one internal library. 2 | package util 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/99-remove-symlinks.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | BuildScripts=mkosi.conf.d/99-remove-symlinks.sh 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/01-seed-data.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=linux-generic 3 | CopyBlocks=auto 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/resolved.conf.d/00-disable-fallback-dns.conf: -------------------------------------------------------------------------------- 1 | [Resolve] 2 | FallbackDNS= 3 | -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-boot-order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-boot-order.png -------------------------------------------------------------------------------- /doc/images/physical-secureboot-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/physical-secureboot-keys.png -------------------------------------------------------------------------------- /doc/images/proxmox-vm-configure-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/proxmox-vm-configure-iso.png -------------------------------------------------------------------------------- /incus-osd/api/images/doc.go: -------------------------------------------------------------------------------- 1 | // Package images contains the API files used for image server artifacts. 2 | package images 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/02-basic-config.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | Timezone=UTC 3 | Locale=C.UTF-8 4 | RootPassword=hashed:! 5 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/journald.conf.d/00-persistent-journal.conf: -------------------------------------------------------------------------------- 1 | [Journal] 2 | Storage=persistent 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/tmpfiles.d/04-java.conf: -------------------------------------------------------------------------------- 1 | L /etc/java-21-openjdk/ - - - - /usr/share/java-21-openjdk/ 2 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/systemd-fsck.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | ExecStart= 3 | ExecStart=-/usr/lib/systemd/systemd-fsck %f 4 | -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-configure-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-configure-iso.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-configure-tpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-configure-tpm.png -------------------------------------------------------------------------------- /doc/images/proxmox-vm-incusos-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/proxmox-vm-incusos-running.png -------------------------------------------------------------------------------- /doc/images/proxmox-vm-install-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/proxmox-vm-install-complete.png -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-configure-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-configure-iso.png -------------------------------------------------------------------------------- /incus-osd/internal/nftables/doc.go: -------------------------------------------------------------------------------- 1 | // Package nftables offers functions to manage the nftables firewall. 2 | package nftables 3 | -------------------------------------------------------------------------------- /incus-osd/internal/reset/doc.go: -------------------------------------------------------------------------------- 1 | // Package reset is used to perform application and/or OS-level factory resets. 2 | package reset 3 | -------------------------------------------------------------------------------- /incus-osd/internal/storage/doc.go: -------------------------------------------------------------------------------- 1 | // Package storage contains methods for interacting with local storage. 2 | package storage 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/tmpfiles.d/10-prometheus-node-exporter.conf: -------------------------------------------------------------------------------- 1 | d /var/lib/prometheus/node-exporter - - - - 2 | -------------------------------------------------------------------------------- /mkosi.repart/01-seed-data.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=linux-generic 3 | Label=seed-data 4 | SizeMinBytes=100M 5 | SizeMaxBytes=100M 6 | -------------------------------------------------------------------------------- /doc/images/incus-cli-vm-incusos-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-cli-vm-incusos-running.png -------------------------------------------------------------------------------- /doc/images/incus-cli-vm-install-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-cli-vm-install-complete.png -------------------------------------------------------------------------------- /doc/images/incus-webui-vm-configure-iso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-vm-configure-iso.png -------------------------------------------------------------------------------- /doc/images/incus-webui-vm-configure-tpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-vm-configure-tpm.png -------------------------------------------------------------------------------- /doc/images/incus-webui-vm-incusos-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-vm-incusos-running.png -------------------------------------------------------------------------------- /doc/images/libvirt-cli-vm-incusos-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-cli-vm-incusos-running.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-incusos-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-incusos-running.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-install-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-install-complete.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-no-install-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-no-install-media.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-reattach-cdrom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-reattach-cdrom.png -------------------------------------------------------------------------------- /doc/images/physical-secureboot-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/physical-secureboot-overview.png -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-first-boot-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-first-boot-error.png -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-incusos-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-incusos-running.png -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-install-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-install-complete.png -------------------------------------------------------------------------------- /incus-osd/internal/backup/doc.go: -------------------------------------------------------------------------------- 1 | // Package backup provides logic to backup and restore the OS and/or applications. 2 | package backup 3 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/response/doc.go: -------------------------------------------------------------------------------- 1 | // Package response provides functions to handle standard HTTP responses. 2 | package response 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/apparmor.service.d/override.conf: -------------------------------------------------------------------------------- 1 | [Unit] 2 | After=systemd-tmpfiles-setup.service 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/tmpfiles.d/90-certificates.conf: -------------------------------------------------------------------------------- 1 | d /etc/ssl - - - - 2 | L /etc/ssl/certs - - - - /usr/share/certs 3 | -------------------------------------------------------------------------------- /doc/images/incus-webui-vm-install-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-vm-install-complete.png -------------------------------------------------------------------------------- /doc/images/libvirt-cli-vm-install-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-cli-vm-install-complete.png -------------------------------------------------------------------------------- /doc/images/libvirt-cli-vm-no-install-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-cli-vm-no-install-media.png -------------------------------------------------------------------------------- /incus-osd/internal/applications/doc.go: -------------------------------------------------------------------------------- 1 | // Package applications allows retrieving of application-specific functions 2 | package applications 3 | -------------------------------------------------------------------------------- /incus-osd/internal/keyring/doc.go: -------------------------------------------------------------------------------- 1 | // Package keyring provides useful functions to interact with the kernel keyring. 2 | package keyring 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/01-kernel-drbd.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | BuildPackages=coccinelle 3 | BuildScripts=mkosi.conf.d/01-kernel-drbd.sh 4 | -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-configure-customize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-configure-customize.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-configure-secure-boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-configure-secure-boot.png -------------------------------------------------------------------------------- /incus-osd/internal/services/doc.go: -------------------------------------------------------------------------------- 1 | // Package services handles additional system services that require user configuration. 2 | package services 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/11-usr-verity.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr-verity 3 | Label=%M_%A_verity 4 | CopyBlocks=auto 5 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/50-local-data.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=linux-generic 3 | Label=local-data 4 | SizeMinBytes=10G 5 | -------------------------------------------------------------------------------- /doc/images/incus-webui-vm-configure-secure-boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/incus-webui-vm-configure-secure-boot.png -------------------------------------------------------------------------------- /doc/images/libvirt-ui-vm-configure-virtual-disk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/libvirt-ui-vm-configure-virtual-disk.png -------------------------------------------------------------------------------- /doc/images/proxmox-vm-configure-secure-boot-tpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/proxmox-vm-configure-secure-boot-tpm.png -------------------------------------------------------------------------------- /incus-osd/cmd/image-customizer/html/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/incus-osd/cmd/image-customizer/html/img/favicon.ico -------------------------------------------------------------------------------- /mkosi.repart/00-esp.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=esp 3 | Format=vfat 4 | CopyFiles=/boot:/ 5 | CopyFiles=/efi:/ 6 | SizeMinBytes=2G 7 | SizeMaxBytes=2G 8 | -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-clearing-secure-boot-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-clearing-secure-boot-keys.png -------------------------------------------------------------------------------- /doc/images/virtualbox-vm-configure-secure-boot-tpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxc/incus-os/HEAD/doc/images/virtualbox-vm-configure-secure-boot-tpm.png -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/01-kernel-zfs.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | BuildPackages= 3 | openzfs-zfs-dkms 4 | BuildScripts=mkosi.conf.d/01-kernel-zfs.sh 5 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/99-add-cdrom-partitions.rules: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="block", KERNEL=="sr[0-9]*", ACTION=="add", RUN+="/usr/sbin/kpartx -ar /dev/%k" 2 | -------------------------------------------------------------------------------- /incus-osd/internal/tui/doc.go: -------------------------------------------------------------------------------- 1 | // Package tui provides a way to show basic information and recent 2 | // log entries on the system's console. 3 | package tui 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/30-swap.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=swap 3 | Format=swap 4 | SizeMinBytes=4G 5 | SizeMaxBytes=4G 6 | Encrypt=tpm2 7 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/tmpfiles.d/04-services.conf: -------------------------------------------------------------------------------- 1 | L /etc/protocols - - - - /usr/share/protocols 2 | L /etc/services - - - - /usr/share/services 3 | -------------------------------------------------------------------------------- /incus-osd/internal/recovery/doc.go: -------------------------------------------------------------------------------- 1 | // Package recovery implements logic for running recovery hotfix scripts and updates from removable media. 2 | package recovery 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/90-certificates.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | RemoveFiles= 3 | /usr/share/ca-certificates 4 | BuildScripts=mkosi.conf.d/90-certificates.sh 5 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/20-usr-verity-sig.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr-verity-sig 3 | Label=_empty 4 | SizeMinBytes=16K 5 | SizeMaxBytes=16K 6 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/40-root.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=root 3 | Format=ext4 4 | SizeMinBytes=25G 5 | SizeMaxBytes=25G 6 | Encrypt=tpm2 7 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/tmpfiles.d/04-apparmor.conf: -------------------------------------------------------------------------------- 1 | L /etc/apparmor - - - - /usr/share/apparmor 2 | L /etc/apparmor.d - - - - /usr/share/apparmor.d 3 | -------------------------------------------------------------------------------- /mkosi.images/incus-ceph/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ "$1" = "final" ] || exit 0 3 | 4 | # Install the packages. 5 | apt-get install ceph-common --yes 6 | 7 | exit 0 8 | -------------------------------------------------------------------------------- /mkosi.repart/10-usr-verity-sig.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr-verity-sig 3 | Label=%M_%A_verity_sig 4 | Verity=signature 5 | VerityMatchKey=usr 6 | SplitName=%t.%U 7 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/doc.go: -------------------------------------------------------------------------------- 1 | // Package seed offers a number of useful functions to parse 2 | // the user provided initial seed data/configuration. 3 | package seed 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/22-usr.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr 3 | Label=_empty 4 | NoAuto=1 5 | SizeMinBytes=1G 6 | SizeMaxBytes=1G 7 | Weight=2000 8 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/systemd-repart.conf: -------------------------------------------------------------------------------- 1 | [Unit] 2 | OnFailure=initrd-tmpfs-root.service 3 | 4 | [Service] 5 | SuccessExitStatus= 6 | SuccessExitStatus=76 7 | -------------------------------------------------------------------------------- /incus-osd/internal/manifests/doc.go: -------------------------------------------------------------------------------- 1 | // Package manifests provides methods for ingesting mkosi json manifests and producing detailed IncusOS manifests. 2 | package manifests 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/10-usr-verity-sig.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr-verity-sig 3 | Label=%M_%A_verity_sig 4 | SizeMinBytes=16K 5 | SizeMaxBytes=16K 6 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/21-usr-verity.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr-verity 3 | Label=_empty 4 | NoAuto=1 5 | SizeMinBytes=100M 6 | SizeMaxBytes=100M 7 | -------------------------------------------------------------------------------- /incus-osd/internal/install/doc.go: -------------------------------------------------------------------------------- 1 | // Package install provides logic required to install incus-osd from 2 | // a live media environment to a dedicated disk. 3 | package install 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/repart.d/12-usr.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr 3 | Label=%M_%A 4 | SizeMinBytes=1G 5 | SizeMaxBytes=1G 6 | Weight=2000 7 | CopyBlocks=auto 8 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/udev/rules.d/incus-agent.rules: -------------------------------------------------------------------------------- 1 | SYMLINK=="virtio-ports/org.linuxcontainers.incus", TAG+="systemd", ENV{SYSTEMD_WANTS}+="incus-agent.service" 2 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/99-cleanup.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | BuildScripts=mkosi.conf.d/99-cleanup.sh 3 | RemoveFiles= 4 | /usr/share/doc 5 | /usr/share/man 6 | /usr/src 7 | -------------------------------------------------------------------------------- /incus-osd/internal/applications/app_debug.go: -------------------------------------------------------------------------------- 1 | package applications 2 | 3 | type debug struct { 4 | common 5 | } 6 | 7 | func (*debug) Name() string { 8 | return "debug" 9 | } 10 | -------------------------------------------------------------------------------- /incus-osd/internal/providers/doc.go: -------------------------------------------------------------------------------- 1 | // Package providers contains a number of Provider implementations used to fetch OS updates and applications from various sources. 2 | package providers 3 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/doc.go: -------------------------------------------------------------------------------- 1 | // Package systemd offers a number of useful functions to interact 2 | // with systemd units, system updates and system extensions. 3 | package systemd 4 | -------------------------------------------------------------------------------- /mkosi.repart/11-usr-verity.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr-verity 3 | Label=%M_%A_verity 4 | Verity=hash 5 | VerityMatchKey=usr 6 | SizeMinBytes=100M 7 | SizeMaxBytes=100M 8 | SplitName=%t.%U 9 | -------------------------------------------------------------------------------- /doc/reference/applications/incus-ceph.md: -------------------------------------------------------------------------------- 1 | # Incus Ceph 2 | 3 | The Incus Ceph (`incus-ceph`) application installs the Ceph client for Incus deployments needing to use [Ceph storage](../services/ceph.md). 4 | -------------------------------------------------------------------------------- /scripts/lint/licenses.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | go-licenses check --disallowed_types=forbidden,unknown,restricted --ignore github.com/lxc/go-lxc --ignore github.com/rootless-containers/proto/go-proto ./... 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Frequent committers who contribute to Incus on their own time can add 2 | # themselves to the list here so users who feel like sponsoring can find 3 | # them. 4 | github: 5 | - stgraber 6 | -------------------------------------------------------------------------------- /incus-osd/cli/utils_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package cli 4 | 5 | import ( 6 | "golang.org/x/sys/windows" 7 | ) 8 | 9 | func getStdinFd() int { 10 | return int(windows.Stdin) 11 | } 12 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/prometheus-node-exporter.service.d/override.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | Environment="ARGS=--web.listen-address=localhost:9100" 3 | EnvironmentFile= 4 | User=root 5 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | The Incus team uses GitHub for issue and feature tracking, not for user support. 2 | For information on how to get support, see [Support](https://linuxcontainers.org/incus/docs/main/support/). 3 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/10-disable-shell.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | RemoveFiles= 3 | /usr/lib/systemd/system-generators/systemd-getty-generator 4 | /usr/lib/systemd/system/multi-user.target.wants/getty.target 5 | -------------------------------------------------------------------------------- /mkosi.images/operations-center/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Config] 2 | Dependencies=base 3 | 4 | [Output] 5 | Format=sysext 6 | Overlay=yes 7 | ManifestFormat=json 8 | ImageVersion= 9 | 10 | [Content] 11 | BaseTrees=%O/base 12 | -------------------------------------------------------------------------------- /mkosi.sandbox/etc/apt/sources.list.d/linbit-linstor.sources: -------------------------------------------------------------------------------- 1 | Enabled: yes 2 | Types: deb 3 | URIs: http://packages.linbit.com/public 4 | Suites: trixie 5 | Components: misc 6 | Signed-By: /etc/apt/keyrings/linbit.asc 7 | -------------------------------------------------------------------------------- /mkosi.sandbox/etc/apt/sources.list.d/zabbly-ovn-stable.sources: -------------------------------------------------------------------------------- 1 | Enabled: yes 2 | Types: deb 3 | URIs: https://pkgs.zabbly.com/ovn/stable 4 | Suites: trixie 5 | Components: main 6 | Signed-By: /etc/apt/keyrings/zabbly.asc 7 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/changelog: -------------------------------------------------------------------------------- 1 | initrd-tmpfs-root (0.1) UNRELEASED; urgency=medium 2 | 3 | * Initial work 4 | 5 | -- Mathias Gibbens Tue, 29 Apr 2025 08:34:07 -0600 6 | -------------------------------------------------------------------------------- /mkosi.sandbox/etc/apt/sources.list.d/zabbly-incus-stable.sources: -------------------------------------------------------------------------------- 1 | Enabled: yes 2 | Types: deb 3 | URIs: https://pkgs.zabbly.com/incus/stable 4 | Suites: trixie 5 | Components: main 6 | Signed-By: /etc/apt/keyrings/zabbly.asc 7 | -------------------------------------------------------------------------------- /incus-osd/api/images/index.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | // Index represents the content of index.json/index.sjson. 4 | type Index struct { 5 | Format string `json:"format"` 6 | 7 | Updates []UpdateFull `json:"updates"` 8 | } 9 | -------------------------------------------------------------------------------- /mkosi.sandbox/etc/apt/sources.list.d/zabbly-kernel-stable.sources: -------------------------------------------------------------------------------- 1 | Enabled: yes 2 | Types: deb 3 | URIs: https://pkgs.zabbly.com/kernel/stable 4 | Suites: trixie 5 | Components: main zfs 6 | Signed-By: /etc/apt/keyrings/zabbly.asc 7 | -------------------------------------------------------------------------------- /doc/reference/applications/incus-linstor.md: -------------------------------------------------------------------------------- 1 | # Incus Linstor 2 | 3 | The Incus Linstor (`incus-linstor`) application installs Linstor satellite support for Incus deployments needing to use [Linstor storage](../services/linstor.md). 4 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/01-kernel.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | Packages= 3 | kmod 4 | linux-image-zabbly 5 | systemd-boot-efi 6 | BuildPackages= 7 | gcc 8 | libelf1 9 | linux-zabbly 10 | Bootable=false 11 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/kpx.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=kpx proxy daemon 3 | 4 | [Service] 5 | User=nobody 6 | Group=nogroup 7 | ExecStart=/usr/local/bin/kpx -c /etc/kpx.yaml 8 | Restart=on-failure 9 | -------------------------------------------------------------------------------- /mkosi.images/incus/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Config] 2 | Dependencies=base 3 | 4 | [Output] 5 | Format=sysext 6 | Overlay=yes 7 | ManifestFormat=json 8 | ImageVersion= 9 | 10 | [Content] 11 | BaseTrees=%O/base 12 | PrepareScripts=install.sh 13 | -------------------------------------------------------------------------------- /mkosi.repart/12-usr.conf: -------------------------------------------------------------------------------- 1 | [Partition] 2 | Type=usr 3 | Label=%M_%A 4 | Format=erofs 5 | CopyFiles=/usr:/ 6 | Verity=data 7 | VerityMatchKey=usr 8 | SplitName=%t.%U 9 | Compression=zstd 10 | SizeMinBytes=1G 11 | SizeMaxBytes=1G 12 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/90-certificates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | 3 | # Copy the certificate store to /usr. 4 | mkdir -p "${DESTDIR}/usr/share/certs" 5 | cp /etc/ssl/certs/ca-certificates.crt "${DESTDIR}/usr/share/certs/" 6 | 7 | exit 0 8 | -------------------------------------------------------------------------------- /mkosi.images/incus-ceph/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Config] 2 | Dependencies=base 3 | 4 | [Output] 5 | Format=sysext 6 | Overlay=yes 7 | ManifestFormat=json 8 | ImageVersion= 9 | 10 | [Content] 11 | BaseTrees=%O/base 12 | PrepareScripts=install.sh 13 | -------------------------------------------------------------------------------- /mkosi.sandbox/etc/apt/sources.list.d/linbit-drbd9.sources: -------------------------------------------------------------------------------- 1 | Enabled: yes 2 | Types: deb 3 | URIs: http://packages.linbit.com/public 4 | Suites: proxmox-9 5 | Architectures: amd64 6 | Components: drbd-9 7 | Signed-By: /etc/apt/keyrings/linbit.asc 8 | -------------------------------------------------------------------------------- /incus-osd/cli/utils_notwindows.go: -------------------------------------------------------------------------------- 1 | //go:build (linux && !appengine) || darwin || freebsd || openbsd 2 | 3 | package cli 4 | 5 | import ( 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | func getStdinFd() int { 10 | return unix.Stdin 11 | } 12 | -------------------------------------------------------------------------------- /mkosi.images/incus-linstor/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Config] 2 | Dependencies=base 3 | 4 | [Output] 5 | Format=sysext 6 | Overlay=yes 7 | ManifestFormat=json 8 | ImageVersion= 9 | 10 | [Content] 11 | BaseTrees=%O/base 12 | PrepareScripts=install.sh 13 | -------------------------------------------------------------------------------- /mkosi.images/migration-manager/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Config] 2 | Dependencies=base 3 | 4 | [Output] 5 | Format=sysext 6 | Overlay=yes 7 | ManifestFormat=json 8 | ImageVersion= 9 | 10 | [Content] 11 | BaseTrees=%O/base 12 | Packages= 13 | libnbd0 14 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/04-apparmor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | 3 | # Copy apparmor configuration to /usr/share/. 4 | mkdir -p "${DESTDIR}/usr/share/" 5 | cp -r /buildroot/etc/apparmor/ "${DESTDIR}/usr/share/" 6 | cp -r /buildroot/etc/apparmor.d/ "${DESTDIR}/usr/share/" 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/04-services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | 3 | # Copy protocols and services configuration to /usr/share/. 4 | mkdir -p "${DESTDIR}/usr/share/" 5 | cp /buildroot/etc/protocols "${DESTDIR}/usr/share/" 6 | cp /buildroot/etc/services "${DESTDIR}/usr/share/" 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /incus-osd/api/seed/network.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "github.com/lxc/incus-os/incus-osd/api" 5 | ) 6 | 7 | // Network represents the network seed. 8 | type Network struct { 9 | api.SystemNetworkConfig `yaml:",inline"` 10 | 11 | Version string `json:"version" yaml:"version"` 12 | } 13 | -------------------------------------------------------------------------------- /doc/reference/system/power.md: -------------------------------------------------------------------------------- 1 | # Power 2 | 3 | ## Rebooting IncusOS 4 | 5 | IncusOS can be rebooted via 6 | 7 | ``` 8 | incus admin os system reboot 9 | ``` 10 | 11 | ## Powering off IncusOS 12 | 13 | IncusOS can be safely powered off via 14 | 15 | ``` 16 | incus admin os system poweroff 17 | ``` 18 | -------------------------------------------------------------------------------- /incus-osd/api/seed/provider.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "github.com/lxc/incus-os/incus-osd/api" 5 | ) 6 | 7 | // Provider represents the provider seed. 8 | type Provider struct { 9 | api.SystemProviderConfig `yaml:",inline"` 10 | 11 | Version string `json:"version" yaml:"version"` 12 | } 13 | -------------------------------------------------------------------------------- /incus-osd/cmd/image-customizer/html/css/local.css: -------------------------------------------------------------------------------- 1 | div.card, div.accordion-item { 2 | margin-bottom: 1em; 3 | } 4 | 5 | .accordion-item:not(:first-of-type) { 6 | border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color); 7 | } 8 | 9 | label { 10 | margin-left: 0.5em; 11 | } 12 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/wrapper.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type countWrapper struct { 8 | io.ReadCloser 9 | 10 | n int 11 | } 12 | 13 | func (w *countWrapper) Read(p []byte) (int, error) { 14 | n, err := w.ReadCloser.Read(p) 15 | w.n += n 16 | 17 | return n, err 18 | } 19 | -------------------------------------------------------------------------------- /incus-osd/api/debug_tui.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log/slog" 5 | ) 6 | 7 | // DebugTUI defines a struct to hold a message to log along with its severity. 8 | type DebugTUI struct { 9 | Level slog.Level `json:"level" yaml:"level"` 10 | Message string `json:"message" yaml:"message"` 11 | } 12 | -------------------------------------------------------------------------------- /doc/reference/system/resources.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | A detailed low-level dump of information about the IncusOS system hardware can be obtained by running 4 | 5 | ``` 6 | incus admin os system resources show 7 | ``` 8 | 9 | ## Configuration options 10 | 11 | There are no configuration options for this read-only system information. 12 | -------------------------------------------------------------------------------- /doc/.sphinx/.markdownlint/style.rb: -------------------------------------------------------------------------------- 1 | all 2 | exclude_rule 'MD013' 3 | exclude_rule 'MD046' 4 | exclude_rule 'MD041' 5 | exclude_rule 'MD040' 6 | exclude_rule 'MD024' 7 | exclude_rule 'MD033' 8 | exclude_rule 'MD022' 9 | exclude_rule 'MD031' 10 | rule 'MD026', :punctuation => '.,;:!' 11 | rule 'MD003', :style => :atx 12 | rule 'MD007', :indent => 3 13 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/sysupdate.d/12-usr.transfer: -------------------------------------------------------------------------------- 1 | [Transfer] 2 | ProtectVersion=%A 3 | 4 | [Source] 5 | Type=regular-file 6 | Path=/var/lib/updates/ 7 | MatchPattern=%M_@v.usr-%a.@u.raw 8 | 9 | [Target] 10 | Type=partition 11 | Path=auto 12 | MatchPattern=%M_@v 13 | MatchPartitionType=usr 14 | PartitionFlags=0 15 | ReadOnly=1 16 | -------------------------------------------------------------------------------- /mkosi.images/migration-manager/mkosi.extra/usr/lib/systemd/system/migration-manager.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Migration Manager 3 | Documentation=https://github.com/FuturFusion/migration-manager 4 | 5 | [Service] 6 | EnvironmentFile=-/etc/environment 7 | ExecStart=/usr/local/bin/migration-managerd 8 | KillMode=process 9 | Restart=on-failure 10 | -------------------------------------------------------------------------------- /mkosi.images/operations-center/mkosi.extra/usr/lib/systemd/system/operations-center.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Operations Center 3 | Documentation=https://github.com/FuturFusion/operations-center 4 | 5 | [Service] 6 | EnvironmentFile=-/etc/environment 7 | ExecStart=/usr/local/bin/operations-centerd 8 | KillMode=process 9 | Restart=on-failure 10 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/initrd-show-devices.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Show system devices 3 | After=emergency.target 4 | DefaultDependencies=no 5 | 6 | [Service] 7 | Type=oneshot 8 | 9 | Environment=TTYS="/dev/tty1 /dev/ttyS0" 10 | 11 | ExecStart=/usr/bin/initrd-show-devices.sh 12 | 13 | [Install] 14 | WantedBy=emergency.target 15 | -------------------------------------------------------------------------------- /incus-osd/internal/state/state_internal_test.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | // Make sure that we have correctly bumped the schema version. 10 | func TestSchemaVersion(t *testing.T) { 11 | t.Parallel() 12 | 13 | require.Equal(t, len(upgrades), currentStateVersion) 14 | } 15 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/sysupdate.d/11-usr-verity.transfer: -------------------------------------------------------------------------------- 1 | [Transfer] 2 | ProtectVersion=%A 3 | 4 | [Source] 5 | Type=regular-file 6 | Path=/var/lib/updates/ 7 | MatchPattern=%M_@v.usr-%a-verity.@u.raw 8 | 9 | [Target] 10 | Type=partition 11 | Path=auto 12 | MatchPattern=%M_@v_verity 13 | MatchPartitionType=usr-verity 14 | PartitionFlags=0 15 | ReadOnly=1 16 | -------------------------------------------------------------------------------- /doc/reference/applications/debug.md: -------------------------------------------------------------------------------- 1 | # Debug 2 | 3 | The Debug application provides several additional tools that can be useful for developers working on debugging issues in an IncusOS system: 4 | 5 | * `htop` 6 | * `ifstat` 7 | * `iputils-ping` 8 | * `nano` 9 | * `mtr-tiny` 10 | * `net-tools` 11 | * `netcat-openbsd` 12 | * `procps` 13 | * `sqlite3` 14 | * `strace` 15 | * `tcpdump` 16 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/sysupdate.d/10-usr-verity-sig.transfer: -------------------------------------------------------------------------------- 1 | [Transfer] 2 | ProtectVersion=%A 3 | 4 | [Source] 5 | Type=regular-file 6 | Path=/var/lib/updates/ 7 | MatchPattern=%M_@v.usr-%a-verity-sig.@u.raw 8 | 9 | [Target] 10 | Type=partition 11 | Path=auto 12 | MatchPattern=%M_@v_verity_sig 13 | MatchPartitionType=usr-verity-sig 14 | PartitionFlags=0 15 | ReadOnly=1 16 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.postinst.d/00-set-os-release.sh.chroot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat > /usr/lib/os-release < 9 | Incus-Ceph 10 | Incus-Linstor 11 | ``` 12 | -------------------------------------------------------------------------------- /mkosi.images/debug/mkosi.conf: -------------------------------------------------------------------------------- 1 | [Config] 2 | Dependencies=base 3 | 4 | [Output] 5 | Format=sysext 6 | Overlay=yes 7 | ManifestFormat=json 8 | ImageVersion= 9 | 10 | [Content] 11 | BaseTrees=%O/base 12 | Packages= 13 | htop 14 | ifstat 15 | iputils-ping 16 | mtr-tiny 17 | nano 18 | net-tools 19 | netcat-openbsd 20 | procps 21 | sqlite3 22 | strace 23 | tcpdump 24 | -------------------------------------------------------------------------------- /incus-osd/internal/secureboot/doc.go: -------------------------------------------------------------------------------- 1 | // Package secureboot implements logic related to handing secure boot 2 | // key signing updates. 3 | // 4 | // NOTE -- It's assumed that PCR7 is the only one we care about in this code. 5 | package secureboot 6 | 7 | // TPMPCRMismatch holds the string returned by TPMStatus() if there's a PCR mismatch between the TPM and our computed value. 8 | var TPMPCRMismatch = "pending PCR7 update" 9 | -------------------------------------------------------------------------------- /incus-osd/api/seed/incus.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | incusapi "github.com/lxc/incus/v6/shared/api" 5 | ) 6 | 7 | // Incus represents the Incus seed file. 8 | type Incus struct { 9 | Version string `json:"version" yaml:"version"` 10 | 11 | ApplyDefaults bool `json:"apply_defaults" yaml:"apply_defaults"` 12 | Preseed *incusapi.InitPreseed `json:"preseed" yaml:"preseed"` 13 | } 14 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/seed_test.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/lxc/incus-os/incus-osd/api" 9 | ) 10 | 11 | func TestGetFileContents(t *testing.T) { 12 | t.Parallel() 13 | 14 | var config api.SystemNetworkConfig 15 | 16 | err := parseFileContents("testdata.tar", "network", &config) 17 | 18 | require.NoError(t, err) 19 | } 20 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/daemon.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/lxc/incus/v6/shared/subprocess" 7 | ) 8 | 9 | // ReloadDaemon instructs systemd to reload all its units. 10 | func ReloadDaemon(ctx context.Context) error { 11 | _, err := subprocess.RunCommandContext(ctx, "systemctl", "daemon-reload") 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/initrd-boot-message.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Display startup message 3 | Before=veritysetup.target 4 | After=veritysetup-pre.target 5 | DefaultDependencies=no 6 | 7 | [Service] 8 | Type=oneshot 9 | 10 | Environment=OS_NAME="@OSNAME@" 11 | Environment=TTYS="/dev/tty1 /dev/ttyS0" 12 | 13 | ExecStart=/usr/bin/initrd-boot-message.sh 14 | 15 | [Install] 16 | WantedBy=veritysetup.target 17 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/sysusers.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/lxc/incus/v6/shared/subprocess" 7 | ) 8 | 9 | // RefreshUsers instructs systemd-sysusers to re-scan and re-apply user definitions. 10 | func RefreshUsers(ctx context.Context) error { 11 | _, err := subprocess.RunCommandContext(ctx, "systemd-sysusers") 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/tailscale.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Tailscale daemon 3 | 4 | [Service] 5 | ExecStart=/usr/local/bin/tailscaled -tun=_tailscale0 6 | ExecStopPost=/usr/local/bin/tailscaled --cleanup 7 | Restart=on-failure 8 | RuntimeDirectory=tailscale 9 | RuntimeDirectoryMode=0755 10 | StateDirectory=tailscale 11 | StateDirectoryMode=0700 12 | CacheDirectory=tailscale 13 | CacheDirectoryMode=0750 14 | -------------------------------------------------------------------------------- /incus-osd/api/seed/applications.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | // Applications represents the applications seed file. 4 | type Applications struct { 5 | Version string `json:"version" yaml:"version"` 6 | 7 | Applications []Application `json:"applications" yaml:"applications"` 8 | } 9 | 10 | // Application represents a single application with the applications seed. 11 | type Application struct { 12 | Name string `json:"name" yaml:"name"` 13 | } 14 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/sysupdate.d/20-uki.transfer: -------------------------------------------------------------------------------- 1 | [Transfer] 2 | ProtectVersion=%A 3 | 4 | [Source] 5 | Type=regular-file 6 | Path=/var/lib/updates/ 7 | MatchPattern=%M_@v.efi 8 | 9 | [Target] 10 | Type=regular-file 11 | Path=/EFI/Linux 12 | PathRelativeTo=boot 13 | MatchPattern=%M_@v+@l-@d.efi \ 14 | %M_@v+@l.efi \ 15 | %M_@v.efi 16 | Mode=0444 17 | TriesLeft=3 18 | TriesDone=0 19 | InstancesMax=2 20 | -------------------------------------------------------------------------------- /doc/.sphinx/requirements.txt: -------------------------------------------------------------------------------- 1 | furo 2 | gitpython 3 | linkify-it-py 4 | canonical-sphinx-extensions @ git+https://github.com/canonical/canonical-sphinx-extensions@bc648473619f93fdfefb9ae8becbe7d00dad675a 5 | myst-parser 6 | pyspelling 7 | sphinx 8 | sphinx-autobuild 9 | sphinx-copybutton 10 | sphinx-design 11 | sphinx-notfound-page 12 | sphinx-remove-toctrees 13 | sphinx-reredirects 14 | sphinx-tabs 15 | sphinxcontrib-jquery 16 | sphinxext-opengraph 17 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/response/util.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | ) 8 | 9 | // etagHash hashes the provided data and returns the sha256. 10 | func etagHash(data any) (string, error) { 11 | etag := sha256.New() 12 | 13 | err := json.NewEncoder(etag).Encode(data) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | return hex.EncodeToString(etag.Sum(nil)), nil 19 | } 20 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/hostnamectl.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/lxc/incus/v6/shared/subprocess" 7 | ) 8 | 9 | // SetHostname sets the system's hostname to the provided value. 10 | func SetHostname(ctx context.Context, hostname string) error { 11 | _, err := subprocess.RunCommandContext(ctx, "hostnamectl", "hostname", hostname) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/print-incus-osd-journal-errors.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Print incus-osd journal errors 3 | DefaultDependencies=no 4 | 5 | [Service] 6 | Type=oneshot 7 | 8 | Environment=MSG="IncusOS failed to start. journal contents follow:" 9 | Environment=TTYS="/dev/tty1 /dev/ttyS0" 10 | 11 | ExecStart=/bin/sh -c "for TTY in $TTYS; do echo $MSG > $TTY || true; journalctl -b 0 -u incus-osd > $TTY || true; done" 12 | -------------------------------------------------------------------------------- /doc/reference/services/lvm.md: -------------------------------------------------------------------------------- 1 | # LVM 2 | 3 | The LVM service allows configuring of a clustered LVM storage backend. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_lvm.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the LVM service. 12 | 13 | * `system_id`: A cluster-unique host identifier. 14 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/99-remove-symlinks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | mkdir -p "${DESTDIR}/usr/bin" "${DESTDIR}/usr/sbin" 3 | rm -f \ 4 | "${DESTDIR}/usr/bin/awk" \ 5 | "${DESTDIR}/usr/bin/which" \ 6 | "${DESTDIR}/usr/sbin/ovs-vswitchd" 7 | 8 | ln -s "/usr/bin/mawk" "${DESTDIR}/usr/bin/awk" 9 | ln -s "/usr/bin/which.debianutils" "${DESTDIR}/usr/bin/which" 10 | ln -s "/usr/lib/openvswitch-switch/ovs-vswitchd" "${DESTDIR}/usr/sbin/ovs-vswitchd" 11 | 12 | exit 0 13 | -------------------------------------------------------------------------------- /incus-osd/api/images/update_file.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | // UpdateFile represents a file entry in an update. 4 | type UpdateFile struct { 5 | Architecture UpdateFileArchitecture `json:"architecture"` 6 | Component UpdateFileComponent `json:"component"` 7 | Filename string `json:"filename"` 8 | Sha256 string `json:"sha256"` 9 | Size int64 `json:"size"` 10 | Type UpdateFileType `json:"type"` 11 | } 12 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/incus.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "context" 5 | 6 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 7 | ) 8 | 9 | // GetIncus extracts the Incus preseed from the seed data. 10 | func GetIncus(_ context.Context) (*apiseed.Incus, error) { 11 | // Get the preseed. 12 | var preseed apiseed.Incus 13 | 14 | err := parseFileContents(getSeedPath(), "incus", &preseed) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &preseed, nil 20 | } 21 | -------------------------------------------------------------------------------- /doc/reference/services/multipath.md: -------------------------------------------------------------------------------- 1 | # Multipath 2 | 3 | The Multipath service allows configuring multipath storage devices. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_multipath.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the Multipath service. 12 | 13 | * `wwns`: An array of {abbr}`WWN (World Wide Name)`s to configure for multipath. 14 | -------------------------------------------------------------------------------- /doc/reference/services/usbip.md: -------------------------------------------------------------------------------- 1 | # {abbr}`USBIP (USB over IP)` 2 | 3 | The [{abbr}`USBIP (USB over IP)`](https://usbip.sourceforge.net/) service provides access to remote USB devices over IP. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_usbip.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `targets`: An array of USBIP targets, each of which consists of an address and bus ID. 12 | -------------------------------------------------------------------------------- /doc/reference/services/iscsi.md: -------------------------------------------------------------------------------- 1 | # iSCSI 2 | 3 | The iSCSI service allows connecting a remote iSCSI storage device over TCP. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_iscsi.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the iSCSI service. 12 | 13 | * `targets`: An array of iSCSI targets, each of which consists of an address, port, and iSCSI target. 14 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/provider.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "context" 5 | 6 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 7 | ) 8 | 9 | // GetProvider extracts the provider configuration from the seed data. 10 | func GetProvider(_ context.Context) (*apiseed.Provider, error) { 11 | // Get the install configuration. 12 | var config apiseed.Provider 13 | 14 | err := parseFileContents(getSeedPath(), "provider", &config) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &config, nil 20 | } 21 | -------------------------------------------------------------------------------- /doc/reference/services/nvme.md: -------------------------------------------------------------------------------- 1 | # NVMe 2 | 3 | The NVMe service allows connecting a remote NVMe storage device over fibre channel or TCP. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_nvme.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the NVMe service. 12 | 13 | * `targets`: An array of NVMe targets, each of which consists of an address, port, and transport type. 14 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/applications.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "context" 5 | 6 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 7 | ) 8 | 9 | // GetApplications extracts the list of applications from the seed data. 10 | func GetApplications(_ context.Context) (*apiseed.Applications, error) { 11 | // Get applications list 12 | var apps apiseed.Applications 13 | 14 | err := parseFileContents(getSeedPath(), "applications", &apps) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &apps, nil 20 | } 21 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/control: -------------------------------------------------------------------------------- 1 | Source: initrd-tmpfs-root 2 | Section: admin 3 | Priority: optional 4 | Build-Depends: debhelper-compat (=13) 5 | Maintainer: Mathias Gibbens 6 | Standards-Version: 4.7.2 7 | 8 | Package: initrd-tmpfs-root 9 | Architecture: all 10 | Depends: ${misc:Depends} 11 | Description: Mount rootfs as tmpfs when systemd-repart fails 12 | On read-only media, systemd-repart will fail. In this case, 13 | mount the root file system as tmpfs to allow the install 14 | to proceed. 15 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/incus-agent.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Incus - agent 3 | Documentation=https://linuxcontainers.org/incus/docs/main/ 4 | Before=multi-user.target cloud-init.target cloud-init.service cloud-init-local.service 5 | DefaultDependencies=no 6 | 7 | [Service] 8 | Type=notify 9 | WorkingDirectory=-/run/incus_agent 10 | ExecStartPre=/usr/lib/systemd/incus-agent-setup 11 | ExecStart=/run/incus_agent/incus-agent 12 | Restart=on-failure 13 | RestartSec=5s 14 | StartLimitInterval=60 15 | StartLimitBurst=10 16 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/incus-osd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=IncusOS - management daemon 3 | Documentation=https://github.com/lxc/incus-os/ 4 | After=systemd-udevd.service 5 | Before=network-pre.target 6 | Wants=network-pre.target 7 | OnFailure=print-incus-osd-journal-errors.service 8 | 9 | [Service] 10 | ExecStart=/usr/local/bin/incus-osd 11 | Environment=TERM=xterm-256color 12 | KillMode=process 13 | TimeoutStartSec=30s 14 | TimeoutStopSec=30s 15 | Restart=on-failure 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /doc/reference/system/logging.md: -------------------------------------------------------------------------------- 1 | # Logging 2 | 3 | IncusOS can be configured to log to a remote syslog server. 4 | 5 | ## Configuration options 6 | 7 | Configuration fields are defined in the [`SystemLoggingSyslog` struct](https://github.com/lxc/incus-os/blob/main/incus-osd/api/system_logging.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `address`: The remote syslog server IP address. 12 | 13 | * `protocol`: The protocol to use when connecting to the remote syslog server. 14 | 15 | * `log_format`: The format of log entries to use. 16 | -------------------------------------------------------------------------------- /incus-osd/api/system_reset.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // SystemReset defines a struct that takes an optional map of seed data to set as part of the factory reset. 8 | type SystemReset struct { 9 | AllowTPMResetFailure bool `json:"allow_tpm_reset_failure" yaml:"allow_tpm_reset_failure"` 10 | Seeds map[string]json.RawMessage `json:"seeds" yaml:"seeds"` 11 | WipeExistingSeeds bool `json:"wipe_existing_seeds" yaml:"wipe_existing_seeds"` 12 | } 13 | -------------------------------------------------------------------------------- /doc/reference/services/ceph.md: -------------------------------------------------------------------------------- 1 | # Ceph 2 | 3 | The [Ceph](https://ceph.io/) service allows connecting a Ceph storage cluster. In addition to Incus, the `incus-ceph` application must be installed to enable this service. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_ceph.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the Ceph service. 12 | 13 | * `clusters`: A map of Ceph clusters to connect to. 14 | -------------------------------------------------------------------------------- /doc/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | Let's get you started with IncusOS! 3 | 4 | Whether you want to run it on some physical hardware or in a virtual 5 | machine, you'll have to make sure your system meets the current system 6 | requirements, then get yourself an image and finally install it on your 7 | system. 8 | 9 | ```{toctree} 10 | :maxdepth: 1 11 | 12 | System requirements 13 | Getting an image 14 | Installing 15 | Accessing the system 16 | ``` 17 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/migration_manager.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "context" 5 | 6 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 7 | ) 8 | 9 | // GetMigrationManager extracts the Migration Manager preseed from the seed data. 10 | func GetMigrationManager(_ context.Context) (*apiseed.MigrationManager, error) { 11 | // Get the preseed. 12 | var preseed apiseed.MigrationManager 13 | 14 | err := parseFileContents(getSeedPath(), "migration-manager", &preseed) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &preseed, nil 20 | } 21 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/operations_center.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "context" 5 | 6 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 7 | ) 8 | 9 | // GetOperationsCenter extracts the Operations Center preseed from the seed data. 10 | func GetOperationsCenter(_ context.Context) (*apiseed.OperationsCenter, error) { 11 | // Get the preseed. 12 | var preseed apiseed.OperationsCenter 13 | 14 | err := parseFileContents(getSeedPath(), "operations-center", &preseed) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return &preseed, nil 20 | } 21 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/99-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eux 2 | 3 | # Run this from a build script so it runs before the final depmod run. 4 | rm -Rf \ 5 | "${DESTDIR}/usr/lib/modules/*/vmlinuz" \ 6 | "${DESTDIR}/usr/lib/modules/*/kernel/sound" \ 7 | "${DESTDIR}/usr/lib/modules/*/kernel/drivers/gpu" \ 8 | "${DESTDIR}/usr/lib/modules/*/kernel/drivers/infiniband" \ 9 | "${DESTDIR}/usr/lib/modules/*/kernel/drivers/iio" \ 10 | "${DESTDIR}/usr/lib/modules/*/kernel/drivers/media" \ 11 | "${DESTDIR}/usr/lib/modules/*/kernel/drivers/net/wireless" 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/paths.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | var ( 4 | // SystemExtensionsPath is the systemd location for system extensions. 5 | SystemExtensionsPath = "/var/lib/extensions" 6 | 7 | // SystemUpdatesPath is the systemd location for system updates. 8 | SystemUpdatesPath = "/var/lib/updates" 9 | 10 | // SystemdNetworkConfigPath is the location for systemd network config files. 11 | SystemdNetworkConfigPath = "/run/systemd/network/" 12 | 13 | // SystemdTimesyncConfigFile is the configuration file for systemd-timesyncd. 14 | SystemdTimesyncConfigFile = "/run/systemd/timesyncd.conf" 15 | ) 16 | -------------------------------------------------------------------------------- /doc/reference/services/shared-api.md: -------------------------------------------------------------------------------- 1 | # Shared API 2 | 3 | Each IncusOS service shares a common API that can be used to get its state and configuration, update its configuration, and forcefully reset the service if needed. 4 | 5 | ## Getting the service state and configuration 6 | 7 | ``` 8 | incus admin os service show 9 | ``` 10 | 11 | ## Editing the service configuration 12 | 13 | ``` 14 | incus admin os service edit 15 | ``` 16 | 17 | ## Resetting the application 18 | 19 | If needed, a service can be forcefully reset by running 20 | 21 | ``` 22 | incus admin os service reset 23 | ``` 24 | -------------------------------------------------------------------------------- /incus-osd/cli/load.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // Args contains the configuration for a new IncusOS CLI instance. 10 | type Args struct { 11 | DefaultListFormat string 12 | SupportsTarget bool 13 | SupportsRemote bool 14 | DoHTTP func(remoteName string, req *http.Request) (*http.Response, error) 15 | } 16 | 17 | // NewCommand returns a new cobra Command suitable for inclusion by downstreams. 18 | func NewCommand(args *Args) *cobra.Command { 19 | cmd := cmdAdminOS{ 20 | args: args, 21 | } 22 | 23 | return cmd.command() 24 | } 25 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/parse.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // ErrNoSeedPartition is returned when the seed partition couldn't be found. 8 | var ErrNoSeedPartition = errors.New("no seed partition could be found") 9 | 10 | // ErrNoSeedData is returned when a partition could be found but no data was found in it. 11 | var ErrNoSeedData = errors.New("no seed data present in the partition") 12 | 13 | // ErrNoSeedSection is returned when the seed data is available but the requested section/file couldn't be found. 14 | var ErrNoSeedSection = errors.New("requested seed section couldn't be found") 15 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/applications_test.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 9 | ) 10 | 11 | func TestGetApplications(t *testing.T) { 12 | t.Parallel() 13 | 14 | var apps apiseed.Applications 15 | 16 | err := parseFileContents("testdata.tar", "applications", &apps) 17 | 18 | require.NoError(t, err) 19 | 20 | require.Equal(t, "1.2.3", apps.Version) 21 | require.Len(t, apps.Applications, 2) 22 | require.Equal(t, "foo", apps.Applications[0].Name) 23 | require.Equal(t, "bar", apps.Applications[1].Name) 24 | } 25 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/system.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/lxc/incus/v6/shared/subprocess" 7 | ) 8 | 9 | // SystemPowerOff triggers a system shutdown. 10 | func SystemPowerOff(ctx context.Context) error { 11 | _, err := subprocess.RunCommandContext(ctx, "systemctl", "poweroff") 12 | if err != nil { 13 | return err 14 | } 15 | 16 | return nil 17 | } 18 | 19 | // SystemReboot triggers a system reboot. 20 | func SystemReboot(ctx context.Context) error { 21 | _, err := subprocess.RunCommandContext(ctx, "systemctl", "reboot") 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /doc/.sphinx/spellingcheck.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 2 | matrix: 3 | - name: Markdown files 4 | aspell: 5 | lang: en 6 | d: en_US 7 | dictionary: 8 | wordlists: 9 | - doc/.wordlist.txt 10 | output: doc/.sphinx/.wordlist.dic 11 | sources: 12 | - doc/html/**/*.html 13 | pipeline: 14 | - pyspelling.filters.html: 15 | comments: false 16 | attributes: 17 | - title 18 | - alt 19 | ignores: 20 | - code 21 | - pre 22 | - spellexception 23 | - link 24 | - title 25 | - div.relatedlinks 26 | - span.guilabel 27 | - div.visually-hidden 28 | - img 29 | - a.p-navigation__link 30 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/initrd-tmpfs-root.links: -------------------------------------------------------------------------------- 1 | usr/lib/systemd/system/initrd-boot-message.service usr/lib/systemd/system/veritysetup.target.wants/initrd-boot-message.service 2 | usr/lib/systemd/system/initrd-show-devices.service usr/lib/systemd/system/emergency.target.wants/initrd-show-devices.service 3 | usr/lib/systemd/system/initrd-swtpm.service usr/lib/systemd/system/cryptsetup.target.wants/initrd-swtpm.service 4 | usr/lib/systemd/system/initrd-swtpm.service usr/lib/systemd/system/systemd-cryptsetup@root.service.wants/initrd-swtpm.service 5 | usr/lib/systemd/system/initrd-swtpm.service usr/lib/systemd/system/systemd-repart.service.wants/initrd-swtpm.service 6 | -------------------------------------------------------------------------------- /incus-osd/internal/seed/install.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | apiseed "github.com/lxc/incus-os/incus-osd/api/seed" 8 | ) 9 | 10 | // GetInstall extracts the installation config from the seed data. 11 | func GetInstall() (*apiseed.Install, error) { 12 | // Get the install configuration. 13 | var config apiseed.Install 14 | 15 | err := parseFileContents(getSeedPath(), "install", &config) 16 | if err != nil { 17 | // If we have any empty install file, that should still trigger an install. 18 | if errors.Is(err, io.EOF) { 19 | return &config, nil 20 | } 21 | 22 | return nil, err 23 | } 24 | 25 | return &config, nil 26 | } 27 | -------------------------------------------------------------------------------- /incus-osd/internal/secureboot/fuse.go: -------------------------------------------------------------------------------- 1 | package secureboot 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // BlowTrustedFuse blows a virtual fuse that indicates we can no longer trust this 8 | // system's state, such as when a swtpm-backed TPM is used. There is no way to undo 9 | // this, short of completely reinstalling the system. 10 | func BlowTrustedFuse() error { 11 | _, err := os.Create("/etc/incusos-trusted-fuse-blown") 12 | 13 | return err 14 | } 15 | 16 | // IsTrustedFuseBlown returns a boolean indicating if the system state can be trusted. 17 | func IsTrustedFuseBlown() bool { 18 | _, err := os.Stat("/etc/incusos-trusted-fuse-blown") 19 | 20 | return err == nil 21 | } 22 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/initrd-tmpfs-root.install: -------------------------------------------------------------------------------- 1 | systemd-fsck.conf usr/lib/systemd/system/systemd-fsck@dev-disk-by\x2dpartlabel-esp.service.d/ 2 | systemd-repart.conf usr/lib/systemd/system/systemd-repart.service.d/ 3 | initrd-tmpfs-root.service usr/lib/systemd/system/ 4 | 5 | 99-add-cdrom-partitions.rules usr/lib/udev/rules.d/ 6 | 7 | 00-device-timeout.conf usr/lib/systemd/system.conf.d/ 8 | 9 | initrd-boot-message.service usr/lib/systemd/system/ 10 | initrd-boot-message.sh usr/bin/ 11 | 12 | initrd-show-devices.service usr/lib/systemd/system/ 13 | initrd-show-devices.sh usr/bin/ 14 | 15 | initrd-swtpm.service usr/lib/systemd/system/ 16 | measure-pcrs usr/bin/ 17 | -------------------------------------------------------------------------------- /doc/tutorials.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | The following tutorials demonstrate various ways to configure and use IncusOS. 3 | 4 | ```{toctree} 5 | :maxdepth: 1 6 | Expanding the "local" storage pool 7 | Preparing a storage volume for Incus 8 | Import existing Incus instances from an unencrypted pool 9 | Directly attaching instances to host network 10 | Applying VLAN tagging to physical networks 11 | Emergency Procedure for a Lost Client Certificate 12 | ``` 13 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/01-kernel-zfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | KERNEL="$(ls /buildroot/usr/lib/modules/)" 3 | 4 | mkdir -p "${DESTDIR}/usr/lib/modules/${KERNEL}/updates/dkms/" 5 | 6 | "/buildroot/usr/src/linux-headers-${KERNEL}/scripts/sign-file" \ 7 | sha256 /work/src/mkosi.key /work/src/mkosi.crt \ 8 | "/buildroot/usr/lib/modules/${KERNEL}/updates/dkms/spl.ko" \ 9 | "${DESTDIR}/usr/lib/modules/${KERNEL}/updates/dkms/spl.ko" 10 | 11 | "/buildroot/usr/src/linux-headers-${KERNEL}/scripts/sign-file" \ 12 | sha256 /work/src/mkosi.key /work/src/mkosi.crt \ 13 | "/buildroot/usr/lib/modules/${KERNEL}/updates/dkms/zfs.ko" \ 14 | "${DESTDIR}/usr/lib/modules/${KERNEL}/updates/dkms/zfs.ko" 15 | -------------------------------------------------------------------------------- /incus-osd/api/images/update.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // UpdateFull represents an update entry in the index.json/index.sjson file. 8 | type UpdateFull struct { 9 | Update 10 | 11 | URL string `json:"url,omitempty"` 12 | } 13 | 14 | // Update represents the content of update.json/update.sjson. 15 | type Update struct { 16 | Format string `json:"format"` 17 | 18 | Channels []string `json:"channels"` 19 | Files []UpdateFile `json:"files"` 20 | Origin string `json:"origin"` 21 | PublishedAt time.Time `json:"published_at"` // In UTC. 22 | Severity UpdateSeverity `json:"severity"` 23 | Version string `json:"version"` 24 | } 25 | -------------------------------------------------------------------------------- /doc/reference/services.md: -------------------------------------------------------------------------------- 1 | # Services 2 | IncusOS system services are optional system-wide features, typically 3 | used to integrate with an external system. 4 | 5 | The majority of services today are centered around connecting to 6 | external storage or networking. 7 | 8 | ```{toctree} 9 | :maxdepth: 1 10 | 11 | Ceph 12 | iSCSI 13 | Linstor 14 | LVM 15 | Multipath 16 | NVMe 17 | OVN 18 | Tailscale 19 | USBIP 20 | 21 | Shared API 22 | ``` 23 | -------------------------------------------------------------------------------- /incus-osd/api/application.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // ApplicationConfig represents additional configuration for an application. 8 | type ApplicationConfig struct{} 9 | 10 | // Application represents the state and configuration of a generic application. 11 | type Application struct { 12 | State struct { 13 | Initialized bool `json:"initialized" yaml:"initialized"` 14 | Version string `json:"version" yaml:"version"` 15 | LastRestored *time.Time `json:"last_restored,omitempty" yaml:"last_restored,omitempty"` // In system's timezone. 16 | } `json:"state" yaml:"state"` 17 | 18 | Config ApplicationConfig `json:"config" yaml:"config"` 19 | } 20 | -------------------------------------------------------------------------------- /doc/reference.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | This reference documentation covers every aspect of IncusOS. 3 | 4 | It includes details for all installable applications, system services and general system configuration. 5 | In addition, it also provides some detailed background information on core features of IncusOS. 6 | 7 | ```{toctree} 8 | :maxdepth: 1 9 | 10 | Applications 11 | Services 12 | System configuration 13 | API 14 | Installing without a TPM 15 | Partitioning scheme 16 | Recovery 17 | Security 18 | Seed 19 | ``` 20 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/udev/rules.d/99-partlabel.rules: -------------------------------------------------------------------------------- 1 | # Use link_priority to deterministically create by-partlabel links, preferring CDROM, then USB stick, then local hard drive. 2 | 3 | # Hard drive 4 | ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}", OPTIONS="link_priority=100" 5 | 6 | # USB stick 7 | ENV{ID_BUS}=="usb", ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}", OPTIONS="link_priority=200" 8 | 9 | # CDROM 10 | ENV{DM_NAME}=="sr[0-9]p[0-9]", ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}", OPTIONS="link_priority=300" 11 | -------------------------------------------------------------------------------- /incus-osd/cmd/image-publisher/signing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/lxc/incus/v6/shared/subprocess" 8 | ) 9 | 10 | // Generate a detached signature if provided with a signing certificate. 11 | func sign(ctx context.Context, src string, dst string) error { 12 | if os.Getenv("SIG_KEY") == "" || os.Getenv("SIG_CERTIFICATE") == "" || os.Getenv("SIG_CHAIN") == "" { 13 | return nil 14 | } 15 | 16 | // Generate an SMIME signature. 17 | _, err := subprocess.RunCommandContext(ctx, "openssl", "smime", "-text", "-sign", "-signer", os.Getenv("SIG_CERTIFICATE"), "-inkey", os.Getenv("SIG_KEY"), "-in", src, "-out", dst, "-certfile", os.Getenv("SIG_CHAIN")) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /incus-osd/api/system_provider.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // SystemProviderConfig holds the modifiable part of the provider data. 4 | type SystemProviderConfig struct { 5 | Name string `json:"name" yaml:"name"` 6 | Config map[string]string `json:"config,omitempty" yaml:"config,omitempty"` 7 | } 8 | 9 | // SystemProviderState holds information about the current provider state. 10 | type SystemProviderState struct { 11 | Registered bool `json:"registered" yaml:"registered"` 12 | } 13 | 14 | // SystemProvider defines a struct to hold information about the system's update and configuration provider. 15 | type SystemProvider struct { 16 | Config SystemProviderConfig `json:"config" yaml:"config"` 17 | State SystemProviderState `json:"state" yaml:"state"` 18 | } 19 | -------------------------------------------------------------------------------- /incus-osd/internal/providers/errors.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // ErrProviderUnavailable is returned when a provider isn't ready for use yet. 8 | var ErrProviderUnavailable = errors.New("provider isn't currently available") 9 | 10 | // ErrNoUpdateAvailable is returned if no OS or application update is available. 11 | var ErrNoUpdateAvailable = errors.New("no update available") 12 | 13 | // ErrRegistrationUnsupported is returned if the provider doesn't (currently) support registration. 14 | var ErrRegistrationUnsupported = errors.New("registration unsupported") 15 | 16 | // ErrDeregistrationUnsupported is returned if the provider doesn't (currently) support deregistration. 17 | var ErrDeregistrationUnsupported = errors.New("deregistration unsupported") 18 | -------------------------------------------------------------------------------- /mkosi.images/incus/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ "$1" = "final" ] || exit 0 3 | 4 | # Get the repository keyring key. 5 | if [ ! -e /etc/apt/keyrings/zabbly.asc ]; then 6 | mkdir -p /etc/apt/keyrings/ 7 | curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc 8 | fi 9 | 10 | # Add the repository. 11 | cat < /etc/apt/sources.list.d/zabbly-incus-stable.sources 12 | Enabled: yes 13 | Types: deb 14 | URIs: https://pkgs.zabbly.com/incus/stable 15 | Suites: trixie 16 | Components: main 17 | Signed-By: /etc/apt/keyrings/zabbly.asc 18 | 19 | EOF 20 | 21 | # Install the incus packages. 22 | apt-get update 23 | apt-get install incus incus-ui-canonical --yes 24 | 25 | # Install additional dependencies/recommends. 26 | apt-get install btrfs-progs xfsprogs --yes 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/initrd-swtpm.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Start swtpm 3 | After=boot.mount 4 | Requires=boot.mount 5 | Before=cryptsetup.target systemd-cryptsetup@root.service systemd-repart.service 6 | DefaultDependencies=no 7 | ConditionPathExists=/boot/swtpm/ 8 | 9 | [Service] 10 | Type=exec 11 | 12 | ExecStartPre=/usr/bin/rm -f /boot/swtpm/tpm2-00.volatilestate 13 | ExecStartPre=/usr/sbin/modprobe tpm_vtpm_proxy 14 | ExecStart=/usr/bin/swtpm chardev --tpm2 --vtpm-proxy --tpmstate dir=/boot/swtpm/ --ctrl type=unixio,path=/run/swtpm.sock 15 | ExecStartPost=/usr/bin/sleep 1 16 | ExecStartPost=/usr/bin/measure-pcrs 17 | ExecStartPost=/usr/bin/swtpm_ioctl --unix /run/swtpm.sock -v 18 | 19 | [Install] 20 | WantedBy=cryptsetup.target systemd-cryptsetup@root.service systemd-repart.service 21 | -------------------------------------------------------------------------------- /incus-osd/api/service_usbip.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceUSBIPTarget represents a single USBIP target. 4 | type ServiceUSBIPTarget struct { 5 | Address string `json:"address" yaml:"address"` 6 | BusID string `json:"bus_id" yaml:"bus_id"` 7 | } 8 | 9 | // ServiceUSBIPConfig represents additional configuration for the USBIP service. 10 | type ServiceUSBIPConfig struct { 11 | Targets []ServiceUSBIPTarget `json:"targets" yaml:"targets"` 12 | } 13 | 14 | // ServiceUSBIPState represents state for the USBIP service. 15 | type ServiceUSBIPState struct{} 16 | 17 | // ServiceUSBIP represents the state and configuration of the USBIP service. 18 | type ServiceUSBIP struct { 19 | State ServiceUSBIPState `incusos:"-" json:"state" yaml:"state"` 20 | 21 | Config ServiceUSBIPConfig `json:"config" yaml:"config"` 22 | } 23 | -------------------------------------------------------------------------------- /doc/reference/system.md: -------------------------------------------------------------------------------- 1 | # System configuration 2 | IncusOS runs a management daemon which is responsible not just of 3 | starting and stopping the various applications and services but also of 4 | correctly bringing up system networking and storage, handle updates and 5 | manage encryption keys. 6 | 7 | Those core system functions each have their own state and configuration 8 | as well as relevant actions. 9 | 10 | ```{toctree} 11 | :maxdepth: 1 12 | 13 | Backup/Restore 14 | Logging 15 | Network 16 | Power 17 | Providers 18 | Resources 19 | Security 20 | Storage 21 | Update 22 | ``` 23 | -------------------------------------------------------------------------------- /incus-osd/api/service_tailscale.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceTailscale represents the state and configuration of the Tailscale service. 4 | type ServiceTailscale struct { 5 | State struct{} `json:"state" yaml:"state"` 6 | 7 | Config struct { 8 | Enabled bool `json:"enabled" yaml:"enabled"` 9 | LoginServer string `json:"login_server" yaml:"login_server"` 10 | AuthKey string `json:"auth_key" yaml:"auth_key"` 11 | AcceptRoutes bool `json:"accept_routes" yaml:"accept_routes"` 12 | AdvertisedRoutes []string `json:"advertised_routes" yaml:"advertised_routes"` 13 | ServeEnabled bool `json:"serve_enabled" yaml:"serve_enabled"` 14 | ServePort int16 `json:"serve_port" yaml:"serve_port"` 15 | } `json:"config" yaml:"config"` 16 | } 17 | -------------------------------------------------------------------------------- /incus-osd/cmd/flasher-tool/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // formatSection properly indents a text section. 8 | func formatSection(header string, content string) string { 9 | var out strings.Builder 10 | 11 | // Add section header 12 | if header != "" { 13 | _, _ = out.WriteString(header + ":\n") 14 | } 15 | 16 | // Indent the content 17 | for line := range strings.SplitSeq(content, "\n") { 18 | if line != "" { 19 | _, _ = out.WriteString(" ") 20 | } 21 | 22 | _, _ = out.WriteString(line + "\n") 23 | } 24 | 25 | if header != "" { 26 | // Section separator (when rendering a full section 27 | _, _ = out.WriteString("\n") 28 | 29 | return out.String() 30 | } 31 | 32 | // Remove last newline when rendering partial section 33 | return strings.TrimSuffix(out.String(), "\n") 34 | } 35 | -------------------------------------------------------------------------------- /incus-osd/cmd/image-publisher/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // formatSection properly indents a text section. 8 | func formatSection(header string, content string) string { 9 | var out strings.Builder 10 | 11 | // Add section header 12 | if header != "" { 13 | _, _ = out.WriteString(header + ":\n") 14 | } 15 | 16 | // Indent the content 17 | for line := range strings.SplitSeq(content, "\n") { 18 | if line != "" { 19 | _, _ = out.WriteString(" ") 20 | } 21 | 22 | _, _ = out.WriteString(line + "\n") 23 | } 24 | 25 | if header != "" { 26 | // Section separator (when rendering a full section 27 | _, _ = out.WriteString("\n") 28 | 29 | return out.String() 30 | } 31 | 32 | // Remove last newline when rendering partial section 33 | return strings.TrimSuffix(out.String(), "\n") 34 | } 35 | -------------------------------------------------------------------------------- /incus-osd/internal/tui/modal.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | // Modal holds the information for a given modal dialog. 4 | type Modal struct { 5 | title string 6 | category string 7 | message string 8 | progress float64 9 | isDone bool 10 | 11 | t *TUI 12 | } 13 | 14 | // Update sets the current message of the modal dialog. 15 | func (m *Modal) Update(message string) { 16 | m.message = message 17 | 18 | m.t.quickDraw() 19 | } 20 | 21 | // UpdateProgress sets the current modal's progress, expressed as a float between 0 and 1. 22 | func (m *Modal) UpdateProgress(progress float64) { 23 | m.progress = progress 24 | 25 | m.t.quickDraw() 26 | } 27 | 28 | // Done indicates that the modal is no longer needed and should be removed. 29 | func (m *Modal) Done() { 30 | m.isDone = true 31 | m.category = "__DONE__" 32 | 33 | m.t.quickDraw() 34 | } 35 | -------------------------------------------------------------------------------- /doc/reference/services/linstor.md: -------------------------------------------------------------------------------- 1 | # Linstor 2 | 3 | The [Linstor](https://linbit.com/linstor/) service allows connecting a Linstor deployment. In addition to Incus, the `incus-linstor` application must be installed to enable this service. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_linstor.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the Linstor service. 12 | 13 | * `listen_address`: The address and port to listen on (default to all addresses). 14 | 15 | * `tls_server_certificate`: When using TLS, the server certificate to use. 16 | 17 | * `tls_server_key`: When using TLS, the server key to use. 18 | 19 | * `tls_trusted_certificates`: When using TLS, the list of certificates to trust. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | debian-tools 3 | mkosi.output 4 | .mkosi-private 5 | 6 | mkosi.crt 7 | mkosi.key 8 | certs/ 9 | 10 | mkosi.images/base/mkosi.extra/boot/EFI/ 11 | mkosi.images/base/mkosi.extra/usr/local/bin/ 12 | mkosi.images/migration-manager/mkosi.extra/ 13 | mkosi.images/operations-center/mkosi.extra/ 14 | 15 | app-build/ 16 | 17 | incus-osd/flasher-tool 18 | incus-osd/generate-manifests 19 | incus-osd/image-customizer 20 | incus-osd/image-publisher 21 | incus-osd/incus-osd 22 | incus-osd/measure-pcrs 23 | 24 | mkosi.packages/initrd-tmpfs-root* 25 | 26 | *.swp 27 | 28 | # Sphinx 29 | doc/html/ 30 | doc/.sphinx/.doctrees/ 31 | doc/.sphinx/_static/swagger-ui/ 32 | doc/.sphinx/deps/ 33 | doc/.sphinx/venv/ 34 | doc/.sphinx/warnings.txt 35 | doc/.sphinx/.wordlist.dic 36 | doc/__pycache__ 37 | 38 | # MacOS 39 | .DS_Store 40 | __MACOSX/ 41 | .AppleDouble 42 | .LSOverride 43 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/initrd-tmpfs-root.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Mount rootfs as tmpfs when systemd-repart fails 3 | 4 | [Service] 5 | Type=oneshot 6 | 7 | ExecStart=/usr/bin/rm /run/systemd/generator.late/initrd-root-device.target.d/50-root-device.conf 8 | ExecStart=/usr/bin/touch /run/systemd/generator.late/initrd-root-device.target.d/50-root-device.conf 9 | ExecStart=/usr/bin/chattr +i /run/systemd/generator.late/initrd-root-device.target.d/50-root-device.conf 10 | 11 | ExecStart=/bin/sh -c '/usr/bin/echo -e "[Unit]\nDescription=Root Partition\nBefore=initrd-root-fs.target\nRequires=systemd-fsck-root.service\nAfter=systemd-fsck-root.service\n\n[Mount]\nWhat=tmpfs\nWhere=/sysroot\nType=tmpfs" > /run/systemd/generator.late/sysroot.mount' 12 | ExecStart=/usr/bin/chattr +i /run/systemd/generator.late/sysroot.mount 13 | 14 | ExecStart=/usr/bin/systemctl daemon-reload 15 | -------------------------------------------------------------------------------- /scripts/test/switch-secure-boot-signing-key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is a TEST script to switch a TEST secure boot signing key. 4 | 5 | set -e 6 | 7 | OS_NAME="TestOS" 8 | 9 | if [ "$#" -ne 1 ]; then 10 | echo "Usage: $0 " 11 | exit 1 12 | fi 13 | 14 | if [ ! -d certs/ ]; then 15 | echo "Directory './certs/' doesn't exist, exiting" 16 | exit 1 17 | fi 18 | 19 | rm -f ./mkosi.crt ./mkosi.key ./mkosi.images/base/mkosi.extra/boot/EFI/mkosi.der 20 | 21 | # mkosi seems to have several hard-coded assumptions that the secure boot key will always be called "mkosi.{crt,key}". 22 | ln -s "./certs/${OS_NAME}-secure-boot-$1.crt" ./mkosi.crt 23 | ln -s "./certs/${OS_NAME}-secure-boot-$1.key" ./mkosi.key 24 | 25 | mkdir -p mkosi.images/base/mkosi.extra/boot/EFI/ 26 | openssl x509 -in mkosi.crt -out mkosi.images/base/mkosi.extra/boot/EFI/mkosi.der -outform DER 27 | -------------------------------------------------------------------------------- /incus-osd/api/images/changelog.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | // Changelog represents the changes between two published IncusOS releases. 4 | type Changelog struct { 5 | CurrnetVersion string `json:"current_version" yaml:"current_version"` 6 | PriorVersion string `json:"prior_version" yaml:"prior_version"` 7 | Channel string `json:"channel" yaml:"channel"` 8 | Components map[string]ChangelogEntries `json:"components" yaml:"components"` 9 | } 10 | 11 | // ChangelogEntries lists packages/artifacts added, updated, or removed for a given component. 12 | type ChangelogEntries struct { 13 | Added []string `json:"added,omitempty" yaml:"added,omitempty"` 14 | Updated []string `json:"updated,omitempty" yaml:"updated,omitempty"` 15 | Removed []string `json:"removed,omitempty" yaml:"removed,omitempty"` 16 | } 17 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.conf.d/03-core-packages.conf: -------------------------------------------------------------------------------- 1 | [Content] 2 | Packages= 3 | apparmor 4 | ca-certificates 5 | cryptsetup 6 | curl 7 | dbus 8 | dosfstools 9 | e2fsprogs 10 | efitools 11 | erofs-utils 12 | gdisk 13 | iproute2 14 | lvm2 15 | lvm2-lockd 16 | multipath-tools 17 | nftables 18 | nvme-cli 19 | open-iscsi 20 | openvswitch-switch 21 | openzfs-zfsutils 22 | ovn-host 23 | polkitd 24 | prometheus-node-exporter 25 | sanlock 26 | smartmontools 27 | swtpm-tools 28 | systemd 29 | systemd-boot 30 | systemd-container 31 | systemd-cryptsetup 32 | systemd-netlogd 33 | systemd-repart 34 | systemd-resolved 35 | systemd-timesyncd 36 | tpm2-tools 37 | tzdata 38 | udev 39 | usbip 40 | wireguard-tools 41 | zstd 42 | RemoveFiles= 43 | /usr/lib/systemd/system/nftables.service 44 | -------------------------------------------------------------------------------- /incus-osd/api/system_logging.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // SystemLoggingSyslog contains the configuration options for a remote syslog server. 4 | type SystemLoggingSyslog struct { 5 | Address string `json:"address" yaml:"address"` 6 | Protocol string `json:"protocol" yaml:"protocol"` 7 | LogFormat string `json:"log_format" yaml:"log_format"` 8 | } 9 | 10 | // SystemLoggingConfig holds the modifiable part of the logging data. 11 | type SystemLoggingConfig struct { 12 | Syslog SystemLoggingSyslog `json:"syslog" yaml:"syslog"` 13 | } 14 | 15 | // SystemLoggingState represents state for the systme's logging configuration. 16 | type SystemLoggingState struct{} 17 | 18 | // SystemLogging defines a struct to hold information about the system's logging configuration. 19 | type SystemLogging struct { 20 | Config SystemLoggingConfig `json:"config" yaml:"config"` 21 | State SystemLoggingState `incusos:"-" json:"state" yaml:"state"` 22 | } 23 | -------------------------------------------------------------------------------- /app-build/update-application-tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import subprocess 5 | 6 | applications = {} 7 | with open("applications.json", "r") as f: 8 | applications = json.load(f) 9 | 10 | 11 | for app in applications: 12 | if app == "incus-osd": 13 | continue 14 | 15 | if applications[app]["version"] == "main": 16 | continue 17 | 18 | version = subprocess.run(["sh", "-c", """ git ls-remote --sort="v:refname" --tags "%s" 2>/dev/null | grep -v '{}' | grep -v \\\\.99 | grep -Ev 'rc|beta|alpha|pre' | tail -1 | sed "s#.*refs/tags/v##g" """ % applications[app]["repo"]], capture_output=True, check=True).stdout.strip().decode("utf-8") 19 | 20 | if applications[app]["version"] != version: 21 | print(app + " updated to version " + version) 22 | applications[app]["version"] = version 23 | 24 | with open("applications.json", "w") as f: 25 | json.dump(applications, f, indent=" ") 26 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: * 4 | Copyright: 2025 FuturFusion 5 | License: Apache-2.0 6 | 7 | License: Apache-2.0 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | . 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | . 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | Comment: 20 | On Debian systems, the complete text of the Apache version 2.0 license 21 | can be found in "/usr/share/common-licenses/Apache-2.0". 22 | -------------------------------------------------------------------------------- /mkosi.images/incus-linstor/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ "$1" = "final" ] || exit 0 3 | 4 | # Install the packages. 5 | mkdir -p /run/lock 6 | apt-get update 7 | apt-get install drbd-utils linstor-satellite lsscsi socat thin-send-recv --yes 8 | 9 | rm \ 10 | "/buildroot/usr/bin/java" \ 11 | "/buildroot/usr/bin/jexec" \ 12 | "/buildroot/usr/bin/jpackage" \ 13 | "/buildroot/usr/bin/keytool" \ 14 | "/buildroot/usr/bin/rmiregistry" 15 | 16 | ln -s "/usr/lib/jvm/java-21-openjdk-amd64/lib/jexec" "/buildroot/usr/bin/jexec" 17 | ln -s "/usr/lib/jvm/java-21-openjdk-amd64/bin/java" "/buildroot/usr/bin/java" 18 | ln -s "/usr/lib/jvm/java-21-openjdk-amd64/bin/jpackage" "/buildroot/usr/bin/jpackage" 19 | ln -s "/usr/lib/jvm/java-21-openjdk-amd64/bin/keytool" "/buildroot/usr/bin/keytool" 20 | ln -s "/usr/lib/jvm/java-21-openjdk-amd64/bin/rmiregistry" "/buildroot/usr/bin/rmiregistry" 21 | 22 | mv /buildroot/etc/java-21-openjdk/ /buildroot/usr/share/java-21-openjdk/ 23 | 24 | exit 0 25 | -------------------------------------------------------------------------------- /doc/reference/applications.md: -------------------------------------------------------------------------------- 1 | # Applications 2 | IncusOS itself doesn't listen on the network. For the system to be 3 | accessible and manageable over the network, it requires a primary 4 | application to be installed. 5 | 6 | The primary application is responsible for listening on the network and 7 | for handling user authentication. It then provides access to the IncusOS 8 | management API through its own API. 9 | 10 | IncusOS also supports [additional](applications/non-primary.md) (non-primary) 11 | applications which can extend the base system (for example for debugging) or 12 | provide additional features to another application. 13 | 14 | ```{toctree} 15 | :maxdepth: 1 16 | 17 | Incus 18 | Migration Manager 19 | Operations Center 20 | 21 | Shared API 22 | 23 | Non-primary applications 24 | ``` 25 | -------------------------------------------------------------------------------- /doc/.sphinx/.markdownlint/doc-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | 3 | if ! command -v mdl >/dev/null; then 4 | echo "Install mdl with 'apt install markdownlint' first." 5 | exit 1 6 | fi 7 | 8 | trap "rm -rf .tmp/" EXIT 9 | 10 | ## Preprocessing 11 | 12 | # shellcheck disable=SC2044 13 | for fn in $(find doc/ -name '*.md'); do 14 | mkdir -p "$(dirname .tmp/"$fn")"; 15 | sed -E "s/(\(.+\)=)/\1\n/" "$fn" > ".tmp/$fn"; 16 | done 17 | 18 | mdl .tmp/doc -sdoc/.sphinx/.markdownlint/style.rb -udoc/.sphinx/.markdownlint/rules.rb --ignore-front-matter > .tmp/errors.txt || true 19 | 20 | if [ ! -s ".tmp/errors.txt" ]; then 21 | echo "Passed!" 22 | exit 0 23 | fi 24 | 25 | ## Postprocessing 26 | 27 | sed '/^$/Q' -i .tmp/errors.txt 28 | filtered_errors="$(grep -vxFf doc/.sphinx/.markdownlint/exceptions.txt .tmp/errors.txt || true)" 29 | if [ -z "$filtered_errors" ]; then 30 | echo "Passed!" 31 | exit 0 32 | else 33 | echo "Failed!" 34 | echo "$filtered_errors" 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /incus-osd/api/service_iscsi.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceISCSITarget represents a single ISCSI target. 4 | type ServiceISCSITarget struct { 5 | Target string `json:"target" yaml:"target"` 6 | Address string `json:"address" yaml:"address"` 7 | Port int `json:"port" yaml:"port"` 8 | } 9 | 10 | // ServiceISCSIConfig represents additional configuration for the ISCSI service. 11 | type ServiceISCSIConfig struct { 12 | Enabled bool `json:"enabled" yaml:"enabled"` 13 | Targets []ServiceISCSITarget `json:"targets" yaml:"targets"` 14 | } 15 | 16 | // ServiceISCSI represents the state and configuration of the ISCSI service. 17 | type ServiceISCSI struct { 18 | State ServiceISCSIState `incusos:"-" json:"state" yaml:"state"` 19 | 20 | Config ServiceISCSIConfig `json:"config" yaml:"config"` 21 | } 22 | 23 | // ServiceISCSIState represents the state for the ISCSI service. 24 | type ServiceISCSIState struct { 25 | InitiatorName string `json:"initiator_name" yaml:"initiator_name"` 26 | } 27 | -------------------------------------------------------------------------------- /incus-osd/api/service_linstor.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceLinstorConfig represents the Linstor service configuration. 4 | type ServiceLinstorConfig struct { 5 | Enabled bool `json:"enabled" yaml:"enabled"` 6 | ListenAddress string `json:"listen_address" yaml:"listen_address"` 7 | TLSServerCertificate string `json:"tls_server_certificate" yaml:"tls_server_certificate"` 8 | TLSServerKey string `json:"tls_server_key" yaml:"tls_server_key"` 9 | TLSTrustedCertificates []string `json:"tls_trusted_certificates" yaml:"tls_trusted_certificates"` 10 | } 11 | 12 | // ServiceLinstorState represents state for the Linstor service. 13 | type ServiceLinstorState struct{} 14 | 15 | // ServiceLinstor represents the state and configuration of the Linstor service. 16 | type ServiceLinstor struct { 17 | State ServiceLinstorState `incusos:"-" json:"state" yaml:"state"` 18 | 19 | Config ServiceLinstorConfig `json:"config" yaml:"config"` 20 | } 21 | -------------------------------------------------------------------------------- /incus-osd/tests/incusos_tests/tests_incusos_api_system_resources.py: -------------------------------------------------------------------------------- 1 | from .incus_test_vm import IncusTestVM, IncusOSException, util 2 | 3 | def TestIncusOSAPISystemResources(install_image): 4 | test_name = "incusos-api-system-resources" 5 | test_seed = { 6 | "install.json": "{}", 7 | } 8 | 9 | test_image, incusos_version = util._prepare_test_image(install_image, test_seed) 10 | 11 | with IncusTestVM(test_name, test_image) as vm: 12 | vm.WaitSystemReady(incusos_version) 13 | 14 | # Perform a basic sanity check of the returned data. 15 | result = vm.APIRequest("/1.0/system/resources") 16 | if result["status_code"] != 200: 17 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 18 | 19 | keys = result["metadata"].keys() 20 | for key in ["cpu", "memory", "network", "storage"]: 21 | if key not in keys: 22 | raise IncusOSException(f"missing expected key {key} in returned resources") 23 | -------------------------------------------------------------------------------- /incus-osd/api/service_nvme.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceNVMETarget represents a single NVME target. 4 | type ServiceNVMETarget struct { 5 | Transport string `json:"transport" yaml:"transport"` 6 | Address string `json:"address" yaml:"address"` 7 | Port int `json:"port" yaml:"port"` 8 | } 9 | 10 | // ServiceNVMEConfig represents additional configuration for the NVME service. 11 | type ServiceNVMEConfig struct { 12 | Enabled bool `json:"enabled" yaml:"enabled"` 13 | Targets []ServiceNVMETarget `json:"targets" yaml:"targets"` 14 | } 15 | 16 | // ServiceNVME represents the state and configuration of the NVME service. 17 | type ServiceNVME struct { 18 | State ServiceNVMEState `incusos:"-" json:"state" yaml:"state"` 19 | 20 | Config ServiceNVMEConfig `json:"config" yaml:"config"` 21 | } 22 | 23 | // ServiceNVMEState represents the state for the NVME service. 24 | type ServiceNVMEState struct { 25 | HostID string `json:"host_id" yaml:"host_id"` 26 | HostNQN string `json:"host_nqn" yaml:"host_nqn"` 27 | } 28 | -------------------------------------------------------------------------------- /incus-osd/internal/applications/struct.go: -------------------------------------------------------------------------------- 1 | package applications 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "io" 7 | ) 8 | 9 | // Application represents an installed application. 10 | type Application interface { //nolint:interfacebloat 11 | AddTrustedCertificate(ctx context.Context, name string, cert string) error 12 | FactoryReset(ctx context.Context) error 13 | GetBackup(archive io.Writer, complete bool) error 14 | GetClientCertificate() (*tls.Certificate, error) 15 | GetDependencies() []string 16 | GetServerCertificate() (*tls.Certificate, error) 17 | Initialize(ctx context.Context) error 18 | IsPrimary() bool 19 | IsRunning(ctx context.Context) bool 20 | Name() string 21 | NeedsLateUpdateCheck() bool 22 | Restart(ctx context.Context, version string) error 23 | RestoreBackup(ctx context.Context, archive io.Reader) error 24 | Start(ctx context.Context, version string) error 25 | Stop(ctx context.Context, version string) error 26 | Update(ctx context.Context, version string) error 27 | WipeLocalData() error 28 | } 29 | -------------------------------------------------------------------------------- /incus-osd/internal/util/luks.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | // GetLUKSVolumePartitions returns the underlying partitions that hold the root and swap LUKS volumes. 11 | // We can't just rely on /dev/disk/by-partlabel/root-ARCH, because as soon as an overlay is applied 12 | // that symlink is repointed to the newly mapped loop device. 13 | func GetLUKSVolumePartitions() (map[string]string, error) { 14 | // /dev/disk/by-partlabel/swap should always point to the correct underlying device. 15 | linkDest, err := os.Readlink("/dev/disk/by-partlabel/swap") 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | absSwapDev := filepath.Join("/dev/disk/by-partlabel", linkDest) 21 | 22 | absRootDev, found := strings.CutSuffix(absSwapDev, "9") 23 | if !found { 24 | return nil, fmt.Errorf("unexpected swap device: '%s'", absSwapDev) 25 | } 26 | 27 | absRootDev += "10" 28 | 29 | return map[string]string{ 30 | "root": absRootDev, 31 | "swap": absSwapDev, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /doc/reference/applications/migration-manager.md: -------------------------------------------------------------------------------- 1 | # Migration Manager 2 | 3 | The Migration Manager application includes the latest tagged release of [Migration Manager](https://github.com/FuturFusion/migration-manager). 4 | 5 | At least one trusted client certificate must be provided in the Migration Manger seed, otherwise it will be impossible to authenticate to any API endpoint or the web UI post-install. 6 | 7 | ## Default configuration 8 | 9 | If no preseed configuration is provided, Migration Manager will start up listening on port 8443 on all network interfaces. Any trusted client certificate provided will be able to authenticate via API or web UI. 10 | 11 | ## Install seed details 12 | 13 | Important seed fields include: 14 | 15 | * `trusted_client_certificates`: An array of one or more PEM-encoded client certificates that should be trusted by default. 16 | 17 | * `preseed`: A struct referencing various Migration Manager system configuration options. For details, please review Migration Manager's [API](https://github.com/FuturFusion/migration-manager/blob/main/shared/api/system.go). 18 | -------------------------------------------------------------------------------- /doc/reference/applications/operations-center.md: -------------------------------------------------------------------------------- 1 | # Operations Center 2 | 3 | The Operations Center application includes the latest tagged release of [Operations Center](https://github.com/FuturFusion/operations-center). 4 | 5 | At least one trusted client certificate must be provided in the Operations Center seed, otherwise it will be impossible to authenticate to any API endpoint or the web UI post-install. 6 | 7 | ## Default configuration 8 | 9 | If no preseed configuration is provided, Operations Center will start up listening on port 8443 on all network interfaces. Any trusted client certificate provided will be able to authenticate via API or web UI. 10 | 11 | ## Install seed details 12 | 13 | Important seed fields include: 14 | 15 | * `trusted_client_certificates`: An array of one or more PEM-encoded client certificates that should be trusted by default. 16 | 17 | * `preseed`: A struct referencing various Operations Center system configuration options. For details, please review Operations Center's [API](https://github.com/FuturFusion/operations-center/blob/main/shared/api/system.go). 18 | -------------------------------------------------------------------------------- /incus-osd/tests/incusos_tests/tests_incusos_api.py: -------------------------------------------------------------------------------- 1 | from .incus_test_vm import IncusTestVM, IncusOSException, util 2 | 3 | def TestIncusOSAPI(install_image): 4 | test_name = "incusos-api" 5 | test_seed = { 6 | "install.json": "{}", 7 | } 8 | 9 | test_image, incusos_version = util._prepare_test_image(install_image, test_seed) 10 | 11 | with IncusTestVM(test_name, test_image) as vm: 12 | vm.WaitSystemReady(incusos_version) 13 | 14 | # Test top-level /1.0 endpoint. 15 | result = vm.APIRequest("/1.0") 16 | if result["status_code"] != 200: 17 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 18 | 19 | if result["metadata"]["environment"]["os_name"] != "IncusOS": 20 | raise IncusOSException("unexpected OS Name: " + result["metadata"]["environment"]["os_name"]) 21 | 22 | if result["metadata"]["environment"]["os_version"] != incusos_version: 23 | raise IncusOSException("unexpected OS Version: " + result["metadata"]["environment"]["os_version"]) 24 | -------------------------------------------------------------------------------- /doc/getting-started/requirements.md: -------------------------------------------------------------------------------- 1 | # System requirements 2 | IncusOS is designed to provide an extremely secure environment in which to 3 | run Incus. It requires a lot of modern system features and will not function 4 | properly on older unsupported systems. 5 | 6 | Minimum system requirements: 7 | 8 | - Modern Intel/AMD (`x86_64`) or ARM (`aarch64`) system 9 | - For `x86_64`, the CPU must support `x86_64_v3` 10 | - Support for UEFI with Secure Boot 11 | - {abbr}`TPM (Trusted Platform Module)` 2.0 security module 12 | - At least 4GiB of RAM (for system use only) 13 | - At least 50GiB of storage 14 | - At least one wired network port 15 | 16 | ```{note} 17 | For homelab and evaluation use, it is possible for IncusOS to rely on a software-backed TPM implementation. This is useful in scenarios such as running IncusOS on most consumer-grade ARM systems that may lack physical TPM chips. 18 | 19 | Be aware that this will weaken the overall security of the IncusOS server, and is not supported in enterprise deployments. For further details, see [Installing without a TPM](../reference/installing-without-tpm.md). 20 | ``` 21 | -------------------------------------------------------------------------------- /.github/workflows/commits.yml: -------------------------------------------------------------------------------- 1 | name: Commits 2 | on: 3 | - pull_request 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | dco-check: 10 | permissions: 11 | pull-requests: read # for tim-actions/get-pr-commits to get list of commits from the PR 12 | name: Signed-off-by (DCO) 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Get PR Commits 16 | id: 'get-pr-commits' 17 | uses: tim-actions/get-pr-commits@master 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | - name: Check that all commits are signed-off 22 | uses: tim-actions/dco@master 23 | with: 24 | commits: ${{ steps.get-pr-commits.outputs.commits }} 25 | 26 | target-branch: 27 | permissions: 28 | contents: none 29 | name: Branch target 30 | runs-on: ubuntu-22.04 31 | steps: 32 | - name: Check branch target 33 | env: 34 | TARGET: ${{ github.event.pull_request.base.ref }} 35 | run: | 36 | set -x 37 | [ "${TARGET}" = "main" ] && exit 0 38 | 39 | echo "Invalid branch target: ${TARGET}" 40 | exit 1 41 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system/swtpm.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Start swtpm 3 | After=boot.mount 4 | Requires=boot.mount 5 | Before=cryptsetup.target systemd-cryptsetup@root.service systemd-cryptsetup@swap.service incus-osd.service systemd-repart.service 6 | DefaultDependencies=no 7 | ConditionPathExists=/boot/swtpm/ 8 | 9 | [Service] 10 | Type=exec 11 | 12 | ExecStartPre=/usr/sbin/modprobe tpm_vtpm_proxy 13 | ExecStart=/usr/bin/swtpm chardev --tpm2 --vtpm-proxy --tpmstate dir=/boot/swtpm/ 14 | ExecStartPost=/usr/bin/sleep 1 15 | # Extend PCR11 with the "leave-initrd", "sysinit", and "ready" userspace TPM events 16 | ExecStartPost=/usr/bin/tpm2_pcrextend 11:sha256=3be261aff7db92bf507eae947f4003ffa2bcad0bffe3524601d62d0bc8be7135 17 | ExecStartPost=/usr/bin/tpm2_pcrextend 11:sha256=730bb5a583ba880c277e656d2dc8aba1a314a11b14d25b05153d2bab82567a48 18 | ExecStartPost=/usr/bin/tpm2_pcrextend 11:sha256=b24d6d33736ecd5604a4b17bc9c6481039fac362bb7df044ef1c10a2bfd21db6 19 | 20 | [Install] 21 | WantedBy=cryptsetup.target systemd-cryptsetup@root.service systemd-cryptsetup@swap.service incus-osd.service systemd-repart.service 22 | -------------------------------------------------------------------------------- /.github/workflows/daily.yml: -------------------------------------------------------------------------------- 1 | name: Daily 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | api-tests: 11 | name: IncusOS API tests 12 | strategy: 13 | fail-fast: false 14 | timeout-minutes: 360 15 | runs-on: 16 | - self-hosted 17 | - cpu-4 18 | - mem-8G 19 | - disk-100G 20 | - arch-amd64 21 | - image-debian-13 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: stable 30 | 31 | - name: Install dependencies 32 | run: | 33 | sudo apt-get install --yes \ 34 | dosfstools \ 35 | gdisk \ 36 | genisoimage \ 37 | mtools 38 | 39 | - name: Setup Incus 40 | run: | 41 | curl https://pkgs.zabbly.com/get/incus-daily | sudo sh 42 | sudo chmod 666 /var/lib/incus/unix.socket 43 | incus admin init --auto 44 | 45 | - name: Run API tests 46 | run: | 47 | incus-osd/tests/api-tests.py 48 | -------------------------------------------------------------------------------- /doc/reference/system/providers.md: -------------------------------------------------------------------------------- 1 | # Providers 2 | 3 | IncusOS receives [updates](update.md) from the currently configured provider. Two providers are supported: 4 | 5 | * `images`: The default IncusOS provider, which fetches updates from the [Linux Containers {abbr}`CDN (Content Delivery Network)`](https://images.linuxcontainers.org/os/). 6 | 7 | * `operations-center`: When IncusOS is deployed in a managed environment controlled by [Operations Center](../applications/operations-center.md), it is registered with the `operations-center` provider. This allows an administrator to centrally control all IncusOS systems, even in restricted or air-gaped environments that may not have Internet access. 8 | 9 | ## Configuration options 10 | 11 | Configuration fields are defined in the [`SystemProviderConfig` struct](https://github.com/lxc/incus-os/blob/main/incus-osd/api/system_provider.go). 12 | 13 | The following configuration options can be set: 14 | 15 | * `name`: The name of the provider. One of `images`, `operations-center`, or `local`. `local` is intended for use by developers working on IncusOS. 16 | 17 | * `config`: A map of provider-specific configuration key-value pairs. 18 | -------------------------------------------------------------------------------- /incus-osd/internal/services/struct.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | // Service represents a system service. 9 | type Service interface { 10 | Get(ctx context.Context) (any, error) 11 | ShouldStart() bool 12 | Reset(ctx context.Context) error 13 | Start(ctx context.Context) error 14 | Stop(ctx context.Context) error 15 | Struct() any 16 | Supported() bool 17 | Update(ctx context.Context, req any) error 18 | } 19 | 20 | type common struct{} 21 | 22 | func (*common) Get(_ context.Context) (any, error) { 23 | return nil, nil //nolint:nilnil 24 | } 25 | 26 | func (*common) ShouldStart() bool { 27 | return true 28 | } 29 | 30 | func (*common) Start(_ context.Context) error { 31 | return nil 32 | } 33 | 34 | func (*common) Stop(_ context.Context) error { 35 | return nil 36 | } 37 | 38 | func (*common) Reset(_ context.Context) error { 39 | return errors.New("Reset isn't supported by this service") 40 | } 41 | 42 | func (*common) Struct() any { 43 | return nil 44 | } 45 | 46 | func (*common) Supported() bool { 47 | return true 48 | } 49 | 50 | func (*common) Update(_ context.Context, _ any) error { 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /incus-osd/api/service_ovn.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceOVNConfig represents additional configuration for the OVN service. 4 | type ServiceOVNConfig struct { 5 | Enabled bool `json:"enabled" yaml:"enabled"` 6 | ICChassis bool `json:"ic_chassis" yaml:"ic_chassis"` 7 | Database string `json:"database" yaml:"database"` 8 | TLSClientCertificate string `json:"tls_client_certificate" yaml:"tls_client_certificate"` 9 | TLSClientKey string `json:"tls_client_key" yaml:"tls_client_key"` 10 | TLSCACertificate string `json:"tls_ca_certificate" yaml:"tls_ca_certificate"` 11 | TunnelAddress string `json:"tunnel_address" yaml:"tunnel_address"` 12 | TunnelProtocol string `json:"tunnel_protocol" yaml:"tunnel_protocol"` 13 | } 14 | 15 | // ServiceOVNState represents state for the OVN service. 16 | type ServiceOVNState struct{} 17 | 18 | // ServiceOVN represents the state and configuration of the OVN service. 19 | type ServiceOVN struct { 20 | State ServiceOVNState `incusos:"-" json:"state" yaml:"state"` 21 | 22 | Config ServiceOVNConfig `json:"config" yaml:"config"` 23 | } 24 | -------------------------------------------------------------------------------- /doc/reference/services/tailscale.md: -------------------------------------------------------------------------------- 1 | # Tailscale 2 | 3 | The [Tailscale](https://tailscale.com/) service allows configuring a Tailscale VPN client. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_tailscale.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the Tailscale service. 12 | 13 | * `login_server`: The Tailscale login server. 14 | 15 | * `auth_key`: A Tailscale authentication key. 16 | 17 | * `accept_routes`: If `true`, accept routes. 18 | 19 | * `advertised_routes`: An array of routes to advertise. 20 | 21 | * `serve_enabled`: If `true`, expose `localhost:8443` (typically the Incus application) via [Tailscale Serve](https://tailscale.com/kb/1242/tailscale-serve) 22 | 23 | * `serve_port`: TCP port to expose the HTTPS server to, for example `443` would expose the Incus application on: `https://{hostname}.{tailnet}.ts.net:443/` 24 | 25 | ```{warning} 26 | Enabling Tailscale Serve requires provisioning HTTPS certificates on the dashboard beforehand ([documentation](https://tailscale.com/kb/1153/enabling-https#configure-https)) 27 | ``` 28 | -------------------------------------------------------------------------------- /incus-osd/internal/systemd/netlogd.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/lxc/incus-os/incus-osd/api" 9 | ) 10 | 11 | // SetSyslog sets the system's remote syslog configuration. 12 | func SetSyslog(ctx context.Context, syslog api.SystemLoggingSyslog) error { 13 | // Handle disabling logging. 14 | if syslog.Address == "" { 15 | err := os.Remove("/etc/systemd/netlogd.conf") 16 | if err != nil && !os.IsNotExist(err) { 17 | return err 18 | } 19 | 20 | return StopUnit(ctx, "systemd-netlogd") 21 | } 22 | 23 | // Set defaults. 24 | if syslog.Protocol == "" { 25 | syslog.Protocol = "udp" 26 | } 27 | 28 | if syslog.LogFormat == "" { 29 | syslog.LogFormat = "rfc5424" 30 | } 31 | 32 | // Write the configuration. 33 | w, err := os.Create("/etc/systemd/netlogd.conf") 34 | if err != nil { 35 | return err 36 | } 37 | 38 | defer func() { _ = w.Close() }() 39 | 40 | _, err = fmt.Fprintf(w, `[Network] 41 | Address=%s 42 | Protocol=%s 43 | LogFormat=%s 44 | `, syslog.Address, syslog.Protocol, syslog.LogFormat) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | // Start the daemon. 50 | return RestartUnit(ctx, "systemd-netlogd") 51 | } 52 | -------------------------------------------------------------------------------- /doc/support.md: -------------------------------------------------------------------------------- 1 | # Support 2 | ## Releases 3 | IncusOS uses a rolling release model. 4 | 5 | A new stable release is tagged at least once a week to pick up the latest bug fixes to the Linux kernel, Incus and any other component that we ship. 6 | 7 | When reporting an issue, please first ensure that your system is running the latest stable release. 8 | 9 | ```{note} 10 | The use of other update channels like `testing` isn't supported and should be limited to development and debugging use on non-critical systems. 11 | ``` 12 | 13 | ## Support and community 14 | 15 | The following channels are available for you to interact with the IncusOS community. 16 | 17 | ## Bug reports 18 | 19 | You can file bug reports and feature requests at: [`https://github.com/lxc/incus-os/issues/new`](https://github.com/lxc/incus-os/issues/new) 20 | 21 | ## Community support 22 | 23 | Community support is handled at: [`https://discuss.linuxcontainers.org`](https://discuss.linuxcontainers.org) 24 | 25 | ## Commercial support 26 | 27 | Commercial support is currently available from [Zabbly](https://zabbly.com) for standalone IncusOS deployments. 28 | For large scale deployments of IncusOS, support can be obtained from [FuturFusion](https://futurfusion.io) as part of FuturFusion Cloud. 29 | -------------------------------------------------------------------------------- /incus-osd/api/weekday.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Weekday defines our own type. The time package's Weekday doesn't include any way to indicate 8 | // an empty value, which we need. 9 | type Weekday string 10 | 11 | // Names of each day of the week. 12 | const ( 13 | NONE Weekday = "" 14 | Sunday Weekday = "Sunday" 15 | Monday Weekday = "Monday" 16 | Tuesday Weekday = "Tuesday" 17 | Wednesday Weekday = "Wednesday" 18 | Thursday Weekday = "Thursday" 19 | Friday Weekday = "Friday" 20 | Saturday Weekday = "Saturday" 21 | ) 22 | 23 | // ToWeekday converts our string representation into the time package's int-based Weekeday. 24 | // It is assumed the value has already been checked to be a valid weekday, as no such 25 | // error checking is performed here. 26 | func (w Weekday) ToWeekday() time.Weekday { 27 | switch w { //nolint:exhaustive 28 | case Sunday: 29 | return time.Sunday 30 | case Monday: 31 | return time.Monday 32 | case Tuesday: 33 | return time.Tuesday 34 | case Wednesday: 35 | return time.Wednesday 36 | case Thursday: 37 | return time.Thursday 38 | case Friday: 39 | return time.Friday 40 | case Saturday: 41 | return time.Saturday 42 | default: 43 | return -1 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /doc/reference/services/ovn.md: -------------------------------------------------------------------------------- 1 | # {abbr}`OVN (Open Virtual Network)` 2 | 3 | The [{abbr}`OVN (Open Virtual Network)`](https://www.ovn.org/) service allows configuring an OVN software defined network. 4 | 5 | ## Configuration options 6 | 7 | The full API structs for the service can be viewed [online](https://github.com/lxc/incus-os/blob/main/incus-osd/api/service_ovn.go). 8 | 9 | The following configuration options can be set: 10 | 11 | * `enabled`: If `true`, enable the OVN service. 12 | 13 | * `ic_chassis`: Boolean indicating if the chassis is used as an interconnection gateway. 14 | 15 | * `database`: The OVN database that the system should connect to for its configuration. 16 | 17 | * `tls_client_certificate`: A PEM-encoded client certificate. 18 | 19 | * `tls_client_key`: A PEM-encoded client key. 20 | 21 | * `tls_ca_certificate`: A PEM-encoded CA certificate. 22 | 23 | * `tunnel_address`: The IP address that a chassis should use to connect to this node using encapsulation types specified by `tunnel_protocol`. Multiple encapsulation IPs may be specified with a comma-separated list. 24 | 25 | * `tunnel_protocol`: The encapsulation type that a chassis should use to connect to this node. Multiple encapsulation types may be specified with a comma-separated list. 26 | -------------------------------------------------------------------------------- /incus-osd/api/service_ceph.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceCephCluster represents a single Ceph cluster. 4 | type ServiceCephCluster struct { 5 | FSID string `json:"fsid" yaml:"fsid"` 6 | Monitors []string `json:"monitors" yaml:"monitors"` 7 | Keyrings map[string]ServiceCephKeyring `json:"keyrings" yaml:"keyrings"` 8 | ClientConfig map[string]string `json:"client_config" yaml:"client_config"` 9 | } 10 | 11 | // ServiceCephKeyring represents a single Ceph keyring entry. 12 | type ServiceCephKeyring struct { 13 | Key string `json:"key" yaml:"key"` 14 | } 15 | 16 | // ServiceCephConfig represents additional configuration for the Ceph service. 17 | type ServiceCephConfig struct { 18 | Enabled bool `json:"enabled" yaml:"enabled"` 19 | Clusters map[string]ServiceCephCluster `json:"clusters" yaml:"clusters"` 20 | } 21 | 22 | // ServiceCephState represents state for the Ceph service. 23 | type ServiceCephState struct{} 24 | 25 | // ServiceCeph represents the state and configuration of the Ceph service. 26 | type ServiceCeph struct { 27 | State ServiceCephState `incusos:"-" json:"state" yaml:"state"` 28 | 29 | Config ServiceCephConfig `json:"config" yaml:"config"` 30 | } 31 | -------------------------------------------------------------------------------- /incus-osd/tests/incusos_tests/tests_incusos_api_services.py: -------------------------------------------------------------------------------- 1 | from .incus_test_vm import IncusTestVM, IncusOSException, util 2 | 3 | ### TODO -- There's not really much actual testing of the individual services yet. 4 | 5 | def TestIncusOSAPIServices(install_image): 6 | test_name = "incusos-api-services" 7 | test_seed = { 8 | "install.json": "{}", 9 | } 10 | 11 | test_image, incusos_version = util._prepare_test_image(install_image, test_seed) 12 | 13 | with IncusTestVM(test_name, test_image) as vm: 14 | vm.WaitSystemReady(incusos_version) 15 | 16 | # Test top-level /1.0/services endpoint. 17 | result = vm.APIRequest("/1.0/services") 18 | if result["status_code"] != 200: 19 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 20 | 21 | if len(result["metadata"]) == 0: 22 | raise IncusOSException("expected at least one services endpoint") 23 | 24 | # Do a simple query of each service. 25 | for service in result["metadata"]: 26 | result = vm.APIRequest(service) 27 | if result["status_code"] != 200: 28 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 29 | -------------------------------------------------------------------------------- /doc/.wordlist.txt: -------------------------------------------------------------------------------- 1 | backend 2 | CDN 3 | CDROM 4 | Ceph 5 | CIDR 6 | CPUs 7 | customizations 8 | customizer 9 | datastore 10 | db 11 | dbx 12 | DCO 13 | decrypt 14 | DHCP 15 | DNS 16 | ECDSA 17 | EFI 18 | EOF 19 | ESXi 20 | FAT 21 | fibre 22 | formatters 23 | Furo 24 | FuturFusion 25 | GiB 26 | Github 27 | homelab 28 | hotfix 29 | HTTPS 30 | Incus 31 | IncusOS 32 | IPs 33 | IPv 34 | iSCSI 35 | ISO 36 | JSON 37 | KEK 38 | Kerberos 39 | libvirt 40 | Linstor 41 | LLDP 42 | LLMs 43 | LUKS 44 | LVM 45 | MAC 46 | MACs 47 | MacOS 48 | MOK 49 | MTU 50 | multipath 51 | Multipath 52 | NAT'ed 53 | Netbird 54 | NICs 55 | NTP 56 | NVMe 57 | OCI 58 | OEM 59 | OVMF 60 | OVN 61 | OVS 62 | parsable 63 | PCR 64 | PCRs 65 | PEM 66 | PK 67 | PKCS 68 | Pre 69 | preseed 70 | proxied 71 | Proxmox 72 | Proxmox's 73 | raidz 74 | RaspberryPi 75 | resilver 76 | RSA 77 | SLAAC 78 | struct 79 | structs 80 | syslog 81 | systemd 82 | systemd's 83 | Tailscale 84 | TCP 85 | TDB 86 | TLS 87 | TPM 88 | UDP 89 | UEFI 90 | UI 91 | UKI 92 | Uncheck 93 | Unencrypted 94 | unencrypted 95 | unmanaged 96 | Unmount 97 | USBIP 98 | VirtIO 99 | VirtualBox 100 | VLAN 101 | VLANs 102 | VMware 103 | VPN 104 | vSphere 105 | WWN 106 | YAML 107 | Zabbly 108 | ZFS 109 | -------------------------------------------------------------------------------- /incus-osd/api/seed/migration_manager.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "github.com/FuturFusion/migration-manager/shared/api" 5 | ) 6 | 7 | // MigrationManager represents a Migration Manager seed file. 8 | type MigrationManager struct { 9 | Version string `json:"version" yaml:"version"` 10 | 11 | // A list of PEM-encoded trusted client certificates. The SHA256 fingerprint of 12 | // each certificate will be added to the list of any SHA256 fingerprints provided 13 | // in SystemSecurity.TrustedTLSClientCertFingerprints. 14 | TrustedClientCertificates []string `json:"trusted_client_certificates,omitempty" yaml:"trusted_client_certificates,omitempty"` 15 | 16 | ApplyDefaults bool `json:"apply_defaults" yaml:"apply_defaults"` 17 | Preseed *MigrationManagerPreseed `json:"preseed" yaml:"preseed"` 18 | } 19 | 20 | // MigrationManagerPreseed holds seed configuration for Migration Manager. 21 | type MigrationManagerPreseed struct { 22 | SystemCertificate *api.SystemCertificatePost `json:"system_certificate,omitempty" yaml:"system_certificate,omitempty"` 23 | SystemNetwork *api.SystemNetwork `json:"system_network,omitempty" yaml:"system_network,omitempty"` 24 | SystemSecurity *api.SystemSecurity `json:"system_security,omitempty" yaml:"system_security,omitempty"` 25 | } 26 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/system-preset/00-incus-os.preset: -------------------------------------------------------------------------------- 1 | # iSCSI 2 | disable iscsid.service 3 | disable iscsid.socket 4 | disable open-iscsi.service 5 | 6 | # LVM 7 | disable lvm2-monitor.service 8 | disable lvmlockd.service 9 | disable lvmlocks.service 10 | disable sanlock.service 11 | disable wdmd.service 12 | 13 | # Multipath 14 | disable multipathd.service 15 | disable multipathd.socket 16 | 17 | # OVN 18 | disable openvswitch-switch.service 19 | disable ovs-record-hostname.service 20 | 21 | # System 22 | disable dpkg-db-backup.service 23 | disable dpkg-db-backup.timer 24 | disable systemd-journald-audit.socket 25 | disable systemd-netlogd.service 26 | disable systemd-sysupdate-reboot.service 27 | disable systemd-sysupdate-reboot.timer 28 | disable systemd-sysupdate.service 29 | disable systemd-sysupdate.timer 30 | disable systemd-timesyncd.service 31 | disable uuidd.socket 32 | 33 | # TPM (state is pre-calculated) 34 | disable systemd-pcrlock-file-system.service 35 | disable systemd-pcrlock-firmware-code.service 36 | disable systemd-pcrlock-firmware-config.service 37 | disable systemd-pcrlock-machine-id.service 38 | disable systemd-pcrlock-make-policy.service 39 | disable systemd-pcrlock-secureboot-authority.service 40 | disable systemd-pcrlock-secureboot-policy.service 41 | 42 | # ZFS 43 | disable zfs-import-cache.service 44 | disable zfs-share.service 45 | -------------------------------------------------------------------------------- /scripts/spawn-image: -------------------------------------------------------------------------------- 1 | #!/bin/sh -eu 2 | if [ -z "${1:-}" ]; then 3 | echo "Usage: ${0} NAME" 4 | exit 1 5 | fi 6 | 7 | # Check if instance already exists 8 | if incus info "${1}" >/dev/null 2>&1; then 9 | echo "Instance ${1} already exists" 10 | exit 1 11 | fi 12 | 13 | # Setup temporary directory 14 | cleanup() { 15 | rm -f IncusOS_*.iso 16 | } 17 | trap cleanup EXIT HUP INT TERM 18 | 19 | # Grab and configure the image 20 | INCUSOS_SEED_TAR=test/seed.install.tar INCUSOS_IMAGE_FORMAT=iso incus-osd/flasher-tool 21 | # shellcheck disable=SC2010 22 | IMG_NAME=$(ls IncusOS_*.iso | grep -v usr | grep -v esp | sort | tail -1) 23 | 24 | # Create an instance 25 | echo "=> Creating an IncusOS instance" 26 | incus create --vm --empty "${1}" \ 27 | -c security.secureboot=false \ 28 | -c limits.cpu=4 \ 29 | -c limits.memory=8GiB \ 30 | -d root,size=50GiB 31 | incus config device add "${1}" vtpm tpm 32 | incus config device add "${1}" boot-media disk source="$(pwd)/${IMG_NAME}" boot.priority=10 33 | 34 | echo "=> Starting IncusOS for installation" 35 | incus start "${1}" --console 36 | sleep 5 37 | incus console "${1}" 38 | sleep 5 39 | clear 40 | 41 | # Remove install media 42 | incus stop -f "${1}" 43 | incus config device remove "${1}" boot-media 44 | 45 | # Start the installed system 46 | echo "=> Starting installed IncusOS" 47 | incus start "${1}" --console 48 | -------------------------------------------------------------------------------- /incus-osd/api/images/update_file_architecture.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | // UpdateFileArchitecture represents the architecture for a given file. 4 | type UpdateFileArchitecture string 5 | 6 | const ( 7 | // UpdateFileArchitectureUndefined represents an unknown architecture. 8 | UpdateFileArchitectureUndefined UpdateFileArchitecture = "" 9 | 10 | // UpdateFileArchitecture64BitX86 represents an x86_64 system. 11 | UpdateFileArchitecture64BitX86 UpdateFileArchitecture = "x86_64" 12 | 13 | // UpdateFileArchitecture64BitARM represents an aarch64 system. 14 | UpdateFileArchitecture64BitARM UpdateFileArchitecture = "aarch64" 15 | ) 16 | 17 | // UpdateFileArchitectures is a map of the supported file architectures. 18 | var UpdateFileArchitectures = map[UpdateFileArchitecture]struct{}{ 19 | UpdateFileArchitectureUndefined: {}, 20 | UpdateFileArchitecture64BitX86: {}, 21 | UpdateFileArchitecture64BitARM: {}, 22 | } 23 | 24 | func (u *UpdateFileArchitecture) String() string { 25 | return string(*u) 26 | } 27 | 28 | // MarshalText implements the encoding.TextMarshaler interface. 29 | func (u *UpdateFileArchitecture) MarshalText() ([]byte, error) { 30 | return []byte(*u), nil 31 | } 32 | 33 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 34 | func (u *UpdateFileArchitecture) UnmarshalText(text []byte) error { 35 | *u = UpdateFileArchitecture(text) 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /scripts/inject-secure-boot-vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Inject signed default EFI secure boot variables into the final image. 4 | # There doesn't seem to be a nice mkosi hook to automate this. 5 | 6 | set -e 7 | 8 | if [ "$#" -ne 1 ]; then 9 | echo "Usage: $0 " 10 | exit 1 11 | fi 12 | 13 | if [ ! -d certs/ ]; then 14 | echo "Directory './certs/' doesn't exist, exiting" 15 | exit 1 16 | fi 17 | 18 | if [ "$(id -u)" -ne 0 ]; then 19 | echo "This script must be run as root" 20 | exit 1 21 | fi 22 | 23 | # Originally we had been loop-mounting the raw image and just copying in the 24 | # needed secure boot certificates. However, occasionally the mount failed in 25 | # CI runs. So, switch to using mtools to directly manipulate the ESP vfat 26 | # partition in the image so we don't need to mount anything. 27 | 28 | # This is the offset to the beginning of the ESP partition. 29 | OFFSET=1048576 30 | 31 | # Remove any existing certificates we will be overwriting. 32 | mdeltree -i "$1"@@$OFFSET ::loader/keys/auto/ || true 33 | mdeltree -i "$1"@@$OFFSET ::keys/ || true 34 | mdel -i "$1"@@$OFFSET ::mkosi.der || true 35 | 36 | # Push the new enrollment keys. 37 | mmd -i "$1"@@$OFFSET ::loader/keys/auto 38 | mcopy -i "$1"@@$OFFSET certs/efi/*.auth ::loader/keys/auto/ 39 | 40 | # Push the keys as DER. 41 | mmd -i "$1"@@$OFFSET ::keys 42 | mcopy -i "$1"@@$OFFSET certs/efi/*.der ::keys/ || true 43 | -------------------------------------------------------------------------------- /incus-osd/internal/keyring/keys.go: -------------------------------------------------------------------------------- 1 | package keyring 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // PlatformKeyring is the SecureBoot platform keyring. 11 | var PlatformKeyring = "1f010000" 12 | 13 | // Key represents a key in the Linux kernel keyring. 14 | type Key struct { 15 | Description string 16 | Fingerprint string 17 | Type string 18 | } 19 | 20 | // GetKeys returns a list of keys in the requested keyring. 21 | func GetKeys(_ context.Context, keyring string) ([]Key, error) { 22 | keys := []Key{} 23 | 24 | // Read the key list. 25 | fd, err := os.Open("/proc/keys") 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | defer fd.Close() 31 | 32 | // Iterate over the entries.. 33 | fdScan := bufio.NewScanner(fd) 34 | for fdScan.Scan() { 35 | fields := strings.Fields(fdScan.Text()) 36 | 37 | if len(fields) < 10 { 38 | // Skipping invalid entries. 39 | continue 40 | } 41 | 42 | if fields[4] != keyring { 43 | // Skipping entries outside of the platform (SecureBoot) keyring. 44 | continue 45 | } 46 | 47 | keyFields := strings.Split(strings.Join(fields[8:], " "), ": ") 48 | 49 | keys = append(keys, Key{ 50 | Description: strings.Join(keyFields[0:len(keyFields)-2], ": "), 51 | Fingerprint: keyFields[len(keyFields)-2], 52 | Type: strings.Fields(keyFields[len(keyFields)-1])[0], 53 | }) 54 | } 55 | 56 | return keys, nil 57 | } 58 | -------------------------------------------------------------------------------- /incus-osd/api/seed/operations_center.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | import ( 4 | "github.com/FuturFusion/operations-center/shared/api" 5 | ) 6 | 7 | // OperationsCenter represents an Operations Center seed file. 8 | type OperationsCenter struct { 9 | Version string `json:"version" yaml:"version"` 10 | 11 | // A list of PEM-encoded trusted client certificates. The SHA256 fingerprint of 12 | // each certificate will be added to the list of any SHA256 fingerprints provided 13 | // in SystemSecurity.TrustedTLSClientCertFingerprints. 14 | TrustedClientCertificates []string `json:"trusted_client_certificates,omitempty" yaml:"trusted_client_certificates,omitempty"` 15 | 16 | ApplyDefaults bool `json:"apply_defaults" yaml:"apply_defaults"` 17 | Preseed *OperationsCenterPreseed `json:"preseed" yaml:"preseed"` 18 | } 19 | 20 | // OperationsCenterPreseed holds seed configuration for Operations Center. 21 | type OperationsCenterPreseed struct { 22 | SystemCertificate *api.SystemCertificatePost `json:"system_certificate,omitempty" yaml:"system_certificate,omitempty"` 23 | SystemNetwork *api.SystemNetworkPut `json:"system_network,omitempty" yaml:"system_network,omitempty"` 24 | SystemSecurity *api.SystemSecurityPut `json:"system_security,omitempty" yaml:"system_security,omitempty"` 25 | SystemUpdates *api.SystemUpdatesPut `json:"system_updates,omitempty" yaml:"system_updates,omitempty"` 26 | } 27 | -------------------------------------------------------------------------------- /incus-osd/internal/providers/load.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/lxc/incus-os/incus-osd/internal/state" 9 | ) 10 | 11 | // Load gets a specific provider and initializes it with the provider configuration. 12 | func Load(ctx context.Context, s *state.State) (Provider, error) { 13 | var p Provider 14 | 15 | switch s.System.Provider.Config.Name { 16 | case "images": 17 | // Setup the images provider. 18 | p = &images{ 19 | state: s, 20 | } 21 | 22 | case "local": 23 | // Setup the local provider. 24 | p = &local{ 25 | state: s, 26 | } 27 | 28 | case "operations-center": 29 | // Setup the Operations Center provider. 30 | p = &operationsCenter{ 31 | state: s, 32 | } 33 | 34 | default: 35 | return nil, fmt.Errorf("unknown provider %q", s.System.Provider.Config.Name) 36 | } 37 | 38 | err := p.load(ctx) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return p, nil 44 | } 45 | 46 | // Refresh is a hook being called whenever the current provider should be refreshed. 47 | func Refresh(ctx context.Context, s *state.State) error { 48 | if s.System.Provider.Config.Name == "" { 49 | return nil 50 | } 51 | 52 | p, err := Load(ctx, s) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | err = p.RefreshRegister(ctx) 58 | if err != nil && !errors.Is(err, ErrRegistrationUnsupported) { 59 | return err 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /incus-osd/cli/cli_services.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | cli "github.com/lxc/incus/v6/shared/cmd" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // IncusOS service command. 9 | type cmdAdminOSService struct { 10 | os *cmdAdminOS 11 | } 12 | 13 | func (c *cmdAdminOSService) command() *cobra.Command { 14 | cmd := &cobra.Command{} 15 | cmd.Use = cli.Usage("service") 16 | cmd.Short = "Manage IncusOS services" 17 | cmd.Long = cli.FormatSection("Description", "Manage IncusOS services") 18 | 19 | // Edit. 20 | editCmd := cmdGenericEdit{os: c.os, entity: "service", entityShort: "service", endpoint: "services"} 21 | cmd.AddCommand(editCmd.command()) 22 | 23 | // List. 24 | listCmd := cmdGenericList{os: c.os, entity: "services", endpoint: "services"} 25 | cmd.AddCommand(listCmd.command()) 26 | 27 | // Reset. 28 | resetCmd := cmdGenericRun{ 29 | os: c.os, 30 | action: "reset", 31 | description: "Reset the service", 32 | endpoint: "services", 33 | entity: "service", 34 | confirm: "reset the service", 35 | } 36 | cmd.AddCommand(resetCmd.command()) 37 | 38 | // Show. 39 | showCmd := cmdGenericShow{os: c.os, entity: "service", entityShort: "service", endpoint: "services"} 40 | cmd.AddCommand(showCmd.command()) 41 | 42 | // Workaround for subcommand usage errors. See: https://github.com/spf13/cobra/issues/706. 43 | cmd.Args = cobra.NoArgs 44 | cmd.Run = func(cmd *cobra.Command, _ []string) { _ = cmd.Usage() } 45 | 46 | return cmd 47 | } 48 | -------------------------------------------------------------------------------- /incus-osd/internal/applications/load.go: -------------------------------------------------------------------------------- 1 | package applications 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/lxc/incus-os/incus-osd/internal/state" 8 | ) 9 | 10 | // ErrNoPrimary is returned when the system doesn't yet have a primary application. 11 | var ErrNoPrimary = errors.New("no primary application") 12 | 13 | // Load retrieves and returns the application specific logic. 14 | func Load(_ context.Context, s *state.State, name string) (Application, error) { 15 | var app Application 16 | 17 | switch name { 18 | case "debug": 19 | app = &debug{common: common{state: s}} 20 | case "incus": 21 | app = &incus{common: common{state: s}} 22 | case "incus-ceph": 23 | app = &incusCeph{common: common{state: s}} 24 | case "incus-linstor": 25 | app = &incusLinstor{common: common{state: s}} 26 | case "migration-manager": 27 | app = &migrationManager{common: common{state: s}} 28 | case "operations-center": 29 | app = &operationsCenter{common: common{state: s}} 30 | default: 31 | return nil, errors.New("unknown application") 32 | } 33 | 34 | return app, nil 35 | } 36 | 37 | // GetPrimary returns the current primary application. 38 | func GetPrimary(ctx context.Context, s *state.State) (Application, error) { 39 | for appName := range s.Applications { 40 | app, err := Load(ctx, s, appName) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | if app.IsPrimary() { 46 | return app, nil 47 | } 48 | } 49 | 50 | return nil, ErrNoPrimary 51 | } 52 | -------------------------------------------------------------------------------- /incus-osd/cli/cli_root.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | cli "github.com/lxc/incus/v6/shared/cmd" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // IncusOS management command. 12 | type cmdAdminOS struct { 13 | args *Args 14 | 15 | flagTarget string 16 | } 17 | 18 | func (c *cmdAdminOS) command() *cobra.Command { 19 | cmd := &cobra.Command{} 20 | cmd.Use = cli.Usage("os") 21 | cmd.Short = "Manage IncusOS systems" 22 | cmd.Long = cli.FormatSection("Description", "Manage IncusOS systems") 23 | 24 | // Applications. 25 | applicationCmd := cmdAdminOSApplication{os: c} 26 | cmd.AddCommand(applicationCmd.command()) 27 | 28 | // Debug. 29 | debugCmd := cmdAdminOSDebug{os: c} 30 | cmd.AddCommand(debugCmd.command()) 31 | 32 | // Services. 33 | serviceCmd := cmdAdminOSService{os: c} 34 | cmd.AddCommand(serviceCmd.command()) 35 | 36 | // Show. 37 | showCmd := cmdGenericShow{os: c} 38 | cmd.AddCommand(showCmd.command()) 39 | 40 | // System. 41 | systemCmd := cmdAdminOSSystem{os: c} 42 | cmd.AddCommand(systemCmd.command()) 43 | 44 | // Show a warning. 45 | cmd.PersistentPreRun = func(_ *cobra.Command, _ []string) { 46 | _, _ = fmt.Fprint(os.Stderr, "WARNING: The IncusOS API and configuration is subject to change\n\n") 47 | } 48 | 49 | // Workaround for subcommand usage errors. See: https://github.com/spf13/cobra/issues/706. 50 | cmd.Args = cobra.NoArgs 51 | cmd.Run = func(cmd *cobra.Command, _ []string) { _ = cmd.Usage() } 52 | 53 | return cmd 54 | } 55 | -------------------------------------------------------------------------------- /incus-osd/api/images/update_severity.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | // UpdateSeverity represents the severity field in an update. 4 | type UpdateSeverity string 5 | 6 | const ( 7 | // UpdateSeverityNone represents an unknown/unset severity. 8 | UpdateSeverityNone UpdateSeverity = "none" 9 | 10 | // UpdateSeverityLow represents the lowest severity. 11 | UpdateSeverityLow UpdateSeverity = "low" 12 | 13 | // UpdateSeverityMedium represents the medium severity. 14 | UpdateSeverityMedium UpdateSeverity = "medium" 15 | 16 | // UpdateSeverityHigh represents the high severity. 17 | UpdateSeverityHigh UpdateSeverity = "high" 18 | 19 | // UpdateSeverityCritical represents the critical severity. 20 | UpdateSeverityCritical UpdateSeverity = "critical" 21 | ) 22 | 23 | // UpdateSeverities is a map of the supported update severities. 24 | var UpdateSeverities = map[UpdateSeverity]struct{}{ 25 | UpdateSeverityNone: {}, 26 | UpdateSeverityLow: {}, 27 | UpdateSeverityMedium: {}, 28 | UpdateSeverityHigh: {}, 29 | UpdateSeverityCritical: {}, 30 | } 31 | 32 | func (u *UpdateSeverity) String() string { 33 | return string(*u) 34 | } 35 | 36 | // MarshalText implements the encoding.TextMarshaler interface. 37 | func (u *UpdateSeverity) MarshalText() ([]byte, error) { 38 | return []byte(*u), nil 39 | } 40 | 41 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 42 | func (u *UpdateSeverity) UnmarshalText(text []byte) error { 43 | *u = UpdateSeverity(text) 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /doc/.sphinx/.markdownlint/rules.rb: -------------------------------------------------------------------------------- 1 | rule 'Myst-MD031', 'Fenced code blocks should be surrounded by blank lines' do 2 | tags :code, :blank_lines 3 | aliases 'blanks-around-fences' 4 | check do |doc| 5 | errors = [] 6 | # Some parsers (including kramdown) have trouble detecting fenced code 7 | # blocks without surrounding whitespace, so examine the lines directly. 8 | in_code = false 9 | fence = nil 10 | lines = [''] + doc.lines + [''] 11 | lines.each_with_index do |line, linenum| 12 | line.strip.match(/^(`{3,}|~{3,})/) 13 | unless Regexp.last_match(1) && 14 | ( 15 | !in_code || 16 | (Regexp.last_match(1).slice(0, fence.length) == fence) 17 | ) 18 | next 19 | end 20 | 21 | fence = in_code ? nil : Regexp.last_match(1) 22 | in_code = !in_code 23 | if (in_code && !(lines[linenum - 1].empty? || lines[linenum - 1].match(/^[:\-\*]*\s*\% /))) || 24 | (!in_code && !(lines[linenum + 1].empty? || lines[linenum + 1].match(/^\s*:/))) 25 | errors << linenum 26 | end 27 | end 28 | errors 29 | end 30 | end 31 | 32 | 33 | rule 'Myst-IDs', 'MyST IDs should be preceded by a blank line' do 34 | check do |doc| 35 | errors = [] 36 | ids = doc.matching_text_element_lines(/^\(.+\)=\s*$/) 37 | ids.each do |linenum| 38 | if (linenum > 1) && !doc.lines[linenum - 2].empty? 39 | errors << linenum 40 | end 41 | end 42 | errors.sort 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /doc/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | # Installing IncusOS 2 | IncusOS is designed to run on modern physical hardware as that's the 3 | optimal environment to run an Incus server. 4 | 5 | But we also support having it run inside of a virtual machine, making it 6 | easier to evaluate or debug. In general, any physical or virtual 7 | environment which matches our [hardware requirements](requirements.md) 8 | should do fine. That said we recommend using generic storage and network 9 | adapters whenever possible, with NVMe, VirtIO or Intel virtual devices 10 | usually being preferred. 11 | 12 | ```{note} 13 | For virtual machines, storage drives should be configured to use the `VirtIO-scsi` driver. Using `VirtIO-blk` does not work as the resulting drives will not appear to IncusOS in the same way as physical drives do. 14 | ``` 15 | 16 | ## Supported platforms 17 | 18 | ```{toctree} 19 | :maxdepth: 1 20 | 21 | Installing on hardware 22 | Installing on Incus 23 | Installing on libvirt 24 | Installing on Proxmox 25 | Installing on VirtualBox 26 | Installing on VMware 27 | ``` 28 | 29 | ## Unsupported platforms 30 | 31 | So far we're aware that IncusOS cannot be installed on top of Microsoft 32 | Hyper-V due to that virtualization platform not supporting custom Secure 33 | Boot keys. 34 | -------------------------------------------------------------------------------- /doc/reference/api.md: -------------------------------------------------------------------------------- 1 | # REST API 2 | 3 | ```{note} 4 | The IncusOS API is typically proxied through an installed application, such as [Incus](applications/incus.md). 5 | 6 | If interacting with the API manually, you will need to prefix `/os/` to correctly reach the IncusOS endpoints. For example, to get a list of applications you could run `curl https://1.2.3.4:8443/os/1.0/applications`. 7 | ``` 8 | 9 | ```{warning} 10 | The IncusOS debug API endpoints have no guarantee of API stability, and should not be used 11 | in normal day-to-day operations. 12 | ``` 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 41 | -------------------------------------------------------------------------------- /incus-osd/cmd/generate-manifests/main.go: -------------------------------------------------------------------------------- 1 | // Helper utility to generate manifests for each image created. 2 | package main 3 | 4 | import ( 5 | "context" 6 | "log/slog" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/lxc/incus-os/incus-osd/internal/manifests" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) != 2 { 15 | slog.Error("Usage: " + os.Args[0] + " ") 16 | os.Exit(1) 17 | } 18 | 19 | dirEntries, err := os.ReadDir(filepath.Join(os.Args[1], "mkosi.images/")) 20 | if err != nil { 21 | slog.Error("Error: " + err.Error()) 22 | os.Exit(1) 23 | } 24 | 25 | // The list of images to generate manifests for. We assume base will always be first, so manually 26 | // insert it and then skip it when iterating through the other images. 27 | images := []string{"base"} 28 | 29 | for _, dir := range dirEntries { 30 | if !dir.IsDir() { 31 | continue 32 | } 33 | 34 | if dir.Name() == "base" { 35 | continue 36 | } 37 | 38 | images = append(images, dir.Name()) 39 | } 40 | 41 | m, err := manifests.ReadManifests(filepath.Join(os.Args[1], "mkosi.output/"), images) 42 | if err != nil { 43 | slog.Error("Error: " + err.Error()) 44 | os.Exit(1) 45 | } 46 | 47 | m, err = manifests.GenerateManifests(context.Background(), os.Args[1], m) 48 | if err != nil { 49 | slog.Error("Error: " + err.Error()) 50 | os.Exit(1) 51 | } 52 | 53 | err = manifests.WriteManifests(filepath.Join(os.Args[1], "upload/"), m) 54 | if err != nil { 55 | slog.Error("Error: " + err.Error()) 56 | os.Exit(1) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /doc/reference/applications/incus.md: -------------------------------------------------------------------------------- 1 | # Incus 2 | 3 | The Incus application includes the current Incus feature release as packaged from the [Zabbly stable channel](https://github.com/zabbly/incus). It includes everything needed to run containers, OCI images, and virtual machines. 4 | 5 | At least one trusted client certificate must be provided in the Incus preseed, otherwise it will be impossible to authenticate to any API endpoint or the web UI post-install. 6 | 7 | ## Default configuration 8 | 9 | If the Incus seed field `apply_defaults` is `true`, the Incus application will perform the following initialization steps: 10 | 11 | * Create a default ZFS-backed storage pool "local" for use by Incus. This storage pool will use all remaining free space on the main system drive. 12 | 13 | * Create a local network bridge `incusbr0`. 14 | 15 | * Set the list of provided trusted client certificates. 16 | 17 | * Listen on port 8443 on all network interfaces. 18 | 19 | ## Install seed details 20 | 21 | Important seed fields include: 22 | 23 | * `apply_defaults`: If `true`, apply a reasonable set of defaults for configuring Incus. 24 | 25 | * `preseed`: A struct referencing Incus' `InitPreseed` configuration options. For details, please review Incus' [API](https://github.com/lxc/incus/blob/main/shared/api/init.go). 26 | 27 | ## Additional features 28 | 29 | Two additional applications exist which extend the main Incus application: 30 | 31 | * `incus-ceph`: Adds [Ceph](../services/ceph.md) client support 32 | * `incus-linstor`: Adds [Linstor](../services/linstor.md) satellite support 33 | -------------------------------------------------------------------------------- /incus-osd/tests/incusos_tests/tests_upgrade.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from .incus_test_vm import IncusTestVM, util 4 | 5 | def TestBaselineUpgrade(install_image): 6 | test_name = "baseline-upgrade" 7 | test_seed = { 8 | "install.json": "{}" 9 | } 10 | 11 | test_image, incusos_version = util._prepare_test_image(install_image, test_seed) 12 | 13 | with IncusTestVM(test_name, test_image) as vm: 14 | # Perform IncusOS install. 15 | vm.StartVM() 16 | vm.WaitAgentRunning() 17 | vm.WaitExpectedLog("incus-osd", "Installing IncusOS source=/dev/sdb target=/dev/sda") 18 | vm.WaitExpectedLog("incus-osd", "IncusOS was successfully installed") 19 | 20 | # Stop the VM post-install and remove install media. 21 | vm.StopVM() 22 | vm.RemoveDevice("boot-media") 23 | 24 | # Start freshly installed IncusOS and expect an immediate upgrade. 25 | vm.StartVM() 26 | vm.WaitAgentRunning() 27 | vm.WaitExpectedLog("incus-osd", "Auto-generating encryption recovery key, this may take a few seconds") 28 | match = vm.WaitExpectedLog("incus-osd", "Downloading OS update version=(\\d+)", regex=True) 29 | new_version = match.group(1) 30 | vm.WaitExpectedLog("incus-osd", "Applying OS update version="+new_version) 31 | 32 | # Allow some time for the update to apply. 33 | time.sleep(30) 34 | 35 | # Wait for the system to automatically reboot after installing the upgrade. 36 | vm.WaitAgentRunning() 37 | vm.WaitExpectedLog("incus-osd", "System is ready version="+new_version) 38 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/response/swagger.go: -------------------------------------------------------------------------------- 1 | //nolint:unused 2 | package response 3 | 4 | // Empty sync response 5 | // 6 | // swagger:response EmptySyncResponse 7 | type swaggerEmptySyncResponse struct { 8 | // Empty sync response 9 | // in: body 10 | Body struct { 11 | // Example: sync 12 | Type string `json:"type"` 13 | 14 | // Example: Success 15 | Status string `json:"status"` 16 | 17 | // Example: 200 18 | StatusCode int `json:"status_code"` 19 | } 20 | } 21 | 22 | // Bad Request 23 | // 24 | // swagger:response BadRequest 25 | type swaggerBadRequest struct { 26 | // Bad Request 27 | // in: body 28 | Body struct { 29 | // Example: error 30 | Type string `json:"type"` 31 | 32 | // Example: bad request 33 | Error string `json:"error"` 34 | 35 | // Example: 400 36 | ErrorCode int `json:"error_code"` 37 | } 38 | } 39 | 40 | // Not found 41 | // 42 | // swagger:response NotFound 43 | type swaggerNotFound struct { 44 | // Not found 45 | // in: body 46 | Body struct { 47 | // Example: error 48 | Type string `json:"type"` 49 | 50 | // Example: not found 51 | Error string `json:"error"` 52 | 53 | // Example: 404 54 | ErrorCode int `json:"error_code"` 55 | } 56 | } 57 | 58 | // Internal Server Error 59 | // 60 | // swagger:response InternalServerError 61 | type swaggerInternalServerError struct { 62 | // Internal server Error 63 | // in: body 64 | Body struct { 65 | // Example: error 66 | Type string `json:"type"` 67 | 68 | // Example: internal server error 69 | Error string `json:"error"` 70 | 71 | // Example: 500 72 | ErrorCode int `json:"error_code"` 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/initrd-boot-message.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck disable=SC2028 4 | 5 | # Print startup message 6 | for TTY in $TTYS; do 7 | echo "$OS_NAME is starting..." > "$TTY" || true 8 | done 9 | 10 | # Check if SecureBoot is enabled 11 | if [ -e /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c ]; then 12 | raw_secure_boot_state=$(tail -c 1 /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c) 13 | secure_boot_state=$(printf "%d" "'$raw_secure_boot_state") 14 | 15 | if [ "$secure_boot_state" != 1 ]; then 16 | for TTY in $TTYS; do 17 | echo "\033[31mSecureBoot is disabled. $OS_NAME cannot start until SecureBoot is enabled.\033[0m" > "$TTY" || true 18 | done 19 | sleep 3600 20 | fi 21 | else 22 | for TTY in $TTYS; do 23 | echo "\033[31mUnable to determine SecureBoot state. $OS_NAME cannot start until SecureBoot is enabled.\033[0m" > "$TTY" || true 24 | done 25 | sleep 3600 26 | fi 27 | 28 | # Check if a v2.0 TPM is present 29 | if [ -e /sys/class/tpm/tpm0/tpm_version_major ]; then 30 | tpm_version=$(cat /sys/class/tpm/tpm0/tpm_version_major) 31 | 32 | if [ "$tpm_version" != 2 ]; then 33 | for TTY in $TTYS; do 34 | echo "\033[31mUnsupported TPM version detected. $OS_NAME requires a v2.0 TPM.\033[0m" > "$TTY" || true 35 | done 36 | sleep 3600 37 | fi 38 | else 39 | for TTY in $TTYS; do 40 | echo "\033[0;33mNo TPM detected. $OS_NAME will attempt to fall back to a less-secure swtpm implementation.\033[0m" > "$TTY" || true 41 | done 42 | fi 43 | -------------------------------------------------------------------------------- /incus-osd/internal/storage/storage_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCalculateScrubProgress(t *testing.T) { 10 | t.Parallel() 11 | 12 | cases := []struct { 13 | name string 14 | stats zpoolScanStats 15 | expected string 16 | }{ 17 | { 18 | name: "Finished returns 100.00% regardless of values", 19 | stats: zpoolScanStats{ 20 | State: ZpoolFinished, 21 | Examined: 4268032, 22 | ToExamine: 4276224, 23 | }, 24 | expected: "100.00%", 25 | }, 26 | { 27 | name: "Scanning returns current progress", 28 | stats: zpoolScanStats{ 29 | State: ZpoolScanning, 30 | Examined: 4268032, 31 | ToExamine: 4276224, 32 | }, 33 | expected: "99.81%", 34 | }, 35 | { 36 | name: "Scanning with progress overflow", 37 | stats: zpoolScanStats{ 38 | State: ZpoolScanning, 39 | Examined: 5268081, 40 | ToExamine: 4276224, 41 | }, 42 | expected: "99.99%", 43 | }, 44 | { 45 | name: "Scanning with no reported ToExamine", 46 | stats: zpoolScanStats{ 47 | State: ZpoolScanning, 48 | Examined: 5268081, 49 | ToExamine: 0, 50 | }, 51 | expected: "0.00%", 52 | }, 53 | { 54 | name: "Scanning with no reported Examined", 55 | stats: zpoolScanStats{ 56 | State: ZpoolScanning, 57 | Examined: 0, 58 | ToExamine: 4276224, 59 | }, 60 | expected: "0.00%", 61 | }, 62 | } 63 | 64 | for _, tc := range cases { 65 | t.Run(tc.name, func(t *testing.T) { 66 | t.Parallel() 67 | 68 | got := calculateScrubProgress(tc.stats) 69 | require.Equal(t, tc.expected, got, tc.name) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /incus-osd/api/service_multipath.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ServiceMultipathDevice represents a single Multipath device. 4 | type ServiceMultipathDevice struct { 5 | Vendor string `json:"vendor" yaml:"vendor"` 6 | Size string `json:"size" yaml:"size"` 7 | PathGroups []ServiceMultipathPathGroup `json:"path_groups" yaml:"path_groups"` 8 | } 9 | 10 | // ServiceMultipathPathGroup represents a single Multipath path group. 11 | type ServiceMultipathPathGroup struct { 12 | Policy string `json:"policy" yaml:"policy"` 13 | Priority uint64 `json:"priority" yaml:"priority"` 14 | Status string `json:"status" yaml:"status"` 15 | Paths []ServiceMultipathPath `json:"paths" yaml:"paths"` 16 | } 17 | 18 | // ServiceMultipathPath represents a single Multipath path. 19 | type ServiceMultipathPath struct { 20 | ID string `json:"id" yaml:"id"` 21 | Status string `json:"status" yaml:"status"` 22 | } 23 | 24 | // ServiceMultipathConfig represents additional configuration for the Multipath service. 25 | type ServiceMultipathConfig struct { 26 | Enabled bool `json:"enabled" yaml:"enabled"` 27 | WWNs []string `json:"wwns" yaml:"wwns"` 28 | } 29 | 30 | // ServiceMultipath represents the state and configuration of the Multipath service. 31 | type ServiceMultipath struct { 32 | State ServiceMultipathState `incusos:"-" json:"state" yaml:"state"` 33 | 34 | Config ServiceMultipathConfig `json:"config" yaml:"config"` 35 | } 36 | 37 | // ServiceMultipathState represents the state for the Multipath service. 38 | type ServiceMultipathState struct { 39 | Devices map[string]ServiceMultipathDevice `json:"devices" yaml:"devices"` 40 | } 41 | -------------------------------------------------------------------------------- /incus-osd/api/seed/install.go: -------------------------------------------------------------------------------- 1 | package seed 2 | 3 | // Install represents the install seed. 4 | type Install struct { 5 | Version string `json:"version" yaml:"version"` 6 | 7 | ForceInstall bool `json:"force_install" yaml:"force_install"` // If true, ignore any existing data on target install disk. 8 | ForceReboot bool `json:"force_reboot" yaml:"force_reboot"` // If true, reboot the system automatically upon completion rather than waiting for the install media to be removed. 9 | Security *InstallSecurity `json:"security,omitempty" yaml:"security,omitempty"` // Optional install options to allow IncusOS to run in a degraded security state. 10 | Target *InstallTarget `json:"target" yaml:"target"` // Optional selector for the target install disk; if not set, expect a single drive to be present. 11 | } 12 | 13 | // InstallSecurity defines a set of mutually exclusive options that allow IncusOS to run in a degraded security state. 14 | // !!THESE OPTIONS WILL REDUCE THE SYSTEM'S SECURITY COMPARED TO USING PROPERLY CONFIGURED SECURE BOOT AND A TPM!! 15 | type InstallSecurity struct { 16 | MissingTPM bool `json:"missing_tpm" yaml:"missing_tpm"` // If true, and only if no physical TPM is present, allow fallback to swtpm-backed TPM implementation. 17 | MissingSecureBoot bool `json:"missing_secure_boot" yaml:"missing_secure_boot"` // If true, and only if Secure Boot is in a disabled state, allow fallback to booting without Secure Boot checks. 18 | } 19 | 20 | // InstallTarget defines options used to select the target install disk. 21 | type InstallTarget struct { 22 | ID string `json:"id" yaml:"id"` // Name as listed in /dev/disk/by-id/, glob supported. 23 | } 24 | -------------------------------------------------------------------------------- /incus-osd/cmd/measure-pcrs/main.go: -------------------------------------------------------------------------------- 1 | // Package main populates PCRs when using a swtpm-backed TPM. 2 | package main 3 | 4 | import ( 5 | "crypto/sha256" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/google/go-eventlog/register" 10 | "github.com/google/go-eventlog/tcg" 11 | "github.com/google/go-tpm/legacy/tpm2" 12 | "github.com/google/go-tpm/tpmutil" 13 | 14 | "github.com/lxc/incus-os/incus-osd/internal/secureboot" 15 | ) 16 | 17 | func main() { 18 | err := run() 19 | if err != nil { 20 | _, _ = fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error()) 21 | 22 | os.Exit(1) 23 | } 24 | } 25 | 26 | func run() error { 27 | // Open the TPM. 28 | tpmDev, err := tpm2.OpenTPM("/dev/tpm0") 29 | if err != nil { 30 | return fmt.Errorf("can't open TPM: %s", err.Error()) 31 | } 32 | defer tpmDev.Close() 33 | 34 | // Get a synthetic event log that should be used to populate the TPM's PCR values. 35 | rawLog, err := secureboot.SynthesizeTPMEventLog() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | // Parse the event log. 41 | log, err := tcg.ParseEventLog(rawLog, tcg.ParseOpts{}) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | events := log.Events(register.HashSHA256) 47 | 48 | // Measure each event into the TPM. 49 | for _, event := range events { 50 | pcr := tpmutil.Handle(event.Index) //nolint:gosec 51 | 52 | err := tpm2.PCRExtend(tpmDev, pcr, tpm2.AlgSHA256, event.ReplayedDigest(), "") 53 | if err != nil { 54 | return err 55 | } 56 | } 57 | 58 | // Measure the "enter-initrd" userspace TPM event into PCR11. 59 | h := sha256.Sum256([]byte("enter-initrd")) 60 | 61 | err = tpm2.PCRExtend(tpmDev, tpmutil.Handle(11), tpm2.AlgSHA256, h[:], "") 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /mkosi.sandbox/etc/apt/keyrings/linbit.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBGZsIboBEADLvvRG52QXsGL7d33xOYQzxjriCdLsXTnBhsC/bQc4dpQBfAp7 4 | FEYhp93NcrwwPXLp5thYHfg3RF/O5pbyvpAnKZJt0rXMEvwy2K3JVJL7ddYs4cU6 5 | I4m1n6W+YYQydEeMmZUFc77DNwQ74Dw3Xx/natz1dK2Y9S0pyBPb2ZMtWwDMuDpV 6 | P/Y/W/wuDMWusbtSjVb+4TXJy046vPBobzi2jtQyaPJFfqwRiYqJYrQmsnns7L3F 7 | ksd63Y4veqK9rvNiYDVXZNsXja0An59rUdwhMlHj/wJWhr23SnSbcQv3hkvY/I19 8 | YT5FD9Y9srmRG2tIiQbK2rAjLqjKNGvYINRBlwnLDCCnoiCLJTahi2CT90aOr0Ae 9 | 7QKITi9cpDZJO+/vaLw7JnHEmhpt5W1WySFtSgSQWIYdkhF2Rx1aOkQwT/Ig6rDV 10 | tkA8CjJ4ACMYXAx+zh+pxIRg2JNkLoGdl7pc/+5f4VV9iup3VW5U9MoC3KzZBDGJ 11 | /DNF9x/1Rkf/H7tP3pYU3UmaPOCMvOx3zuwo1qqEUT9pKxybZo+lOPdNVgZbAuKY 12 | C2jJa3Mv64IqyBds9tfZY8Sj+pbhfSvgTSmXR0WARXPS3po1PjUicnrStc+cWfEY 13 | g7vh4Wn2EsExlgmdARvDl1TgENQCLshTI2jTIPu9XXUZlDVltGJZuGem4wARAQAB 14 | tClMSU5CSVQgUGFja2FnZSBhbmQgUmVwb3NpdG9yeSBTaWduaW5nIEtleYkCTgQT 15 | AQoAOBYhBE5ThVRnJtE8tkmHLPwFox24Jv5IBQJmbCG6AhsDBQsJCAcCBhUKCQgL 16 | AgQWAgMBAh4BAheAAAoJEPwFox24Jv5IaTIP/0qHRPuK0cKPsMEw7Bx+y9liST9y 17 | XC6xEbcWEPm9qkjW73/ZdTLRKI4Ty9UGH7Q0zQ1otUdGYOolidtDAYKS2V3++PwQ 18 | mbESJJiKOeKs1rjXeEBUr0usyhsL3P4/hFLCMf1ctgZNWklanu/N/aMQVjTQoYJu 19 | rDCxq1y58/3hAyrWQ2kITHjVvpP5cXqXpDlwiLKy+oYNxvUdE6FUzNbuQ6htzWyQ 20 | ugmMyjjqjlfD6gC7OqDcXk9eUf7AHbgf5+UQ+RbjMZ+YBoH9gihL/1TI1Ith50NX 21 | 5cVRMyscZ9inWFK8Fw2ubD8ZifmIQfrMZJMW0iajgJhX0GAqczl54Ihb08EODeRO 22 | 82oOgoeIJ7H14y2WfYA7Pb2Zap6WLrvA+k3arsr94aSDGutpThTBABR9F7F3fOLo 23 | 8dlv+jGXnsHtTeU9qh2+ZifyPZ3BVukyizoQk+TDxOavqNryQc7zLpwKsGymRJjP 24 | M3ODV1fUVm6hLgPSMJP0tLakvK76+o/RCr5z5gCUyxjnnV0pbkYLhhE7FtLeSoPb 25 | rxmNRzSFfdXk2uD1idlh54559JdSSh/HzqdZ1biizLs+xJK8JZPZI+3F/whVylwU 26 | iwOhLRojCowfN2DhiiAencH0fsjgec3PT51bdCU6agvH8f6l1ivAuRb1RGT9WGpo 27 | Xe+WxMjgfi4lbTsF 28 | =SQ0K 29 | -----END PGP PUBLIC KEY BLOCK----- 30 | -------------------------------------------------------------------------------- /mkosi.images/base/mkosi.extra/usr/lib/systemd/incus-agent-setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | PREFIX="/run/incus_agent" 4 | CDROM="/dev/disk/by-id/scsi-0QEMU_QEMU_CD-ROM_incus_agent" 5 | 6 | # Functions. 7 | mount_cdrom() { 8 | mount "${CDROM}" "${PREFIX}.mnt" >/dev/null 2>&1 9 | } 10 | 11 | mount_9p() { 12 | modprobe 9pnet_virtio >/dev/null 2>&1 || true 13 | mount -t 9p config "${PREFIX}.mnt" -o access=0,trans=virtio,size=1048576 >/dev/null 2>&1 14 | } 15 | 16 | fail() { 17 | # Check if we already have an agent in place. 18 | # This will typically be true during restart in the case of a cdrom-based setup. 19 | if [ -x "${PREFIX}/incus-agent" ]; then 20 | echo "${1}, re-using existing agent" 21 | exit 0 22 | fi 23 | 24 | # Cleanup and fail. 25 | umount -l "${PREFIX}" >/dev/null 2>&1 || true 26 | eject "${CDROM}" >/dev/null 2>&1 || true 27 | rmdir "${PREFIX}" >/dev/null 2>&1 || true 28 | echo "${1}, failing" 29 | 30 | exit 1 31 | } 32 | 33 | # Try getting an agent drive. 34 | mkdir -p "${PREFIX}.mnt" 35 | mount_9p || mount_cdrom || fail "Couldn't mount 9p or cdrom" 36 | 37 | # Setup the mount target. 38 | umount -l "${PREFIX}" >/dev/null 2>&1 || true 39 | mkdir -p "${PREFIX}" 40 | mount -t tmpfs tmpfs "${PREFIX}" -o mode=0700,size=50M 41 | 42 | # Copy the data. 43 | cp -Ra "${PREFIX}.mnt/"* "${PREFIX}" 44 | 45 | # Unmount the temporary mount. 46 | umount "${PREFIX}.mnt" 47 | rmdir "${PREFIX}.mnt" 48 | 49 | # Eject the cdrom in case it's present. 50 | eject "${CDROM}" >/dev/null 2>&1 || true 51 | 52 | # Fix up permissions. 53 | chown -R root:root "${PREFIX}" 54 | 55 | # Legacy. 56 | if [ ! -e "${PREFIX}/incus-agent" ] && [ -e "${PREFIX}/lxd-agent" ]; then 57 | ln -s lxd-agent "${PREFIX}"/incus-agent 58 | fi 59 | 60 | # Attempt to restore SELinux labels. 61 | restorecon -R "${PREFIX}" >/dev/null 2>&1 || true 62 | 63 | exit 0 64 | -------------------------------------------------------------------------------- /incus-osd/internal/services/load.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "slices" 7 | 8 | "github.com/lxc/incus-os/incus-osd/internal/state" 9 | ) 10 | 11 | // Supported returns the list of all valid services for this system. 12 | // The list is sorted in recommended startup order to handle service dependencies. 13 | func Supported(s *state.State) []string { 14 | services := []string{"ceph", "iscsi", "linstor", "nvme", "multipath", "lvm", "ovn", "tailscale", "usbip"} 15 | supported := make([]string, 0, len(services)) 16 | 17 | for _, service := range services { 18 | srv, err := loadByName(s, service) 19 | if err != nil { 20 | continue 21 | } 22 | 23 | if !srv.Supported() { 24 | continue 25 | } 26 | 27 | supported = append(supported, service) 28 | } 29 | 30 | return supported 31 | } 32 | 33 | // Load returns a handler for the given system service. 34 | func Load(_ context.Context, s *state.State, name string) (Service, error) { 35 | if !slices.Contains(Supported(s), name) { 36 | return nil, errors.New("unknown service") 37 | } 38 | 39 | // Load the service. 40 | srv, err := loadByName(s, name) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return srv, nil 46 | } 47 | 48 | func loadByName(s *state.State, name string) (Service, error) { 49 | var srv Service 50 | 51 | switch name { 52 | case "ceph": 53 | srv = &Ceph{state: s} 54 | case "iscsi": 55 | srv = &ISCSI{state: s} 56 | case "linstor": 57 | srv = &Linstor{state: s} 58 | case "lvm": 59 | srv = &LVM{state: s} 60 | case "multipath": 61 | srv = &Multipath{state: s} 62 | case "nvme": 63 | srv = &NVME{state: s} 64 | case "ovn": 65 | srv = &OVN{state: s} 66 | case "tailscale": 67 | srv = &Tailscale{state: s} 68 | case "usbip": 69 | srv = &USBIP{state: s} 70 | default: 71 | return nil, errors.New("unknown service") 72 | } 73 | 74 | return srv, nil 75 | } 76 | -------------------------------------------------------------------------------- /mkosi.conf: -------------------------------------------------------------------------------- 1 | [Build] 2 | ToolsTreeDistribution=debian 3 | ToolsTreeRelease=trixie 4 | History=yes 5 | ToolsTree=default 6 | CacheDirectory=mkosi.cache 7 | WithNetwork=true 8 | 9 | [Output] 10 | ImageId=IncusOS 11 | OutputDirectory=mkosi.output 12 | Format=disk 13 | SplitArtifacts=yes 14 | 15 | [Distribution] 16 | Distribution=debian 17 | Release=trixie 18 | Mirror=http://deb.debian.org/debian 19 | Repositories=non-free-firmware 20 | 21 | [Validation] 22 | SecureBoot=true 23 | SecureBootAutoEnroll=true 24 | SecureBootSignTool=systemd-sbsign 25 | SecureBootKey=./mkosi.key 26 | SecureBootCertificate=./mkosi.crt 27 | 28 | SignExpectedPcr=true 29 | VerityKey=./mkosi.key 30 | VerityCertificate=./mkosi.crt 31 | 32 | [Content] 33 | Bootable=true 34 | BaseTrees=%O/base 35 | UnifiedKernelImages=true 36 | UnifiedKernelImageFormat=%i_%v 37 | KernelCommandLine=rw vt.handoff=1 iommu=pt intel_iommu=on amd_iommu=on quiet loglevel=0 systemd.show_status=0 rootflags=noexec,nodev,nosuid rd.systemd.mount-extra=/dev/disk/by-partlabel/esp:/boot:vfat:rw 38 | KernelModulesInitrd=true 39 | KernelModulesInitrdExclude=.* 40 | KernelModulesInitrdInclude=default 41 | bfa 42 | hid-generic 43 | /hid.ko 44 | hpsa 45 | hv_storvsc 46 | isofs 47 | lpfc 48 | megaraid_sas 49 | mptfc 50 | qla2xxx 51 | tpm_vtpm_proxy 52 | uas 53 | usbhid 54 | usb-storage 55 | vmd 56 | InitrdPackages=initrd-tmpfs-root 57 | kpartx 58 | pciutils 59 | usbutils 60 | swtpm-tools 61 | RemoveFiles=/boot/*zabbly* 62 | /boot/EFI/mkosi.der 63 | -------------------------------------------------------------------------------- /doc/reference/system/security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | IncusOS has a fairly robust [security setup](../security.md) that is enforced on all systems. Under normal operation, IncusOS relies on the TPM to automatically unlock the main system drive, which in turn holds the encryption keys for any [storage pools](storage.md). 4 | 5 | As part of its first boot, IncusOS generates a strong recovery key that can be used to decrypt the main system drive in recovery scenarios, such accidental TPM reset or needing to perform offline data recovery. Additional recovery keys can be added if desired. It is imperative to protect the recovery key(s) in a manner consistent with the importance of data stored on the corresponding IncusOS system. 6 | 7 | The recovery key(s) can be retrieved by running 8 | 9 | ``` 10 | incus admin os system security show 11 | ``` 12 | 13 | ## Configuration options 14 | 15 | Configuration fields are defined in the [`SystemSecurityConfig` struct](https://github.com/lxc/incus-os/blob/main/incus-osd/api/system_security.go). 16 | 17 | The following configuration options can be set: 18 | 19 | * `encryption_recovery_keys`: An array of one or more encryption recovery keys for the IncusOS main system drive. At least one recovery key must always be provided. Any existing recovery key(s) not present in the array will be removed, and any new key(s) will be added. A very simple complexity policy is enforced by IncusOS: 20 | * At least 15 characters long 21 | * Contain at least one special character 22 | * Consist of at least five unique characters 23 | * Some other simple complexity checks are applied, and any encryption recovery key that doesn't pass will be rejected with an error 24 | 25 | ## Resetting TPM bindings 26 | 27 | If IncusOS fails to automatically unlock the main system drive, after booting using a recovery key, it is possible to forcefully reset the TPM bindings: 28 | 29 | ``` 30 | incus admin os system security tpm-rebind 31 | ``` 32 | -------------------------------------------------------------------------------- /incus-osd/internal/state/file.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import ( 4 | "log/slog" 5 | "os" 6 | 7 | "github.com/lxc/incus-os/incus-osd/api" 8 | ) 9 | 10 | var currentStateVersion = 6 11 | 12 | // LoadOrCreate parses the on-disk state file and returns a State struct. 13 | // If no file exists, a new empty one is created. 14 | func LoadOrCreate(path string) (*State, error) { 15 | s := State{ 16 | path: path, 17 | 18 | StateVersion: currentStateVersion, 19 | 20 | Applications: map[string]api.Application{}, 21 | } 22 | 23 | body, err := os.ReadFile(s.path) 24 | if err == nil { 25 | err = Decode(body, nil, &s) 26 | 27 | return &s, err 28 | } 29 | 30 | if os.IsNotExist(err) { 31 | // Initialize with default values. 32 | err = s.initialize() 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | // State file doesn't exist, create it and return it. 38 | err = s.Save() 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return &s, nil 44 | } 45 | 46 | return nil, err 47 | } 48 | 49 | // Save writes out the current state struct into its on-disk storage. 50 | func (s *State) Save() error { 51 | // If we failed to fully load the existing state, refuse to save any changes to prevent accidental data loss. 52 | if len(s.UnrecognizedFields) > 0 { 53 | slog.Error("Refusing to save state because we previously failed to properly load the existing state") 54 | 55 | return nil 56 | } 57 | 58 | body, err := Encode(s) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | err = os.WriteFile(s.path, body, 0o600) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | 71 | // initialize sets default values for a new state file. 72 | func (s *State) initialize() error { 73 | // Use the default update channel. 74 | s.System.Update.Config.Channel = "stable" 75 | 76 | // Set the initial update frequency to 6 hours. 77 | s.System.Update.Config.CheckFrequency = "6h" 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/api_system_reset.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/lxc/incus-os/incus-osd/api" 8 | "github.com/lxc/incus-os/incus-osd/internal/reset" 9 | "github.com/lxc/incus-os/incus-osd/internal/rest/response" 10 | ) 11 | 12 | // swagger:operation POST /1.0/system/:factory-reset system system_post_reset 13 | // 14 | // Perform a factory reset of the system 15 | // 16 | // Factory reset the entire system and immediately reboot. This is a DESTRUCTIVE action and will wipe all installed applications, configuration, and the "local" ZFS datapool. 17 | // 18 | // --- 19 | // produces: 20 | // - application/json 21 | // parameters: 22 | // - in: body 23 | // name: configuration 24 | // description: Reset data 25 | // required: false 26 | // schema: 27 | // type: object 28 | // example: {"allow_tpm_reset_failure":false,"wipe_existing_seeds":true,"seeds":{"incus":{"apply_defaults":true}}} 29 | // responses: 30 | // "200": 31 | // $ref: "#/responses/EmptySyncResponse" 32 | // "400": 33 | // $ref: "#/responses/BadRequest" 34 | // "500": 35 | // $ref: "#/responses/InternalServerError" 36 | func (*Server) apiSystemFactoryReset(w http.ResponseWriter, r *http.Request) { 37 | w.Header().Set("Content-Type", "application/json") 38 | 39 | if r.Method != http.MethodPost { 40 | _ = response.NotImplemented(nil).Render(w) 41 | 42 | return 43 | } 44 | 45 | resetData := &api.SystemReset{} 46 | 47 | counter := &countWrapper{ReadCloser: r.Body} 48 | 49 | err := json.NewDecoder(counter).Decode(resetData) 50 | if err != nil && counter.n > 0 { 51 | _ = response.BadRequest(err).Render(w) 52 | 53 | return 54 | } 55 | 56 | err = reset.PerformOSFactoryReset(r.Context(), resetData) 57 | if err != nil { 58 | _ = response.InternalError(err).Render(w) 59 | 60 | return 61 | } 62 | 63 | // Will never actually reach here, since the system will auto-reboot. 64 | _ = response.EmptySyncResponse.Render(w) 65 | } 66 | -------------------------------------------------------------------------------- /incus-osd/cmd/image-publisher/main.go: -------------------------------------------------------------------------------- 1 | // Package main is used for the image publisher. 2 | package main 3 | 4 | import ( 5 | "errors" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | type cmdGlobal struct { 12 | flagHelp bool 13 | } 14 | 15 | func main() { 16 | app := &cobra.Command{} 17 | app.Use = "image-publisher" 18 | app.Short = "Maintains an IncusOS update server" 19 | app.Long = `Description: 20 | Maintain an IncusOS update server 21 | 22 | This tool handles publishing, promotion and cleanup of IncusOS updates. 23 | ` 24 | app.SilenceUsage = true 25 | app.CompletionOptions = cobra.CompletionOptions{DisableDefaultCmd: true} 26 | 27 | // Global flags. 28 | globalCmd := cmdGlobal{} 29 | app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help") 30 | 31 | // Help handling. 32 | app.SetHelpCommand(&cobra.Command{ 33 | Use: "no-help", 34 | Hidden: true, 35 | }) 36 | 37 | // demote sub-command. 38 | demoteCmd := cmdDemote{global: &globalCmd} 39 | app.AddCommand(demoteCmd.command()) 40 | 41 | // promote sub-command. 42 | promoteCmd := cmdPromote{global: &globalCmd} 43 | app.AddCommand(promoteCmd.command()) 44 | 45 | // prune sub-command. 46 | pruneCmd := cmdPrune{global: &globalCmd} 47 | app.AddCommand(pruneCmd.command()) 48 | 49 | // sync sub-command. 50 | syncCmd := cmdSync{global: &globalCmd} 51 | app.AddCommand(syncCmd.command()) 52 | 53 | // Run the main command and handle errors. 54 | err := app.Execute() 55 | if err != nil { 56 | os.Exit(1) 57 | } 58 | } 59 | 60 | // CheckArgs validates the number of arguments passed to the function and shows the help if incorrect. 61 | func (*cmdGlobal) CheckArgs(cmd *cobra.Command, args []string, minArgs int, maxArgs int) (bool, error) { 62 | if len(args) < minArgs || (maxArgs != -1 && len(args) > maxArgs) { 63 | _ = cmd.Help() 64 | 65 | if len(args) == 0 { 66 | return true, nil 67 | } 68 | 69 | return true, errors.New("invalid number of arguments") 70 | } 71 | 72 | return false, nil 73 | } 74 | -------------------------------------------------------------------------------- /doc/getting-started/installation/virtual-proxmox.md: -------------------------------------------------------------------------------- 1 | # Installing in a Proxmox virtual machine 2 | 3 | IncusOS can be easily installed in a Proxmox virtual machine. 4 | 5 | ## Get and import install media 6 | 7 | Follow the instructions to [get an IncusOS image](../download.md). This document will assume an ISO image is used. 8 | 9 | Once downloaded, upload the ISO image to Proxmox's storage. 10 | 11 | ![Proxmox ISO image import](../../images/proxmox-import-iso.png) 12 | 13 | ## Create a new virtual machine 14 | 15 | Create a new virtual machine and add the ISO image. 16 | 17 | ![Proxmox VM configuring ISO](../../images/proxmox-vm-configure-iso.png) 18 | 19 | ### Secure Boot and TPM configuration 20 | 21 | IncusOS depends on Secure Boot and a v2.0 TPM. When configuring the virtual machine, make the following selections: 22 | 23 | * BIOS should be "OVMF (UEFI)" 24 | 25 | * Uncheck "Pre-Enroll keys" 26 | * This will allow the IncusOS installer to automatically enroll the necessary Secure Boot keys 27 | 28 | * Check "Add TPM" and set the version to be "v2.0" 29 | 30 | ![Proxmox VM configuring Secure Boot and TPM](../../images/proxmox-vm-configure-secure-boot-tpm.png) 31 | 32 | ### CPU, memory, network, and local storage 33 | 34 | Configure the CPU and memory for the virtual machine as desired and add at least one network interface. 35 | 36 | Remember that the main system drive must be at least 50GiB or larger. 37 | 38 | ## IncusOS installation 39 | 40 | Start the virtual machine, and IncusOS will begin its installation. 41 | 42 | ![Proxmox VM installing IncusOS](../../images/proxmox-vm-install.png) 43 | 44 | Upon completion of the install, stop the virtual machine and remove the CDROM device. 45 | 46 | ![Proxmox VM installation complete](../../images/proxmox-vm-install-complete.png) 47 | 48 | ## IncusOS is ready for use 49 | 50 | Start the virtual machine, and IncusOS will perform its first boot configuration. Once complete, follow the instructions for [accessing the system](../access.md). 51 | 52 | ![Proxmox VM running IncusOS](../../images/proxmox-vm-incusos-running.png) 53 | -------------------------------------------------------------------------------- /incus-osd/tests/incusos_tests/incus_test_vm/util.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import random 4 | import shutil 5 | import string 6 | import subprocess 7 | import tarfile 8 | 9 | def _get_random_string(): 10 | return "".join(random.choices(string.ascii_letters + string.digits, k=10)) 11 | 12 | def _prepare_test_image(image, seed): 13 | ext = ".img" if image.endswith(".img") else ".iso" 14 | 15 | parent_dir = os.path.dirname(image) 16 | basename = os.path.basename(image) 17 | test_image = os.path.join(parent_dir, basename.replace(ext, "_"+_get_random_string()+ext)) 18 | 19 | # Create a copy of the install image. 20 | shutil.copy(image, test_image) 21 | 22 | # Inject seed data, if any. 23 | if seed is not None: 24 | with open(test_image, "rb+") as f: 25 | f.seek(4196352*512) 26 | 27 | with tarfile.open(mode="w", fileobj=f) as tar: 28 | for filename, contents in seed.items(): 29 | raw = contents.encode("utf-8") 30 | buf = io.BytesIO(raw) 31 | ti = tarfile.TarInfo(name=filename) 32 | ti.size = len(raw) 33 | 34 | tar.addfile(ti, buf) 35 | 36 | # Return the path of the customized install image and IncusOS version. 37 | return test_image, basename.replace("IncusOS_", "").replace(ext, "") 38 | 39 | def _create_user_media(f, d, media_type, media_size, media_label): 40 | if media_type == "img": 41 | f.truncate(media_size) 42 | subprocess.run(["/sbin/sgdisk", "-n", "1", "-c", "1:" + media_label, f.name], capture_output=True, check=True) 43 | subprocess.run(["/sbin/mkfs.vfat", "-S", "512", "--offset=2048", f.name], capture_output=True, check=True) 44 | 45 | for entry in os.scandir(d): 46 | subprocess.run(["mcopy", "-s", "-i", f.name+"@@1048576", entry.path, "::" + entry.path.removeprefix(d)], capture_output=True, check=True) 47 | else: 48 | subprocess.run(["mkisofs", "-V", media_label, "-joliet-long", "-rock", "-o", f.name, d], capture_output=True, check=True) 49 | -------------------------------------------------------------------------------- /incus-osd/cmd/image-publisher/main_prune.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log/slog" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | 11 | "github.com/spf13/cobra" 12 | 13 | apiupdate "github.com/lxc/incus-os/incus-osd/api/images" 14 | ) 15 | 16 | type cmdPrune struct { 17 | global *cmdGlobal 18 | } 19 | 20 | func (c *cmdPrune) command() *cobra.Command { 21 | cmd := &cobra.Command{} 22 | cmd.Use = "prune " 23 | cmd.Short = "Prune the image server" 24 | cmd.Long = formatSection("Description", 25 | `Prunes the image server 26 | 27 | This will prune images that aren't needed to satisfy the specified 28 | per-channel retention policy. 29 | `) 30 | cmd.RunE = c.run 31 | 32 | return cmd 33 | } 34 | 35 | func (c *cmdPrune) run(cmd *cobra.Command, args []string) error { 36 | ctx := context.TODO() 37 | 38 | // Quick checks. 39 | exit, err := c.global.CheckArgs(cmd, args, 2, 2) 40 | if exit { 41 | return err 42 | } 43 | 44 | // Parse the retention policy. 45 | retention, err := strconv.Atoi(args[1]) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | // Read the index. 51 | var metaIndex apiupdate.Index 52 | 53 | metaFile, err := os.Open(filepath.Join(args[0], "index.json")) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | defer func() { _ = metaFile.Close() }() 59 | 60 | err = json.NewDecoder(metaFile).Decode(&metaIndex) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | // Identify images to be deleted. 66 | imagesPerChannel := map[string]int{} 67 | 68 | for _, update := range metaIndex.Updates { 69 | used := false 70 | 71 | for _, channel := range update.Channels { 72 | if imagesPerChannel[channel] < retention { 73 | imagesPerChannel[channel]++ 74 | used = true 75 | } 76 | } 77 | 78 | if !used { 79 | slog.InfoContext(ctx, "Removing unused image", "image", update.Version) 80 | 81 | err = os.RemoveAll(filepath.Join(args[0], update.Version)) 82 | if err != nil { 83 | return err 84 | } 85 | } 86 | } 87 | 88 | // Re-generate the index. 89 | return generateIndex(ctx, args[0]) 90 | } 91 | -------------------------------------------------------------------------------- /doc/reference/partitioning-scheme.md: -------------------------------------------------------------------------------- 1 | # Partitioning scheme 2 | 3 | IncusOS utilizes `systemd-repart` to automatically partition the main system drive at first boot. The layout of the partition table looks like the following: 4 | 5 | EFI ESP (2GiB) 6 | seed data (100MiB) 7 | A-side root partition signing (16kiB) 8 | A-side root partition hashes (100MiB) 9 | A-side root partition (1GiB) 10 | B-side root partition signing (16KiB) 11 | B-side root partition hashes (100Mib) 12 | B-side root partition (1GiB 13 | LUKS encrypted swap (4GiB) 14 | LUKS encrypted ext4 system data (25 GiB) 15 | ZFS encrypted pool "local" (remaining space) 16 | 17 | ## A/B updates 18 | 19 | The EFI ESP partition holds two different signed UKI images, each corresponding to an A- or B-side root partition. When an OS update is applied, the non-booted UKI is replaced and its corresponding signing, hash, and data partitions are atomically updated. On reboot, `systemd-boot` will automatically select the updated UKI. 20 | 21 | ## Seed data partition 22 | 23 | The seed data partition is used during install or [factory reset](system/backup.md). 24 | 25 | ## Encrypted partitions 26 | 27 | Partitions that hold user data are encrypted. The swap and ext4 system partitions are both encrypted and under normal operation are automatically unlocked during boot by the TPM. If unlocking fails for some reason, a recovery key can be provided to allow the system to boot. 28 | 29 | Each ZFS pool created by IncusOS is encrypted with a randomly generated key. These keys are stored in the encrypted system partition. 30 | 31 | The encryption keys can be retrieved via the [security API](system/security.md). 32 | 33 | ### System partition 34 | 35 | The system partition holds any system data that is not part of the immutable IncusOS images. 36 | 37 | ### "local" ZFS pool 38 | 39 | The "local" ZFS pool consumes all remaining space on the main system drive. It is available for use by applications; for example, when Incus is installed it will create a dataset `local/incus` to use as the default storage pool for containers and virtual machines. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | [IncusOS](https://linuxcontainers.org/incus-os) is an immutable OS image dedicated to running [Incus](https://linuxcontainers.org/incus). 3 | It's based on [Debian](https://www.debian.org) 13 and built using [mkosi](https://github.com/systemd/mkosi). 4 | 5 | IncusOS can be installed on modern amd64 (x86_64) and arm64 systems. 6 | 7 | This aims at providing a very fast, safe and reliable way to run an Incus server. 8 | It's got a strong focus on security, actively relying on UEFI Secure Boot and TPM 2.0 for boot security and disk encryption. 9 | 10 | You can read more about how to get started with IncusOS 11 | [here](https://linuxcontainers.org/incus-os/docs/main/getting-started/) 12 | including detailed instructions for physical installation or for running 13 | IncusOS on a variety of virtual machine platforms. 14 | 15 | The full documentation for IncusOS can be [found here](https://linuxcontainers.org/incus-os/docs/main). 16 | 17 | # Development 18 | This repository includes all the sources used to build the production IncusOS images. 19 | 20 | Builds are triggered by pushing a new tag to this repository which kicks 21 | in a full image build, that then gets downloaded and validated by our 22 | publishing server. The image is then made available in the `testing` 23 | channel until it's manually validated and promoted to the `stable` 24 | channel. 25 | 26 | The most recent image build logs can be found here: https://github.com/lxc/incus-os/actions/workflows/build.yml 27 | With the resulting images being published to: https://images.linuxcontainers.org/os/ 28 | 29 | A daily test is also run, exercising most of the API endpoints and 30 | running tests that would be impractical (too slow) to run for every pull 31 | request. 32 | 33 | [![Daily API tests](https://github.com/lxc/incus-os/actions/workflows/daily.yml/badge.svg)](https://github.com/lxc/incus-os/actions/workflows/daily.yml) 34 | 35 | # Contributing 36 | This repository is released under the terms of the Apache 2.0 license. 37 | 38 | Detailed contribution guidelines can be found in [our documentation](https://linuxcontainers.org/incus-os/docs/main/contributing/). 39 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/api_system_resources.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/lxc/incus/v6/shared/resources" 7 | 8 | "github.com/lxc/incus-os/incus-osd/internal/rest/response" 9 | ) 10 | 11 | // swagger:operation GET /1.0/system/resources system system_get_resources 12 | // 13 | // Get details about system resources 14 | // 15 | // Returns a detailed low-level dump of the system's resources. 16 | // 17 | // --- 18 | // produces: 19 | // - application/json 20 | // responses: 21 | // "200": 22 | // description: Low-level details about system resources 23 | // schema: 24 | // type: object 25 | // description: Sync response 26 | // properties: 27 | // type: 28 | // description: Response type 29 | // example: sync 30 | // type: string 31 | // status: 32 | // type: string 33 | // description: Status description 34 | // example: Success 35 | // status_code: 36 | // type: integer 37 | // description: Status code 38 | // example: 200 39 | // metadata: 40 | // type: json 41 | // description: Low-level details about system resources 42 | // example: {"cpu":{"architecture":"x86_64","sockets":[{"name":"AMD Ryzen Threadripper PRO 7965WX 24-Cores","vendor":"AuthenticAMD","socket":0,"cache":[{"level":1,"type":"Data","size":65536},{"level":1,"type":"Instruction","size":65536},{"level":2,"type":"Unified","size":524288},{"level":3,"type":"Unified","size":16777216}],"cores":[{"core":0,"die":0,"threads":[{"id":0,"numa_node":0,"thread":0,"online":true,"isolated":false}]}]}]}} 43 | // "500": 44 | // $ref: "#/responses/InternalServerError" 45 | func (*Server) apiSystemResources(w http.ResponseWriter, r *http.Request) { 46 | w.Header().Set("Content-Type", "application/json") 47 | 48 | if r.Method != http.MethodGet { 49 | _ = response.NotImplemented(nil).Render(w) 50 | 51 | return 52 | } 53 | 54 | resp, err := resources.GetResources() 55 | if err != nil { 56 | _ = response.InternalError(err).Render(w) 57 | 58 | return 59 | } 60 | 61 | _ = response.SyncResponse(true, resp).Render(w) 62 | } 63 | -------------------------------------------------------------------------------- /incus-osd/api/images/update_file_component.go: -------------------------------------------------------------------------------- 1 | package images 2 | 3 | // UpdateFileComponent represents the component affected by an update. 4 | type UpdateFileComponent string 5 | 6 | const ( 7 | // UpdateFileComponentOS represents an OS update. 8 | UpdateFileComponentOS UpdateFileComponent = "os" 9 | 10 | // UpdateFileComponentIncus represents an Incus application update. 11 | UpdateFileComponentIncus UpdateFileComponent = "incus" 12 | 13 | // UpdateFileComponentIncusCeph represents a Ceph application update for Incus. 14 | UpdateFileComponentIncusCeph UpdateFileComponent = "incus-ceph" 15 | 16 | // UpdateFileComponentIncusLinstor represents a Linstor application update for Incus. 17 | UpdateFileComponentIncusLinstor UpdateFileComponent = "incus-linstor" 18 | 19 | // UpdateFileComponentOperationsCenter represents an Operations Center application update. 20 | UpdateFileComponentOperationsCenter UpdateFileComponent = "operations-center" 21 | 22 | // UpdateFileComponentMigrationManager represents a Migration Manager application update. 23 | UpdateFileComponentMigrationManager UpdateFileComponent = "migration-manager" 24 | 25 | // UpdateFileComponentDebug represents a debug application update. 26 | UpdateFileComponentDebug UpdateFileComponent = "debug" 27 | ) 28 | 29 | // UpdateFileComponents is a map of the supported update file components. 30 | var UpdateFileComponents = map[UpdateFileComponent]struct{}{ 31 | UpdateFileComponentOS: {}, 32 | UpdateFileComponentIncus: {}, 33 | UpdateFileComponentIncusCeph: {}, 34 | UpdateFileComponentIncusLinstor: {}, 35 | UpdateFileComponentMigrationManager: {}, 36 | UpdateFileComponentOperationsCenter: {}, 37 | UpdateFileComponentDebug: {}, 38 | } 39 | 40 | func (u *UpdateFileComponent) String() string { 41 | return string(*u) 42 | } 43 | 44 | // MarshalText implements the encoding.TextMarshaler interface. 45 | func (u *UpdateFileComponent) MarshalText() ([]byte, error) { 46 | return []byte(*u), nil 47 | } 48 | 49 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 50 | func (u *UpdateFileComponent) UnmarshalText(text []byte) error { 51 | *u = UpdateFileComponent(text) 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /incus-osd/internal/rest/api_debug_tui.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log/slog" 7 | "net/http" 8 | 9 | "github.com/lxc/incus-os/incus-osd/api" 10 | "github.com/lxc/incus-os/incus-osd/internal/rest/response" 11 | ) 12 | 13 | // swagger:operation POST /1.0/debug/tui/:write-message debug debug_post_tui_write_log 14 | // 15 | // Log a message 16 | // 17 | // Send a message that should be logged by the system. 18 | // 19 | // --- 20 | // consumes: 21 | // - application/json 22 | // produces: 23 | // - application/json 24 | // parameters: 25 | // - in: body 26 | // name: message 27 | // description: Message to be logged 28 | // required: true 29 | // schema: 30 | // type: object 31 | // properties: 32 | // level: 33 | // type: string 34 | // description: The log level 35 | // example: INFO 36 | // message: 37 | // type: string 38 | // description: The log message 39 | // example: Hello, world 40 | // responses: 41 | // "200": 42 | // $ref: "#/responses/EmptySyncResponse" 43 | // "400": 44 | // $ref: "#/responses/BadRequest" 45 | func (*Server) apiDebugTUI(w http.ResponseWriter, r *http.Request) { 46 | w.Header().Set("Content-Type", "application/json") 47 | 48 | if r.Method != http.MethodPost { 49 | _ = response.NotImplemented(nil).Render(w) 50 | 51 | return 52 | } 53 | 54 | logMessage := &api.DebugTUI{} 55 | 56 | err := json.NewDecoder(r.Body).Decode(logMessage) 57 | if err != nil { 58 | _ = response.BadRequest(err).Render(w) 59 | 60 | return 61 | } 62 | 63 | if logMessage.Message == "" { 64 | _ = response.BadRequest(errors.New("no log message provided")).Render(w) 65 | 66 | return 67 | } 68 | 69 | switch { 70 | case logMessage.Level < slog.LevelInfo: 71 | slog.DebugContext(r.Context(), logMessage.Message) 72 | case logMessage.Level < slog.LevelWarn: 73 | slog.InfoContext(r.Context(), logMessage.Message) 74 | case logMessage.Level < slog.LevelError: 75 | slog.WarnContext(r.Context(), logMessage.Message) 76 | default: 77 | slog.ErrorContext(r.Context(), logMessage.Message) 78 | } 79 | 80 | _ = response.EmptySyncResponse.Render(w) 81 | } 82 | -------------------------------------------------------------------------------- /incus-osd/tests/incusos_tests/tests_incusos_api_system_logging.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .incus_test_vm import IncusTestVM, IncusOSException, util 4 | 5 | def TestIncusOSAPISystemLogging(install_image): 6 | test_name = "incusos-api-system-logging" 7 | test_seed = { 8 | "install.json": "{}", 9 | } 10 | 11 | test_image, incusos_version = util._prepare_test_image(install_image, test_seed) 12 | 13 | with IncusTestVM(test_name, test_image) as vm: 14 | vm.WaitSystemReady(incusos_version) 15 | 16 | # Get current logging configuration. 17 | result = vm.APIRequest("/1.0/system/logging") 18 | if result["status_code"] != 200: 19 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 20 | 21 | if result["metadata"]["config"]["syslog"]["address"] != "" or \ 22 | result["metadata"]["config"]["syslog"]["protocol"] != "" or \ 23 | result["metadata"]["config"]["syslog"]["log_format"] != "": 24 | raise IncusOSException("wasn't expecting a populated syslog config") 25 | 26 | # Change the logging configuration. 27 | result["metadata"]["config"]["syslog"]["address"] = "127.0.0.1" 28 | result["metadata"]["config"]["syslog"]["protocol"] = "tcp" 29 | result["metadata"]["config"]["syslog"]["log_format"] = "rfc5424" 30 | 31 | result = vm.APIRequest("/1.0/system/logging", method="PUT", body=json.dumps(result["metadata"])) 32 | if result["status_code"] != 200: 33 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 34 | 35 | # Verify the changes. 36 | result = vm.APIRequest("/1.0/system/logging") 37 | if result["status_code"] != 200: 38 | raise IncusOSException("unexpected status code %d: %s" % (result["status_code"], result["error"])) 39 | 40 | if result["metadata"]["config"]["syslog"]["address"] != "127.0.0.1" or \ 41 | result["metadata"]["config"]["syslog"]["protocol"] != "tcp" or \ 42 | result["metadata"]["config"]["syslog"]["log_format"] != "rfc5424": 43 | raise IncusOSException("returned syslog config was incorrect") 44 | -------------------------------------------------------------------------------- /incus-osd/cmd/image-publisher/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | "path/filepath" 8 | "slices" 9 | "strconv" 10 | 11 | apiupdate "github.com/lxc/incus-os/incus-osd/api/images" 12 | ) 13 | 14 | func generateIndex(ctx context.Context, targetPath string) error { 15 | // Prepare the index. 16 | metaIndex := apiupdate.Index{ 17 | Format: "1.0", 18 | Updates: []apiupdate.UpdateFull{}, 19 | } 20 | 21 | // Go through all current updates. 22 | files, err := os.ReadDir(targetPath) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | for _, entry := range files { 28 | if !entry.IsDir() { 29 | continue 30 | } 31 | 32 | updateFile, err := os.Open(filepath.Join(targetPath, entry.Name(), "update.json")) //nolint:gosec 33 | if err != nil { 34 | if os.IsNotExist(err) { 35 | continue 36 | } 37 | 38 | return err 39 | } 40 | 41 | var update apiupdate.Update 42 | 43 | err = json.NewDecoder(updateFile).Decode(&update) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | err = updateFile.Close() 49 | if err != nil { 50 | return err 51 | } 52 | 53 | metaIndex.Updates = append(metaIndex.Updates, apiupdate.UpdateFull{URL: "/" + entry.Name(), Update: update}) 54 | } 55 | 56 | // Sort the updates. 57 | slices.SortFunc(metaIndex.Updates, func(a apiupdate.UpdateFull, b apiupdate.UpdateFull) int { 58 | aVersion, err := strconv.Atoi(a.Version) 59 | if err != nil { 60 | return -1 61 | } 62 | 63 | bVersion, err := strconv.Atoi(b.Version) 64 | if err != nil { 65 | return -1 66 | } 67 | 68 | if aVersion == bVersion { 69 | return 0 70 | } 71 | 72 | if aVersion < bVersion { 73 | return -1 74 | } 75 | 76 | return 1 77 | }) 78 | slices.Reverse(metaIndex.Updates) 79 | 80 | wr, err := os.Create(filepath.Join(targetPath, "index.json")) //nolint:gosec 81 | if err != nil { 82 | return err 83 | } 84 | 85 | defer func() { _ = wr.Close() }() 86 | 87 | err = json.NewEncoder(wr).Encode(metaIndex) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | err = sign(ctx, filepath.Join(targetPath, "index.json"), filepath.Join(targetPath, "index.sjson")) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /mkosi.packages/initrd-tmpfs-root/initrd-show-devices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck disable=SC2028,SC2086 4 | 5 | while true; do 6 | for TTY in $TTYS; do 7 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 8 | echo "$ systemctl --failed" > "$TTY" || true 9 | systemctl --failed > "$TTY" || true 10 | done 11 | sleep 10 12 | 13 | for TTY in $TTYS; do 14 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 15 | echo "$ journalctl -b 0 --priority 3" > "$TTY" || true 16 | journalctl -b 0 --priority 3 > "$TTY" || true 17 | done 18 | sleep 10 19 | 20 | for TTY in $TTYS; do 21 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 22 | echo "$ lsblk" > "$TTY" || true 23 | lsblk > "$TTY" || true 24 | done 25 | sleep 10 26 | 27 | for TTY in $TTYS; do 28 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 29 | echo "$ lscpi" > "$TTY" || true 30 | lspci > "$TTY" || true 31 | done 32 | sleep 10 33 | 34 | for TTY in $TTYS; do 35 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 36 | echo "$ lsusb" > "$TTY" || true 37 | lsusb > "$TTY" || true 38 | done 39 | sleep 10 40 | 41 | for TTY in $TTYS; do 42 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 43 | echo "$ ls -lh /sys/class/block/*/device/driver" > "$TTY" || true 44 | ls -lh /sys/class/block/*/device/driver > "$TTY" || true 45 | done 46 | sleep 10 47 | 48 | for TTY in $TTYS; do 49 | echo "\033cIncusOS failed to start. Debug information follows." > "$TTY" || true 50 | echo "$ dmesg | grep -i tpm" > "$TTY" || true 51 | dmesg | grep -i tpm > "$TTY" || true 52 | echo "$ ls -lh /dev/tpm*" > "$TTY" || true 53 | ls -lh /dev/tpm* > "$TTY" || true 54 | echo "$ for i in /sys/class/tpm/*/tpm_version_major; do echo \"\$i => \$(cat $i)\"; done" > "$TTY" || true 55 | for i in /sys/class/tpm/*/tpm_version_major; do 56 | echo "$i => $(cat $i)" > "$TTY" || true; 57 | done 58 | done 59 | sleep 10 60 | done 61 | -------------------------------------------------------------------------------- /doc/reference/applications/shared-api.md: -------------------------------------------------------------------------------- 1 | # Shared API 2 | 3 | Each IncusOS application shares a common API that can be used to restart it if needed as well as perform backup, restore, and reset operations. 4 | 5 | ## Getting the application state 6 | 7 | ``` 8 | incus admin os application show 9 | ``` 10 | 11 | ## Restarting the application 12 | 13 | If needed, an application can be restarted by running 14 | 15 | ``` 16 | incus admin os application restart 17 | ``` 18 | 19 | ```{note} 20 | It is expected to receive an EOF error since the application's HTTP REST endpoint will be restarted along with the application. 21 | ``` 22 | 23 | ## Backing up the application 24 | 25 | ```{important} 26 | An IncusOS application backup may contain sensitive data and credentials. As such, the backup should not be stored in any publicly-accessible location. 27 | ``` 28 | 29 | A backup of the application can be created which will include its state and configuration. Optionally, a complete backup can be created which will include all locally cached artifacts or updates. 30 | 31 | ### Configuration options 32 | 33 | * `complete`: If `true`, a full backup will be generated which may be quite large depending on what artifacts or updates are locally cached by the application. 34 | 35 | ### Examples 36 | 37 | Create the backup by running 38 | 39 | ``` 40 | incus admin os application backup archive.tar.gz -d '{"complete":false}' 41 | ``` 42 | 43 | ## Restoring the application 44 | 45 | ```{warning} 46 | Restoring a backup will overwrite any existing application state. As such, use caution when restoring. 47 | ``` 48 | 49 | Restore the backup by running 50 | 51 | ``` 52 | incus admin os application restore backup.tar.gz 53 | ``` 54 | 55 | ```{note} 56 | It is expected to receive an EOF error since the application's HTTP REST endpoint will be restarted along with the application after performing the restoration. 57 | ``` 58 | 59 | ## Factory reset 60 | 61 | ```{warning} 62 | A factory reset will erase all configuration and state for the application. 63 | ``` 64 | 65 | Reset the application by running 66 | 67 | ``` 68 | incus admin os application factory-reset 69 | ``` 70 | 71 | ```{note} 72 | It is expected to receive an EOF error since the application's HTTP REST endpoint will be restarted along with the application after resetting the application. 73 | ``` 74 | --------------------------------------------------------------------------------