diff --git a/flake.lock b/flake.lock
index 1d1d66c..a560b71 100644
--- a/flake.lock
+++ b/flake.lock
@@ -74,6 +74,42 @@
"type": "github"
}
},
+ "nixlib": {
+ "locked": {
+ "lastModified": 1636849918,
+ "narHash": "sha256-nzUK6dPcTmNVrgTAC1EOybSMsrcx+QrVPyqRdyKLkjA=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "28a5b0557f14124608db68d3ee1f77e9329e9dd5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "type": "github"
+ }
+ },
+ "nixos-generators": {
+ "inputs": {
+ "nixlib": "nixlib",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1660727616,
+ "narHash": "sha256-zYTIvdPMYMx/EYqXODAwIIU30RiEHqNHdgarIHuEYZc=",
+ "owner": "nix-community",
+ "repo": "nixos-generators",
+ "rev": "adccd191a0e83039d537e021f19495b7bad546a1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nixos-generators",
+ "type": "github"
+ }
+ },
"nixpkgs": {
"locked": {
"lastModified": 1664195620,
@@ -124,6 +160,7 @@
"inputs": {
"darwin": "darwin",
"home-manager": "home-manager",
+ "nixos-generators": "nixos-generators",
"nixpkgs": "nixpkgs",
"nur": "nur",
"wallpapers": "wallpapers",
diff --git a/flake.nix b/flake.nix
index 5ccbac8..7fd9403 100644
--- a/flake.nix
+++ b/flake.nix
@@ -32,9 +32,15 @@
flake = false;
};
+ # Used to generate NixOS images for other platforms
+ nixos-generators = {
+ url = "github:nix-community/nixos-generators";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
};
- outputs = { self, nixpkgs, darwin, wsl, home-manager, nur, wallpapers }:
+ outputs = { self, nixpkgs, ... }@inputs:
let
@@ -57,14 +63,14 @@
in {
- nixosConfigurations = {
+ nixosConfigurations = with inputs; {
desktop = import ./hosts/desktop {
inherit nixpkgs home-manager nur globals wallpapers;
};
wsl = import ./hosts/wsl { inherit nixpkgs wsl home-manager globals; };
};
- darwinConfigurations = {
+ darwinConfigurations = with inputs; {
macbook = import ./hosts/macbook {
inherit nixpkgs darwin home-manager nur globals;
};
@@ -111,6 +117,14 @@
});
+ # Package servers into images with a generator
+ packages.x86_64-linux = with inputs; {
+ aws = import ./generators/aws {
+ inherit nixpkgs nixos-generators home-manager globals;
+ system = "x86_64-linux";
+ };
+ };
+
# Templates for starting other projects quickly
templates = rec {
default = basic;
diff --git a/generators/aws/default.nix b/generators/aws/default.nix
new file mode 100644
index 0000000..f648c18
--- /dev/null
+++ b/generators/aws/default.nix
@@ -0,0 +1,31 @@
+{ nixpkgs, system, nixos-generators, home-manager, globals, ... }:
+
+nixos-generators.nixosGenerate {
+ inherit system;
+ format = "amazon";
+ modules = [
+ home-manager.nixosModules.home-manager
+ {
+ user = globals.user;
+ fullName = globals.fullName;
+ dotfilesRepo = globals.dotfilesRepo;
+ gitName = globals.gitName;
+ gitEmail = globals.gitEmail;
+ networking.hostName = "sheep";
+ gui.enable = false;
+ colorscheme = (import ../modules/colorscheme/gruvbox);
+ passwordHash =
+ "$6$PZYiMGmJIIHAepTM$Wx5EqTQ5GApzXx58nvi8azh16pdxrN6Qrv1wunDlzveOgawitWzcIxuj76X9V868fsPi/NOIEO8yVXqwzS9UF.";
+ publicKey =
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB+AbmjGEwITk5CK9y7+Rg27Fokgj9QEjgc9wST6MA3s";
+ # AWS settings require this
+ permitRootLogin = "prohibit-password";
+ }
+ ../hosts/common.nix
+ ../modules/nixos
+ ../modules/services/sshd.nix
+ ] ++ [
+ # Required to fix diskSize errors during build
+ ({ ... }: { amazonImage.sizeMB = 16 * 1024; })
+ ];
+}
diff --git a/generators/aws/main.tf b/generators/aws/main.tf
new file mode 100644
index 0000000..4fbb2ca
--- /dev/null
+++ b/generators/aws/main.tf
@@ -0,0 +1,80 @@
+locals {
+ image_file = one(fileset(path.root, "result/nixos-amazon-image-*.vhd"))
+}
+
+# Upload to S3
+resource "aws_s3_object" "image" {
+ bucket = "your_bucket_name"
+ key = basename(local.image_file)
+ source = local.image_file
+ etag = filemd5(local.image_file)
+}
+
+# Setup IAM access for the VM Importer
+data "aws_iam_policy_document" "vmimport_trust_policy" {
+ statement {
+ actions = ["sts:AssumeRole"]
+ principals {
+ type = "Service"
+ identifiers = ["vmie.amazonaws.com"]
+ }
+ }
+}
+
+data "aws_iam_policy_document" "vmimport" {
+ statement {
+ actions = [
+ "s3:GetBucketLocation",
+ "s3:GetObject",
+ "s3:ListBucket",
+ ]
+ resources = [
+ "arn:aws:s3:::${aws_s3_object.image.bucket}",
+ "arn:aws:s3:::${aws_s3_object.image.bucket}/*",
+ ]
+ }
+ statement {
+ actions = [
+ "ec2:ModifySnapshotAttribute",
+ "ec2:CopySnapshot",
+ "ec2:RegisterImage",
+ "ec2:Describe*",
+ ]
+ resources = ["*"]
+ }
+}
+
+resource "aws_iam_role" "vmimport" {
+ name = "vmimport"
+ assume_role_policy = data.aws_iam_policy_document.vmimport_trust_policy.json
+ inline_policy {
+ name = "vmimport"
+ policy = data.aws_iam_policy_document.vmimport.json
+ }
+}
+
+# Import to EBS
+resource "aws_ebs_snapshot_import" "image" {
+ disk_container {
+ format = "VHD"
+ user_bucket {
+ s3_bucket = aws_s3_object.image.bucket
+ s3_key = aws_s3_object.image.key
+ }
+ }
+
+ role_name = aws_iam_role.vmimport.name
+}
+
+# Convert to AMI
+resource "aws_ami" "image" {
+ description = "Created with NixOS."
+ name = replace(basename(local.image_file), "/\\.vhd$/", "")
+ virtualization_type = "hvm"
+
+ ebs_block_device {
+ device_name = "/dev/xvda"
+ snapshot_id = aws_ebs_snapshot_import.image.id
+ volume_size = 8
+ }
+}
diff --git a/generators/aws/workflow.yml b/generators/aws/workflow.yml
new file mode 100644
index 0000000..c0210e2
--- /dev/null
+++ b/generators/aws/workflow.yml
@@ -0,0 +1,260 @@
+name: 'Terraform'
+env:
+
+
+ AWS_ACCOUNT_NUMBER: ''
+ AWS_PLAN_ROLE_NAME: github_actions_plan
+ AWS_APPLY_ROLE_NAME: github_actions_admin
+
+ # Always required. Used for authenticating to AWS, but can also act as your
+ # default region if you don't want to specify in the provider configuration.
+ AWS_REGION: us-east-1
+
+ # You must change these to fit your project.
+ TF_VAR_project: change-me
+ TF_VAR_label: change-me
+ TF_VAR_owner: Your Name Here
+
+ # If storing Terraform in a subdirectory, specify it here.
+ TERRAFORM_DIRECTORY: .
+
+ # Pinned versions of tools to use.
+ # Check for new releases:
+ # - https://github.com/hashicorp/terraform/releases
+ # - https://github.com/fugue/regula/releases
+ # - https://github.com/terraform-linters/tflint/releases
+ TERRAFORM_VERSION: 1.2.6
+ REGULA_VERSION: 2.9.0
+ TFLINT_VERSION: 0.39.1
+
+ # Terraform configuration options
+ TERRAFORM_PARALLELISM: 10
+
+ # These variables are passed to Terraform based on GitHub information.
+ TF_VAR_repo: ${{ github.repository }}
+
+# This workflow is triggered in the following ways.
+on:
+
+ # Any push or merge to these branches.
+ push:
+ branches:
+ - dev
+ - prod
+
+ # Any pull request targeting these branches (plan only).
+ pull_request:
+ branches:
+ - dev
+ - prod
+
+
+ # Any manual trigger on these branches.
+ workflow_dispatch:
+ branches:
+ - dev
+ - prod
+
+# -------------------------------------------------------------------
+# The rest of this workflow can operate without adjustments. Edit the
+# below content at your own risk!
+# -------------------------------------------------------------------
+
+# Used to connect to AWS IAM
+permissions:
+ id-token: write
+ contents: read
+ pull-requests: write
+
+# Only run one workflow at a time for each Terraform state. This prevents
+# lockfile conflicts, especially during PR vs push.
+concurrency: terraform-${{ github.base_ref || github.ref }}
+
+jobs:
+ terraform:
+
+ name: 'Terraform'
+
+ # Change this if you need to run your deployment on-prem.
+ runs-on: ubuntu-latest
+
+ steps:
+
+ # Downloads the current repo code to the runner.
+ - name: Checkout Repo Code
+ uses: actions/checkout@v2
+
+ # Install Nix
+ - name: Install Nix
+ uses: cachix/install-nix-action@v17
+
+ # Build the image
+ - name: Build Image
+ run: nix build .#aws
+
+ # Login to AWS
+ - name: AWS Assume Role
+ uses: aws-actions/configure-aws-credentials@v1.6.1
+ with:
+ role-to-assume: ${{ env.AWS_ROLE_ARN }}
+ aws-region: ${{ env.AWS_REGION }}
+
+ # Exports all GitHub Secrets as environment variables prefixed by
+ # "TF_VAR_", which exposes them to Terraform. The name of each GitHub
+ # Secret must match its Terraform variable name exactly.
+ - name: Export Secrets to Terraform Variables
+ env:
+ ALL_SECRETS: ${{ toJson(secrets) }}
+ run: |
+ echo "$ALL_SECRETS" \
+ | jq "to_entries | .[] | \"TF_VAR_\" + ( .key | ascii_downcase ) + \"=\" + .value" \
+ | tr -d \" >> $GITHUB_ENV
+
+ # Installs the Terraform binary and some other accessory functions.
+ - name: Setup Terraform
+ uses: hashicorp/setup-terraform@v2
+ with:
+ terraform_version: ${{ env.TERRAFORM_VERSION }}
+
+ # Checks whether Terraform is formatted properly. If this fails, you
+ # should install the pre-commit hook.
+ - name: Check Formatting
+ run: |
+ terraform fmt -no-color -check -diff -recursive
+
+ # Downloads a Terraform code lint test.
+ - uses: terraform-linters/setup-tflint@v1
+ name: Setup TFLint
+ with:
+ tflint_version: v${{ env.TFLINT_VERSION }}
+
+ # Sets up linting with this codebase.
+ - name: Init TFLint
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: tflint --init
+
+ # Lints the current code.
+ - name: Run TFLint
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: |
+ tflint -f compact
+ find ./modules/* -type d -maxdepth 0 | xargs -I __ tflint -f compact --disable-rule=terraform_required_providers --disable-rule=terraform_required_version __
+
+ # Connects to remote state backend and download providers.
+ - name: Terraform Init
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: |
+ terraform init \
+ -backend-config="role_arn=${{ env.AWS_STATE_ROLE_ARN }}" \
+ -backend-config="region=us-east-1" \
+ -backend-config="workspace_key_prefix=accounts/${{ env.AWS_ACCOUNT_NUMBER }}/${{ github.repository }}" \
+ -backend-config="key=state.tfstate" \
+ -backend-config="dynamodb_table=global-tf-state-lock"
+
+ # Set the Terraform Workspace to the current branch name.
+ - name: Set Terraform Workspace
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ shell: bash
+ run: |
+ export WORKSPACE=${{ github.base_ref || github.ref_name }}
+ terraform workspace select ${WORKSPACE} || terraform workspace new $WORKSPACE
+ echo "TF_WORKSPACE=$(echo ${WORKSPACE} | sed 's/\//_/g')" >> $GITHUB_ENV
+
+ # Checks differences between current code and infrastructure state.
+ - name: Terraform Plan
+ id: plan
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: |
+ terraform plan \
+ -input=false \
+ -no-color \
+ -out=tfplan \
+ -parallelism=${TERRAFORM_PARALLELISM} \
+ -var-file=variables-${TF_WORKSPACE}.tfvars
+
+ # Gets the results of the plan for pull requests.
+ - name: Terraform Show Plan
+ id: show
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: terraform show -no-color tfplan
+
+ # Adds the results of the plan to the pull request.
+ - name: Comment Plan
+ uses: actions/github-script@v6
+ if: github.event_name == 'pull_request'
+ env:
+ STDOUT: "```terraform\n${{ steps.show.outputs.stdout }}```"
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ // 1. Retrieve existing bot comments for the PR
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ })
+ const botComment = comments.find(comment => {
+ return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
+ })
+
+ // 2. Prepare format of the comment
+ const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
+ #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
+ #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
+ Validation Output
+
+ \`\`\`\n
+ ${{ steps.validate.outputs.stdout }}
+ \`\`\`
+
+
+
+ #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
+
+ Show Plan
+
+ \`\`\`\n
+ ${process.env.PLAN}
+ \`\`\`
+
+
+
+ *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;
+
+ // 3. If we have a comment, update it, otherwise create a new one
+ if (botComment) {
+ github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: botComment.id,
+ body: output
+ })
+ } else {
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: output
+ })
+ }
+
+ # Downloads Regula and checks whether the plan meets compliance requirements.
+ - name: Regula Compliance Check
+ shell: bash
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: |
+ REGULA_URL="https://github.com/fugue/regula/releases/download/v${REGULA_VERSION}/regula_${REGULA_VERSION}_Linux_x86_64.tar.gz"
+ curl -sL "$REGULA_URL" -o regula.tar.gz
+ tar xzf regula.tar.gz
+ terraform show -json tfplan | ./regula run
+
+ # Deploys infrastructure or changes to infrastructure.
+ - name: Terraform Apply
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+ working-directory: ${{ env.TERRAFORM_DIRECTORY }}
+ run: |
+ terraform apply \
+ -auto-approve \
+ -input=false \
+ -parallelism=${TERRAFORM_PARALLELISM} \
+ tfplan
diff --git a/modules/services/sshd.nix b/modules/services/sshd.nix
new file mode 100644
index 0000000..0161aba
--- /dev/null
+++ b/modules/services/sshd.nix
@@ -0,0 +1,30 @@
+{ config, pkgs, lib, ... }: {
+
+ options = {
+ publicKey = lib.mkOption {
+ type = lib.types.str;
+ description = "Public SSH key authorized for this system.";
+ };
+ permitRootLogin = lib.mkOption {
+ type = lib.types.str;
+ description = "Root login settings.";
+ default = "no";
+ };
+ };
+
+ config = {
+ services.openssh = {
+ enable = true;
+ ports = [ 22 ];
+ passwordAuthentication = false;
+ gatewayPorts = "no";
+ forwardX11 = false;
+ allowSFTP = true;
+ permitRootLogin = config.permitRootLogin;
+ };
+
+ users.users.${config.user}.openssh.authorizedKeys.keys =
+ [ config.publicKey ];
+ };
+
+}