From af4afd0687f61c700307901e0da005c6e9f7d2e0 Mon Sep 17 00:00:00 2001 From: Drake Bott Date: Fri, 13 Mar 2026 09:18:01 -0500 Subject: [PATCH] Set up Nix/NixOS for laptop --- .envrc | 1 + .github/workflows/build-iso.yml | 25 ++ .github/workflows/renovate.yml | 18 ++ .gitignore | 4 +- .zshrc | 27 -- README.md | 70 ++++- .../weallcode-background.png | Bin flake.lock | 172 ++++++++++++ flake.nix | 96 +++++++ install.sh | 254 ------------------ logon.script.sh | 59 ---- modules/base.nix | 75 ++++++ modules/broadcom.nix | 8 + modules/disk.nix | 35 +++ modules/hardware-configuration.nix | 21 ++ modules/iso.nix | 67 +++++ modules/networkmanager.nix | 9 + modules/nix-settings.nix | 17 ++ modules/python.nix | 7 + modules/vscode.nix | 52 ++++ modules/xfce.nix | 59 ++++ org.weallcode.logon.plist | 14 - renovate.json | 13 + scripts/machine-setup.py | 68 +++++ scripts/update.py | 53 ++++ settings.json | 34 --- 26 files changed, 866 insertions(+), 392 deletions(-) create mode 100644 .envrc create mode 100644 .github/workflows/build-iso.yml create mode 100644 .github/workflows/renovate.yml delete mode 100644 .zshrc rename weallcode-background.png => assets/weallcode-background.png (100%) create mode 100644 flake.lock create mode 100644 flake.nix delete mode 100755 install.sh delete mode 100755 logon.script.sh create mode 100644 modules/base.nix create mode 100644 modules/broadcom.nix create mode 100644 modules/disk.nix create mode 100644 modules/hardware-configuration.nix create mode 100644 modules/iso.nix create mode 100644 modules/networkmanager.nix create mode 100644 modules/nix-settings.nix create mode 100644 modules/python.nix create mode 100644 modules/vscode.nix create mode 100644 modules/xfce.nix delete mode 100644 org.weallcode.logon.plist create mode 100644 renovate.json create mode 100644 scripts/machine-setup.py create mode 100644 scripts/update.py delete mode 100644 settings.json diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/build-iso.yml b/.github/workflows/build-iso.yml new file mode 100644 index 0000000..fc03efa --- /dev/null +++ b/.github/workflows/build-iso.yml @@ -0,0 +1,25 @@ +name: Build ISO +on: + push: + branches: [main] + pull_request: + branches: [main] +jobs: + build-iso: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + - name: Install nix + uses: cachix/install-nix-action@v27 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - name: Build ISO + run: nix build .#installer + - name: Upload ISO artifact + uses: actions/upload-artifact@v5 + with: + name: weallcode-nixos-iso + path: result/iso/*.iso + retention-days: 30 + compression-level: 0 diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..46c31ac --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,18 @@ +name: Renovate +on: + schedule: + - cron: "0 5 1 * *" + workflow_dispatch: +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Run Renovate + uses: renovatebot/github-action@v46.1.5 + with: + configurationFile: renovate.json + token: ${{ secrets.GITHUB_TOKEN }} + env: + RENOVATE_REPOSITORIES: ${{ github.repository }} diff --git a/.gitignore b/.gitignore index 86f5b2f..bedf104 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -weallcode-logon.app/ +.direnv +.pre-commit-config.yaml +.ruff_cache diff --git a/.zshrc b/.zshrc deleted file mode 100644 index cc3163c..0000000 --- a/.zshrc +++ /dev/null @@ -1,27 +0,0 @@ -autoload -U promptinit -promptinit - -# change the path color -zstyle :prompt:pure:path color white - -prompt pure - -# Set up some basic aliases -alias ll='ls -l' -alias la='ls -A' -alias l='ls -CF' - -alias python="/usr/local/bin/python3" -alias pip="/usr/local/bin/pip3" - -# Set up some basic environment variables -export PATH=$PATH:/usr/local/bin - -# Set up some basic options -setopt AUTO_CD # Automatically cd into a directory if you just type the directory name -setopt CORRECT_ALL # Automatically correct the spelling of commands -# zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # Case insensitive tab completion - -# Set up a simple greeting -echo "Welcome to We All Code, happy coding!" -echo "" diff --git a/README.md b/README.md index 6944280..39e9cd1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,69 @@ -# laptop +# We All Code - Laptop Setup -```bash -/bin/bash -c "$(curl -fsSL wac.fyi/mac)" +NixOS configuration for classroom laptops (Intel MacBooks). + +## Current Software + +- XFCE Desktop +- VS Code with extensions: + - Python and Pylance + - Ruff (linter/formatter) + - Prettier +- Firefox +- Python 3 with packages: + - `tkinter` + - `rich` +- [`weallcode-robot`](https://github.com/WeAllCode/tinybit-python) (installed via pip on update) +- git, curl + +## Installation Steps + +### 1. Download and Flash ISO + +Download the ISO from the latest [GitHub Actions build artifact](../../actions/workflows/build-iso.yml). Flash it to a USB drive with `dd` or [Balena Etcher](https://etcher.balena.io/). + +### 2. Boot from USB + +Shut down the MacBook and insert the USB. Power it on while holding down the **Option** key. Select the USB drive on the boot menu. + +### 3. Connect to WiFi + +The installer boots into an XFCE desktop. To connect to WiFi open the network connection menu in the system tray. An internet connection is required for setup. + +### 4. Install + +Double-click the **Classroom Setup** shortcut on the desktop. This partitions the disk and runs `nixos-install` with the classroom flake config. The laptop will reboot when finished — do not remove the USB until the laptop reboots. + +## Updating + +Each laptop has a **Classroom Update** desktop shortcut. It updates the system to use the latest config on GitHub and upgrades `weallcode-robot` via pip. + +## Development + +To build a development environment you must install [Nix](https://nixos.org/download/) and [direnv](https://direnv.net/). Then from your `weallcode/laptop` directory run: + +```sh +direnv allow +``` + +This will set up formatting, linting, and pre-commit hooks. You can run `nix fmt` to format all files. + +## Project Structure + +``` +├── flake.nix Entry point +├── modules/ +│ ├── base.nix System config +│ ├── disk.nix Partition layout (used by installer and config) +│ ├── iso.nix Installer ISO +│ ├── xfce.nix Classroom desktop environment +│ ├── vscode.nix VS Code extensions and settings +│ ├── python.nix Python interpreter and packages +│ ├── hardware-configuration.nix Boot and kernel modules for Intel MacBooks +│ ├── broadcom.nix Broadcom WiFi driver for Intel MacBooks +│ ├── networkmanager.nix +│ └── nix-settings.nix +└── scripts/ + ├── machine-setup.py Partition and install - shortcut on installer desktop + └── update.py Update system and weallcode-robot - shortcut on classroom desktop ``` diff --git a/weallcode-background.png b/assets/weallcode-background.png similarity index 100% rename from weallcode-background.png rename to assets/weallcode-background.png diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ed0ff28 --- /dev/null +++ b/flake.lock @@ -0,0 +1,172 @@ +{ + "nodes": { + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773889306, + "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", + "owner": "nix-community", + "repo": "disko", + "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1774274588, + "narHash": "sha256-dnHvv5EMUgTzGZmA+3diYjQU2O6BEpGLEOgJ1Qe9LaY=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "cf9686ba26f5ef788226843bc31fda4cf72e373b", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "release-25.11", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773964973, + "narHash": "sha256-NV/J+tTER0P5iJhUDL/8HO5MDjDceLQPRUYgdmy5wXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "812b3986fd1568f7a858f97fcf425ad996ba7d25", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1774104215, + "narHash": "sha256-EAtviqz0sEAxdHS4crqu7JGR5oI3BwaqG0mw7CmXkO8=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "f799ae951fde0627157f40aec28dec27b22076d0", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "disko": "disko", + "flake-parts": "flake-parts", + "home-manager": "home-manager", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773297127, + "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..136f786 --- /dev/null +++ b/flake.nix @@ -0,0 +1,96 @@ +{ + description = "We All Code - NixOS laptop configuration"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + flake-parts = { + url = "github:hercules-ci/flake-parts"; + inputs.nixpkgs-lib.follows = "nixpkgs"; + }; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + pre-commit-hooks = { + url = "github:cachix/git-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + home-manager = { + url = "github:nix-community/home-manager/release-25.11"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + disko = { + url = "github:nix-community/disko"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs @ { flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" "aarch64-darwin" ]; + + imports = [ + inputs.treefmt-nix.flakeModule + inputs.pre-commit-hooks.flakeModule + ]; + + # Dev tooling (formatter, linters, pre-commit, dev shell) + perSystem = { config, pkgs, ... }: { + treefmt.programs = { + nixpkgs-fmt.enable = true; + deadnix = { + enable = true; + no-lambda-arg = true; + no-lambda-pattern-names = true; + }; + statix.enable = true; + yamlfmt.enable = true; + ruff-check.enable = true; + ruff-format.enable = true; + }; + + formatter = config.treefmt.build.wrapper; + + pre-commit.settings.hooks.treefmt.enable = true; + + devShells.default = pkgs.mkShell { + name = "laptop-dev"; + inherit (config.pre-commit.devShell) shellHook; + nativeBuildInputs = with pkgs; [ + config.treefmt.build.wrapper + git + ] ++ config.pre-commit.settings.enabledPackages; + }; + }; + + # NixOS system configurations + flake = + let + classroom = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.disko.nixosModules.disko + inputs.home-manager.nixosModules.home-manager + ./modules/disk.nix + ./modules/base.nix + ]; + }; + + installer = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { + diskoPackage = inputs.disko.packages.x86_64-linux.disko; + }; + modules = [ + "${inputs.nixpkgs}/nixos/modules/installer/cd-dvd/iso-image.nix" + "${inputs.nixpkgs}/nixos/modules/profiles/all-hardware.nix" + ./modules/iso.nix + ]; + }; + in + { + nixosConfigurations = { inherit classroom installer; }; + packages.x86_64-linux.installer = installer.config.system.build.isoImage; + }; + }; +} diff --git a/install.sh b/install.sh deleted file mode 100755 index 402c56c..0000000 --- a/install.sh +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env bash -# -# To install: -# -# /bin/bash -c "$(curl -fsSL wac.fyi/mac)" -# - -{ # this ensures the entire script is downloaded # - - GITHUB_REPO="https://raw.githubusercontent.com/WeAllCode/laptop/main" - - output() { - printf "\n\n✅ %s\n" "$1" - - # TODO: Is this okay? https://unix.stackexchange.com/a/452568 - # setopt local_options no_notify no_monitor - - say -r 300 "$1" & - } - - # install all software updates - runSoftwareUpdate() { - output "Installing software updates" - sudo softwareupdate -i -a - } - - enableGuestAccount() { - output "Enabling guest account" - sudo sysadminctl -guestAccount on - } - - setAutoLoginUser() { - output "Setting auto-login user" - sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string "Guest" - } - - disableAutoLoginUser() { - output "Disabling auto-login user" - sudo defaults delete /Library/Preferences/com.apple.loginwindow autoLoginUser - } - - enableFastUserSwitching() { - output "Enabling fast user switching" - sudo defaults write /Library/Preferences/.GlobalPreferences MultipleSessionEnabled -bool YES - - # TODO: figure out how to enable fast user switching menu extra - # output "Enabling fast user switching menu extra" - # sudo defaults write /Library/Preferences/com.apple.loginwindow SHOWFULLNAME -bool YES - - # # enable icon in menu bar - # sudo defaults write com.apple.systemuiserver menuExtras -array-add "/System/Library/CoreServices/Menu Extras/User.menu" - } - - installHomebrew() { - # Check if Homebrew is installed - if ! command -v brew >/dev/null; then - output "Installing Homebrew" - NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - else - output "Homebrew already installed" - fi - } - - tapHomebrew() { - output "Tapping $1" - brew tap "$1" - } - - brewCaskPin() { - output "Pinning $1" - brew cu pin "$2" - } - - disableHomebrewAnalytics() { - output "Disabling Homebrew analytics" - brew analytics off - } - - updateBrew() { - output "Updating Homebrew" - brew update - } - - upgradeBrew() { - output "Upgrading Homebrew" - brew cu --quiet --force --yes - } - - brewUntap() { - if brew tap | grep "^$1$" >/dev/null; then - output "Untapping $1" - brew untap "$1" - else - output "$1 already untapped" - fi - } - - brewInstall() { - NAME=$1 - FORMULA_NAME=$2 - SPECIFIC_URL=$3 - - # Check if $NAME is installed - if ! brew list -1 | grep "^$FORMULA_NAME$" >/dev/null; then - output "Installing $NAME" - - if [ -z "$SPECIFIC_URL" ]; then - brew install "$FORMULA_NAME" - else - # shellcheck disable=2086 - brew install $SPECIFIC_URL - fi - - else - output "$NAME already installed" - - # output "Upgrading $NAME" - - # if [ -z "$SPECIFIC_URL" ]; then - # brew upgrade "$FORMULA_NAME" - # else - # # shellcheck disable=2086 - # brew upgrade $SPECIFIC_URL - # fi - - fi - } - - brewUninstall() { - NAME=$1 - FORMULA_NAME=$2 - - # Check if $NAME is installed - if brew list -1 | grep "^$FORMULA_NAME$" >/dev/null; then - output "Uninstalling $NAME" - brew uninstall --force "$FORMULA_NAME" - else - output "$NAME already uninstalled" - fi - } - - installPurePrompt() { - # Install Pure Prompt if not installed - output "Installing Pure Prompt" - npm install --global pure-prompt - } - - updateZshRC() { - output "Updating .zshrc" - curl -fsSL "$GITHUB_REPO/.zshrc" -o "$HOME/.zshrc" - } - - installXCode() { - # Install XCode if not installed - if ! command -v xcode-select >/dev/null; then - output "Installing XCode" - # brew install mas - # mas install 497799835 - xcode-select --install - else - output "XCode already installed" - fi - } - - installPythonPackage() { - # Upgrade pip - output "Upgrade pip" - python3 -m pip install --upgrade --force-reinstall pip - - # Install python packages - output "Install python packages" - python3 -m pip install --upgrade --force-reinstall weallcode_robot - pip3 install --upgrade --force-reinstall weallcode_robot - } - - updateDock() { - output "Updating Dock" - dockutil --remove all \ - --add /Applications/Google\ Chrome.app \ - --add /Applications/Visual\ Studio\ Code.app - } - - setLogonScript() { - logonScriptLocation="/Users/Shared/logon.script.sh" - logonScriptURL="$GITHUB_REPO/logon.script.sh" - - output "Downloading logon script" - sudo curl -fsSL "$logonScriptURL" -o "$logonScriptLocation" - - sudo chown root "$logonScriptLocation" - sudo chmod +x "$logonScriptLocation" - - # --------------------------------------------- - - logonPlistLocation="/Library/LaunchAgents/org.weallcode.logon.plist" - logonPlistURL="$GITHUB_REPO/org.weallcode.logon.plist" - - # Download logon plist - output "Downloading logon plist" - sudo curl -fsSL "$logonPlistURL" -o "$logonPlistLocation" - - if launchctl list | grep -q "org.weallcode.logon"; then - output "Unloading org.weallcode.logon" - sudo launchctl unload -w "$logonPlistLocation" - launchctl unload -w "$logonPlistLocation" - fi - - output "Enabling logon plist" - sudo chown root "$logonPlistLocation" - sudo launchctl load -w "$logonPlistLocation" - launchctl load -w "$logonPlistLocation" - } - - # runSoftwareUpdate # Runs slow - enableGuestAccount - # setAutoLoginUser # disabled for now - disableAutoLoginUser - enableFastUserSwitching - - installHomebrew - disableHomebrewAnalytics - installXCode - - updateBrew - brewUninstall "NextDNS" "nextdns" - brewUninstall "Firefox" "firefox" - brewUntap "homebrew/core" - brewUntap "homebrew/cask" - - tapHomebrew "buo/cask-upgrade" # brew cu - - brewInstall "Google Chrome" "google-chrome" - brewInstall "Visual Studio Code" "visual-studio-code" - brewInstall "Git" "git" - # brewInstall "Vim" "vim" - brewInstall "Wallpaper Changer" "wallpaper" - brewInstall "Python 3.x" "python3" - # brewInstall "Node" "node" - # brewInstall "Unity" "unity" "--cask https://raw.githubusercontent.com/Homebrew/homebrew-cask/4dc5194f3806a9b10a289cf4eaf68f7eb5528691/Casks/unity.rb" # 2022.1.23f1,9636b062134a - brewCaskPin "Unity" "unity" - - tapHomebrew "hpedrorodrigues/tools" - brewUninstall "Dockutil" "dockutil" - brewUptap "hpedrorodrigues/tools/dockutil" - brewInstall "Dockutil" "dockutil" - - installPurePrompt - updateZshRC - - upgradeBrew - installPythonPackage - updateDock - setLogonScript -} diff --git a/logon.script.sh b/logon.script.sh deleted file mode 100755 index 05a7c91..0000000 --- a/logon.script.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -GITHUB_REPO="https://raw.githubusercontent.com/WeAllCode/laptop/main" - -# --------------------------------------------- - -# Add VS Code settings.json file to user settings -CODE_SETTINGS_LOCATION="/Users/Guest/Library/Application Support/Code/User/settings.json" - -# Download settings.json -curl -fsSL "$GITHUB_REPO/settings.json" -o "$CODE_SETTINGS_LOCATION" - -# --------------------------------------------- - -# Install VS Code Extensions -code --install-extension ms-python.python - -# Upgrade pip -python3 -m pip install --upgrade --force-reinstall pip - -# Install python packages -python3 -m pip install --upgrade --force-reinstall weallcode_robot -pip3 install --upgrade --force-reinstall weallcode_robot - -# --------------------------------------------- - -# Update zsh prompt -ZSHRC_LOCATION="/Users/Guest/.zshrc" -curl -fsSL "$GITHUB_REPO/.zshrc" -o "$ZSHRC_LOCATION" - -# --------------------------------------------- - -# Open Survey -SURVEY_URL="https://wac.fyi/survey" -open "$SURVEY_URL" - -# --------------------------------------------- - -sleep 1 - -# Update Dock -# don't set background and update dock back to back, they seem to cause a race condition as they restart the dock/desktop -/usr/local/bin/dockutil --remove all \ - --add /Applications/Google\ Chrome.app \ - --add /Applications/Visual\ Studio\ Code.app \ - --add /Applications/Unity/Unity.app - -# --------------------------------------------- - -sleep 1 - -# Set Background -BACKGROUND_URL="$GITHUB_REPO/weallcode-background.png" -BACKGROUND_LOCATION="/Users/Shared/weallcode-background.png" - -curl -fsSL "$BACKGROUND_URL" -o "$BACKGROUND_LOCATION" -wallpaper set "$BACKGROUND_LOCATION" - -# --------------------------------------------- diff --git a/modules/base.nix b/modules/base.nix new file mode 100644 index 0000000..9e7b9b6 --- /dev/null +++ b/modules/base.nix @@ -0,0 +1,75 @@ +{ pkgs, ... }: { + networking.hostName = "classroom"; + + imports = [ + ./hardware-configuration.nix + ./networkmanager.nix + ./xfce.nix + ./python.nix + ./nix-settings.nix + ]; + + boot = { + tmp.cleanOnBoot = true; + kernel.sysctl."fs.inotify.max_user_watches" = 524288; + }; + + users.users.weallcode = { + isNormalUser = true; + extraGroups = [ "wheel" "networkmanager" "audio" "video" ]; + initialPassword = "Coder4life!"; + }; + + system.activationScripts.setupHomeManagerProfileDir.text = '' + install -d -m 0755 -o weallcode -g users /nix/var/nix/profiles/per-user/weallcode + ''; + + services.pipewire = { + enable = true; + alsa.enable = true; + pulse.enable = true; + }; + + hardware.bluetooth.enable = true; + + environment.systemPackages = with pkgs; [ + git + curl + direnv + jetbrains-mono + noto-fonts-color-emoji + firefox + usbutils + pciutils + + (pkgs.writeScriptBin "classroom-update" (builtins.readFile ../scripts/update.py)) + ]; + + home-manager = { + useGlobalPkgs = true; + useUserPackages = true; + backupFileExtension = "hm-backup"; + users.weallcode = _: { + home.stateVersion = "25.11"; + imports = [ ../modules/vscode.nix ]; + + home.file."Desktop/classroom-update.desktop" = { + text = '' + [Desktop Entry] + Name=Classroom Update + Comment=Pull latest config and rebuild the system + Exec=xfce4-terminal --hold -e "classroom-update" + Icon=system-software-update + Terminal=false + Type=Application + ''; + executable = true; + }; + + }; + }; + + powerManagement.cpuFreqGovernor = "ondemand"; + + system.stateVersion = "25.11"; +} diff --git a/modules/broadcom.nix b/modules/broadcom.nix new file mode 100644 index 0000000..2dab292 --- /dev/null +++ b/modules/broadcom.nix @@ -0,0 +1,8 @@ +{ config, lib, ... }: { + hardware.enableRedistributableFirmware = true; + nixpkgs.config.allowUnfree = true; + nixpkgs.config.allowInsecurePredicate = pkg: lib.getName pkg == "broadcom-sta"; + boot.kernelModules = [ "wl" ]; + boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ]; + boot.blacklistedKernelModules = [ "b43" "bcma" ]; +} diff --git a/modules/disk.nix b/modules/disk.nix new file mode 100644 index 0000000..c92cbd5 --- /dev/null +++ b/modules/disk.nix @@ -0,0 +1,35 @@ +# https://github.com/nix-community/disko/blob/master/docs/quickstart.md +_: +{ + disko.devices.disk.main = { + type = "disk"; + device = "/dev/sda"; + content = { + type = "gpt"; + partitions = { + esp = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + swap = { + size = "2G"; + content.type = "swap"; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; +} diff --git a/modules/hardware-configuration.nix b/modules/hardware-configuration.nix new file mode 100644 index 0000000..2db8b39 --- /dev/null +++ b/modules/hardware-configuration.nix @@ -0,0 +1,21 @@ +{ modulesPath, ... }: { + imports = [ + "${modulesPath}/installer/scan/not-detected.nix" + ./broadcom.nix + ]; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + boot.initrd.availableKernelModules = [ + "xhci_pci" + "ahci" + "usbhid" + "usb_storage" + "sd_mod" + "sdhci_pci" + ]; + + boot.kernelModules = [ "kvm-intel" ]; + +} diff --git a/modules/iso.nix b/modules/iso.nix new file mode 100644 index 0000000..24d80bf --- /dev/null +++ b/modules/iso.nix @@ -0,0 +1,67 @@ +{ config, pkgs, diskoPackage, ... }: +let + inherit (config.networking) hostName; +in +{ + imports = [ + ./broadcom.nix + ./networkmanager.nix + ./nix-settings.nix + ./python.nix + ]; + + networking.hostName = "weallcode"; + + config = { + image.fileName = "${hostName}-installer.iso"; + isoImage = { + makeEfiBootable = true; + makeUsbBootable = true; + squashfsCompression = "zstd -Xcompression-level 6"; + }; + + services.xserver.enable = true; + services.xserver.desktopManager.xfce.enable = true; + services.displayManager.autoLogin = { + enable = true; + user = "nixos"; + }; + + environment.systemPackages = with pkgs; [ + git + vim + diskoPackage + + (pkgs.writeScriptBin "classroom-setup" (builtins.readFile ../scripts/machine-setup.py)) + ]; + + system.activationScripts.installDesktopShortcut = + let + desktopFile = pkgs.writeText "classroom-setup.desktop" '' + [Desktop Entry] + Name=Classroom Setup + Comment=Wipe disk and install NixOS classroom config + Exec=xfce4-terminal --hold -e "sudo classroom-setup" + Icon=system-software-install + Terminal=false + Type=Application + ''; + in + '' + mkdir -p /home/nixos/Desktop + cp ${desktopFile} /home/nixos/Desktop/classroom-setup.desktop + chmod +x /home/nixos/Desktop/classroom-setup.desktop + chown -R nixos:users /home/nixos/Desktop + ''; + + users.users.nixos = { + isNormalUser = true; + extraGroups = [ "wheel" "networkmanager" ]; + initialPassword = "Coder4life!"; + }; + + security.sudo.wheelNeedsPassword = false; + + system.stateVersion = "25.11"; + }; +} diff --git a/modules/networkmanager.nix b/modules/networkmanager.nix new file mode 100644 index 0000000..c096062 --- /dev/null +++ b/modules/networkmanager.nix @@ -0,0 +1,9 @@ +_: { + networking.networkmanager.enable = true; + + # Store WiFi passwords system-wide (no secret service needed) + networking.networkmanager.settings."connection-wifi" = { + "match-device" = "type:wifi"; + "connection.permissions" = ""; + }; +} diff --git a/modules/nix-settings.nix b/modules/nix-settings.nix new file mode 100644 index 0000000..b3fdb80 --- /dev/null +++ b/modules/nix-settings.nix @@ -0,0 +1,17 @@ +_: { + nix = { + settings = { + experimental-features = [ "nix-command" "flakes" ]; + trusted-users = [ "root" "@wheel" ]; + auto-optimise-store = true; + http-connections = 32; + substituters = [ "https://nix-community.cachix.org" ]; + trusted-public-keys = [ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" ]; + }; + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 30d"; + }; + }; +} diff --git a/modules/python.nix b/modules/python.nix new file mode 100644 index 0000000..a0fac73 --- /dev/null +++ b/modules/python.nix @@ -0,0 +1,7 @@ +{ pkgs, ... }: +let + python = pkgs.python3.withPackages (ps: [ ps.tkinter ps.rich ps.pip ]); +in +{ + environment.systemPackages = [ python ]; +} diff --git a/modules/vscode.nix b/modules/vscode.nix new file mode 100644 index 0000000..17f43d9 --- /dev/null +++ b/modules/vscode.nix @@ -0,0 +1,52 @@ +{ pkgs, ... }: { + programs.vscode = { + enable = true; + mutableExtensionsDir = false; + profiles.default = { + extensions = with pkgs.vscode-extensions; [ + ms-python.python + ms-python.vscode-pylance + charliermarsh.ruff + esbenp.prettier-vscode + ]; + userSettings = { + "editor.wordWrap" = "on"; + "editor.defaultFormatter" = "charliermarsh.ruff"; + "editor.minimap.enabled" = false; + "editor.formatOnSave" = true; + "editor.fontSize" = 16; + "security.workspace.trust.untrustedFiles" = "open"; + "workbench.startupEditor" = "newUntitledFile"; + "terminal.integrated.fontSize" = 14; + "terminal.integrated.fontFamily" = "'JetBrains Mono', 'DejaVu Sans Mono', monospace"; + "python.defaultInterpreterPath" = "/run/current-system/sw/bin/python"; + "ruff.interpreter" = [ "/run/current-system/sw/bin/python" ]; + "ruff.configuration" = { + "lint" = { "ignore" = [ "F403" "F405" ]; }; + }; + "github.copilot.enable" = { "*" = false; }; + "github.copilot.editor.enableAutoCompletions" = false; + "github.copilot.editor.enableCodeActions" = false; + "github.copilot.nextEditSuggestions.enabled" = false; + "github.copilot.nextEditSuggestions.fixes" = false; + "github.copilot.renameSuggestions.triggerAutomatically" = false; + "github.copilot.chat.reviewSelection.enabled" = false; + "github.copilot.chat.reviewAgent.enabled" = false; + "editor.inlineSuggest.enabled" = false; + "chat.enabled" = false; + "chat.disableAIFeatures" = true; + "chat.agent.enabled" = false; + "chat.commandCenter.enabled" = false; + "chat.detectParticipant.enabled" = false; + "chat.autopilot.enabled" = false; + "inlineChat.lineNaturalLanguageHint" = false; + "terminal.integrated.initialHint" = false; + "[python]" = { + "editor.tabSize" = 4; + "editor.insertSpaces" = true; + "editor.detectIndentation" = false; + }; + }; + }; + }; +} diff --git a/modules/xfce.nix b/modules/xfce.nix new file mode 100644 index 0000000..ea982a9 --- /dev/null +++ b/modules/xfce.nix @@ -0,0 +1,59 @@ +{ pkgs, ... }: +let + monitor = "eDP-1"; + wallpaper = "/etc/weallcode-background.png"; +in +{ + services.xserver.enable = true; + services.xserver.desktopManager.xfce.enable = true; + programs.xfconf.enable = true; + services.displayManager.autoLogin = { + enable = true; + user = "weallcode"; + }; + + environment.etc."weallcode-background.png".source = ../assets/weallcode-background.png; + + environment.systemPackages = [ pkgs.xfce.xfce4-screenshooter ]; + + home-manager.users.weallcode = { + xfconf.settings = { + xfce4-desktop = { + "backdrop/screen0/monitor${monitor}/workspace0/last-image" = wallpaper; + "backdrop/screen0/monitor${monitor}/workspace0/image-style" = 5; # zoomed + }; + xfce4-panel = { + "panels" = [ 1 ]; + "panels/panel-1/plugin-ids" = [ 1 2 3 4 5 6 ]; + "panels/panel-1/position" = "p=8;x=0;y=0"; + "panels/panel-1/position-locked" = true; + "panels/panel-1/size" = 48; + "panels/panel-1/length" = 100.0; + "plugins/plugin-1" = "applicationsmenu"; + "plugins/plugin-2" = "launcher"; + "plugins/plugin-3" = "launcher"; + "plugins/plugin-4" = "separator"; + "plugins/plugin-4/expand" = true; + "plugins/plugin-5" = "systray"; + "plugins/plugin-6" = "clock"; + }; + }; + + xdg.configFile = { + "xfce4/panel/launcher-2/code.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Visual Studio Code + Exec=code + Icon=vscode + ''; + "xfce4/panel/launcher-3/firefox.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Firefox + Exec=firefox + Icon=firefox + ''; + }; + }; +} diff --git a/org.weallcode.logon.plist b/org.weallcode.logon.plist deleted file mode 100644 index 85dddd9..0000000 --- a/org.weallcode.logon.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Label - org.weallcode.logon - ProgramArguments - - /Users/Shared/logon.script.sh - - RunAtLoad - - - diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..6a014ba --- /dev/null +++ b/renovate.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "schedule": ["monthly"], + "timezone": "America/Chicago", + "separateMajorMinor": true, + "lockFileMaintenance": { + "enabled": true + }, + "nix": { + "enabled": true + } +} diff --git a/scripts/machine-setup.py b/scripts/machine-setup.py new file mode 100644 index 0000000..f8a8b21 --- /dev/null +++ b/scripts/machine-setup.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +import subprocess + +from rich.console import Console +from rich.panel import Panel + +console = Console() + +FLAKE_REF = "git+https://github.com/WeAllCode/laptop.git#classroom" + + +def main(): + console.print() + console.print( + Panel.fit( + "[bold]We All Code[/bold] — Installer", + subtitle="[dim]weallcode.org[/dim]", + ) + ) + console.print() + console.print("[bold red]This will ERASE the disk and install NixOS.[/bold red]") + console.print() + + console.print("[bold yellow]Press Enter to wipe and install...[/bold yellow]") + input() + + console.rule("Partitioning") + subprocess.run( + ["disko", "--flake", FLAKE_REF, "--mode", "destroy,format,mount"], + check=True, + ) + + console.rule("Installing") + subprocess.run( + [ + "nixos-install", + "--root", + "/mnt", + "--flake", + FLAKE_REF, + "--no-root-password", + ], + check=True, + ) + + console.rule("Unmounting") + subprocess.run(["umount", "-R", "/mnt"], check=True) + + console.print() + console.print( + Panel.fit( + "[bold green]Done![/bold green] Remove USB and reboot.", + border_style="green", + ) + ) + console.print() + input("Press Enter to reboot...") + subprocess.run(["reboot"], check=True) + + +if __name__ == "__main__": + try: + main() + except subprocess.CalledProcessError as error: + console.print(f"\n[bold red]Install failed.[/bold red]\n{error}\n") + input("Press Enter to close...") + raise SystemExit(1) diff --git a/scripts/update.py b/scripts/update.py new file mode 100644 index 0000000..3a64086 --- /dev/null +++ b/scripts/update.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import subprocess + +from rich.console import Console +from rich.panel import Panel + +console = Console() + +FLAKE_REF = "git+https://github.com/WeAllCode/laptop.git#classroom" +ROBOT_PKG = "git+https://github.com/WeAllCode/tinybit-python.git" + + +def main(): + console.print() + console.print( + Panel.fit( + "[bold]We All Code[/bold] — Updater", + subtitle="[dim]weallcode.org[/dim]", + ) + ) + console.print() + + console.rule("Rebuilding") + subprocess.run( + ["sudo", "nixos-rebuild", "switch", "--refresh", "--flake", FLAKE_REF], + check=True, + ) + + console.rule("Installing weallcode-robot") + subprocess.run( + ["pip", "install", "--user", "--upgrade", ROBOT_PKG], + check=True, + ) + + console.print() + console.print( + Panel.fit( + "[bold green]Done![/bold green] System updated.", + border_style="green", + ) + ) + console.print() + input("Press Enter to close...") + + +if __name__ == "__main__": + try: + main() + except subprocess.CalledProcessError as error: + console.print(f"\n[bold red]Update failed.[/bold red]\n{error}\n") + input("Press Enter to close...") + raise SystemExit(1) diff --git a/settings.json b/settings.json deleted file mode 100644 index b709384..0000000 --- a/settings.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - // Python settings - "python.pythonPath": "/usr/local/bin/python3", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.linting.pylintArgs": [ - "--disable=C0114", // Disable missing-module-docstring warning - "--disable=C0115", // Disable missing-class-docstring warning - "--disable=C0116" // Disable missing-function-docstring warning - ], - "python.formatting.provider": "black", - "python.formatting.blackArgs": ["--line-length", "120"], - - // Editor settings - - "editor.wordWrap": "on", - "editor.minimap.enabled": false, - // "editor.renderWhitespace": "all", - "editor.formatOnSave": true, - "security.workspace.trust.untrustedFiles": "open", - "workbench.startupEditor": "newUntitledFile", - - // Terminal settings - "terminal.integrated.shell.osx": "/bin/zsh", - "terminal.integrated.fontSize": 14, - "terminal.integrated.fontFamily": "Menlo, Monaco, 'Courier New', monospace", - - // Python only - "[python]": { - "editor.tabSize": 4, - "editor.insertSpaces": true, - "editor.detectIndentation": false - } -}