Initial infra scaffold: watcher live on Infomaniak Public Cloud
- flake.nix exposes a devShell (openstackclient, opentofu, sops, age) plus
nixosConfigurations.watcher (runtime) and packages.watcher-image (QCOW2
via nixos-generators / openstack format).
- hosts/watcher/default.nix: SSH-only base, tyro user with key auth, root
SSH disabled, trusted-users set so laptop closure pushes work.
- modules/website.nix: Caddy serves tyrolize.ch from sites/tyrolize.ch/;
lize.ch 301-redirects; firewall opens 80/443. Let's Encrypt via HTTP-01.
- terraform/infomaniak/: OpenStack provider, security group (22/80/443),
keypair, compute instance booted from the uploaded image. Auth via OS_*
env vars sourced from terraform/infomaniak/.env by the devShell hook.
- scripts/build-image.sh + scripts/deploy.sh.
- dns/{tyrolize,lize}.ch.zone: full BIND zone files for the advanced view
in Infomaniak DNS Manager; preserves kSuite mail records on lize.ch.
Watcher live at 195.15.203.200 (IPv6 2001:1600:10💯:b4e), NixOS 25.05.
HTTPS confirmed working on both domains.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
59d742f4ba
18 changed files with 733 additions and 0 deletions
43
terraform/infomaniak/.env.example
Normal file
43
terraform/infomaniak/.env.example
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Copy to `.env` (gitignored) and fill in real values from the clouds.yaml
|
||||
# you can download at: Infomaniak Manager → Public Cloud → your project →
|
||||
# API access → Download clouds.yaml.
|
||||
#
|
||||
# Once filled in, just run `nix develop` from the repo root — the devShell's
|
||||
# shellHook automatically sources this file. Then `openstack ...` and
|
||||
# `terraform ...` are authenticated.
|
||||
|
||||
# --- OpenStack auth (application credential = modern, recommended) ---
|
||||
# In your downloaded clouds.yaml these live under:
|
||||
# clouds.openstack.auth_type
|
||||
# clouds.openstack.auth.auth_url
|
||||
# clouds.openstack.auth.application_credential_id
|
||||
# clouds.openstack.auth.application_credential_secret
|
||||
export OS_AUTH_TYPE=v3applicationcredential
|
||||
export OS_AUTH_URL=https://api.pub1.infomaniak.cloud/identity/v3
|
||||
export OS_APPLICATION_CREDENTIAL_ID=REPLACE_ME
|
||||
export OS_APPLICATION_CREDENTIAL_SECRET=REPLACE_ME
|
||||
|
||||
export OS_REGION_NAME=dc3-a
|
||||
export OS_INTERFACE=public
|
||||
export OS_IDENTITY_API_VERSION=3
|
||||
|
||||
# --- Alternative: legacy username + password auth ---
|
||||
# Only if your clouds.yaml uses passwords instead of an application credential.
|
||||
# Comment out the four OS_AUTH_TYPE / OS_AUTH_URL / OS_APPLICATION_* lines above
|
||||
# and uncomment these instead:
|
||||
#
|
||||
# export OS_AUTH_URL=https://api.pub1.infomaniak.cloud/identity/v3
|
||||
# export OS_PROJECT_ID=REPLACE_ME
|
||||
# export OS_PROJECT_NAME=REPLACE_ME
|
||||
# export OS_USER_DOMAIN_NAME=Default
|
||||
# export OS_USERNAME=REPLACE_ME
|
||||
# export OS_PASSWORD=REPLACE_ME
|
||||
|
||||
# --- Terraform variables ---
|
||||
# Contents of ~/.ssh/id_ed25519.pub (one line, quoted).
|
||||
export TF_VAR_ssh_public_key="ssh-ed25519 AAAA... tyro@lize.ch"
|
||||
|
||||
# Override defaults from variables.tf only if you want a different size/network:
|
||||
# export TF_VAR_flavor_name="a2-ram4-disk50-perf1"
|
||||
# export TF_VAR_external_network="ext-net1"
|
||||
# export TF_VAR_watcher_image_name="nixos-watcher"
|
||||
24
terraform/infomaniak/.terraform.lock.hcl
generated
Normal file
24
terraform/infomaniak/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/terraform-provider-openstack/openstack" {
|
||||
version = "2.1.0"
|
||||
constraints = "~> 2.0"
|
||||
hashes = [
|
||||
"h1:2TcmfEzBOGQPALErrXTaL6v+k/WAL40adao4izRYmdw=",
|
||||
"zh:113661750398bf21c8fe36aade9fb6f5eb82b5bcd3bcd30bd37ac805d83398f4",
|
||||
"zh:1b3c26347b9cd61e413ee93c2f422cc3278a77f55fd3516eaabb3e2a85f65281",
|
||||
"zh:1b751bbf1e4152829a643b532fd3f5967a2e89a41fac381257e0b41665be3306",
|
||||
"zh:1b967bbfd9b344419c0e0df0c3a15fcbd731e91f19a18955a55aace8d9ec039a",
|
||||
"zh:1bc0fc7c0a21e568db043b654501ce668ba19bf7628d37a7d2aaa512fd6e5aeb",
|
||||
"zh:425cbf61757d4b503e7bf0f409ea59835ca3afbd2432d56ad552c2e5d234a572",
|
||||
"zh:67d4f059cb4d73bf6c060313ec32962c4e5bd8dc7be2542a6f2098ab32575cd9",
|
||||
"zh:7fe841ac5b68a4f52fb3cf45070828f3845de44746679d434e4349f3c23e3ef2",
|
||||
"zh:ac1ed4c6ef0b6a3410568a05d3f9933d184497f065988503c43da0b2f0786ab2",
|
||||
"zh:c5c0d14c86fabd9ab6a5d555e6a8d511942665fb5fa948dd452b0d1934068344",
|
||||
"zh:c9ae5c210192275185d6823566a9421983e8e64c2665a4cae00b92dd0706bd19",
|
||||
"zh:ee9865ccc053e7f345e532654fb628d1cf1e81cd2e929643c1691bebffcf7b98",
|
||||
"zh:f3416d2f666095e740522c4964e436470bb9ec17bd53aaae8169ad93297d07bd",
|
||||
"zh:fbca85457dd49e17168989d64f7cfc4a519d55ef4e00e89cea2859e87ad87f83",
|
||||
]
|
||||
}
|
||||
13
terraform/infomaniak/main.tf
Normal file
13
terraform/infomaniak/main.tf
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
terraform {
|
||||
required_version = ">= 1.6"
|
||||
required_providers {
|
||||
openstack = {
|
||||
source = "terraform-provider-openstack/openstack"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Auth comes from OS_* env vars (sourced from terraform/infomaniak/.env
|
||||
# via the devShell's shellHook). No clouds.yaml file needed.
|
||||
provider "openstack" {}
|
||||
14
terraform/infomaniak/outputs.tf
Normal file
14
terraform/infomaniak/outputs.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
output "watcher_ipv4" {
|
||||
value = openstack_compute_instance_v2.watcher.access_ip_v4
|
||||
description = "Public IPv4 of the watcher VM"
|
||||
}
|
||||
|
||||
output "watcher_ipv6" {
|
||||
value = openstack_compute_instance_v2.watcher.access_ip_v6
|
||||
description = "Public IPv6 of the watcher VM"
|
||||
}
|
||||
|
||||
output "watcher_id" {
|
||||
value = openstack_compute_instance_v2.watcher.id
|
||||
description = "OpenStack instance ID (handy for `openstack server show`)"
|
||||
}
|
||||
36
terraform/infomaniak/variables.tf
Normal file
36
terraform/infomaniak/variables.tf
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
variable "ssh_public_key" {
|
||||
description = "Contents of ~/.ssh/id_ed25519.pub (one line, starts with 'ssh-ed25519 ...')"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "ssh_key_name" {
|
||||
description = "Display name for the keypair stored at Infomaniak"
|
||||
type = string
|
||||
default = "tyro-laptop"
|
||||
}
|
||||
|
||||
# Flavor naming (confirmed from Infomaniak's catalogue, 2026-06):
|
||||
# a{vCPU}_ram{GB}_disk{GB}[_perf1]
|
||||
# _perf1 = higher-tier SSD; disk0 = bring-your-own-volume.
|
||||
# 2 vCPU / 4 GB / 20 GB SSD = a2-ram4-disk20-perf1, ~CHF 7.5/mo.
|
||||
|
||||
variable "flavor_name" {
|
||||
description = "OpenStack flavor for the watcher"
|
||||
type = string
|
||||
default = "a2-ram4-disk20-perf1"
|
||||
}
|
||||
|
||||
# ext-net1 is the dual-stack public network — VMs get IPv4 + IPv6 directly.
|
||||
# ext-floating1 is only needed when running instances behind a router.
|
||||
|
||||
variable "external_network" {
|
||||
description = "Name of Infomaniak's public dual-stack network"
|
||||
type = string
|
||||
default = "ext-net1"
|
||||
}
|
||||
|
||||
variable "watcher_image_name" {
|
||||
description = "Name of the NixOS image previously uploaded to Glance"
|
||||
type = string
|
||||
default = "nixos-watcher"
|
||||
}
|
||||
73
terraform/infomaniak/watcher.tf
Normal file
73
terraform/infomaniak/watcher.tf
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
data "openstack_images_image_v2" "watcher" {
|
||||
name = var.watcher_image_name
|
||||
most_recent = true
|
||||
}
|
||||
|
||||
data "openstack_networking_network_v2" "external" {
|
||||
name = var.external_network
|
||||
}
|
||||
|
||||
resource "openstack_compute_keypair_v2" "primary" {
|
||||
name = var.ssh_key_name
|
||||
public_key = var.ssh_public_key
|
||||
}
|
||||
|
||||
resource "openstack_networking_secgroup_v2" "watcher" {
|
||||
name = "watcher"
|
||||
description = "watcher: SSH + HTTP(S)"
|
||||
}
|
||||
|
||||
# SSH in (IPv4 + IPv6)
|
||||
resource "openstack_networking_secgroup_rule_v2" "ssh_v4" {
|
||||
direction = "ingress"
|
||||
ethertype = "IPv4"
|
||||
protocol = "tcp"
|
||||
port_range_min = 22
|
||||
port_range_max = 22
|
||||
remote_ip_prefix = "0.0.0.0/0"
|
||||
security_group_id = openstack_networking_secgroup_v2.watcher.id
|
||||
}
|
||||
|
||||
resource "openstack_networking_secgroup_rule_v2" "ssh_v6" {
|
||||
direction = "ingress"
|
||||
ethertype = "IPv6"
|
||||
protocol = "tcp"
|
||||
port_range_min = 22
|
||||
port_range_max = 22
|
||||
remote_ip_prefix = "::/0"
|
||||
security_group_id = openstack_networking_secgroup_v2.watcher.id
|
||||
}
|
||||
|
||||
# HTTP(S) for Caddy. Open from day one so cert provisioning works the moment
|
||||
# we add the website module — closing again would just be churn.
|
||||
resource "openstack_networking_secgroup_rule_v2" "http" {
|
||||
direction = "ingress"
|
||||
ethertype = "IPv4"
|
||||
protocol = "tcp"
|
||||
port_range_min = 80
|
||||
port_range_max = 80
|
||||
remote_ip_prefix = "0.0.0.0/0"
|
||||
security_group_id = openstack_networking_secgroup_v2.watcher.id
|
||||
}
|
||||
|
||||
resource "openstack_networking_secgroup_rule_v2" "https" {
|
||||
direction = "ingress"
|
||||
ethertype = "IPv4"
|
||||
protocol = "tcp"
|
||||
port_range_min = 443
|
||||
port_range_max = 443
|
||||
remote_ip_prefix = "0.0.0.0/0"
|
||||
security_group_id = openstack_networking_secgroup_v2.watcher.id
|
||||
}
|
||||
|
||||
resource "openstack_compute_instance_v2" "watcher" {
|
||||
name = "watcher"
|
||||
flavor_name = var.flavor_name
|
||||
image_id = data.openstack_images_image_v2.watcher.id
|
||||
key_pair = openstack_compute_keypair_v2.primary.name
|
||||
security_groups = [openstack_networking_secgroup_v2.watcher.name]
|
||||
|
||||
network {
|
||||
name = data.openstack_networking_network_v2.external.name
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue