commit fbd41a72f12459d0455f6a31a9a5e194f759036c Author: Olaf Kolkman Date: Tue May 6 19:41:26 2025 +0200 First Try diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caca63c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +README.md~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4ecd86 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# A NIX Config To Rule Them all +_now mostly focused on my Mac Machines_ + +Started with a configuration generated from [this nixos config template](https://github.com/dustinlyons/nixos-config/) + +Created the full verion with secret-management: + +``` +mkdir -p nixos-config && cd nixos-config && nix flake --extra-experimental-features 'nix-command flakes' init -t github:dustinlyons/nixos-config#starter-with-secrets + +``` \ No newline at end of file diff --git a/apps/aarch64-darwin/apply b/apps/aarch64-darwin/apply new file mode 100755 index 0000000..38325f9 --- /dev/null +++ b/apps/aarch64-darwin/apply @@ -0,0 +1,245 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Determine the operating system +export OS=$(uname) + +# Primary network interface +if [[ "$OS" != "Darwin" ]]; then + export PRIMARY_IFACE=$(ip -o -4 route show to default | awk '{print $5}') + echo -e "${GREEN}Found primary network interface $PRIMARY_IFACE${NC}" +fi + +# Custom print function +_print() { + if [[ "$OS" == "Darwin" ]]; then + echo -e "$1" + else + echo "$1" + fi +} + +# Custom prompt function +_prompt() { + local message="$1" + local variable="$2" + + _print "$message" + read -r $variable +} + +insert_secrets_output() { + local pattern="outputs = { self, darwin, nix-homebrew, homebrew-bundle, homebrew-core, homebrew-cask, home-manager, nixpkgs, disko, agenix } @inputs:" + local insert_text="secrets " + + awk -v pat="$pattern" -v insert="$insert_text" ' + $0 ~ pat { + sub(/} @inputs:/, ", " insert "} @inputs:"); # Replace the closing brace with the insert text followed by the brace + gsub(/ ,/, ","); # Correct any spaces before commas + print + next + } + { print } + ' flake.nix > flake.nix.tmp + + mv flake.nix.tmp flake.nix +} + +insert_secrets_input() { + # Define file path + FILE_PATH="flake.nix" + + # Backup the original file + cp "$FILE_PATH" "${FILE_PATH}.bak" + + # Temporary file for the text to insert + TEMP_FILE="temp_insert.txt" + + # Write the formatted text to the temporary file +cat > "$TEMP_FILE" << 'EOF' + secrets = { + url = "git+ssh://git@github.com/%GITHUB_USER%/%GITHUB_SECRETS_REPO%.git"; + flake = false; + }; +EOF + + # Check if the 'secrets' block already exists + if grep -q 'url = "git+ssh://git@github.com/%GITHUB_USER%/%GITHUB_SECRETS_REPO%.git"' "$FILE_PATH"; then + echo "The 'secrets' block already exists in the file." + rm "$TEMP_FILE" + rm "${FILE_PATH}.bak" + exit 0 + fi + + # Find the start and end line numbers of the 'disko' block + START_LINE=$(grep -n 'disko = {' "$FILE_PATH" | head -n 1 | cut -d: -f1) + END_LINE=$(tail -n +$START_LINE "$FILE_PATH" | grep -n '};' | head -n 1 | cut -d: -f1) + END_LINE=$((START_LINE + END_LINE - 1)) + + # Create a new file with the insertion + { + sed -n "1,${END_LINE}p" "$FILE_PATH" + cat "$TEMP_FILE" + sed -n "$((END_LINE + 1)),\$p" "$FILE_PATH" + } > "${FILE_PATH}.new" + + # Replace the original file with the new file + mv "${FILE_PATH}.new" "$FILE_PATH" + + # Clean up the temporary files + rm "$TEMP_FILE" + rm "${FILE_PATH}.bak" +} + +ask_for_star() { + _print "${YELLOW}Would you like to support my work by starring my GitHub repo? yes/no [yes]: ${NC}" + local response + read -r response + response=${response:-yes} # Set default response to 'yes' if input is empty + if [[ "$response" =~ ^[Yy](es)?$ ]] || [[ -z "$response" ]]; then + if [[ "$OS" == "Darwin" ]]; then + open "https://github.com/dustinlyons/nixos-config" + else + xdg-open "https://github.com/dustinlyons/nixos-config" + fi + fi +} + +ask_for_star + +# Fetch username from the system +export USERNAME=$(whoami) + +# If the username is 'nixos' or 'root', ask the user for their username +if [[ "$USERNAME" == "nixos" ]] || [[ "$USERNAME" == "root" ]]; then + _prompt "${YELLOW}You're running as $USERNAME. Please enter your desired username: ${NC}" USERNAME +fi + +# Check if git is available +if command -v git >/dev/null 2>&1; then + # Fetch email and name from git config + export GIT_EMAIL=$(git config --get user.email) + export GIT_NAME=$(git config --get user.name) +else + _print "${RED}Git is not available on this system.${NC}" +fi + +# If git email is not found or git is not available, ask the user +if [[ -z "$GIT_EMAIL" ]]; then + _prompt "${YELLOW}Please enter your email: ${NC}" GIT_EMAIL +fi + +# If git name is not found or git is not available, ask the user +if [[ -z "$GIT_NAME" ]]; then + _prompt "${YELLOW}Please enter your name: ${NC}" GIT_NAME +fi + +_prompt "${YELLOW}Please enter your Github username: ${NC}" GITHUB_USER +_prompt "${YELLOW}Please enter your Github secrets repository name: ${NC}" GITHUB_SECRETS_REPO + +export GITHUB_USER +export GITHUB_SECRETS_REPO + +select_boot_disk() { + local disks + local _boot_disk + + _print "${YELLOW}Available disks:${NC}" + disks=$(lsblk -nd --output NAME,SIZE | grep -v loop) + echo "$disks" + + # Warning message for data deletion + _print "${RED}WARNING: All data on the chosen disk will be erased during the installation!${NC}" + _prompt "${YELLOW}Please enter the name of your boot disk (e.g., sda, nvme0n1). Do not include the full path ("/dev/"): ${NC}" _boot_disk + + # Confirmation for disk selection to prevent accidental data loss + _print "${YELLOW}You have selected $_boot_disk as the boot disk. This will delete everything on this disk. Are you sure? (Y/N): ${NC}" + read -r confirmation + if [[ "$confirmation" =~ ^[Yy]$ ]]; then + export BOOT_DISK=$_boot_disk + else + _print "${RED}Disk selection cancelled by the user. Please run the script again to select the correct disk.${NC}" + exit 1 + fi +} + +# Set hostname and find primary disk if this is NixOS +if [[ "$OS" != "Darwin" ]]; then + _prompt "${YELLOW}Please enter a hostname for the system: ${NC}" HOST_NAME + export HOST_NAME + select_boot_disk +fi + +# Confirmation step +confirm_details() { + _print "${GREEN}Username: $USERNAME" + _print "Email: $GIT_EMAIL" + _print "Name: $GIT_NAME${NC}" + + if([[ "$OS" != "Darwin" ]]); then + _print "${GREEN}Primary interface: $PRIMARY_IFACE" + _print "Boot disk: $BOOT_DISK" + _print "Hostname: $HOST_NAME${NC}" + fi + + _print "${GREEN}Secrets repository: $GITHUB_USER/$GITHUB_SECRETS_REPO${NC}" + + _prompt "${YELLOW}Is this correct? yes/no: ${NC}" choice + + case "$choice" in + [Nn] | [Nn][Oo] ) + _print "${RED}Exiting script.${NC}" + exit 1 + ;; + [Yy] | [Yy][Ee][Ss] ) + _print "${GREEN}Continuing...${NC}" + ;; + * ) + _print "${RED}Invalid option. Exiting script.${NC}" + exit 1 + ;; + esac +} + +# Call the confirmation function +confirm_details + +# Function to replace tokens in each file +replace_tokens() { + local file="$1" + if [[ $(basename $1) != "apply" ]]; then + if [[ "$OS" == "Darwin" ]]; then + # macOS + LC_ALL=C LANG=C sed -i '' -e "s/%USER%/$USERNAME/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%EMAIL%/$GIT_EMAIL/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%NAME%/$GIT_NAME/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%GITHUB_USER%/$GITHUB_USER/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%GITHUB_SECRETS_REPO%/$GITHUB_SECRETS_REPO/g" "$file" + else + # Linux or other + sed -i -e "s/%USER%/$USERNAME/g" "$file" + sed -i -e "s/%EMAIL%/$GIT_EMAIL/g" "$file" + sed -i -e "s/%NAME%/$GIT_NAME/g" "$file" + sed -i -e "s/%INTERFACE%/$PRIMARY_IFACE/g" "$file" + sed -i -e "s/%DISK%/$BOOT_DISK/g" "$file" + sed -i -e "s/%HOST%/$HOST_NAME/g" "$file" + sed -i -e "s/%GITHUB_USER%/$GITHUB_USER/g" "$file" + sed -i -e "s/%GITHUB_SECRETS_REPO%/$GITHUB_SECRETS_REPO/g" "$file" + fi + fi +} + +# Insert secrets repo into flake +insert_secrets_input +insert_secrets_output + +# Traverse directories and call replace_tokens on each Nix file +export -f replace_tokens +find . -type f -exec bash -c 'replace_tokens "$0"' {} \; + +echo "$USERNAME" > /tmp/username.txt +_print "${GREEN}User $USERNAME information applied.${NC}" diff --git a/apps/aarch64-darwin/build b/apps/aarch64-darwin/build new file mode 100755 index 0000000..0c4ca11 --- /dev/null +++ b/apps/aarch64-darwin/build @@ -0,0 +1,19 @@ +#!/bin/sh -e + +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +RED='\033[1;31m' +NC='\033[0m' + +SYSTEM_TYPE="aarch64-darwin" +FLAKE_SYSTEM="darwinConfigurations.${SYSTEM_TYPE}.system" + +export NIXPKGS_ALLOW_UNFREE=1 + +echo "${YELLOW}Starting build...${NC}" +nix --extra-experimental-features 'nix-command flakes' build .#$FLAKE_SYSTEM $@ + +echo "${YELLOW}Cleaning up...${NC}" +unlink ./result + +echo "${GREEN}Switch to new generation complete!${NC}" diff --git a/apps/aarch64-darwin/build-switch b/apps/aarch64-darwin/build-switch new file mode 100755 index 0000000..a83f85a --- /dev/null +++ b/apps/aarch64-darwin/build-switch @@ -0,0 +1,22 @@ +#!/bin/sh -e + +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +RED='\033[1;31m' +NC='\033[0m' + +SYSTEM_TYPE="aarch64-darwin" +FLAKE_SYSTEM="darwinConfigurations.${SYSTEM_TYPE}.system" + +export NIXPKGS_ALLOW_UNFREE=1 + +echo "${YELLOW}Starting build...${NC}" +nix --extra-experimental-features 'nix-command flakes' build .#$FLAKE_SYSTEM $@ + +echo "${YELLOW}Switching to new generation...${NC}" +./result/sw/bin/darwin-rebuild switch --flake .#${SYSTEM_TYPE} $@ + +echo "${YELLOW}Cleaning up...${NC}" +unlink ./result + +echo "${GREEN}Switch to new generation complete!${NC}" diff --git a/apps/aarch64-darwin/check-keys b/apps/aarch64-darwin/check-keys new file mode 100755 index 0000000..4bd9dd8 --- /dev/null +++ b/apps/aarch64-darwin/check-keys @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +username=${USER} +export SSH_DIR=/Users/${username}/.ssh + +lint_keys() { + if [[ -f "${SSH_DIR}/id_ed25519" && -f "${SSH_DIR}/id_ed25519.pub" && -f "${SSH_DIR}/id_ed25519_agenix" && -f "${SSH_DIR}/id_ed25519_agenix.pub" ]]; then + echo -e "${GREEN}All SSH keys are present.${NC}" + else + echo -e "${RED}Some SSH keys are missing.${NC}" + if [[ ! -f "${SSH_DIR}/id_ed25519" ]]; then + echo -e "${RED}Missing: id_ed25519${NC}" + fi + if [[ ! -f "${SSH_DIR}/id_ed25519.pub" ]]; then + echo -e "${RED}Missing: id_ed25519.pub${NC}" + fi + if [[ ! -f "${SSH_DIR}/id_ed25519_agenix" ]]; then + echo -e "${RED}Missing: id_ed25519_agenix${NC}" + fi + if [[ ! -f "${SSH_DIR}/id_ed25519_agenix.pub" ]]; then + echo -e "${RED}Missing: id_ed25519_agenix.pub${NC}" + fi + echo -e "${GREEN}Run the createKeys command to generate the missing keys.${NC}" + exit 1 + fi +} + +lint_keys diff --git a/apps/aarch64-darwin/copy-keys b/apps/aarch64-darwin/copy-keys new file mode 100755 index 0000000..904cc15 --- /dev/null +++ b/apps/aarch64-darwin/copy-keys @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +username=${USER} +export SSH_DIR=/Users/${username}/.ssh + +handle_no_usb() { + echo -e ${RED}No USB drive found or mounted.${NC}" + echo -e ${GREEN}If you have not yet set up your keys, run the script to generate new SSH keys.${NC}" + exit 1 +} + +mount_usb() { + MOUNT_PATH="" + for dev in $(diskutil list | grep -o 'disk[0-9]'); do + MOUNT_PATH="$(diskutil info /dev/${dev} | grep \"Mount Point\" | awk -F: '{print $2}' | xargs)" + if [ -n "${MOUNT_PATH}" ]; then + echo -e "${GREEN}USB drive found at ${MOUNT_PATH}.${NC}" + break + fi + done + + if [ -z "${MOUNT_PATH}" ]; then + echo -e "${RED}No USB drive found.${NC}" + fi +} + +copy_keys() { + if [ -n "${MOUNT_PATH}" ]; then + cp "${MOUNT_PATH}/id_ed25519_agenix.pub" ${SSH_DIR} + cp "${MOUNT_PATH}/id_ed25519_agenix" ${SSH_DIR} + chmod 600 ${SSH_DIR}/id_ed25519_{agenix,agenix.pub} + else + echo -e "${RED}No USB drive found. Aborting.${NC}" + exit 1 + fi +} + +setup_ssh_directory() { + mkdir -p ${SSH_DIR} +} + +set_keys() { + cp ${MOUNT_PATH}/id_ed25519_github.pub ${SSH_DIR}/id_ed25519.pub + cp ${MOUNT_PATH}/id_ed25519_github ${SSH_DIR}/id_ed25519 + chmod 600 ${SSH_DIR}/id_ed25519 + chmod 644 ${SSH_DIR}/id_ed25519.pub +} + +change_ownership() { + chown ${username}:staff ${SSH_DIR}/id_ed25519{,.pub} + chown ${username}:staff ${SSH_DIR}/id_ed25519_{agenix,agenix.pub} +} + +setup_ssh_directory +mount_usb + +if [ -z "${MOUNT_PATH}" ]; then + handle_no_usb +else + copy_keys + set_keys + change_ownership +fi diff --git a/apps/aarch64-darwin/create-keys b/apps/aarch64-darwin/create-keys new file mode 100755 index 0000000..9fdfe43 --- /dev/null +++ b/apps/aarch64-darwin/create-keys @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +username=${USER} +export SSH_DIR=/Users/${username}/.ssh + +setup_ssh_directory() { + mkdir -p ${SSH_DIR} +} + +prompt_for_key_generation() { + local key_name=$1 + if [[ -f "${SSH_DIR}/${key_name}" ]]; then + echo -e "${RED}Existing SSH key found for ${key_name}.${NC}" + cat "${SSH_DIR}/${key_name}.pub" + read -p "Do you want to replace it? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + return 0 # Indicate key should be replaced + else + return 1 # Indicate key should be kept + fi + fi + return 0 # Indicate no key exists, so it should be created +} + +generate_key() { + local key_name=$1 + if prompt_for_key_generation "$key_name"; then + ssh-keygen -t ed25519 -f "${SSH_DIR}/${key_name}" -N "" + chown ${username}:staff "${SSH_DIR}/${key_name}"{,.pub} + else + echo -e "${GREEN}Kept existing ${key_name}.${NC}" + fi +} + +setup_ssh_directory +generate_key "id_ed25519" +generate_key "id_ed25519_agenix" + +echo -e "${GREEN}SSH key setup complete.${NC}" +echo -e "${GREEN}Remember to add the necessary keys to Github or other services as required.${NC}" diff --git a/apps/aarch64-darwin/rollback b/apps/aarch64-darwin/rollback new file mode 100755 index 0000000..611ffaa --- /dev/null +++ b/apps/aarch64-darwin/rollback @@ -0,0 +1,24 @@ +#!/bin/sh -e + +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +RED='\033[1;31m' +NC='\033[0m' + +FLAKE="macos" + +echo "${YELLOW}Available generations:${NC}" +/run/current-system/sw/bin/darwin-rebuild --list-generations + +echo "${YELLOW}Enter the generation number for rollback:${NC}" +read GEN_NUM + +if [ -z "$GEN_NUM" ]; then + echo "${RED}No generation number entered. Aborting rollback.${NC}" + exit 1 +fi + +echo "${YELLOW}Rolling back to generation $GEN_NUM...${NC}" +/run/current-system/sw/bin/darwin-rebuild switch --flake .#$FLAKE --switch-generation $GEN_NUM + +echo "${GREEN}Rollback to generation $GEN_NUM complete!${NC}" diff --git a/apps/aarch64-linux b/apps/aarch64-linux new file mode 120000 index 0000000..9bdfd5f --- /dev/null +++ b/apps/aarch64-linux @@ -0,0 +1 @@ +x86_64-linux \ No newline at end of file diff --git a/apps/x86_64-darwin/apply b/apps/x86_64-darwin/apply new file mode 100644 index 0000000..38325f9 --- /dev/null +++ b/apps/x86_64-darwin/apply @@ -0,0 +1,245 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Determine the operating system +export OS=$(uname) + +# Primary network interface +if [[ "$OS" != "Darwin" ]]; then + export PRIMARY_IFACE=$(ip -o -4 route show to default | awk '{print $5}') + echo -e "${GREEN}Found primary network interface $PRIMARY_IFACE${NC}" +fi + +# Custom print function +_print() { + if [[ "$OS" == "Darwin" ]]; then + echo -e "$1" + else + echo "$1" + fi +} + +# Custom prompt function +_prompt() { + local message="$1" + local variable="$2" + + _print "$message" + read -r $variable +} + +insert_secrets_output() { + local pattern="outputs = { self, darwin, nix-homebrew, homebrew-bundle, homebrew-core, homebrew-cask, home-manager, nixpkgs, disko, agenix } @inputs:" + local insert_text="secrets " + + awk -v pat="$pattern" -v insert="$insert_text" ' + $0 ~ pat { + sub(/} @inputs:/, ", " insert "} @inputs:"); # Replace the closing brace with the insert text followed by the brace + gsub(/ ,/, ","); # Correct any spaces before commas + print + next + } + { print } + ' flake.nix > flake.nix.tmp + + mv flake.nix.tmp flake.nix +} + +insert_secrets_input() { + # Define file path + FILE_PATH="flake.nix" + + # Backup the original file + cp "$FILE_PATH" "${FILE_PATH}.bak" + + # Temporary file for the text to insert + TEMP_FILE="temp_insert.txt" + + # Write the formatted text to the temporary file +cat > "$TEMP_FILE" << 'EOF' + secrets = { + url = "git+ssh://git@github.com/%GITHUB_USER%/%GITHUB_SECRETS_REPO%.git"; + flake = false; + }; +EOF + + # Check if the 'secrets' block already exists + if grep -q 'url = "git+ssh://git@github.com/%GITHUB_USER%/%GITHUB_SECRETS_REPO%.git"' "$FILE_PATH"; then + echo "The 'secrets' block already exists in the file." + rm "$TEMP_FILE" + rm "${FILE_PATH}.bak" + exit 0 + fi + + # Find the start and end line numbers of the 'disko' block + START_LINE=$(grep -n 'disko = {' "$FILE_PATH" | head -n 1 | cut -d: -f1) + END_LINE=$(tail -n +$START_LINE "$FILE_PATH" | grep -n '};' | head -n 1 | cut -d: -f1) + END_LINE=$((START_LINE + END_LINE - 1)) + + # Create a new file with the insertion + { + sed -n "1,${END_LINE}p" "$FILE_PATH" + cat "$TEMP_FILE" + sed -n "$((END_LINE + 1)),\$p" "$FILE_PATH" + } > "${FILE_PATH}.new" + + # Replace the original file with the new file + mv "${FILE_PATH}.new" "$FILE_PATH" + + # Clean up the temporary files + rm "$TEMP_FILE" + rm "${FILE_PATH}.bak" +} + +ask_for_star() { + _print "${YELLOW}Would you like to support my work by starring my GitHub repo? yes/no [yes]: ${NC}" + local response + read -r response + response=${response:-yes} # Set default response to 'yes' if input is empty + if [[ "$response" =~ ^[Yy](es)?$ ]] || [[ -z "$response" ]]; then + if [[ "$OS" == "Darwin" ]]; then + open "https://github.com/dustinlyons/nixos-config" + else + xdg-open "https://github.com/dustinlyons/nixos-config" + fi + fi +} + +ask_for_star + +# Fetch username from the system +export USERNAME=$(whoami) + +# If the username is 'nixos' or 'root', ask the user for their username +if [[ "$USERNAME" == "nixos" ]] || [[ "$USERNAME" == "root" ]]; then + _prompt "${YELLOW}You're running as $USERNAME. Please enter your desired username: ${NC}" USERNAME +fi + +# Check if git is available +if command -v git >/dev/null 2>&1; then + # Fetch email and name from git config + export GIT_EMAIL=$(git config --get user.email) + export GIT_NAME=$(git config --get user.name) +else + _print "${RED}Git is not available on this system.${NC}" +fi + +# If git email is not found or git is not available, ask the user +if [[ -z "$GIT_EMAIL" ]]; then + _prompt "${YELLOW}Please enter your email: ${NC}" GIT_EMAIL +fi + +# If git name is not found or git is not available, ask the user +if [[ -z "$GIT_NAME" ]]; then + _prompt "${YELLOW}Please enter your name: ${NC}" GIT_NAME +fi + +_prompt "${YELLOW}Please enter your Github username: ${NC}" GITHUB_USER +_prompt "${YELLOW}Please enter your Github secrets repository name: ${NC}" GITHUB_SECRETS_REPO + +export GITHUB_USER +export GITHUB_SECRETS_REPO + +select_boot_disk() { + local disks + local _boot_disk + + _print "${YELLOW}Available disks:${NC}" + disks=$(lsblk -nd --output NAME,SIZE | grep -v loop) + echo "$disks" + + # Warning message for data deletion + _print "${RED}WARNING: All data on the chosen disk will be erased during the installation!${NC}" + _prompt "${YELLOW}Please enter the name of your boot disk (e.g., sda, nvme0n1). Do not include the full path ("/dev/"): ${NC}" _boot_disk + + # Confirmation for disk selection to prevent accidental data loss + _print "${YELLOW}You have selected $_boot_disk as the boot disk. This will delete everything on this disk. Are you sure? (Y/N): ${NC}" + read -r confirmation + if [[ "$confirmation" =~ ^[Yy]$ ]]; then + export BOOT_DISK=$_boot_disk + else + _print "${RED}Disk selection cancelled by the user. Please run the script again to select the correct disk.${NC}" + exit 1 + fi +} + +# Set hostname and find primary disk if this is NixOS +if [[ "$OS" != "Darwin" ]]; then + _prompt "${YELLOW}Please enter a hostname for the system: ${NC}" HOST_NAME + export HOST_NAME + select_boot_disk +fi + +# Confirmation step +confirm_details() { + _print "${GREEN}Username: $USERNAME" + _print "Email: $GIT_EMAIL" + _print "Name: $GIT_NAME${NC}" + + if([[ "$OS" != "Darwin" ]]); then + _print "${GREEN}Primary interface: $PRIMARY_IFACE" + _print "Boot disk: $BOOT_DISK" + _print "Hostname: $HOST_NAME${NC}" + fi + + _print "${GREEN}Secrets repository: $GITHUB_USER/$GITHUB_SECRETS_REPO${NC}" + + _prompt "${YELLOW}Is this correct? yes/no: ${NC}" choice + + case "$choice" in + [Nn] | [Nn][Oo] ) + _print "${RED}Exiting script.${NC}" + exit 1 + ;; + [Yy] | [Yy][Ee][Ss] ) + _print "${GREEN}Continuing...${NC}" + ;; + * ) + _print "${RED}Invalid option. Exiting script.${NC}" + exit 1 + ;; + esac +} + +# Call the confirmation function +confirm_details + +# Function to replace tokens in each file +replace_tokens() { + local file="$1" + if [[ $(basename $1) != "apply" ]]; then + if [[ "$OS" == "Darwin" ]]; then + # macOS + LC_ALL=C LANG=C sed -i '' -e "s/%USER%/$USERNAME/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%EMAIL%/$GIT_EMAIL/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%NAME%/$GIT_NAME/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%GITHUB_USER%/$GITHUB_USER/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%GITHUB_SECRETS_REPO%/$GITHUB_SECRETS_REPO/g" "$file" + else + # Linux or other + sed -i -e "s/%USER%/$USERNAME/g" "$file" + sed -i -e "s/%EMAIL%/$GIT_EMAIL/g" "$file" + sed -i -e "s/%NAME%/$GIT_NAME/g" "$file" + sed -i -e "s/%INTERFACE%/$PRIMARY_IFACE/g" "$file" + sed -i -e "s/%DISK%/$BOOT_DISK/g" "$file" + sed -i -e "s/%HOST%/$HOST_NAME/g" "$file" + sed -i -e "s/%GITHUB_USER%/$GITHUB_USER/g" "$file" + sed -i -e "s/%GITHUB_SECRETS_REPO%/$GITHUB_SECRETS_REPO/g" "$file" + fi + fi +} + +# Insert secrets repo into flake +insert_secrets_input +insert_secrets_output + +# Traverse directories and call replace_tokens on each Nix file +export -f replace_tokens +find . -type f -exec bash -c 'replace_tokens "$0"' {} \; + +echo "$USERNAME" > /tmp/username.txt +_print "${GREEN}User $USERNAME information applied.${NC}" diff --git a/apps/x86_64-darwin/build b/apps/x86_64-darwin/build new file mode 100644 index 0000000..93bdd64 --- /dev/null +++ b/apps/x86_64-darwin/build @@ -0,0 +1,19 @@ +#!/bin/sh -e + +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +RED='\033[1;31m' +NC='\033[0m' + +SYSTEM_TYPE="x86_64-darwin" +FLAKE_SYSTEM="darwinConfigurations.${SYSTEM_TYPE}.system" + +export NIXPKGS_ALLOW_UNFREE=1 + +echo "${YELLOW}Starting build...${NC}" +nix --extra-experimental-features 'nix-command flakes' build .#$FLAKE_SYSTEM $@ + +echo "${YELLOW}Cleaning up...${NC}" +unlink ./result + +echo "${GREEN}Switch to new generation complete!${NC}" diff --git a/apps/x86_64-darwin/build-switch b/apps/x86_64-darwin/build-switch new file mode 100644 index 0000000..3277860 --- /dev/null +++ b/apps/x86_64-darwin/build-switch @@ -0,0 +1,22 @@ +#!/bin/sh -e + +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +RED='\033[1;31m' +NC='\033[0m' + +SYSTEM_TYPE="x86_64-darwin" +FLAKE_SYSTEM="darwinConfigurations.${SYSTEM_TYPE}.system" + +export NIXPKGS_ALLOW_UNFREE=1 + +echo "${YELLOW}Starting build...${NC}" +nix --extra-experimental-features 'nix-command flakes' build .#$FLAKE_SYSTEM $@ + +echo "${YELLOW}Switching to new generation...${NC}" +./result/sw/bin/darwin-rebuild switch --flake .#${SYSTEM_TYPE} $@ + +echo "${YELLOW}Cleaning up...${NC}" +unlink ./result + +echo "${GREEN}Switch to new generation complete!${NC}" diff --git a/apps/x86_64-darwin/check-keys b/apps/x86_64-darwin/check-keys new file mode 100644 index 0000000..4bd9dd8 --- /dev/null +++ b/apps/x86_64-darwin/check-keys @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +username=${USER} +export SSH_DIR=/Users/${username}/.ssh + +lint_keys() { + if [[ -f "${SSH_DIR}/id_ed25519" && -f "${SSH_DIR}/id_ed25519.pub" && -f "${SSH_DIR}/id_ed25519_agenix" && -f "${SSH_DIR}/id_ed25519_agenix.pub" ]]; then + echo -e "${GREEN}All SSH keys are present.${NC}" + else + echo -e "${RED}Some SSH keys are missing.${NC}" + if [[ ! -f "${SSH_DIR}/id_ed25519" ]]; then + echo -e "${RED}Missing: id_ed25519${NC}" + fi + if [[ ! -f "${SSH_DIR}/id_ed25519.pub" ]]; then + echo -e "${RED}Missing: id_ed25519.pub${NC}" + fi + if [[ ! -f "${SSH_DIR}/id_ed25519_agenix" ]]; then + echo -e "${RED}Missing: id_ed25519_agenix${NC}" + fi + if [[ ! -f "${SSH_DIR}/id_ed25519_agenix.pub" ]]; then + echo -e "${RED}Missing: id_ed25519_agenix.pub${NC}" + fi + echo -e "${GREEN}Run the createKeys command to generate the missing keys.${NC}" + exit 1 + fi +} + +lint_keys diff --git a/apps/x86_64-darwin/copy-keys b/apps/x86_64-darwin/copy-keys new file mode 100644 index 0000000..904cc15 --- /dev/null +++ b/apps/x86_64-darwin/copy-keys @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +username=${USER} +export SSH_DIR=/Users/${username}/.ssh + +handle_no_usb() { + echo -e ${RED}No USB drive found or mounted.${NC}" + echo -e ${GREEN}If you have not yet set up your keys, run the script to generate new SSH keys.${NC}" + exit 1 +} + +mount_usb() { + MOUNT_PATH="" + for dev in $(diskutil list | grep -o 'disk[0-9]'); do + MOUNT_PATH="$(diskutil info /dev/${dev} | grep \"Mount Point\" | awk -F: '{print $2}' | xargs)" + if [ -n "${MOUNT_PATH}" ]; then + echo -e "${GREEN}USB drive found at ${MOUNT_PATH}.${NC}" + break + fi + done + + if [ -z "${MOUNT_PATH}" ]; then + echo -e "${RED}No USB drive found.${NC}" + fi +} + +copy_keys() { + if [ -n "${MOUNT_PATH}" ]; then + cp "${MOUNT_PATH}/id_ed25519_agenix.pub" ${SSH_DIR} + cp "${MOUNT_PATH}/id_ed25519_agenix" ${SSH_DIR} + chmod 600 ${SSH_DIR}/id_ed25519_{agenix,agenix.pub} + else + echo -e "${RED}No USB drive found. Aborting.${NC}" + exit 1 + fi +} + +setup_ssh_directory() { + mkdir -p ${SSH_DIR} +} + +set_keys() { + cp ${MOUNT_PATH}/id_ed25519_github.pub ${SSH_DIR}/id_ed25519.pub + cp ${MOUNT_PATH}/id_ed25519_github ${SSH_DIR}/id_ed25519 + chmod 600 ${SSH_DIR}/id_ed25519 + chmod 644 ${SSH_DIR}/id_ed25519.pub +} + +change_ownership() { + chown ${username}:staff ${SSH_DIR}/id_ed25519{,.pub} + chown ${username}:staff ${SSH_DIR}/id_ed25519_{agenix,agenix.pub} +} + +setup_ssh_directory +mount_usb + +if [ -z "${MOUNT_PATH}" ]; then + handle_no_usb +else + copy_keys + set_keys + change_ownership +fi diff --git a/apps/x86_64-darwin/create-keys b/apps/x86_64-darwin/create-keys new file mode 100644 index 0000000..9fdfe43 --- /dev/null +++ b/apps/x86_64-darwin/create-keys @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +username=${USER} +export SSH_DIR=/Users/${username}/.ssh + +setup_ssh_directory() { + mkdir -p ${SSH_DIR} +} + +prompt_for_key_generation() { + local key_name=$1 + if [[ -f "${SSH_DIR}/${key_name}" ]]; then + echo -e "${RED}Existing SSH key found for ${key_name}.${NC}" + cat "${SSH_DIR}/${key_name}.pub" + read -p "Do you want to replace it? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + return 0 # Indicate key should be replaced + else + return 1 # Indicate key should be kept + fi + fi + return 0 # Indicate no key exists, so it should be created +} + +generate_key() { + local key_name=$1 + if prompt_for_key_generation "$key_name"; then + ssh-keygen -t ed25519 -f "${SSH_DIR}/${key_name}" -N "" + chown ${username}:staff "${SSH_DIR}/${key_name}"{,.pub} + else + echo -e "${GREEN}Kept existing ${key_name}.${NC}" + fi +} + +setup_ssh_directory +generate_key "id_ed25519" +generate_key "id_ed25519_agenix" + +echo -e "${GREEN}SSH key setup complete.${NC}" +echo -e "${GREEN}Remember to add the necessary keys to Github or other services as required.${NC}" diff --git a/apps/x86_64-linux/apply b/apps/x86_64-linux/apply new file mode 100644 index 0000000..41f7b1c --- /dev/null +++ b/apps/x86_64-linux/apply @@ -0,0 +1,183 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Determine the operating system +export OS=$(uname) + +# Primary network interface +if [[ "$OS" != "Darwin" ]]; then + export PRIMARY_IFACE=$(ip -o -4 route show to default | awk '{print $5}') + echo -e "${GREEN}Found primary network interface $PRIMARY_IFACE${NC}" +fi + +# Custom print function +_print() { + if [[ "$OS" == "Darwin" ]]; then + echo -e "$1" + else + echo "$1" + fi +} + +# Custom prompt function +_prompt() { + local message="$1" + local variable="$2" + + _print "$message" + read -r $variable +} + +ask_for_star() { + _print "${YELLOW}Would you like to support my work by starring my GitHub repo? yes/no [yes]: ${NC}" + local response + read -r response + response=${response:-yes} # Set default response to 'yes' if input is empty + if [[ "$response" =~ ^[Yy](es)?$ ]] || [[ -z "$response" ]]; then + if [[ "$OS" == "Darwin" ]]; then + open "https://github.com/dustinlyons/nixos-config" + else + xdg-open "https://github.com/dustinlyons/nixos-config" + fi + fi +} + +ask_for_star + +# Fetch username from the system +export USERNAME=$(whoami) + +# If the username is 'nixos' or 'root', ask the user for their username +if [[ "$USERNAME" == "nixos" ]] || [[ "$USERNAME" == "root" ]]; then + _prompt "${YELLOW}You're running as $USERNAME. Please enter your desired username: ${NC}" USERNAME +fi + +# Check if git is available +if command -v git >/dev/null 2>&1; then + # Fetch email and name from git config + export GIT_EMAIL=$(git config --get user.email) + export GIT_NAME=$(git config --get user.name) +else + _print "${RED}Git is not available on this system.${NC}" +fi + +# If git email is not found or git is not available, ask the user +if [[ -z "$GIT_EMAIL" ]]; then + _prompt "${YELLOW}Please enter your email: ${NC}" GIT_EMAIL +fi + +# If git name is not found or git is not available, ask the user +if [[ -z "$GIT_NAME" ]]; then + _prompt "${YELLOW}Please enter your name: ${NC}" GIT_NAME +fi + +if [[ -z "$GITHUB_USER" ]]; then + _prompt "${YELLOW}Please enter your Github username: ${NC}" GITHUB_USER +fi + +if [[ -z "$GITHUB_SECRETS_REPO" ]]; then + _prompt "${YELLOW}Please enter your Github secrets repository name: ${NC}" GITHUB_SECRETS_REPO +fi + +export GITHUB_USER +export GITHUB_SECRETS_REPO + +select_boot_disk() { + local disks + local _boot_disk + + _print "${YELLOW}Available disks:${NC}" + disks=$(lsblk -nd --output NAME,SIZE | grep -v loop) + echo "$disks" + + # Warning message for data deletion + _print "${RED}WARNING: All data on the chosen disk will be erased during the installation!${NC}" + _prompt "${YELLOW}Please enter the name of your boot disk (e.g., sda, nvme0n1). Do not include the full path ("/dev/"): ${NC}" _boot_disk + + # Confirmation for disk selection to prevent accidental data loss + _print "${YELLOW}You have selected $_boot_disk as the boot disk. This will delete everything on this disk. Are you sure? (Y/N): ${NC}" + read -r confirmation + if [[ "$confirmation" =~ ^[Yy]$ ]]; then + export BOOT_DISK=$_boot_disk + else + _print "${RED}Disk selection cancelled by the user. Please run the script again to select the correct disk.${NC}" + exit 1 + fi +} + +# Set hostname and find primary disk if this is NixOS +if [[ "$OS" != "Darwin" ]]; then + _prompt "${YELLOW}Please enter a hostname for the system: ${NC}" HOST_NAME + export HOST_NAME + select_boot_disk +fi + +# Confirmation step +confirm_details() { + _print "${GREEN}Username: $USERNAME" + _print "Email: $GIT_EMAIL" + _print "Name: $GIT_NAME${NC}" + + if([[ "$OS" != "Darwin" ]]); then + _print "${GREEN}Primary interface: $PRIMARY_IFACE" + _print "Boot disk: $BOOT_DISK" + _print "Hostname: $HOST_NAME${NC}" + fi + + _print "${GREEN}Secrets repository: $GITHUB_USER/$GITHUB_SECRETS_REPO${NC}" + + _prompt "${YELLOW}Is this correct? yes/no: ${NC}" choice + + case "$choice" in + [Nn] | [Nn][Oo] ) + _print "${RED}Exiting script.${NC}" + exit 1 + ;; + [Yy] | [Yy][Ee][Ss] ) + _print "${GREEN}Continuing...${NC}" + ;; + * ) + _print "${RED}Invalid option. Exiting script.${NC}" + exit 1 + ;; + esac +} + +# Call the confirmation function +confirm_details + +# Function to replace tokens in each file +replace_tokens() { + local file="$1" + if [[ $(basename $1) != "apply" ]]; then + if [[ "$OS" == "Darwin" ]]; then + # macOS + LC_ALL=C LANG=C sed -i '' -e "s/%USER%/$USERNAME/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%EMAIL%/$GIT_EMAIL/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%NAME%/$GIT_NAME/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%GITHUB_USER%/$GITHUB_USER/g" "$file" + LC_ALL=C LANG=C sed -i '' -e "s/%GITHUB_SECRETS_REPO%/$GITHUB_SECRETS_REPO/g" "$file" + else + # Linux or other + sed -i -e "s/%USER%/$USERNAME/g" "$file" + sed -i -e "s/%EMAIL%/$GIT_EMAIL/g" "$file" + sed -i -e "s/%NAME%/$GIT_NAME/g" "$file" + sed -i -e "s/%INTERFACE%/$PRIMARY_IFACE/g" "$file" + sed -i -e "s/%DISK%/$BOOT_DISK/g" "$file" + sed -i -e "s/%HOST%/$HOST_NAME/g" "$file" + sed -i -e "s/%GITHUB_USER%/$GITHUB_USER/g" "$file" + sed -i -e "s/%GITHUB_SECRETS_REPO%/$GITHUB_SECRETS_REPO/g" "$file" + fi + fi +} + +# Traverse directories and call replace_tokens on each Nix file +export -f replace_tokens +find . -type f -exec bash -c 'replace_tokens "$0"' {} \; + +echo "$USERNAME" > /tmp/username.txt +_print "${GREEN}User $USERNAME information applied.${NC}" diff --git a/apps/x86_64-linux/build-switch b/apps/x86_64-linux/build-switch new file mode 100644 index 0000000..0a54a87 --- /dev/null +++ b/apps/x86_64-linux/build-switch @@ -0,0 +1,30 @@ +#!/bin/sh -e + +VERSION=1.0 + +GREEN='\033[1;32m' +RED='\033[1;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +SYSTEM=$(uname -m) + +case "$SYSTEM" in + x86_64) + FLAKE_TARGET="x86_64-linux" + ;; + aarch64) + FLAKE_TARGET="aarch64-linux" + ;; + *) + echo -e "${RED}Unsupported architecture: $SYSTEM${NC}" + exit 1 + ;; +esac + +echo -e "${YELLOW}Starting...${NC}" + +# We pass SSH from user to root so root can download secrets from our private Github +sudo SSH_AUTH_SOCK=$SSH_AUTH_SOCK /run/current-system/sw/bin/nixos-rebuild switch --flake .#$FLAKE_TARGET $@ + +echo -e "${GREEN}Switch to new generation complete!${NC}" diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..df9e003 --- /dev/null +++ b/flake.nix @@ -0,0 +1,118 @@ +{ + description = "Starter Configuration with secrets for MacOS and NixOS"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + agenix.url = "github:ryantm/agenix"; + home-manager.url = "github:nix-community/home-manager"; + darwin = { + url = "github:LnL7/nix-darwin/master"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nix-homebrew = { + url = "github:zhaofengli-wip/nix-homebrew"; + }; + homebrew-bundle = { + url = "github:homebrew/homebrew-bundle"; + flake = false; + }; + homebrew-core = { + url = "github:homebrew/homebrew-core"; + flake = false; + }; + homebrew-cask = { + url = "github:homebrew/homebrew-cask"; + flake = false; + }; + disko = { + url = "github:nix-community/disko"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + outputs = { self, darwin, nix-homebrew, homebrew-bundle, homebrew-core, homebrew-cask, home-manager, nixpkgs, disko, agenix } @inputs: + let + user = "%USER%"; + linuxSystems = [ "x86_64-linux" "aarch64-linux" ]; + darwinSystems = [ "aarch64-darwin" "x86_64-darwin" ]; + forAllSystems = f: nixpkgs.lib.genAttrs (linuxSystems ++ darwinSystems) f; + devShell = system: let pkgs = nixpkgs.legacyPackages.${system}; in { + default = with pkgs; mkShell { + nativeBuildInputs = with pkgs; [ bashInteractive git age age-plugin-yubikey ]; + shellHook = with pkgs; '' + export EDITOR=vim + ''; + }; + }; + mkApp = scriptName: system: { + type = "app"; + program = "${(nixpkgs.legacyPackages.${system}.writeScriptBin scriptName '' + #!/usr/bin/env bash + PATH=${nixpkgs.legacyPackages.${system}.git}/bin:$PATH + echo "Running ${scriptName} for ${system}" + exec ${self}/apps/${system}/${scriptName} + '')}/bin/${scriptName}"; + }; + mkLinuxApps = system: { + "apply" = mkApp "apply" system; + "build-switch" = mkApp "build-switch" system; + "copy-keys" = mkApp "copy-keys" system; + "create-keys" = mkApp "create-keys" system; + "check-keys" = mkApp "check-keys" system; + "install" = mkApp "install" system; + "install-with-secrets" = mkApp "install-with-secrets" system; + }; + mkDarwinApps = system: { + "apply" = mkApp "apply" system; + "build" = mkApp "build" system; + "build-switch" = mkApp "build-switch" system; + "copy-keys" = mkApp "copy-keys" system; + "create-keys" = mkApp "create-keys" system; + "check-keys" = mkApp "check-keys" system; + "rollback" = mkApp "rollback" system; + }; + in + { + devShells = forAllSystems devShell; + apps = nixpkgs.lib.genAttrs linuxSystems mkLinuxApps // nixpkgs.lib.genAttrs darwinSystems mkDarwinApps; + + darwinConfigurations = nixpkgs.lib.genAttrs darwinSystems (system: + darwin.lib.darwinSystem { + inherit system; + specialArgs = inputs; + modules = [ + home-manager.darwinModules.home-manager + nix-homebrew.darwinModules.nix-homebrew + { + nix-homebrew = { + inherit user; + enable = true; + taps = { + "homebrew/homebrew-core" = homebrew-core; + "homebrew/homebrew-cask" = homebrew-cask; + "homebrew/homebrew-bundle" = homebrew-bundle; + }; + mutableTaps = false; + autoMigrate = true; + }; + } + ./hosts/darwin + ]; + } + ); + + nixosConfigurations = nixpkgs.lib.genAttrs linuxSystems (system: nixpkgs.lib.nixosSystem { + inherit system; + specialArgs = inputs; + modules = [ + disko.nixosModules.disko + home-manager.nixosModules.home-manager { + home-manager = { + useGlobalPkgs = true; + useUserPackages = true; + users.${user} = import ./modules/nixos/home-manager.nix; + }; + } + ./hosts/nixos + ]; + }); + }; +} diff --git a/hosts/darwin/default.nix b/hosts/darwin/default.nix new file mode 100644 index 0000000..fcffaa8 --- /dev/null +++ b/hosts/darwin/default.nix @@ -0,0 +1,93 @@ +{ agenix, config, pkgs, ... }: + +let user = "%USER%"; in + +{ + + imports = [ + ../../modules/darwin/secrets.nix + ../../modules/darwin/home-manager.nix + ../../modules/shared + agenix.darwinModules.default + ]; + + # Setup user, packages, programs + nix = { + package = pkgs.nix; + + settings = { + trusted-users = [ "@admin" "${user}" ]; + substituters = [ "https://nix-community.cachix.org" "https://cache.nixos.org" ]; + trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ]; + }; + + gc = { + automatic = true; + interval = { Weekday = 0; Hour = 2; Minute = 0; }; + options = "--delete-older-than 30d"; + }; + + extraOptions = '' + experimental-features = nix-command flakes + ''; + }; + + # Turn off NIX_PATH warnings now that we're using flakes + system.checks.verifyNixPath = false; + + # Load configuration that is shared across systems + environment.systemPackages = with pkgs; [ + emacs-unstable + agenix.packages."${pkgs.system}".default + ] ++ (import ../../modules/shared/packages.nix { inherit pkgs; }); + + launchd.user.agents.emacs.path = [ config.environment.systemPath ]; + launchd.user.agents.emacs.serviceConfig = { + KeepAlive = true; + ProgramArguments = [ + "/bin/sh" + "-c" + "/bin/wait4path ${pkgs.emacs}/bin/emacs && exec ${pkgs.emacs}/bin/emacs --fg-daemon" + ]; + StandardErrorPath = "/tmp/emacs.err.log"; + StandardOutPath = "/tmp/emacs.out.log"; + }; + + system = { + stateVersion = 4; + + defaults = { + NSGlobalDomain = { + AppleShowAllExtensions = true; + ApplePressAndHoldEnabled = false; + + # 120, 90, 60, 30, 12, 6, 2 + KeyRepeat = 2; + + # 120, 94, 68, 35, 25, 15 + InitialKeyRepeat = 15; + + "com.apple.mouse.tapBehavior" = 1; + "com.apple.sound.beep.volume" = 0.0; + "com.apple.sound.beep.feedback" = 0; + }; + + dock = { + autohide = false; + show-recents = false; + launchanim = true; + orientation = "bottom"; + tilesize = 48; + }; + + finder = { + _FXShowPosixPathInTitle = false; + }; + + trackpad = { + Clicking = true; + TrackpadThreeFingerDrag = true; + }; + }; + }; +} diff --git a/hosts/nixos/default.nix b/hosts/nixos/default.nix new file mode 100644 index 0000000..229fb6a --- /dev/null +++ b/hosts/nixos/default.nix @@ -0,0 +1,307 @@ +{ config, inputs, pkgs, agenix, ... }: + +let user = "%USER%"; + keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOk8iAnIaa1deoc7jw8YACPNVka1ZFJxhnU4G74TmS+p" ]; in +{ + imports = [ + ../../modules/nixos/secrets.nix + ../../modules/nixos/disk-config.nix + ../../modules/shared + agenix.nixosModules.default + ]; + + # Use the systemd-boot EFI boot loader. + boot = { + loader = { + systemd-boot = { + enable = true; + configurationLimit = 42; + }; + efi.canTouchEfiVariables = true; + }; + initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ]; + # Uncomment for AMD GPU + # initrd.kernelModules = [ "amdgpu" ]; + kernelPackages = pkgs.linuxPackages_latest; + kernelModules = [ "uinput" ]; + }; + + # Set your time zone. + time.timeZone = "America/New_York"; + + # The global useDHCP flag is deprecated, therefore explicitly set to false here. + # Per-interface useDHCP will be mandatory in the future, so this generated config + # replicates the default behaviour. + networking = { + hostName = "%HOST%"; # Define your hostname. + useDHCP = false; + interfaces."%INTERFACE%".useDHCP = true; + }; + + nix = { + nixPath = [ "nixos-config=/home/${user}/.local/share/src/nixos-config:/etc/nixos" ]; + settings = { + allowed-users = [ "${user}" ]; + trusted-users = [ "@admin" "${user}" ]; + substituters = [ "https://nix-community.cachix.org" "https://cache.nixos.org" ]; + trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ]; + }; + + package = pkgs.nix; + extraOptions = '' + experimental-features = nix-command flakes + ''; + }; + + # Manages keys and such + programs = { + gnupg.agent.enable = true; + + # Needed for anything GTK related + dconf.enable = true; + + # My shell + zsh.enable = true; + }; + + services = { + displayManager.defaultSession = "none+bspwm"; + xserver = { + enable = true; + + # Uncomment these for AMD or Nvidia GPU + # videoDrivers = [ "amdgpu" ]; + # videoDrivers = [ "nvidia" ]; + + # Uncomment this for Nvidia GPU + # This helps fix tearing of windows for Nvidia cards + # services.xserver.screenSection = '' + # Option "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}" + # Option "AllowIndirectGLXProtocol" "off" + # Option "TripleBuffer" "on" + # ''; + + # LightDM Display Manager + displayManager.lightdm = { + enable = true; + greeters.slick.enable = true; + background = ../../modules/nixos/config/login-wallpaper.png; + }; + + # Tiling window manager + windowManager.bspwm = { + enable = true; + }; + + xkb = { + # Turn Caps Lock into Ctrl + layout = "us"; + options = "ctrl:nocaps"; + }; + }; + + # Better support for general peripherals + libinput.enable = true; + + # Let's be able to SSH into this machine + openssh.enable = true; + + # Sync state between machines + # Sync state between machines + syncthing = { + enable = true; + openDefaultPorts = true; + dataDir = "/home/${user}/.local/share/syncthing"; + configDir = "/home/${user}/.config/syncthing"; + user = "${user}"; + group = "users"; + guiAddress = "127.0.0.1:8384"; + overrideFolders = true; + overrideDevices = true; + + settings = { + devices = {}; + options.globalAnnounceEnabled = false; # Only sync on LAN + }; + }; + + # Picom, my window compositor with fancy effects + # + # Notes on writing exclude rules: + # + # class_g looks up index 1 in WM_CLASS value for an application + # class_i looks up index 0 + # + # To find the value for a specific application, use `xprop` at the + # terminal and then click on a window of the application in question + # + picom = { + enable = true; + settings = { + animations = true; + animation-stiffness = 300.0; + animation-dampening = 35.0; + animation-clamping = false; + animation-mass = 1; + animation-for-workspace-switch-in = "auto"; + animation-for-workspace-switch-out = "auto"; + animation-for-open-window = "slide-down"; + animation-for-menu-window = "none"; + animation-for-transient-window = "slide-down"; + corner-radius = 12; + rounded-corners-exclude = [ + "class_i = 'polybar'" + "class_g = 'i3lock'" + ]; + round-borders = 3; + round-borders-exclude = []; + round-borders-rule = []; + shadow = true; + shadow-radius = 8; + shadow-opacity = 0.4; + shadow-offset-x = -8; + shadow-offset-y = -8; + fading = false; + inactive-opacity = 0.8; + frame-opacity = 0.7; + inactive-opacity-override = false; + active-opacity = 1.0; + focus-exclude = [ + ]; + + opacity-rule = [ + "100:class_g = 'i3lock'" + "60:class_g = 'Dunst'" + "100:class_g = 'Alacritty' && focused" + "90:class_g = 'Alacritty' && !focused" + ]; + + blur-kern = "3x3box"; + blur = { + method = "kernel"; + strength = 8; + background = false; + background-frame = false; + background-fixed = false; + kern = "3x3box"; + }; + + shadow-exclude = [ + "class_g = 'Dunst'" + ]; + + blur-background-exclude = [ + "class_g = 'Dunst'" + ]; + + backend = "glx"; + vsync = false; + mark-wmwin-focused = true; + mark-ovredir-focused = true; + detect-rounded-corners = true; + detect-client-opacity = false; + detect-transient = true; + detect-client-leader = true; + use-damage = true; + log-level = "info"; + + wintypes = { + normal = { fade = true; shadow = false; }; + tooltip = { fade = true; shadow = false; opacity = 0.75; focus = true; full-shadow = false; }; + dock = { shadow = false; }; + dnd = { shadow = false; }; + popup_menu = { opacity = 1.0; }; + dropdown_menu = { opacity = 1.0; }; + }; + }; + }; + + gvfs.enable = true; # Mount, trash, and other functionalities + tumbler.enable = true; # Thumbnail support for images + + # Emacs runs as a daemon + emacs = { + enable = true; + package = pkgs.emacs-unstable; + }; + }; + + # When emacs builds from no cache, it exceeds the 90s timeout default + systemd.user.services.emacs = { + serviceConfig.TimeoutStartSec = "7min"; + }; + + # Enable CUPS to print documents + # services.printing.enable = true; + # services.printing.drivers = [ pkgs.brlaser ]; # Brother printer driver + + # Enable sound + # sound.enable = true; + # hardware.pulseaudio.enable = true; + + # Video support + hardware = { + graphics.enable = true; + # nvidia.modesetting.enable = true; + + # Enable Xbox support + # xone.enable = true; + + # Crypto wallet support + ledger.enable = true; + }; + + + # Add docker daemon + virtualisation.docker.enable = true; + virtualisation.docker.logDriver = "json-file"; + + # It's me, it's you, it's everyone + users.users = { + ${user} = { + isNormalUser = true; + extraGroups = [ + "wheel" # Enable ‘sudo’ for the user. + "docker" + ]; + shell = pkgs.zsh; + openssh.authorizedKeys.keys = keys; + }; + + root = { + openssh.authorizedKeys.keys = keys; + }; + }; + + # Don't require password for users in `wheel` group for these commands + security.sudo = { + enable = true; + extraRules = [{ + commands = [ + { + command = "${pkgs.systemd}/bin/reboot"; + options = [ "NOPASSWD" ]; + } + ]; + groups = [ "wheel" ]; + }]; + }; + + fonts.packages = with pkgs; [ + dejavu_fonts + emacs-all-the-icons-fonts + feather-font # from overlay + jetbrains-mono + font-awesome + noto-fonts + noto-fonts-emoji + ]; + + environment.systemPackages = with pkgs; [ + agenix.packages."${pkgs.system}".default # "x86_64-linux" + gitAndTools.gitFull + inetutils + ]; + + system.stateVersion = "21.05"; # Don't change this +} diff --git a/modules/darwin/README.md b/modules/darwin/README.md new file mode 100644 index 0000000..90ab44e --- /dev/null +++ b/modules/darwin/README.md @@ -0,0 +1,11 @@ + +## Layout +``` +. +├── dock # MacOS dock configuration +├── casks.nix # List of homebrew casks +├── default.nix # Defines module, system-level config +├── files.nix # Non-Nix, static configuration files (now immutable!) +├── home-manager.nix # Defines user programs +├── packages.nix # List of packages to install for MacOS +``` diff --git a/modules/darwin/casks.nix b/modules/darwin/casks.nix new file mode 100644 index 0000000..98f15c3 --- /dev/null +++ b/modules/darwin/casks.nix @@ -0,0 +1,26 @@ +_: + +[ + # Development Tools + "homebrew/cask/docker" + "visual-studio-code" + + # Communication Tools + "discord" + "notion" + "slack" + "telegram" + "zoom" + + # Utility Tools + "syncthing" + + # Entertainment Tools + "vlc" + + # Productivity Tools + "raycast" + + # Browsers + "google-chrome" +] diff --git a/modules/darwin/dock/default.nix b/modules/darwin/dock/default.nix new file mode 100644 index 0000000..fa3226d --- /dev/null +++ b/modules/darwin/dock/default.nix @@ -0,0 +1,70 @@ +{ config, pkgs, lib, ... }: + +# Original source: https://gist.github.com/antifuchs/10138c4d838a63c0a05e725ccd7bccdd + +with lib; +let + cfg = config.local.dock; + inherit (pkgs) stdenv dockutil; +in +{ + options = { + local.dock.enable = mkOption { + description = "Enable dock"; + default = stdenv.isDarwin; + example = false; + }; + + local.dock.entries = mkOption + { + description = "Entries on the Dock"; + type = with types; listOf (submodule { + options = { + path = lib.mkOption { type = str; }; + section = lib.mkOption { + type = str; + default = "apps"; + }; + options = lib.mkOption { + type = str; + default = ""; + }; + }; + }); + readOnly = true; + }; + }; + + config = + mkIf cfg.enable + ( + let + normalize = path: if hasSuffix ".app" path then path + "/" else path; + entryURI = path: "file://" + (builtins.replaceStrings + [" " "!" "\"" "#" "$" "%" "&" "'" "(" ")"] + ["%20" "%21" "%22" "%23" "%24" "%25" "%26" "%27" "%28" "%29"] + (normalize path) + ); + wantURIs = concatMapStrings + (entry: "${entryURI entry.path}\n") + cfg.entries; + createEntries = concatMapStrings + (entry: "${dockutil}/bin/dockutil --no-restart --add '${entry.path}' --section ${entry.section} ${entry.options}\n") + cfg.entries; + in + { + system.activationScripts.postUserActivation.text = '' + echo >&2 "Setting up the Dock..." + haveURIs="$(${dockutil}/bin/dockutil --list | ${pkgs.coreutils}/bin/cut -f2)" + if ! diff -wu <(echo -n "$haveURIs") <(echo -n '${wantURIs}') >&2 ; then + echo >&2 "Resetting Dock." + ${dockutil}/bin/dockutil --no-restart --remove all + ${createEntries} + killall Dock + else + echo >&2 "Dock setup complete." + fi + ''; + } + ); +} diff --git a/modules/darwin/files.nix b/modules/darwin/files.nix new file mode 100644 index 0000000..f0bbac1 --- /dev/null +++ b/modules/darwin/files.nix @@ -0,0 +1,34 @@ +{ user, config, pkgs, ... }: + +let + xdg_configHome = "${config.users.users.${user}.home}/.config"; + xdg_dataHome = "${config.users.users.${user}.home}/.local/share"; + xdg_stateHome = "${config.users.users.${user}.home}/.local/state"; in +{ + + # Raycast script so that "Run Emacs" is available and uses Emacs daemon + "${xdg_dataHome}/bin/emacsclient" = { + executable = true; + text = '' + #!/bin/zsh + # + # Required parameters: + # @raycast.schemaVersion 1 + # @raycast.title Run Emacs + # @raycast.mode silent + # + # Optional parameters: + # @raycast.packageName Emacs + # @raycast.icon ${xdg_dataHome}/img/icons/Emacs.icns + # @raycast.iconDark ${xdg_dataHome}/img/icons/Emacs.icns + + if [[ $1 = "-t" ]]; then + # Terminal mode + ${pkgs.emacs}/bin/emacsclient -t $@ + else + # GUI mode + ${pkgs.emacs}/bin/emacsclient -c -n $@ + fi + ''; + }; +} diff --git a/modules/darwin/home-manager.nix b/modules/darwin/home-manager.nix new file mode 100644 index 0000000..5a33ef5 --- /dev/null +++ b/modules/darwin/home-manager.nix @@ -0,0 +1,103 @@ +{ config, pkgs, lib, home-manager, ... }: + +let + user = "%USER%"; + # Define the content of your file as a derivation + myEmacsLauncher = pkgs.writeScript "emacs-launcher.command" '' + #!/bin/sh + emacsclient -c -n & + ''; + sharedFiles = import ../shared/files.nix { inherit config pkgs; }; + additionalFiles = import ./files.nix { inherit user config pkgs; }; +in +{ + imports = [ + ./dock + ]; + + # It me + users.users.${user} = { + name = "${user}"; + home = "/Users/${user}"; + isHidden = false; + shell = pkgs.zsh; + }; + + homebrew = { + enable = true; + casks = pkgs.callPackage ./casks.nix {}; + # onActivation.cleanup = "uninstall"; + + # These app IDs are from using the mas CLI app + # mas = mac app store + # https://github.com/mas-cli/mas + # + # $ nix shell nixpkgs#mas + # $ mas search + # + # If you have previously added these apps to your Mac App Store profile (but not installed them on this system), + # you may receive an error message "Redownload Unavailable with This Apple ID". + # This message is safe to ignore. (https://github.com/dustinlyons/nixos-config/issues/83) + + masApps = { + "1password" = 1333542190; + "wireguard" = 1451685025; + }; + }; + + # Enable home-manager + home-manager = { + useGlobalPkgs = true; + users.${user} = { pkgs, config, lib, ... }:{ + home = { + enableNixpkgsReleaseCheck = false; + packages = pkgs.callPackage ./packages.nix {}; + file = lib.mkMerge [ + sharedFiles + additionalFiles + { "emacs-launcher.command".source = myEmacsLauncher; } + ]; + + stateVersion = "23.11"; + }; + programs = {} // import ../shared/home-manager.nix { inherit config pkgs lib; }; + + # Marked broken Oct 20, 2022 check later to remove this + # https://github.com/nix-community/home-manager/issues/3344 + manual.manpages.enable = false; + }; + }; + + # Fully declarative dock using the latest from Nix Store + local = { + dock = { + enable = true; + entries = [ + { path = "/Applications/Slack.app/"; } + { path = "/System/Applications/Messages.app/"; } + { path = "/System/Applications/Facetime.app/"; } + { path = "${pkgs.alacritty}/Applications/Alacritty.app/"; } + { path = "/System/Applications/Music.app/"; } + { path = "/System/Applications/News.app/"; } + { path = "/System/Applications/Photos.app/"; } + { path = "/System/Applications/Photo Booth.app/"; } + { path = "/System/Applications/TV.app/"; } + { path = "/System/Applications/Home.app/"; } + { + path = toString myEmacsLauncher; + section = "others"; + } + { + path = "${config.users.users.${user}.home}/.local/share/"; + section = "others"; + options = "--sort name --view grid --display folder"; + } + { + path = "${config.users.users.${user}.home}/.local/share/downloads"; + section = "others"; + options = "--sort name --view grid --display stack"; + } + ]; + }; + }; +} diff --git a/modules/darwin/packages.nix b/modules/darwin/packages.nix new file mode 100644 index 0000000..7eaa1d8 --- /dev/null +++ b/modules/darwin/packages.nix @@ -0,0 +1,7 @@ +{ pkgs }: + +with pkgs; +let shared-packages = import ../shared/packages.nix { inherit pkgs; }; in +shared-packages ++ [ + dockutil +] diff --git a/modules/darwin/secrets.nix b/modules/darwin/secrets.nix new file mode 100644 index 0000000..2dccd91 --- /dev/null +++ b/modules/darwin/secrets.nix @@ -0,0 +1,37 @@ +{ config, pkgs, agenix, secrets, ... }: + +let user = "%USER%"; in +{ + age.identityPaths = [ + "/Users/${user}/.ssh/id_ed25519" + ]; + + # Your secrets go here + # + # Note: the installWithSecrets command you ran to boostrap the machine actually copies over + # a Github key pair. However, if you want to store the keypair in your nix-secrets repo + # instead, you can reference the age files and specify the symlink path here. Then add your + # public key in shared/files.nix. + # + # If you change the key name, you'll need to update the SSH configuration in shared/home-manager.nix + # so Github reads it correctly. + + # + # age.secrets."github-ssh-key" = { + # symlink = true; + # path = "/Users/${user}/.ssh/id_github"; + # file = "${secrets}/github-ssh-key.age"; + # mode = "600"; + # owner = "${user}"; + # group = "staff"; + # }; + + # age.secrets."github-signing-key" = { + # symlink = false; + # path = "/Users/${user}/.ssh/pgp_github.key"; + # file = "${secrets}/github-signing-key.age"; + # mode = "600"; + # owner = "${user}"; + # }; + +} diff --git a/modules/nixos/README.md b/modules/nixos/README.md new file mode 100644 index 0000000..bfa2a9e --- /dev/null +++ b/modules/nixos/README.md @@ -0,0 +1,11 @@ +## Layout +``` +. +├── config # Config files not written in Nix +├── default.nix # Defines module, system-level config, +├── disk-config.nix # Disks, partitions, and filesystems +├── files.nix # Non-Nix, static configuration files (now immutable!) +├── home-manager.nix # Defines user programs +├── packages.nix # List of packages to install for NixOS +├── secrets.nix # Age-encrypted secrets with agenix +``` diff --git a/modules/nixos/config/login-wallpaper.png b/modules/nixos/config/login-wallpaper.png new file mode 100644 index 0000000..7a91f64 Binary files /dev/null and b/modules/nixos/config/login-wallpaper.png differ diff --git a/modules/nixos/config/polybar/bars.ini b/modules/nixos/config/polybar/bars.ini new file mode 100644 index 0000000..2cd1a7a --- /dev/null +++ b/modules/nixos/config/polybar/bars.ini @@ -0,0 +1,498 @@ +;; ┌────────────────────────────────────────────────────┐ +;; │░█▀█░█▀█░█░░░█░█░█▀▄░█▀█░█▀▄░░░░░░░░░█▀▄░█▀█░█▀▄░█▀▀│ +;; │░█▀▀░█░█░█░░░░█░░█▀▄░█▀█░█▀▄░░░░▀░░░░█▀▄░█▀█░█▀▄░▀▀█│ +;; │░▀░░░▀▀▀░▀▀▀░░▀░░▀▀░░▀░▀░▀░▀░░░░▀░░░░▀▀░░▀░▀░▀░▀░▀▀▀│ +;; │░Created░By░Aditya░Shakya░@adi1090x░░░░░░░░░░░░░░░░░│ +;; └────────────────────────────────────────────────────┘ + +;; _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ + +[bar] +fill =  +empty =  +indicator = ⏽ +; Nerd font :   ,  ⏽,  樂 籠 錄 , 雷 絛 + +;; _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ + +[module/volume] +type = internal/alsa + +; Soundcard to be used +; Usually in the format hw:# where # is the card number +; You can find the different card numbers in `/proc/asound/cards` +master-soundcard = default +speaker-soundcard = default +headphone-soundcard = default + +; Name of the master, speaker and headphone mixers +; Use the following command to list available mixer controls: +; $ amixer scontrols | sed -nr "s/.*'([[:alnum:]]+)'.*/\1/p" +; If master, speaker or headphone-soundcard isn't the default, +; use `amixer -c # scontrols` instead where # is the number +; of the master, speaker or headphone soundcard respectively +; +; Default: Master +master-mixer = Master + +; Optionally define speaker and headphone mixers +; Default: none +;;speaker-mixer = Speaker +; Default: none +;;headphone-mixer = Headphone + +; NOTE: This is required if headphone_mixer is defined +; Use the following command to list available device controls +; $ amixer controls | sed -r "/CARD/\!d; s/.*=([0-9]+).*name='([^']+)'.*/printf '%3.0f: %s\n' '\1' '\2'/e" | sort +; You may also need to use `amixer -c # controls` as above for the mixer names +; Default: none +;;headphone-id = 9 + +; Use volume mapping (similar to amixer -M and alsamixer), where the increase in volume is linear to the ear +; Default: false +;;mapped = true + +; Interval for volume increase/decrease (in percent points) +; Default: 5 +interval = 5 + +; Available tags: +; (default) +; +; +format-volume = + +; Available tags: +; (default) +; +; +format-muted = +format-muted-prefix =  + +; Available tokens: +; %percentage% (default) +label-volume = %percentage%% + +; Available tokens: +; %percentage% (default +label-muted = " Muted" +label-muted-foreground = ${color.foreground-alt} + +; Only applies if is used +ramp-volume-0 =  +ramp-volume-1 =  +ramp-volume-2 =  + +; Only applies if is used +bar-volume-width = 10 +bar-volume-gradient = false + +bar-volume-indicator = ${bar.indicator} +bar-volume-indicator-foreground = ${color.foreground} + +bar-volume-fill = ${bar.fill} +bar-volume-foreground-0 = ${color.foreground} +bar-volume-foreground-1 = ${color.foreground} +bar-volume-foreground-2 = ${color.foreground} + +bar-volume-empty = ${bar.empty} +bar-volume-empty-foreground = ${color.foreground} + +; If defined, it will replace when +; headphones are plugged in to `headphone_control_numid` +; If undefined, will be used for both +; Only applies if is used +ramp-headphones-0 =  + +;; _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ + +[module/brightness] +;type = internal/xbacklight +type = internal/backlight + +; Use the following command to list available cards: +; $ ls -1 /sys/class/backlight/ +;card = intel_backlight +card = amdgpu_bl0 + +; Available tags: +;