diff --git a/.github/workflows/flame.yml b/.github/workflows/flame.yml new file mode 100644 index 0000000..99d6726 --- /dev/null +++ b/.github/workflows/flame.yml @@ -0,0 +1,175 @@ +name: Flame + +run-name: Flame - ${{ inputs.rebuild && 'Rebuild and ' || '' }}${{ inputs.action == 'create' && 'Create' || ( inputs.action == 'destroy' && 'Destroy' || 'No Action' ) }} + +env: + TERRAFORM_DIRECTORY: deploy/oracle + DEPLOY_IDENTITY_BASE64: ${{ secrets.DEPLOY_IDENTITY_BASE64 }} + FLAME_IDENTITY_BASE64: ${{ secrets.FLAME_IDENTITY_BASE64 }} + ZONE_NAME: masu.rs + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} + OCI_CLI_USER: "ocid1.user.oc1..aaaaaaaa6lro2eoxdajjypjysepvzcavq5yn4qyozjyebxdiaoqziribuqba" + OCI_CLI_TENANCY: "ocid1.tenancy.oc1..aaaaaaaaudwr2ozedhjnrn76ofjgglgug6gexknjisd7gb7tkj3mjdp763da" + OCI_CLI_FINGERPRINT: "dd:d0:da:6d:83:46:8b:b3:d9:45:2b:c7:56:ae:30:94" + OCI_CLI_KEY_CONTENT: "${{ secrets.OCI_PRIVATE_KEY }}" + TF_VAR_oci_private_key: "${{ secrets.OCI_PRIVATE_KEY }}" + OCI_CLI_REGION: "us-ashburn-1" + +on: + workflow_dispatch: + inputs: + rebuild: + description: Whether or not to rebuild image + type: boolean + default: false + action: + description: Which action to take with Terraform + type: choice + required: true + default: create + options: + - create + - destroy + - nothing + +permissions: + id-token: write + contents: write + +jobs: + build-deploy: + name: Build and Deploy + runs-on: ubuntu-24.04-arm + steps: + - name: Checkout Repo Code + uses: actions/checkout@v4 + + # - name: Write OCI Key to File + # run: | + # echo "${{ env.OCI_PRIVATE_KEY_BASE64 }}" | base64 -d > OCI_PRIVATE_KEY + + # Enable access to KVM, required to build an image + - name: Enable KVM group perms + if: inputs.rebuild && inputs.action != 'destroy' + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # Install Nix + - name: Install Nix + if: inputs.rebuild && inputs.action != 'destroy' + uses: cachix/install-nix-action@v17 + + # Build the image + - name: Build Image + if: inputs.rebuild && inputs.action != 'destroy' + run: nix build .#flame-qcow + + - name: List Images + if: inputs.rebuild && inputs.action != 'destroy' + run: | + ls -lh result/ + echo "IMAGE_NAME=$(ls result/nixos-qcow-image-*.qcow*) >> $GITHUB_ENV + + - name: Upload Image to S3 + if: inputs.rebuild && inputs.action != 'destroy' + # env: + # AWS_ACCESS_KEY_ID: "" + # AWS_SECRET_ACCESS_KEY: "" + # AWS_DEFAULT_REGION: "us-ashburn-1" # e.g., us-ashburn-1, us-phoenix-1 + # AWS_ENDPOINT_URL: "https://masur.compat.objectstorage.us-ashburn-1.oraclecloud.com" + uses: oracle-actions/run-oci-cli-command@v1.3.2 + with: + command: | + oci os object put \ + --namespace "masur" \ + --bucket-name "noahmasur-images" \ + --name "nixos.qcow2" \ + --file "${IMAGE_NAME}" \ + --part-size 128 # Optional: Specify part size in MiB for multipart uploads, default is 128 MiB + --parallel-upload-count 5 # Optional: Number of parallel uploads, default is 3 + + # Login to AWS + - name: AWS Assume Role + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::286370965832:role/github_actions_admin + aws-region: us-east-1 + + # Installs the Terraform binary and some other accessory functions. + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + + # Checks whether Terraform is formatted properly. If this fails, you + # should install the pre-commit hook. + - name: Check Formatting + working-directory: ${{ env.TERRAFORM_DIRECTORY }} + run: | + terraform fmt -no-color -check -diff -recursive + + # Connects to remote state backend and download providers. + - name: Terraform Init + working-directory: ${{ env.TERRAFORM_DIRECTORY }} + run: terraform init + + # Deploys infrastructure or changes to infrastructure. + - name: Terraform Apply + if: inputs.action == 'create' + working-directory: ${{ env.TERRAFORM_DIRECTORY }} + run: | + terraform apply \ + -auto-approve \ + -input=false + + # Removes infrastructure. + - name: Terraform Destroy + if: inputs.action == 'destroy' + working-directory: ${{ env.TERRAFORM_DIRECTORY }} + run: | + terraform destroy \ + -auto-approve \ + -input=false + + - name: Get Host IP + if: inputs.action == 'create' + id: host + working-directory: ${{ env.TERRAFORM_DIRECTORY }} + run: terraform output -raw host_ip + + - name: Wait on SSH + if: inputs.action == 'create' + run: | + for i in $(seq 1 15); do + if $(nc -z -w 3 ${{ steps.host.outputs.stdout }} 22); then + exit 0 + fi + sleep 10 + done + + - name: Write Identity Keys to Files + if: inputs.action == 'create' + run: | + echo "${{ env.DEPLOY_IDENTITY_BASE64 }}" | base64 -d > deploy_ed25519 + chmod 0600 deploy_ed25519 + echo "${{ env.FLAME_IDENTITY_BASE64 }}" | base64 -d > flame_ed25519 + chmod 0600 flame_ed25519 + + - name: Copy Identity File to Host + if: inputs.action == 'create' + run: | + ssh -i deploy_ed25519 -o StrictHostKeyChecking=accept-new noah@${{ steps.host.outputs.stdout }} 'mkdir -pv .ssh' + scp -i deploy_ed25519 flame_ed25519 noah@${{ steps.host.outputs.stdout }}:~/.ssh/id_ed25519 + + # - name: Wipe Records + # if: ${{ inputs.action == 'destroy' }} + # run: | + # RECORD_ID=$(curl --request GET \ + # --url https://api.cloudflare.com/client/v4/zones/${{ env.CLOUDFLARE_ZONE_ID }}/dns_records \ + # --header 'Content-Type: application/json' \ + # --header "Authorization: Bearer ${{ env.CLOUDFLARE_API_TOKEN }}" | jq -r '.result[] | select(.name == "n8n2.${{ env.ZONE_NAME }}") | .id') + # curl --request DELETE \ + # --url https://api.cloudflare.com/client/v4/zones/${{ env.CLOUDFLARE_ZONE_ID }}/dns_records/${RECORD_ID} \ + # --header 'Content-Type: application/json' \ + # --header "Authorization: Bearer ${{ env.CLOUDFLARE_API_TOKEN }}" diff --git a/deploy/oracle/main.tf b/deploy/oracle/main.tf new file mode 100644 index 0000000..ff5348d --- /dev/null +++ b/deploy/oracle/main.tf @@ -0,0 +1,103 @@ +terraform { + backend "s3" { + region = "us-east-1" + use_lockfile = true + } + required_version = ">= 1.0.0" + required_providers { + oci = { + source = "oracle/oci" + version = "7.7.0" + } + } +} + +provider "oci" { + auth = "APIKey" + tenancy_ocid = var.compartment_ocid + user_ocid = "ocid1.user.oc1..aaaaaaaa6lro2eoxdajjypjysepvzcavq5yn4qyozjyebxdiaoqziribuqba" + private_key = var.oci_private_key + fingerprint = "dd:d0:da:6d:83:46:8b:b3:d9:45:2b:c7:56:ae:30:94" + region = "us-ashburn-1" +} + +# # Get the latest Ubuntu image OCID +# # We'll filter for a recent Ubuntu LTS version (e.g., 22.04 or 24.04) and pick the latest. +# # Note: Image OCIDs are region-specific. This data source helps find the correct one. +# data "oci_core_images" "ubuntu_image" { +# compartment_id = var.compartment_ocid +# operating_system = "Canonical Ubuntu" +# # Adjust this version if you prefer a different Ubuntu LTS (e.g., "24.04") +# operating_system_version = "24.04" +# shape_filter = var.instance_shape # Filter by the shape to ensure compatibility +# sort_by = "TIMECREATED" +# sort_order = "DESC" +# limit = 1 # Get only the latest +# } + +resource "oci_core_image" "my_custom_image" { + compartment_id = var.compartment_ocid + display_name = "noah-nixos" + + image_source_details { + source_type = "objectStorageTuple" # Use this if specifying namespace, bucket, and object name + # source_type = "objectStorageUri" # Use this if you have a pre-authenticated request URL (PAR) + namespace_name = var.object_storage_namespace + bucket_name = var.object_storage_bucket_name + object_name = var.object_storage_object_name + + source_image_type = "QCOW2" # e.g., "QCOW2", "VMDK" + } + + # These properties help OCI understand how to launch instances from this image + # Adjust based on your custom image's OS and boot mode + launch_mode = "PARAVIRTUALIZED" # Or "NATIVE", "EMULATED", "CUSTOM" + operating_system = "NixOS" # e.g., "CentOS", "Debian", "Windows" + operating_system_version = "25.05" # e.g., "7", "11", "2019" + + # Optional: for specific launch options if your image requires them + # launch_options { + # boot_volume_type = "PARAVIRTUALIZED" + # firmware = "UEFI_64" # Or "BIOS" + # network_type = "PARAVIRTUALIZED" + # } + + # Time out for image import operation. Can take a while for large images. + timeouts { + create = "60m" # Default is 20m, often needs to be increased + } +} + +resource "oci_core_instance" "my_compute_instance" { + compartment_id = var.compartment_ocid + availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name + shape = var.instance_shape + display_name = var.instance_display_name + + source_details { + source_type = "image" + # # Use the OCID of the latest Ubuntu image found by the data source + # source_id = data.oci_core_images.ubuntu_image.images[0].id + # Use the OCID of the newly imported custom image + source_id = oci_core_image.my_custom_image.id + # Specify the boot volume size + boot_volume_size_in_gbs = var.boot_volume_size_in_gbs + } + + create_vnic_details { + subnet_id = oci_core_subnet.my_public_subnet.id # Use the created subnet's ID + display_name = "primary_vnic" + assign_public_ip = true + } + + metadata = { + ssh_authorized_keys = var.ssh_public_key + user_data = base64encode(var.cloud_init_script) + } + + # Optional: For flexible shapes (e.g., VM.Standard.E4.Flex), you might need to specify OCPUs and memory + shape_config { + ocpus = 4 + memory_in_gbs = 24 + } +} diff --git a/deploy/oracle/network.tf b/deploy/oracle/network.tf new file mode 100644 index 0000000..2d33a25 --- /dev/null +++ b/deploy/oracle/network.tf @@ -0,0 +1,126 @@ +resource "oci_core_vcn" "my_vpc" { + compartment_id = var.compartment_ocid + display_name = "main" + cidr_block = "10.0.0.0/16" + is_ipv6enabled = false + dns_label = "mainvcn" # Must be unique within your tenancy +} + +resource "oci_core_internet_gateway" "my_igw" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.my_vpc.id + display_name = "main-igw" + is_enabled = true +} + +resource "oci_core_route_table" "my_public_route_table" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.my_vpc.id + display_name = "main-public-rt" + + # Default route to the Internet Gateway + route_rules { + destination = "0.0.0.0/0" + destination_type = "CIDR_BLOCK" + network_entity_id = oci_core_internet_gateway.my_igw.id + } +} + +resource "oci_core_security_list" "my_public_security_list" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.my_vpc.id + display_name = "main-public-sl" + + # Egress Rules (Allow all outbound traffic) + egress_security_rules { + destination = "0.0.0.0/0" + destination_type = "CIDR_BLOCK" + protocol = "all" + } + + # Ingress Rules + ingress_security_rules { + # SSH (TCP 22) + protocol = "6" # TCP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + tcp_options { + min = 22 + max = 22 + } + } + + ingress_security_rules { + # HTTP (TCP 80) + protocol = "6" # TCP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + tcp_options { + min = 80 + max = 80 + } + } + + ingress_security_rules { + # HTTPS (TCP 443) + protocol = "6" # TCP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + tcp_options { + min = 443 + max = 443 + } + } + + ingress_security_rules { + # Custom Minecraft + protocol = "6" # TCP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + tcp_options { + min = 49732 + max = 49732 + } + } + + ingress_security_rules { + # HTTPS (UDP 443) - For QUIC or specific UDP services + protocol = "17" # UDP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + udp_options { + min = 443 + max = 443 + } + } + + ingress_security_rules { + # ICMP (Ping) + protocol = "1" # ICMP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + icmp_options { + type = 3 # Destination Unreachable (common for connectivity checks) + code = 4 # Fragmentation needed + } + } + ingress_security_rules { + protocol = "1" # ICMP + source = "0.0.0.0/0" + source_type = "CIDR_BLOCK" + icmp_options { + type = 8 # Echo Request (ping) + } + } +} + +resource "oci_core_subnet" "my_public_subnet" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.my_vpc.id + display_name = "main-public-subnet" + cidr_block = "10.0.0.0/24" + prohibit_public_ip_on_vnic = false # Allows instances in this subnet to get public IPs + route_table_id = oci_core_route_table.my_public_route_table.id + security_list_ids = [oci_core_security_list.my_public_security_list.id] + dns_label = "mainsub" # Must be unique within the VCN +} diff --git a/deploy/oracle/outputs.tf b/deploy/oracle/outputs.tf new file mode 100644 index 0000000..9fc0c78 --- /dev/null +++ b/deploy/oracle/outputs.tf @@ -0,0 +1,19 @@ +output "host_ip" { + description = "The public IP address of the launched instance." + value = oci_core_instance.ubuntu_compute_instance.public_ip +} + +output "instance_id" { + description = "The OCID of the launched instance." + value = oci_core_instance.ubuntu_compute_instance.id +} + +output "vpc_ocid" { + description = "The OCID of the created VCN." + value = oci_core_vcn.my_vpc.id +} + +output "subnet_ocid" { + description = "The OCID of the created public subnet." + value = oci_core_subnet.my_public_subnet.id +} diff --git a/deploy/oracle/variables.tf b/deploy/oracle/variables.tf new file mode 100644 index 0000000..4f89392 --- /dev/null +++ b/deploy/oracle/variables.tf @@ -0,0 +1,62 @@ +variable "boot_volume_size_in_gbs" { + description = "The size of the boot volume in GBs." + type = number + default = 150 +} + +variable "cloud_init_script" { + description = "A cloud-init script to run on instance launch." + type = string + default = <<-EOF + #!/bin/bash + echo "Hello from cloud-init!" > /home/ubuntu/cloud-init-output.txt + EOF +} + +variable "compartment_ocid" { + description = "The OCID of the compartment where the instance will be created." + type = string + default = "ocid1.tenancy.oc1..aaaaaaaaudwr2ozedhjnrn76ofjgglgug6gexknjisd7gb7tkj3mjdp763da" +} + +variable "instance_display_name" { + description = "A user-friendly name for the instance." + type = string + default = "noah-nixos" +} + +variable "instance_shape" { + description = "The shape of the OCI compute instance." + type = string + default = "VM.Standard.A1.Flex" # Example shape. Choose one available in your region/AD. +} + +variable "object_storage_namespace" { + description = "Your OCI Object Storage namespace (usually your tenancy name)." + type = string + default = "masur" +} + +variable "object_storage_bucket_name" { + description = "The name of the Object Storage bucket where your custom image is located." + type = string + default = "noahmasur-images" +} + +variable "object_storage_object_name" { + description = "The object name (file name) of your custom image in Object Storage." + type = string + default = "nixos.qcow2" +} + +variable "oci_private_key" { + type = string + description = "API private key for Oracle Cloud management" + sensitive = true +} + +variable "ssh_public_key" { + description = "Your public SSH key content." + type = string + default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB+AbmjGEwITk5CK9y7+Rg27Fokgj9QEjgc9wST6MA3s" +} diff --git a/flake.nix b/flake.nix index 8385722..5bf8abf 100644 --- a/flake.nix +++ b/flake.nix @@ -213,6 +213,11 @@ format = "iso"; specialArgs = { inherit hostnames; }; }; + "${name}-qcow" = lib.generateImage { + inherit system module; + format = "qcow"; + specialArgs = { inherit hostnames; }; + }; }) hosts) ) lib.linuxHosts # x86_64-linux = { arrow = ...; swan = ...; } ; @@ -229,7 +234,7 @@ lib.pkgsBySystem.${system}.nmasur // # Share generated images for each relevant host - generators.${system} + (if (lib.hasInfix "linux" system) then generators.${system} else { }) ); # Development environments diff --git a/lib/default.nix b/lib/default.nix index 0d1b555..da01b72 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -193,6 +193,10 @@ lib nmasur.profiles.wsl.enable = lib.mkForce false; boot.loader.grub.enable = lib.mkForce false; }; + qcow = { + nmasur.profiles.wsl.enable = lib.mkForce false; + boot.loader.grub.enable = lib.mkForce false; + }; }; generateImage = diff --git a/platforms/nixos/modules/nmasur/presets/services/restic/restic.nix b/platforms/nixos/modules/nmasur/presets/services/restic/restic.nix index c36b23e..c3d498d 100644 --- a/platforms/nixos/modules/nmasur/presets/services/restic/restic.nix +++ b/platforms/nixos/modules/nmasur/presets/services/restic/restic.nix @@ -54,6 +54,11 @@ in "--keep-monthly 12" "--keep-yearly 100" ]; + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + RandomizedDelaySec = "3h"; + }; }; }; diff --git a/platforms/nixos/modules/nmasur/profiles/communications.nix b/platforms/nixos/modules/nmasur/profiles/communications.nix index a0e5b61..0127ae8 100644 --- a/platforms/nixos/modules/nmasur/profiles/communications.nix +++ b/platforms/nixos/modules/nmasur/profiles/communications.nix @@ -20,24 +20,24 @@ in msmtp.enable = lib.mkDefault true; }; services = { - actualbudget.enable = lib.mkDefault true; + # actualbudget.enable = lib.mkDefault true; caddy.enable = lib.mkDefault true; cloudflare.enable = lib.mkDefault true; cloudflared.enable = lib.mkDefault true; - gitea.enable = lib.mkDefault true; - grafana.enable = lib.mkDefault true; - influxdb2.enable = lib.mkDefault true; - karakeep.enable = lib.mkDefault true; - litestream.enable = lib.mkDefault true; + # gitea.enable = lib.mkDefault true; + # grafana.enable = lib.mkDefault true; + # influxdb2.enable = lib.mkDefault true; + # karakeep.enable = lib.mkDefault true; + # litestream.enable = lib.mkDefault true; mathesar.enable = lib.mkDefault true; minecraft-server.enable = lib.mkDefault true; - n8n.enable = lib.mkDefault true; + # n8n.enable = lib.mkDefault true; nix-autoupgrade.enable = lib.mkDefault false; # On by default for communications - ntfy-sh.enable = lib.mkDefault true; + # ntfy-sh.enable = lib.mkDefault true; pgweb.enable = lib.mkDefault true; postgresql.enable = lib.mkDefault true; - thelounge.enable = lib.mkDefault true; - uptime-kuma.enable = lib.mkDefault true; + # thelounge.enable = lib.mkDefault true; + # uptime-kuma.enable = lib.mkDefault true; vaultwarden.enable = lib.mkDefault true; victoriametrics.enable = lib.mkDefault true; };