diff --git a/.conform.yaml b/.conform.yaml
new file mode 100644
index 0000000..cc6699f
--- /dev/null
+++ b/.conform.yaml
@@ -0,0 +1,38 @@
+policies:
+ - type: commit
+ spec:
+ header:
+ length: 89
+ imperative: true
+ case: lower
+ invalidLastCharacters: .
+ dco: true
+ gpg:
+ required: true
+# identity:
+# gitHubOrganization: supernetes
+ spellcheck:
+ locale: US
+ maximumOfOneCommit: true
+ conventional:
+ types:
+ - ci
+ - docs
+ - meta
+ - refactor
+ - release
+ - test
+ - type: license
+ spec:
+ skipPaths:
+ - .git/
+ includeSuffixes:
+ - container
+ - cpouta
+ allowPrecedingComments: true
+ header: |
+ # SPDX-License-Identifier: MPL-2.0
+ #
+ # This Source Code Form is subject to the terms of the Mozilla Public
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml
new file mode 100644
index 0000000..ef08eb0
--- /dev/null
+++ b/.github/workflows/check.yaml
@@ -0,0 +1,29 @@
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+
+name: Check
+jobs:
+ conform:
+ name: Conformance
+ runs-on: ubuntu-latest
+ container:
+ image: golang:1
+ options: --user 1001 # https://github.com/actions/runner/issues/2033#issuecomment-1598547465
+ steps:
+ - name: Check out sources
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+ - name: Fetch master branch for reference
+ # The main branch detection of siderolabs/conform relies on the branch tracking the "origin" remote, see
+ # https://github.com/siderolabs/conform/blob/2feadaa74eef93dd35f303582f2e82afa62a119d/cmd/conform/enforce.go#L74
+ run: git checkout master && git checkout -
+ if: github.ref_name != 'master'
+ - name: Install siderolabs/conform
+ run: go install github.com/siderolabs/conform/cmd/conform@latest
+ - name: Run siderolabs/conform
+ run: conform enforce
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1762984
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+/private/**
+!/private/**/
+!/private/**/.gitkeep
+
+/work/**
+!/work/**/
+!/work/cpouta
+!/work/supernetes-cluster.yaml
+!/work/patch/cilium.yaml
+!/work/patch/single-node.yaml
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..a2ec5a4
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "bootstrap"]
+ path = bootstrap
+ url = https://github.com/twelho/talos-bootstrap.git
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..eff1bec
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,104 @@
+# Base environment
+FROM alpine:3 as base
+
+RUN apk --no-cache upgrade && \
+ apk --no-cache add bash bash-completion cosign curl g++ git helm jq k9s kubectl kustomize less linux-headers make moreutils nano nano-syntax openssl pipx python3-dev yq
+
+# Build environment for tooling
+FROM base as build
+
+RUN mkdir /build /out
+WORKDIR /build
+
+# Talos
+FROM build as talos
+
+RUN curl -fL https://talos.dev/install | sh && cp /usr/local/bin/talosctl /out/
+
+# Flux
+FROM build as flux
+
+RUN curl -fL https://fluxcd.io/install.sh | bash && cp /usr/local/bin/flux /out/
+
+# Cilium
+FROM build as cilium
+
+RUN CILIUM_CLI_VERSION=$(curl -fL https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt) && \
+ CLI_ARCH=amd64 && if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi && \
+ curl -fL --remote-name-all https://github.com/cilium/cilium-cli/releases/download/"${CILIUM_CLI_VERSION}"/cilium-linux-"${CLI_ARCH}".tar.gz{,.sha256sum} && \
+ sha256sum -c cilium-linux-"${CLI_ARCH}".tar.gz.sha256sum && \
+ tar xzvf cilium-linux-"${CLI_ARCH}".tar.gz -C /out/ && \
+ rm -r /build
+
+# Hubble CLI
+FROM build as hubble
+
+RUN HUBBLE_VERSION=$(curl -fL https://raw.githubusercontent.com/cilium/hubble/master/stable.txt) && \
+ HUBBLE_ARCH=amd64 && if [ "$(uname -m)" = "aarch64" ]; then HUBBLE_ARCH=arm64; fi && \
+ curl -L --fail --remote-name-all https://github.com/cilium/hubble/releases/download/"${HUBBLE_VERSION}"/hubble-linux-"${HUBBLE_ARCH}".tar.gz{,.sha256sum} && \
+ sha256sum -c hubble-linux-"${HUBBLE_ARCH}".tar.gz.sha256sum && \
+ tar xzvf hubble-linux-"${HUBBLE_ARCH}".tar.gz -C /out/ && \
+ rm -r /build
+
+# SOPS
+FROM build as sops
+
+RUN set -x && \
+ SOPS_CLI_VERSION=$(curl -fL https://api.github.com/repos/getsops/sops/releases/latest | jq -r ".tag_name") && \
+ CLI_ARCH=amd64 && if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi && \
+ curl -fL --remote-name-all https://github.com/getsops/sops/releases/download/"${SOPS_CLI_VERSION}"/sops-"${SOPS_CLI_VERSION}".{linux."${CLI_ARCH}",checksums.txt,checksums.pem,checksums.sig} && \
+ cosign verify-blob sops-"${SOPS_CLI_VERSION}".checksums.txt \
+ --certificate sops-"${SOPS_CLI_VERSION}".checksums.pem \
+ --signature sops-"${SOPS_CLI_VERSION}".checksums.sig \
+ --certificate-identity-regexp=https://github.com/getsops \
+ --certificate-oidc-issuer=https://token.actions.githubusercontent.com && \
+ grep sops-"${SOPS_CLI_VERSION}".linux."${CLI_ARCH}" sops-"${SOPS_CLI_VERSION}".checksums.txt > sops-"${SOPS_CLI_VERSION}".checksums.filtered.txt && \
+ sha256sum -c sops-"${SOPS_CLI_VERSION}".checksums.filtered.txt && \
+ mv sops-"${SOPS_CLI_VERSION}".linux."${CLI_ARCH}" /out/sops && chmod +x /out/sops && \
+ rm -r /build
+
+# Krew
+FROM build as krew
+
+RUN CLI_ARCH=amd64 && if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi && KREW=krew-linux_"${CLI_ARCH}" && \
+ curl -fL --remote-name-all https://github.com/kubernetes-sigs/krew/releases/latest/download/"${KREW}".tar.gz{,.sha256} && \
+ echo "$(cat "${KREW}".tar.gz.sha256) ${KREW}.tar.gz" > "${KREW}".tar.gz.sha256sum && \
+ sha256sum -c "${KREW}".tar.gz.sha256sum && \
+ tar xzvf "${KREW}".tar.gz && \
+ ./"${KREW}" install krew && \
+ mv ~/.krew /out/ && \
+ rm -r /build
+
+# Management container image
+FROM base
+
+# Installation
+WORKDIR /usr/local/bin
+COPY --from=talos /out/ .
+COPY --from=flux /out/ .
+COPY --from=cilium /out/ .
+COPY --from=hubble /out/ .
+COPY --from=sops /out/ .
+
+WORKDIR /root
+COPY --from=krew /out/ .
+
+# Configuration
+ENV EDITOR=nano
+ENV HISTCONTROL=ignoreboth
+RUN update-ca-certificates && \
+ talosctl completion bash >> ~/.bashrc && \
+ cilium completion bash >> ~/.bashrc && \
+ hubble completion bash >> ~/.bashrc && \
+ flux completion bash >> ~/.bashrc && \
+ sed -ri 's|^# (set afterends)$|\1|' /etc/nanorc && \
+ sed -ri 's|^# (include "/usr/share/nano/\*\.nanorc")$|\1|' /etc/nanorc && \
+ register-python-argcomplete pipx >> ~/.bashrc && pipx ensurepath && \
+ pipx install python-openstackclient && ~/.local/bin/openstack complete >> ~/.bashrc && \
+ echo "pipx install -e /bootstrap &> /dev/null &" >> ~/.bashrc && \
+ echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bashrc
+# PATH="$HOME/.krew/bin:$PATH" kubectl krew install ...
+
+# Sleep forever, use `exec` to enter the container
+ENTRYPOINT ["/bin/sh", "-c", "trap 'exit 0' INT TERM; sleep infinity & wait"]
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8816192
--- /dev/null
+++ b/README.md
@@ -0,0 +1,65 @@
+# Bootstrapping Utilities for Supernetes
+
+This repository contains tooling that helps with setting up a [Talos Linux](https://www.talos.dev/) Kubernetes cluster for developing and evaluating [Supernetes](https://github.com/twelho/talos-bootstrap.git). More specifically, the tools help with
+
+- creating an unprovisioned Talos Linux cluster in [CSC's cPouta](https://research.csc.fi/-/cpouta) OpenStack environment, and
+- bootstrapping an unprovisioned Talos Linux cluster using [talos-boostrap](https://github.com/twelho/talos-bootstrap).
+
+Both steps can be invoked individually, so you don't need to run Talos Linux on cPouta if you just need to bootstrap Supernetes on it.
+
+## Usage
+
+The scripts assume a standard set of tooling that are provided in a container built from this repo. Podman or Docker is required to be present on the host. Build, start and enter the container simply by issuing
+
+```shell
+./container
+```
+
+For utilities available in the container, please see the [Dockerfile](./Dockerfile). Some tools are also installed during the `Configuration` step.
+
+### Creating a New Cluster in cPouta
+
+
+
+
+Start by logging into cPouta and selecting the right project from the top left. The project must have sufficient resources available for running VMs.
+
+> [!WARNING]
+> Please choose an empty project, all existing resources may be automatically deleted as part of the idempotency!
+
+
+
+Then, click on your username from the top right and select `OpenStack RC File`. This will give you a file named `project_1234567-openrc.sh` which will be used by the scripts for API access. Save it into the `work` directory, which is used as the working directory of the container.
+
+Finally, inside the [container](#usage), run
+
+```shell
+. project_1234567-openrc.sh # Load the configuration, you might need to log in
+talosctl gen secrets # Generate Talos secrets (one-time)
+./cpouta up # Bring up the VMs
+```
+
+The nodes should now be running with the baseline configuration, and ready to be fully configured with `talos-bootstrap`.
+
+### Applying Supernetes Configuration with `talos-bootstrap`
+
+The full configuration is applied using [talos-boostrap](https://github.com/twelho/talos-bootstrap), which is provided as a submodule in this repo.
+
+1. Edit the configuration file [`work/supernetes-cluster.yaml`](work/supernetes-cluster.yaml) and change the path `controlplane/record` to point to the Talos/Kubernetes endpoint address (public IP in case of cPouta).
+2. Inside the [container](#usage), run
+
+ ```shell
+ bootstrap supernetes-cluster.yaml
+ ```
+
+ and wait for the bootstrap process to finish. Your cluster should now be configured with [Flux](https://fluxcd.io/) automatically reconciling in the Supernetes controller.
+
+Refer to the documentation of [talos-boostrap](https://github.com/twelho/talos-bootstrap) for details, such as how to apply patches and additional manifests etc.
+
+## Authors
+
+- Dennis Marttinen ([@twelho](https://github.com/twelho))
+
+## License
+
+[MPL-2.0](https://spdx.org/licenses/MPL-2.0.html) ([LICENSE](LICENSE))
diff --git a/bootstrap b/bootstrap
new file mode 160000
index 0000000..6eaf853
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1 @@
+Subproject commit 6eaf853041416da8dcdd4533611f90128783a84f
diff --git a/container b/container
new file mode 100755
index 0000000..0e518a4
--- /dev/null
+++ b/container
@@ -0,0 +1,66 @@
+#!/bin/sh -e
+#
+# Utility container for bootstrapping and configuring Talos Linux for Supernetes
+# (c) Dennis Marttinen 2024
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+_build() {
+ # If _FULL_RECREATE is set, append --no-cache
+ "$RUNTIME" build -t supernetes-bootstrap ${_FULL_RECREATE+--no-cache} .
+ unset _FULL_RECREATE # Don't keep re-downloading in case this loops
+}
+
+unset _RECREATE
+unset _FULL_RECREATE
+
+if [ -z "$RUNTIME" ]; then RUNTIME=$(command -v podman ||:); fi
+if [ -z "$RUNTIME" ]; then RUNTIME=$(command -v docker ||:); fi
+if [ -z "$RUNTIME" ]; then echo "Error: no container runtime found"; exit 1; fi
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ -f | --full)
+ _FULL_RECREATE=
+ shift
+ ;;
+ -r | --recreate)
+ _RECREATE=
+ shift
+ ;;
+ *)
+ echo "Usage: $0 [-r|--recreate [-f|--full]]"
+ exit 1
+ ;;
+ esac
+done
+
+if [ -n "${_RECREATE+x}" ]; then
+ "$RUNTIME" rm -f supernetes-bootstrap
+ _build
+fi
+
+while
+ "$RUNTIME" exec -it supernetes-bootstrap bash
+ [ "$?" -eq 125 ] # Container not found
+do
+ while
+ "$RUNTIME" run -d --rm \
+ --name supernetes-bootstrap \
+ --pull never \
+ -p 13000-13009:13000-13009 \
+ -v ./bootstrap:/bootstrap:Z \
+ -v ./private/kube:/root/.kube:Z \
+ -v ./private/talos:/root/.talos:Z \
+ -v ./work:/work:Z \
+ -w /work \
+ supernetes-bootstrap
+ [ "$?" -eq 125 ] # Image not found
+ do
+ _build
+ done
+done
diff --git a/docs/openstack_project.png b/docs/openstack_project.png
new file mode 100644
index 0000000..f760267
Binary files /dev/null and b/docs/openstack_project.png differ
diff --git a/docs/openstack_rc.png b/docs/openstack_rc.png
new file mode 100644
index 0000000..6bc09f1
Binary files /dev/null and b/docs/openstack_rc.png differ
diff --git a/private/kube/.gitkeep b/private/kube/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/private/talos/.gitkeep b/private/talos/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/work/cpouta b/work/cpouta
new file mode 100755
index 0000000..80ebe62
--- /dev/null
+++ b/work/cpouta
@@ -0,0 +1,130 @@
+#!/bin/bash -e
+#
+# Idempotently install Talos Linux on cPouta
+# (c) Dennis Marttinen 2024
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# References:
+# - https://www.clouditlab.com/deploying-a-single-node-kubernetes-cluster-in-aws-using-talos/
+
+_p() { (set -x; "$@"); }
+alias openstack="_p openstack"
+shopt -s expand_aliases
+set -o pipefail
+
+{
+ _machine=talos-control-plane-1
+ _subnet=192.168.10.0/24
+ _private_ip=192.168.10.10
+ _nameservers=(1.1.1.1 1.0.0.1)
+
+ _usage() {
+ cat <<- EOF
+ Usage: $0
+
+ -i, --image Also remove images from OpenStack
+ EOF
+ exit "$1"
+ }
+
+ while [ "$#" -gt 0 ]; do
+ case "$1" in
+ up|down)
+ _mode="$1"
+ ;;
+ -i|--image)
+ if [ "$_mode" != down ]; then
+ _usage 1
+ fi
+ _delete_images=
+ ;;
+ -h|--help)
+ _usage 0
+ ;;
+ *)
+ _usage 1
+ esac
+ shift
+ done
+
+ if [ -z "${_mode+x}" ]; then
+ _usage 1
+ fi
+
+ _delete() {
+ openstack "${@:3}" list -f json | jq --arg key "$1" --arg value "$2" -rc 'map(select(.[$key] == $value)) | .[].ID' | while read -r id; do openstack "${@:3}" delete "$id"; done
+ }
+
+ _delete Name "$_machine" server
+ _delete "Fixed IP Address" "$_private_ip" floating ip
+ _delete Name "$_machine" port
+ _delete Name talos security group
+ openstack router remove subnet talos talos ||:
+ _delete Name talos router
+ _delete Name talos subnet
+ _delete Name talos network
+ if [ -n "${_delete_images+x}" ]; then
+ _delete Name talos image
+ fi
+
+ if [ "$_mode" == down ]; then
+ exit 0
+ fi
+
+ # Upload disk image
+ if [ "$(openstack image list --name talos -f json | jq length)" -eq 0 ]; then
+ _talos_version=$(talosctl version --client --short | grep -Eo "v([0-9.]+)$")
+ openstack image create --disk-format raw --file <(curl -L "https://github.com/siderolabs/talos/releases/download/$_talos_version/openstack-amd64.raw.xz" | sponge | unxz -) talos
+ fi
+
+ # Create network and subnet
+ openstack network create talos
+ openstack subnet create --network talos --subnet-range "$_subnet" "${_nameservers[@]/#/--dns-nameserver=}" talos
+
+ # Configure router
+ openstack router create --external-gateway public talos
+ openstack router add subnet talos talos ||:
+
+ # Configure firewall
+ openstack security group create talos
+ openstack security group rule create --protocol tcp --dst-port 6443 --description "Kubernetes API" talos ||:
+ openstack security group rule create --protocol tcp --dst-port 50000 --description "Talos API" talos ||:
+
+ # Create ports
+ openstack port create --network talos --fixed-ip subnet=talos,ip-address="$_private_ip" --security-group talos "$_machine"
+
+ # Create floating IPs
+ openstack floating ip create --port "$_machine" public ||:
+ _public_ip=$(openstack floating ip list --port "$_machine" -f json | jq -r '.[0] | ."Floating IP Address"')
+
+ # Create baseline Talos configuration, override this with talos-bootstrap
+ talosctl gen config -f cpouta "https://$_private_ip:6443" --with-secrets secrets.yaml --config-patch @<(cat <<- EOF
+ machine:
+ type: init # Bootstrap automatically
+ cluster:
+ allowSchedulingOnControlPlanes: true # Single-node cluster
+ EOF
+ )
+ talosctl --talosconfig talosconfig config endpoint "$_public_ip"
+ talosctl --talosconfig talosconfig config node "$_private_ip"
+ talosctl config merge talosconfig
+
+ # Create VMs
+ openstack server create --flavor standard.large --nic port-id="$_machine" --image talos --user-data controlplane.yaml "$_machine"
+
+ # Wait for the cluster to become healthy
+ while ! talosctl health; do sleep 1; done
+ talosctl kubeconfig -fm
+ sed -i "s/$_private_ip/$_public_ip/g" ~/.kube/config
+
+ # Print IPs for convenient access
+ echo "Private IP: $_private_ip"
+ echo "Public IP: $_public_ip"
+
+ exit
+}
diff --git a/work/patch/cilium.yaml b/work/patch/cilium.yaml
new file mode 100644
index 0000000..84a0409
--- /dev/null
+++ b/work/patch/cilium.yaml
@@ -0,0 +1,6 @@
+cluster:
+ network:
+ cni:
+ name: none # Don't install the default CNI, we'll use Cilium instead
+ proxy:
+ disabled: true # Full kube-proxy replacement mode
diff --git a/work/patch/single-node.yaml b/work/patch/single-node.yaml
new file mode 100644
index 0000000..dedd179
--- /dev/null
+++ b/work/patch/single-node.yaml
@@ -0,0 +1,2 @@
+cluster:
+ allowSchedulingOnControlPlanes: true # Single-node cluster
diff --git a/work/supernetes-cluster.yaml b/work/supernetes-cluster.yaml
new file mode 100644
index 0000000..352545e
--- /dev/null
+++ b/work/supernetes-cluster.yaml
@@ -0,0 +1,24 @@
+cluster:
+ name: supernetes-cluster # The name of the cluster. The domain is appended to this to form a FQDN.
+ secrets: secrets.yaml # Cluster secrets file. Generate one with `talosctl gen secrets`.
+ cilium:
+ hardening:
+ enabled: false # Disable NetworkPolicy hardening for evaluation purposes
+ audit-mode: false # Audit mode is not needed without hardening
+ flux: # Configuration for Flux (GitOps) (optional)
+ # Install specific (extra) Flux components, see https://fluxcd.io/flux/components/ for details
+ components: source-controller,kustomize-controller # (optional)
+ all-namespaces: false # Set to "false" to make Flux only watch the installation namespace (optional)
+ patches: # Any cluster-wide patches to apply when creating the configuration with `talosctl gen config` (optional)
+ - "@patch/cilium.yaml"
+ - "@patch/single-node.yaml"
+ manifests: manifests # Kustomization directory for additional manifests to be applied into the cluster (optional)
+
+controlplane:
+ record: 1.2.3.4 # The control plane IP or DNS record. The domain is appended to this to form a FQDN.
+ record-as-endpoint: true # Remote cluster, use the above address for the Talos API as well.
+ nodes: # Addresses of the control plane nodes. DNS names are recommended, but static IPs should work as well.
+ 192.168.10.10: # Node-specific patches can be applied here as a list (optional)
+
+worker:
+ nodes: # No worker nodes configured