diff --git a/.forgejo/workflows/iso-nixos-installer.yaml b/.forgejo/workflows/iso-nixos-installer.yaml new file mode 100644 index 0000000..d9a9373 --- /dev/null +++ b/.forgejo/workflows/iso-nixos-installer.yaml @@ -0,0 +1,43 @@ +on: [push] +jobs: + build-installer: + runs-on: builder + steps: + - uses: https://code.forgejo.org/actions/checkout@v4 + - run: nix build ./installer + - name: Publish release if ISO changed + env: + TOKEN: ${{ forgejo.token }} + SERVER: ${{ forgejo.server_url }} + REPO: ${{ forgejo.repository }} + run: | + ISO="result/iso/mirea-nixos-installer.iso" + NEW_SHA=$(sha256sum "$ISO" | awk '{print $1}') + + LATEST_BODY=$(curl -sf \ + -H "Authorization: token $TOKEN" \ + "$SERVER/api/v1/repos/$REPO/releases/latest" \ + | jq -r '.body // ""' || echo "") + OLD_SHA=$(echo "$LATEST_BODY" | grep -oE '[a-f0-9]{64}' | head -1 || echo "") + + if [ "$NEW_SHA" = "$OLD_SHA" ]; then + echo "ISO unchanged (sha256: $NEW_SHA), skipping release." + exit 0 + fi + + TAG="iso-$(date -u +%Y%m%d-%H%M%S)" + RELEASE_ID=$(curl -sf -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + "$SERVER/api/v1/repos/$REPO/releases" \ + -d "$(jq -n --arg tag "$TAG" --arg sha "$NEW_SHA" \ + '{tag_name: $tag, name: ("NixOS Installer " + $tag), body: $sha}')" \ + | jq -r '.id') + + curl -sf -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/octet-stream" \ + "$SERVER/api/v1/repos/$REPO/releases/$RELEASE_ID/assets?name=mirea-nixos-installer.iso" \ + --data-binary "@$ISO" + + echo "Published release $TAG (sha256: $NEW_SHA)" diff --git a/configuration.nix b/configuration.nix index 841ef65..b0e8ab8 100644 --- a/configuration.nix +++ b/configuration.nix @@ -1,33 +1,67 @@ { + lib, config, pkgs, - simintech, - stm32cubemx, inputs, ... }: let serverIP = "10.0.174.12"; + buildNodeYggIP = "200:ccc6:1361:5c84:aba0:1244:35f5:c903"; hasBootPartition = config.fileSystems ? "/boot"; - isNvidia = (builtins.readFile (pkgs.runCommandLocal "isNvidia" {} '' - ${pkgs.pciutils}/bin/lspci | ${pkgs.busybox}/bin/grep NVIDIA | ${pkgs.busybox}/bin/grep VGA > $out - '')) != ""; + isNvidia = + (builtins.readFile ( + pkgs.runCommandLocal "isNvidia" { } '' + ${pkgs.pciutils}/bin/lspci | ${pkgs.busybox}/bin/grep NVIDIA | ${pkgs.busybox}/bin/grep VGA > $out || true + '' + )) != ""; + isAdata = # ADATA is a famous cheap troubleful NVMe controllers + (builtins.readFile ( + pkgs.runCommandLocal "isAdata" { } '' + ${pkgs.pciutils}/bin/lspci | ${pkgs.busybox}/bin/grep ADATA | ${pkgs.busybox}/bin/grep -i NVMe > $out || true + '' + )) != ""; in { + nixpkgs.overlays = [ + (final: prev: { + staruml = prev.staruml.overrideAttrs (old: { + src = final.fetchurl { + url = "http://${serverIP}/StarUML_6.2.2_amd64.deb"; + hash = "sha256-1zxrT7phXeQYNbWHWMyPuHiUglrPSMPP0bfAcfvt8dM="; + }; + }); + stm32cubemx = prev.stm32cubemx.override { + fetchzip = args: prev.fetchzip (args // { + url = "http://${serverIP}/stm32cube_mx_v6111-lin.zip"; + hash = "sha256-By9T43GLM1J63TkRi3kl05h1RflBorU1QHgYOrXQ9N0="; + }); + buildFHSEnv = args: prev.buildFHSEnv ( args // { + unshareNet = true; + }); + }; + }) + ]; users.users.student = { isNormalUser = true; initialPassword = "student"; # вход беспарольный, но пароль student - extraGroups = [ "video" "sound" "input" "storage" ]; + extraGroups = [ + "video" + "sound" + "input" + "storage" + "dialout" + ]; packages = let # определение как скачивать плагины jetbrains (скачивать с нашего сервера копию) - customJBPlugin = + customJBPlugin = nam: ver: sha: pkgs.stdenv.mkDerivation { name = nam; version = ver; src = pkgs.fetchurl { - url = "http://kafpi.local/custom-jetbrains-plugins/${nam}-${ver}.zip"; + url = "http://${serverIP}/custom-jetbrains-plugins/${nam}-${ver}.zip"; sha256 = sha; }; nativeBuildInputs = with pkgs; [ unzip ]; @@ -43,20 +77,20 @@ in # и Database Nagivator. Остальное - любителям и любознательным. (with inputs.nix-jetbrains-plugins.plugins."${system}"; [ idea-community."2024.1"."mobi.hsz.idea.gitignore" - idea-community."2024.1"."org.jetbrains.erlang" + #idea-community."2024.1"."org.jetbrains.erlang" idea-community."2024.1"."JProfiler" idea-community."2024.1"."DBN" idea-community."2024.1"."MatlabSupport" idea-community."2024.1"."PlantUML integration" idea-community."2024.1"."org.intellij.scala" idea-community."2024.1"."PythonCore" - idea-community."2024.1"."net.sjrx.intellij.plugins.systemdunitfiles" + #idea-community."2024.1"."net.sjrx.intellij.plugins.systemdunitfiles" idea-community."2024.1"."com.intellij.jsonpath" idea-community."2024.1"."Lombook Plugin" idea-community."2024.1"."org.mapstruct.intellij" idea-community."2024.1"."ski.chrzanow.foldableprojectview" idea-community."2024.1"."String Manipulation" - idea-community."2024.1"."quokka.js" + #idea-community."2024.1"."quokka.js" idea-community."2024.1"."org.asciidoctor.intellij.asciidoc" idea-community."2024.1"."R4Intellij" idea-community."2024.1"."com.redhat.devtools.lsp4ij" @@ -73,21 +107,33 @@ in )) chromium # браузер - pinta # графический редактор - sakura # терминал + pinta # графический редактор + sakura # терминал unzipNLS # поддержка zip - unrar # поддержка rar - gtk3 whitesur-icon-theme # необходимо для иконок - (pkgs.writeShellScriptBin "powermenu" (builtins.readFile inputs.mireadesktop.packages.x86_64-linux.powermenu)) - (pkgs.writeShellScriptBin "resethome" (builtins.readFile inputs.mireadesktop.packages.x86_64-linux.resethome)) + unrar # поддержка rar + mate.engrampa + gtk3 + whitesur-icon-theme # необходимо для иконок + (pkgs.writeShellScriptBin "powermenu" ( + builtins.readFile inputs.mireadesktop.packages.x86_64-linux.powermenu + )) + (pkgs.writeShellScriptBin "resethome" ( + builtins.readFile inputs.mireadesktop.packages.x86_64-linux.resethome + )) + + inputs.kompas3d.packages.${pkgs.system}.default # KOMPAS-3D + freecad elmerfem gmsh # FEM calculation + + #inputs.stm32cubemx.packages.x86_64-linux.stm32cubemx + stm32cubemx pandoc - #onlyoffice-desktopeditors # к сожалению, пока у OnlyOffice баг со шрифтами. Ждём фикс - libreoffice # поэтому пока что у нас Libreoffice... + onlyoffice-desktopeditors # к сожалению, пока у OnlyOffice баг со шрифтами. Ждём фикс + #libreoffice # поэтому пока что у нас Libreoffice... # Напоминаю что у нас есть СЕТЕВОЙ ДИСК по адресу 10.0.174.12 # и для студентов пока есть единая учётка student@mirea.ru, - # рассматривается вариант авторизации через login.mirea.ru. + # рассматривается вариант авторизации через sso.mirea.ru. # Преподаватели могут зарегистрироваться ОТДЕЛЬНО seafile-client @@ -100,16 +146,17 @@ in # поставка Python, полный список возможностей # см. на github.com/gregorybednov/mireapython - inputs.mireapython.packages.x86_64-linux.mireapython + inputs.mireapython.packages.x86_64-linux.mireapython # базовая поддержка C/C++ и дебага clang clang-tools lldb + valgrind cpplint cppcheck gcc-arm-embedded # компиляция С/C++ под STM32 и прочие arm - + octaveFull # GNU Octave shellcheck # проверка шелл-кода (bash, POSIX sh, ...) @@ -117,9 +164,10 @@ in # Другие возможные языки и инструменты, например: # rustc # - Rust # go gopls delve golint go-tools # - Go - jdk kotlin # - Java, Kotlin (идут вместе с IJ IDEA) + jdk + kotlin # - Java, Kotlin (идут вместе с IJ IDEA) # ghc haskell-language-server # - Haskell - # nodePackages.intelephense # - PHP + # nodePackages.intelephense # - PHP # dotnet-sdk # - C# ####### Проектирование и разработка баз данных, ИУС, ... ####### @@ -127,47 +175,449 @@ in # Workbench для управления и ER-моделирования БД на mysql # у КАЖДОГО nixos есть свой локальный сервер mysql, см. ниже по файлу mysql-workbench - + # аналог Bizagi Modeler, расширенный вариант bpmn.io camunda-modeler # графическое моделирование UML-диаграмм - # также доступно моделирование BPMN и ER - staruml + # также доступно моделирование BPMN и ER + (staruml.overrideAttrs (oldAttrs: { + nativeBuildInputs = (oldAttrs.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ]; + + postFixup = + (oldAttrs.postFixup or "") + + '' + # Rename the original binary + mv $out/bin/staruml $out/bin/.staruml-org + + # Create the wrapper script + makeWrapper ${pkgs.bash}/bin/bash $out/bin/staruml \ + --add-flags "-c 'rm -rf \$HOME/.config/StarUML && exec $out/bin/.staruml-org \"\$@\"'" + ''; + })) # Язык текстового описания UML-диаграмм # актуально для разработчиков ПО (встраивание диаграмм в код) # поддерживается нашей поставкой IJ IDEA CE plantuml - - archi # поддержка archimate + graphviz + archi # поддержка archimate ##### Утилиты и программы для STM32 ##### # 1) STM32CubeIDE упаковать не удалось # 2) есть основания полагать, что её функциональность лучше встроить # в IJ IDEA CE, как это сделано в настоящем Clion; - stm32cubemx.packages.x86_64-linux.stm32cubemx - stm32flash stlink stlink-gui stm32loader + openocd - ##### СЕТЕВЫЕ УТИЛИТЫ ####### + ##### СЕТЕВЫЕ УТИЛИТЫ ###### + # установлен Cisco Packet Tracer, обернут в firejail чтобы не стучался на Netacad.com nodePackages.node-red # - лоукод-платформа программирования устройств, в частности интернета вещей - httpie httpie-desktop # - передовой клиент HTTP-запросов - netcat socat # - низкоуровневые простейшие утилиты установления TCP или UDP между компьютерами или с ПЛК - opcua-client-gui # - простой графический клиент OPC UA + httpie + httpie-desktop # - передовой клиент HTTP-запросов + netcat + socat # - низкоуровневые простейшие утилиты установления TCP или UDP между компьютерами или с ПЛК + opcua-client-gui # - простой графический клиент OPC UA ###### Другой софт ####### logisim-evolution unityhub # UnityHub - 3D-моделирование, визуализация, геймдев, AR/VR + (pkgs.stdenv.mkDerivation rec { + pname = "dwsim"; + version = "9.0.5"; + + src = pkgs.fetchurl { + url = "http://${serverIP}/dwsim_9.0.5-amd64.deb"; + sha256 = "sha256:0352mxz9czfh52z4lf7njlyqvikvbh2bgs2h4zi2dsjrssql3h2j"; + }; + + nativeBuildInputs = [ + pkgs.dpkg + pkgs.makeWrapper + pkgs.bubblewrap + ]; + + buildInputs = ( + with pkgs; + [ + dotnet-runtime + gtk3 + glib + pango + cairo + gdk-pixbuf + atk + fontconfig + freetype + ] + ++ (with pkgs.xorg; [ + libX11 + libXcursor + libXrandr + libXinerama + libXi + ]) + ); + + unpackPhase = '' + dpkg-deb -x $src . + ''; + + installPhase = '' + mkdir -p $out/lib/dwsim + mkdir -p $out/bin + + cp -r usr/local/lib/dwsim/* $out/lib/dwsim/ + mkdir -p $out/share/icons/hicolor/256x256/apps + cp $out/lib/dwsim/bitmaps/DWSIM_ico.png \ + $out/share/icons/hicolor/256x256/apps/dwsim.png + mkdir -p "$out/lib/dwsim/DWSIM Application Data" + + cat > $out/bin/dwsim << 'EOF' + #!/bin/sh + set -e + + + APPDIR="@out@/lib/dwsim" + APPDATA="$HOME/.local/share/dwsim-appdata" + + mkdir -p "$APPDATA" + + # LD_LIBRARY_PATH для GtkSharp + export LD_LIBRARY_PATH="@gtk3@/lib:@glib@/lib:@pango@/lib:@cairo@/lib:@gdk-pixbuf@/lib:@atk@/lib:@fontconfig@/lib:@freetype@/lib:$LD_LIBRARY_PATH" + + export DISPLAY="$DISPLAY" + + exec @bwrap@ \ + --bind "@out@" "@out@" \ + --bind "$APPDATA" "$APPDIR/DWSIM Application Data" \ + --ro-bind /nix /nix \ + --ro-bind "$XAUTHORITY" "$HOME/.Xauthority" \ + --setenv XAUTHORITY "$HOME/.Xauthority" \ + --dev /dev \ + --proc /proc \ + --bind /tmp/.X11-unix /tmp/.X11-unix \ + --setenv DISPLAY $DISPLAY \ + --chdir "$APPDIR" \ + @dotnet@ "$APPDIR/DWSIM.UI.Desktop.exe" "$@" + EOF + + substituteInPlace $out/bin/dwsim \ + --subst-var out \ + --subst-var-by bwrap ${pkgs.bubblewrap}/bin/bwrap \ + --subst-var-by dotnet ${pkgs.dotnet-runtime}/bin/dotnet \ + --subst-var-by gtk3 ${pkgs.gtk3} \ + --subst-var-by glib ${pkgs.glib} \ + --subst-var-by pango ${pkgs.pango} \ + --subst-var-by cairo ${pkgs.cairo} \ + --subst-var-by gdk-pixbuf ${pkgs.gdk-pixbuf} \ + --subst-var-by atk ${pkgs.atk} \ + --subst-var-by fontconfig ${pkgs.fontconfig} \ + --subst-var-by freetype ${pkgs.freetype} + + chmod +x $out/bin/dwsim + + mkdir -p $out/share/applications + + cat > $out/share/applications/dwsim.desktop << EOF + [Desktop Entry] + Type=Application + Name=DWSIM + GenericName=Process Simulator + Comment=Chemical Process Simulation Environment + Exec=dwsim + Icon=dwsim + Terminal=false + Categories=Development + StartupWMClass=DWSIM + EOF + + + ''; + }) + (pkgs.stdenv.mkDerivation rec { + pname = "cnc_vmc_sim"; + version = "1.0.0-share"; + src = builtins.fetchTarball { + url = "http://${serverIP}/linux_cnc_vmc_share.tar.xz"; + sha256 = "sha256:1hjkm2wq6dfmddb2van1q8hdvf41bp4snslcbkj0b1myi3zpi1md"; + }; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin + mkdir -p $out/share/applications + mkdir -p $out/share/icons + cp ${src}/icon.png $out/share/icons/${pname}.png + cp ${fhsEnv}/bin/${pname}-fhs $out/bin/${pname} + cp ${desktopItem}/share/applications/*.desktop $out/share/applications + ''; + + fhsEnv = pkgs.buildFHSEnv { + name = "${pname}-fhs"; + targetPkgs = + p: with p; [ + eudev.out + libGL.out + openal.out + xorg.libX11.out + ]; + runScript = "${src}/${pname}"; + }; + + desktopItem = pkgs.makeDesktopItem { + name = "CNC VMC (free)"; + exec = "${pname}"; + desktopName = "CNC VMC (free)"; + categories = [ "Development" ]; + icon = "${pname}"; + terminal = false; + startupNotify = false; + }; + }) + (pkgs.stdenv.mkDerivation rec { + pname = "ramus"; + version = "0.1.3"; + src = builtins.fetchTarball { + url = "http://${serverIP}/ramus.tar.xz"; + sha256 = "sha256:1a9fha7zwyyag0q4glk58pzigqdczshxpnax7cdzg1bry4aw6f9d"; + }; + installPhase = '' + sed -e "s~ramus.ico~$out/share/icons/ramus.ico~; s~Other~Database~" -i share/applications/ramus.desktop + mkdir $out + cp -r share bin lib $out + echo "JDK_JAVA_OPTIONS='-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel' ${pkgs.jre8}/bin/java -jar $out/bin/ramus-startup.jar" > $out/bin/ramus + chmod +x $out/bin/ramus + ''; + }) # поставка SimInTech. Кодогенерация библиотек для ПК работает, # но несовместима с .dll из windows! - simintech.packages.x86_64-linux.simintech - + #simintech.packages.x86_64-linux.simintech + (pkgs.stdenv.mkDerivation rec { + pname = "simintech"; + version = "2.23.13"; + src = builtins.fetchTarball { + url = "http://${serverIP}/simintech_rus_linux.tgz"; + sha256 = "sha256:1i9plx6vx6jm38bx4kbh4piak5jwrpc4hxxjd3b89zyng1g4abvs"; + }; + dontFixLibtool = true; + + fhsEnv = pkgs.buildFHSEnv { + name = "${pname}-fhs-env"; + targetPkgs = + p: with p; [ + at-spi2-atk.out + gdk-pixbuf.out + glamoroustoolkit.out + glib.out + gtk2.out + libGL.out + libGLU.out + pango.out + xorg.libX11.out + zlib.out + xorg.libX11.out + zlib.out + (pkgs.writeShellScriptBin "firefox" "exec ${pkgs.chromium}/bin/chromium \"$@\"") # похоже, что SimInTech захардкодил Firefox, но не все так юзают Firefox + ]; + runScript = "${src}/bin/mmain"; + }; + + desktopItem = pkgs.makeDesktopItem { + name = "SimInTech"; + exec = "simintech"; + desktopName = "SimInTech"; + categories = [ "Development" ]; + icon = "simintech"; + terminal = false; + startupNotify = false; + mimeTypes = [ "x-scheme-handler/prt" ]; + }; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin + mkdir -p $out/share/applications + mkdir -p $out/share/icons + #cp ${src}/share/icon.svg $out/share/icons/simintech.svg + cp ${fhsEnv}/bin/${pname}-fhs-env $out/bin/simintech + cp ${desktopItem}/share/applications/*.desktop $out/share/applications + runHook postInstall + ''; + }) + + (stdenv.mkDerivation { + pname = "erlu-idef0-editor"; + version = "1.0.1"; + + src = builtins.fetchGit { + url = "http://forgejo.gregorbednov.ru/gregorbednov/erlu.git"; + ref = "master"; + rev = "58198c6ecd8572f1c7db42b90c8189954c9a32fc"; + }; + nativeBuildInputs = with pkgs; [ + cmake + pkg-config + qt6.wrapQtAppsHook + qt6.qttools + ]; + + buildInputs = with pkgs; [ + qt6.qtbase + qt6.qtsvg + ]; + + configurePhase = '' + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=$out \ + -DBUILD_COLORS_PLUGIN=ON + ''; + + buildPhase = '' + cmake --build build + find . -path "*/translations/*.ts" -print0 | while IFS= read -r -d "" f; do + out_path=$(printf '%s\n' "$f" | sed 's/\.ts$/.qm/') + lrelease "$f" -qm "$out_path" + done + ''; + + installPhase = '' + runHook preInstall + cmake --install build --prefix $out + # app translations + if [ -d translations ]; then + mkdir -p $out/share/idef0/translations + find translations -maxdepth 1 -type f -name '*.qm' -print0 | xargs -0 -r cp -t $out/share/idef0/translations + fi + # plugin translations + find src/plugins -path "*/translations/*.qm" -print0 | while IFS= read -r -d "" f; do + rel="''${f#src/plugins/}" # e.g., color/translations/colors_en.qm + plugdir="''${rel%%/translations/*}" # e.g., color + dest="$out/plugins/$plugdir/translations" + mkdir -p "$dest" + cp "$f" "$dest/" + done + runHook postInstall + ''; + + qtWrapperArgs = [ "--set" "QT_LOGGING_RULES" "qt.qpa.wayland.textinput=false" ]; + meta = with lib; { + description = "IDEF0 diagram editor built with Qt 6 Widgets"; + license = licenses.lgpl3Plus; + mainProgram = "erlu_idef0_editor"; + platforms = platforms.linux; + }; + }) + + (pkgs.writeShellScriptBin "gcc" ''${pkgs.clang}/bin/clang "$@"'') + (pkgs.writeShellScriptBin "g++" ''${pkgs.clang}/bin/clang++ "$@"'') + + (vscode-with-extensions.override { + vscodeExtensions = + with vscode-extensions; + [ + ms-vscode.cpptools-extension-pack + ms-dotnettools.csharp + formulahendry.code-runner + tomoki1207.pdf + ms-vscode.hexeditor + ms-vscode.makefile-tools + ms-ceintl.vscode-language-pack-ru + ms-python.python + ms-python.pylint + ms-python.isort + ms-python.black-formatter + jebbs.plantuml + ms-toolsai.jupyter + ms-toolsai.vscode-jupyter-slideshow + ms-toolsai.vscode-jupyter-cell-tags + ms-vscode-remote.remote-ssh + timonwong.shellcheck + ] + ++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [ + { + name = "vscode-cppcheck"; + publisher = "brobeson"; + version = "0.1.0"; + sha256 = "sha256-RDgO8/XenttOcYQSGEu4N2FKg9+FZaQe6HYkVjztH5o="; + } + { + name = "cortex-debug"; + publisher = "marus25"; + version = "1.12.1"; + sha256 = "sha256-ioK6gwtkaAcfxn11lqpwhrpILSfft/byeEqoEtJIfM0="; + } + { + name = "vscode-serial-monitor"; + publisher = "ms-vscode"; + version = "0.13.251128001"; + sha256 = "sha256-eTQcLyF6DMvzDByKLw2KR8PrjVwejsOU60Hew7IOmY8="; + } + { + name = "linux-arm-none-eabi"; + publisher = "metalcode-eu"; + version = "0.1.2"; + sha256 = "sha256-MCpBsd491pCO1K9tgL5KBJQbtr4IuVEuTPQzQHeQSWQ="; + } + { + name = "arm-assembly-syntax"; + publisher = "ciprianelies"; + version = "1.7.6"; + sha256 = "sha256-4vWw0ogtZ3mOw0lT95k/5I/7GMvU5kfaaztwc+eI/do="; + } + { + name = "stm32-for-vscode"; + publisher = "bmd"; + version = "3.2.13"; + sha256 = "sha256-Dzj++F0xTDOaL9VA3X/bVtAhkxd8kPPl3Oz8LWrXpAA="; + } + { + name = "qt-cpp-pack"; + publisher = "TheQtCompany"; + version = "1.0.2"; + sha256 = "sha256-Kd2/kk3qJUOahz71Uh3mUI1wDuwS8/t84tgdWorRPG4"; + } + { + name = "debug-tracker-vscode"; + publisher = "mcu-debug"; + version = "0.0.15"; + sha256 = "sha256-2u4Moixrf94vDLBQzz57dToLbqzz7OenQL6G9BMCn3I="; + } + { + name = "memory-view"; + publisher = "mcu-debug"; + version = "0.0.28"; + sha256 = "sha256-mQr/uLulKoPVXz0GaMMEHZ/ZSmSAEfO9UpFzy4MfcW4="; + } + { + name = "rtos-views"; + publisher = "mcu-debug"; + version = "0.0.13"; + sha256 = "sha256-eTEdZkHbAGSDXP9A06YL6jDBfO8rU1kC9RysxOUxI1U="; + } + { + name = "peripheral-viewer"; + publisher = "mcu-debug"; + version = "1.6.0"; + sha256 = "sha256-nKK8HRzeqDixpdKmgacjhNzanJaTsAnYLC6nCbmWXuU="; + } + ]; + + }) + + qtcreator + qt6.full + qt6.qtbase + gdb + + isort black + + # POSIX утилиты для совместимости om4 pax @@ -180,15 +630,15 @@ in uucp util-linux cflow - ncompress + ncompress ]; }; # вставленные флешки автоматически монтируются - services.devmon.enable = true; + services.devmon.enable = true; # необходимо для совместимости с POSIX по команде at, при отсутствии необходимости можно удалить - services.atd = { + services.atd = { enable = true; allowEveryone = true; }; @@ -201,11 +651,11 @@ in services.postgresql = { enable = true; authentication = pkgs.lib.mkOverride 10 '' - #type database DBuser auth-method - local all all trust - ''; + #type database DBuser auth-method + local all all trust + ''; }; - + # и оба поддерживают вход без пароля в "руты" system.userActivationScripts.mycnf = { text = '' @@ -227,51 +677,211 @@ in }; }; - imports = [ ./hardware-configuration.nix ]; + + + systemd.user.services.stm32cubemx-repo = + let + stm32f1-v180 = builtins.fetchTarball { url = "http://${serverIP}/STM32Cube.tar.gz"; sha256 = "sha256:1zlcp82xq16anx02ghiv9xa5bi2fsz78h945miqmi26bwcjxdagn"; }; + + in { + description = "STM32CubeMX firmware repository setup"; + wantedBy = [ "default.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = pkgs.writeShellScript "stm32cubemx-repo-setup" '' + cp -r ${stm32f1-v180}/. "$HOME/STM32Cube" + chmod -R u+w "$HOME/STM32Cube" + ''; + }; + }; + services.grdcontrol.enable = true; + + imports = [ + inputs.guardant.nixosModules.grdcontrol + ./hardware-configuration.nix + ]; + programs.tmux = { + enable = true; + withUtempter = true; + historyLimit = 5000; + clock24 = true; + }; + programs.chromium = { + enable = true; + extraOpts = { + "BrowserSignin" = 0; + "SyncDisabled" = true; + "PasswordManagerEnabled" = false; + "SpellcheckEnabled" = true; + "SpellcheckLanguage" = [ + "ru" + "en-US" + "fr" + ]; + }; + }; # если машина установлена на диск с Windows, # то она позволяет "увидеть" и выбрать Windows в течение 30 с # иначе - 5 c таймаута (для виртуалок) - boot.loader = if hasBootPartition then { - efi.canTouchEfiVariables = true; - timeout = 30; - grub = { - enable = true; - efiSupport = true; - device = "nodev"; - useOSProber = true; - theme = "${pkgs.sleek-grub-theme.override{ withStyle = "orange"; withBanner = "Выберите ОС"; }}"; - }; - } else { - timeout = 5; - grub = { - enable = true; - device = "/dev/sda"; - theme = "${pkgs.sleek-grub-theme.override{ withStyle = "orange"; withBanner = "Загрузчик Linux"; }}"; - }; - }; + boot.loader = + if hasBootPartition then + { + efi.canTouchEfiVariables = true; + timeout = 30; + grub = { + enable = true; + efiSupport = true; + configurationLimit = 5; + device = "nodev"; + useOSProber = true; + theme = "${pkgs.sleek-grub-theme.override { + withStyle = "orange"; + withBanner = "Выберите ОС"; + }}"; + }; + } + else + { + timeout = 5; + grub = { + enable = true; + device = "/dev/sda"; + configurationLimit = 5; + theme = "${pkgs.sleek-grub-theme.override { + withStyle = "orange"; + withBanner = "Загрузчик Linux"; + }}"; + }; + }; time.hardwareClockInLocalTime = hasBootPartition; # настройки Nix - nixpkgs.config.allowUnfree = true; - nix.settings.auto-optimise-store = true; - nix.extraOptions = '' - experimental-features = nix-command flakes - ''; + nixpkgs.config = { + allowUnfree = true; + nvidia.acceptLicense = true; + }; + nix.settings = { + # Локальный кэш идёт первым — большинство пакетов уже собраны на build-ноде + substituters = [ + "http://nixos.builds.node" + "https://cache.nixos.org" + ]; + trusted-public-keys = [ + "nixos.builds.node:E/XNkcdzB0EyTyEJuOTXJH8qynxgCfQ87JHkbD88uF8=" + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + ]; + trusted-substituters = [ "http://nixos.builds.node" ]; + auto-optimise-store = true; + # Если build-нода временно недоступна — переходим к cache.nixos.org, не зависаем + connect-timeout = 5; + experimental-features = [ "nix-command" "flakes" ]; + # На дисках 50 GiB автоматически запускать GC при нехватке места + min-free = 2147483648; # 2 GiB — порог запуска GC + max-free = 5368709120; # 5 GiB — цель освобождения + }; + nix.gc = { + automatic = true; + dates = "weekly"; + randomizedDelaySec = "45min"; # машины не запускают GC одновременно + options = "--delete-older-than 14d"; + }; + + # Периодический TRIM для SSD (безопасен и на HDD — просто no-op) + services.fstrim.enable = true; + # nix-daemon стартует только после Yggdrasil, иначе nixos.builds.node недостижим + systemd.services.nix-daemon = { + after = [ "yggdrasil.service" ]; + wants = [ "yggdrasil.service" ]; + }; + # Уникальный hostname на основе последних 6 цифр MAC-адреса основного интерфейса. + # networking.hostName = "nixos" остаётся fallback-значением в /etc/hostname; + # сервис перекрывает его transient-именем при каждой загрузке. + systemd.services.hostname-from-mac = { + description = "Set unique hostname from primary NIC MAC address"; + wantedBy = [ "network-pre.target" ]; + before = [ "network-pre.target" ]; + after = [ "systemd-udev-settle.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + mac=$(${pkgs.iproute2}/bin/ip link show \ + | grep -v LOOPBACK \ + | grep -oE '([0-9a-f]{2}:){5}[0-9a-f]{2}' \ + | head -1 \ + | tr -d ':') + if [ -n "$mac" ]; then + ${pkgs.systemd}/bin/hostnamectl set-hostname --transient "nixos-''${mac: -6}" + fi + ''; + }; + # Директория для хранения данных о физическом расположении компьютера + systemd.tmpfiles.rules = [ + "d /var/lib/place 0755 student users -" + ]; ##### НАСТРОЙКИ ГРАФИКИ И РАБОЧЕГО СТОЛА ###### hardware.graphics = { enable = true; - enable32Bit = true; + #enable32Bit = true; + extraPackages = + if isNvidia then + with pkgs; + [ + nvidia-vaapi-driver + vaapiVdpau + libvdpau-va-gl + ] + else + [ ]; }; services.xserver = { enable = true; + videoDrivers = + if isNvidia then + [ "nvidia" ] + else + [ + "modesetting" + "fbdev" + ]; xkb.layout = "us,ru"; xkb.options = "grp:alt_shift_toggle"; }; + hardware.enableAllFirmware = true; + hardware.nvidia = { + package = config.boot.kernelPackages.nvidiaPackages.stable; + modesetting.enable = true; + powerManagement.enable = true; + open = false; + nvidiaSettings = false; + }; + + services.udev.packages = [ pkgs.stlink pkgs.openocd ]; + services.udev.extraRules = if isAdata then '' + ACTION=="add|change",KERNEL=="nvme[0-9]", ATTR{queue/scheduler}="none" + '' else ""; + boot.kernelParams = lib.optionals isNvidia [ "nvidia-drm.modeset=1" ] + ++ lib.optionals isAdata [ "nvme_core.default_ps_max_latency_us=0" "nvme_core.io_timeout=4294967295" ]; + boot.blacklistedKernelModules = lib.optionals isNvidia [ "nouveau" ]; + boot.extraModprobeConfig = + if isNvidia then + "options nvidia " + + lib.concatStringsSep " " [ + "NVreg_UsePageAttributeTable=1" + "NVreg_EnablePCIeGen3=1" + "NVreg_RegistryDwords=RMUseSwI2c=0x01;RMI2cSpeed=100" + ] + else + ""; + services.displayManager.autoLogin.user = "student"; services.getty.autologinUser = "student"; services.xserver.displayManager.lightdm.enable = true; + services.xserver.displayManager.startx.enable = true; services.displayManager.defaultSession = "MIREA-WindowsLike+Metacity"; services.xserver.displayManager.session = [ { @@ -281,6 +891,8 @@ in start = '' ${inputs.mireadesktop.packages.x86_64-linux.tint2} & ${inputs.mireadesktop.packages.x86_64-linux.pcmanfm} & + ${inputs.mireadesktop.packages.x86_64-linux.sxhkd} & + #${inputs.mireadesktop.packages.x86_64-linux.xcape} & waitPID=$! ''; } @@ -295,33 +907,24 @@ in ]; ##### СЕТЕВЫЕ НАСТРОЙКИ ####### - # каждый компьютер виден под уникальным именем через Avahi - services.avahi = { - hostName = "nixos" - + builtins.readFile ((pkgs.runCommandLocal "uuid" {} '' - mkdir $out - cat /proc/sys/kernel/random/uuid > $out/uuid - '')+"/uuid"); + services.yggdrasil = { enable = true; - nssmdns4 = true; - publish = { - enable = true; - hinfo = true; - addresses = true; + persistentKeys = true; + settings = { + Peers = [ "tcp://${serverIP}:12345" ]; }; }; # каждый компьютер имеет выход в интернет - networking.networkmanager.enable = true; - + networking.networkmanager.enable = ! config.services.harmonia.enable; networking.hostName = "nixos"; - - # каждый компьютер ресолвит DNS-запросы СТРОГО через наш сервер - networking.nameservers = [ serverIP ]; - + networking.firewall.allowedTCPPorts = [ 22 ] ++ lib.optionals config.services.harmonia.enable [ 80 ]; + + # каждый компьютер знает, что kafpi.local - это адрес нашего сервера networking.hosts."${serverIP}" = [ "kafpi.local" ]; - + networking.hosts."${buildNodeYggIP}" = [ "nixos.builds.node" ]; + #### ЛОКАЛИЗАЦИЯ ##### time.timeZone = "Europe/Moscow"; @@ -342,13 +945,63 @@ in liberation_ttf ]; - ####### ПРОЧИЕ НАСТРОЙКИ ####### environment.systemPackages = with pkgs; [ vim tree wget git + yad + # Утилита фиксации физического расположения компьютера в аудитории. + # Использование: place [АУДИТОРИЯ] (например: place А-101) + # Запись: /var/lib/place/location (JSON, доступен root по SSH) + # При запуске от root через SSH автоматически подключается к X-сессии student на :0 + (pkgs.writeShellApplication { + name = "place"; + runtimeInputs = [ yad coreutils util-linux ]; + text = '' + ROOM_ARG="''${1:-}" + PLACE_FILE="/var/lib/place/location" + + # Когда запускается от root по SSH — показать диалог на локальном X :0 + if [ -z "''${DISPLAY:-}" ]; then + export DISPLAY=":0" + export XAUTHORITY="/home/student/.Xauthority" + fi + + if ! result=$(yad \ + --title="Место компьютера" \ + --text="Зафиксируйте расположение этого компьютера в аудитории.\nДанные сохраняются в /var/lib/place/location." \ + --form \ + --field="Аудитория" \ + --field="Ряд:NUM" \ + --field="Место:NUM" \ + --button="Сохранить:0" \ + --button="Отмена:1" \ + --width=340 \ + -- "''${ROOM_ARG}" 1 1 \ + 2>/dev/null); then + exit 0 # Нажата «Отмена» — без ошибки + fi + + room=$(printf '%s' "''${result}" | cut -d'|' -f1) + row=$(printf '%s' "''${result}" | cut -d'|' -f2 | cut -d'.' -f1) + seat=$(printf '%s' "''${result}" | cut -d'|' -f3 | cut -d'.' -f1) + + if [ -z "''${room}" ]; then + echo "Ошибка: поле «Аудитория» не заполнено" >&2 + exit 1 + fi + + mkdir -p "$(dirname "''${PLACE_FILE}")" + printf '{"room":"%s","row":%s,"seat":%s,"host":"%s","at":"%s"}\n' \ + "''${room}" "''${row}" "''${seat}" "$(hostname)" "$(date -Iseconds)" \ + > "''${PLACE_FILE}" + + echo "Сохранено: аудитория ''${room}, ряд ''${row}, место ''${seat}" + echo "Файл: ''${PLACE_FILE}" + ''; + }) ]; # удаленный доступ в пределах нашей сети @@ -356,23 +1009,27 @@ in enable = true; settings.PermitRootLogin = "yes"; }; + users.users.root.openssh.authorizedKeys.keys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCyBYo/E/FkFZVABzMixLS2TWaipfN5T24y8f+E6Px1t+IG8PLnQ38dLJiCR8k971DOycLuJUfKWsC06BK3XLWTO0+PmpfGKNT4NI6dwP2REl/umaignP/QQSs2w9Ff49WqPjIYTSmATTsCNZSVB0VtM0eJ+Y9Ff4CXb1frtt4GYztk6XB3jc3TxV72qzB0g6DqrHkf6pT5YAq2UeuFGZYSZCqBvVXCGcvKHkO1KBubuo95itVA5XbzK3INQTZpQowbtK4ULhUYlaGBcX5tYq1bdiTCDlTcLt6MfxYfFHSFiHbJOzdGPd+mXM7urOQhq49uQOf07dHt9qAzQHajItQb+X3FOgyFt4n6Y9Q37gn/6KC3PH1zClldq9DtgttuG/Xk15q+uvCldji9YIgb80aRHBIp6DY8PlYodmGGesBLiBMGQ7hgKupfkqszjfMkxnMYIkZodUCQVgaqsxsEQ7lD84JJMgBY8HkNLxKhy+6dP6kTc4vTtrpjezq/Ph95PFE= bednov@kafpi-108-1-mainserver2" ]; services.gnome.gnome-keyring.enable = true; programs = { + firejail = { + enable = true; + wrappedBinaries = { + packettracer7 = { + executable = "${inputs.packettracer7.packages.x86_64-linux.default}/bin/packettracer7"; + extraArgs = [ + "--net=none" + "--noprofile" + ]; + }; + }; + }; java.enable = true; gnupg.agent = { enable = true; enableSSHSupport = true; }; - chromium = { - enable = true; - extraOpts = { - "SyncDisabled" = true; - "PasswordManagerEnabled" = false; - "SpellcheckEnabled" = false; - "homepageLocation" = "https://ya.ru"; - }; - }; udevil.enable = true; # тоже нужно для флешек }; diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..df914b8 --- /dev/null +++ b/flake.lock @@ -0,0 +1,303 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": [ + "nix-jetbrains-plugins", + "systems" + ] + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "gostfont": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1736170321, + "narHash": "sha256-/dY+z0JBdFvyJy4fuIidNpEGKQPR+JB1Lc6PbNoyvvs=", + "ref": "refs/heads/main", + "rev": "4f11a7fdd2b0a390f98c8764e8f6830f75201096", + "revCount": 6, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/gostfont.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/gostfont.git" + } + }, + "guardant": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1771271444, + "narHash": "sha256-tN8hBh6zr7xT0lYWLiJLLDXVNAjdjxoklStbJbJoZl4=", + "ref": "refs/heads/master", + "rev": "e20554b30029b6d84f04cdf7fb226aff3be7e5d5", + "revCount": 1, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/guardant.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/guardant.git" + } + }, + "kompas3d": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1771306148, + "narHash": "sha256-6waxhcHMzmReOp5KUCAkycTR4YHgPCKF/C+3r5lCGkQ=", + "ref": "refs/heads/master", + "rev": "6e1860b78382cbd42f43663b459487e457abd251", + "revCount": 13, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/kompas3d.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/kompas3d.git" + } + }, + "mireadesktop": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772927247, + "narHash": "sha256-qU9kHhp99W2ymeoZsC7L29BKUAonxw8ckFa0ratpx1s=", + "ref": "refs/heads/main", + "rev": "a19e2f8ac520ef2be55d0c0472aab125a4d3a357", + "revCount": 45, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/mireadesktop.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/mireadesktop.git" + } + }, + "mireapython": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1737741645, + "narHash": "sha256-MaT05qTfUSHsoWgWJqv33LRie7spTz0b6FReJWDV4yk=", + "ref": "refs/heads/main", + "rev": "b7172d602ff62aa02d68bd9852168bd3c32aa726", + "revCount": 12, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/mireapython.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/mireapython.git" + } + }, + "nix-jetbrains-plugins": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1740136774, + "narHash": "sha256-LJ6bXznu20nuRHe1D0sPmKozuS1wXeJvrDscCHRU85w=", + "ref": "refs/heads/main", + "rev": "c1b6f1b23bd29d520ac3f46fd596564ac6e84e2a", + "revCount": 44, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/nix-jetbrains-plugins.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/nix-jetbrains-plugins.git" + } + }, + "nix-onlyoffice": { + "locked": { + "lastModified": 1785830074, + "narHash": "sha256-pMPYdCZGAZritK9OO4WGsjfNB3DLB6CnP0vRBYAsnCo=", + "owner": "JakeHPark", + "repo": "nix-onlyoffice", + "rev": "6987ae3b498f2cf8a0f78b921c63eb44cf5852e6", + "type": "github" + }, + "original": { + "owner": "JakeHPark", + "repo": "nix-onlyoffice", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1745742390, + "narHash": "sha256-1rqa/XPSJqJg21BKWjzJZC7yU0l/YTVtjRi0RJmipus=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "26245db0cb552047418cfcef9a25da91b222d6c7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "packettracer7": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1740143112, + "narHash": "sha256-sawn/IVgT++IzbCsy/EhJ+w3+s2bxUF9jZ0kIPIXmcE=", + "ref": "refs/heads/main", + "rev": "eb673a5d201d9c32e8fc642e47b9c66ed6130379", + "revCount": 2, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/packettracer7.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/packettracer7.git" + } + }, + "root": { + "inputs": { + "gostfont": "gostfont", + "guardant": "guardant", + "kompas3d": "kompas3d", + "mireadesktop": "mireadesktop", + "mireapython": "mireapython", + "nix-jetbrains-plugins": "nix-jetbrains-plugins", + "nix-onlyoffice": "nix-onlyoffice", + "nixpkgs": "nixpkgs", + "packettracer7": "packettracer7", + "simintech": "simintech", + "stm32cubemx": "stm32cubemx" + } + }, + "simintech": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1736199151, + "narHash": "sha256-maK9G10f/AD/BS3kZP3h3ADhLzYh5qXBUj2HXEr7t4s=", + "ref": "refs/heads/main", + "rev": "3ce193218e3f5b8cc5b5a212be550785d8e64e86", + "revCount": 10, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/simintech_nix.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/simintech_nix.git" + } + }, + "stm32cubemx": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1735908219, + "narHash": "sha256-5VRV0ThLNWpCet8+Zpk9LqJnjJXQKI2pfak/I0Cxhvs=", + "ref": "refs/heads/main", + "rev": "d1ebff9193ed7be660e14167f8fb1625be8df4f0", + "revCount": 31, + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/stm32cubemx.git" + }, + "original": { + "type": "git", + "url": "https://forgejo.gregorbednov.ru/gregorbednov/stm32cubemx.git" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 5c9ccf8..3f3eb8f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,54 @@ { inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; - inputs.simintech.url = "github:gregorybednov/simintech_nix"; # /94cb0192edb8f42e21362dae2c3712a4cfa7d71e; - inputs.mireadesktop.url = "github:gregorybednov/mireadesktop"; - inputs.stm32cubemx.url = "github:gregorybednov/stm32cubemx"; - inputs.gostfont.url = "github:gregorybednov/gostfont"; - inputs.nix-jetbrains-plugins.url = "github:gregorybednov/nix-jetbrains-plugins"; - inputs.mireapython.url = "github:gregorybednov/mireapython"; + inputs.simintech = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/simintech_nix.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.mireadesktop = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/mireadesktop.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.stm32cubemx = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/stm32cubemx.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.packettracer7 = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/packettracer7.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.gostfont = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/gostfont.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.nix-jetbrains-plugins = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/nix-jetbrains-plugins.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.mireapython = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/mireapython.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.kompas3d = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/kompas3d.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.guardant = { + url = "git+https://forgejo.gregorbednov.ru/gregorbednov/guardant.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + inputs.nix-onlyoffice = { + url = "github:JakeHPark/nix-onlyoffice"; + inputs.nixpkgs.follows = "nixpkgs"; + }; outputs = { @@ -13,16 +56,17 @@ nixpkgs, simintech, stm32cubemx, + packettracer7, + guardant, + nix-onlyoffice, + kompas3d, ... }@inputs: { nixosConfigurations.nixos = nixpkgs.lib.nixosSystem { - specialArgs = { - inherit simintech; - inherit stm32cubemx; - inherit inputs; - }; + specialArgs = { inherit inputs; }; modules = [ + nix-onlyoffice.nixosModules.onlyoffice ./configuration.nix ]; }; diff --git a/installer/flake.lock b/installer/flake.lock new file mode 100644 index 0000000..fe08f56 --- /dev/null +++ b/installer/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/installer/flake.nix b/installer/flake.nix new file mode 100644 index 0000000..e2e0d01 --- /dev/null +++ b/installer/flake.nix @@ -0,0 +1,22 @@ +{ + description = "MIREA NixOS USB Installer"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + in + { + nixosConfigurations.installer = nixpkgs.lib.nixosSystem { + inherit system; + modules = [ + "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" + ./installer-configuration.nix + ]; + }; + + packages.${system}.default = + self.nixosConfigurations.installer.config.system.build.isoImage; + }; +} diff --git a/installer/installer-configuration.nix b/installer/installer-configuration.nix new file mode 100644 index 0000000..1836847 --- /dev/null +++ b/installer/installer-configuration.nix @@ -0,0 +1,74 @@ +{ pkgs, lib, config, ... }: +let + serverIP = "10.0.174.12"; + buildNodeYggIP = "200:ccc6:1361:5c84:aba0:1244:35f5:c903"; + configRepoUrl = "https://forgejo.gregorbednov.ru/gregorbednov/configuration.nix.git"; +in +{ + isoImage.isoName = lib.mkForce "mirea-nixos-installer.iso"; + isoImage.squashfsCompression = "zstd -Xcompression-level 6"; + + services.getty.autologinUser = lib.mkForce "root"; + + environment.systemPackages = with pkgs; [ + parted + gptfdisk + util-linux + dosfstools + e2fsprogs + git + pv + curl + (writeShellScriptBin "mirea-install" ( + builtins.replaceStrings + [ "@CONFIG_REPO_URL@" ] + [ configRepoUrl ] + (builtins.readFile ./mirea-install.sh) + )) + ]; + + # Yggdrasil запускается на установщике — благодаря ему доступен локальный кэш сборок + services.yggdrasil = { + enable = true; + persistentKeys = false; + settings.Peers = [ "tcp://${serverIP}:12345" ]; + }; + + networking.networkmanager.enable = true; + networking.wireless.enable = lib.mkForce false; + networking.hosts."${buildNodeYggIP}" = [ "nixos.builds.node" ]; + + nix.settings = { + substituters = [ + "http://nixos.builds.node" + "https://cache.nixos.org" + ]; + trusted-public-keys = [ + "nixos.builds.node:E/XNkcdzB0EyTyEJuOTXJH8qynxgCfQ87JHkbD88uF8=" + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + ]; + trusted-substituters = [ "http://nixos.builds.node" ]; + connect-timeout = 5; + experimental-features = [ "nix-command" "flakes" ]; + }; + + systemd.services.nix-daemon = { + after = [ "yggdrasil.service" ]; + wants = [ "yggdrasil.service" ]; + }; + + i18n.defaultLocale = "ru_RU.UTF-8"; + time.timeZone = "Europe/Moscow"; + console.font = "cyr-sun16"; + + environment.etc."issue".text = '' + + ┌─────────────────────────────────────────┐ + │ Установщик NixOS MIREA │ + │ Запустите: mirea-install │ + └─────────────────────────────────────────┘ + + ''; + + system.stateVersion = "24.11"; +} diff --git a/installer/mirea-install.sh b/installer/mirea-install.sh new file mode 100644 index 0000000..00d8e46 --- /dev/null +++ b/installer/mirea-install.sh @@ -0,0 +1,366 @@ +#!/usr/bin/env bash +# MIREA NixOS Installer +# Устанавливает NixOS рядом с Windows или на чистый диск. +# NixOS root: 50 GiB, EFI (при чистой установке): 512 MiB. +# Идемпотентен: если установка прервалась, просто запустите повторно. +set -euo pipefail + +CONFIG_REPO_URL="@CONFIG_REPO_URL@" +NIXOS_SIZE_GIB=50 +EFI_SIZE_MIB=512 +MNT=/mnt + +R='\033[0;31m' G='\033[0;32m' Y='\033[1;33m' B='\033[0;34m' N='\033[0m' +info() { echo -e "${G}[i]${N} $*"; } +warn() { echo -e "${Y}[!]${N} $*"; } +error() { echo -e "${R}[x]${N} $*" >&2; exit 1; } +step() { echo -e "\n${B}══${N} $* ${B}══${N}"; } +skip() { echo -e "${Y}[→]${N} уже выполнено: $* — пропуск"; } + +# ── Проверки ────────────────────────────────────────────────────────────────── + +[[ $EUID -eq 0 ]] || error "Запустите от root" +for cmd in sgdisk parted mkfs.fat mkfs.ext4 blkid findmnt; do + command -v "$cmd" &>/dev/null || error "$cmd не найден" +done + +# ── Утилиты ─────────────────────────────────────────────────────────────────── + +# Размер наибольшего свободного блока на диске в ГиБ +get_free_gib() { + local disk="$1" + local pttype + pttype=$(lsblk -d -o PTTYPE -n "$disk" 2>/dev/null | tr -d ' ') + if [[ "$pttype" == "gpt" ]]; then + local sector_size free_first free_last + sector_size=$(cat "/sys/block/$(basename "$disk")/queue/logical_block_size" 2>/dev/null || echo 512) + free_first=$(sgdisk -f "$disk" 2>/dev/null || echo 0) + free_last=$(sgdisk -E "$disk" 2>/dev/null || echo 0) + if (( free_last > free_first )); then + echo $(( (free_last - free_first + 1) * sector_size / 1024 / 1024 / 1024 )) + else + echo 0 + fi + else + lsblk -d -b -o SIZE -n "$disk" 2>/dev/null | awk '{print int($1/1024/1024/1024)}' + fi +} + +blkid_attr() { blkid -s "$1" -o value "$2" 2>/dev/null || true; } + +# ── Выбор диска ─────────────────────────────────────────────────────────────── + +step "Выбор диска" +echo + +mapfile -t DISK_NAMES < <(lsblk -d -o NAME -n | grep -Ev '^(loop|sr|fd|ram)') +(( ${#DISK_NAMES[@]} > 0 )) || error "Блочные устройства не найдены" + +DEFAULT_NUM=0 +declare -a DISK_FREE DISK_STATUS + +for i in "${!DISK_NAMES[@]}"; do + dev="/dev/${DISK_NAMES[$i]}" + free_gib=$(get_free_gib "$dev") + DISK_FREE[$i]=$free_gib + # Диск с существующим разделом NixOS — продолжение прерванной установки + if lsblk -r -o PARTLABEL -n "$dev" 2>/dev/null | grep -q "^NixOS$"; then + DISK_STATUS[$i]="→продолж." + [[ $DEFAULT_NUM -eq 0 ]] && DEFAULT_NUM=$(( i + 1 )) + else + DISK_STATUS[$i]="${free_gib} ГиБ" + if [[ $DEFAULT_NUM -eq 0 ]] && (( free_gib >= NIXOS_SIZE_GIB )); then + DEFAULT_NUM=$(( i + 1 )) + fi + fi +done + +printf " %-4s %-14s %-8s %-12s %s\n" "№" "Диск" "Размер" "Свободно" "Модель" +printf " %-4s %-14s %-8s %-12s %s\n" "─" "──────────────" "──────" "────────────" "──────" +for i in "${!DISK_NAMES[@]}"; do + dev="/dev/${DISK_NAMES[$i]}" + size=$(lsblk -d -o SIZE -n "$dev" 2>/dev/null | tr -d ' ') + model=$(lsblk -d -o MODEL -n "$dev" 2>/dev/null | sed 's/[[:space:]]*$//') + num=$(( i + 1 )) + if [[ $num -eq $DEFAULT_NUM ]]; then + printf "${G} [%s] %-14s %-8s %-12s %s ← рекомендуется${N}\n" \ + "$num" "$dev" "$size" "${DISK_STATUS[$i]}" "$model" + else + printf " [%s] %-14s %-8s %-12s %s\n" "$num" "$dev" "$size" "${DISK_STATUS[$i]}" "$model" + fi +done +echo + +while true; do + if [[ $DEFAULT_NUM -gt 0 ]]; then + read -rp "Выберите номер диска [${DEFAULT_NUM}]: " choice + [[ -z "$choice" ]] && choice=$DEFAULT_NUM + else + read -rp "Выберите номер диска: " choice + fi + if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#DISK_NAMES[@]} )); then + DISK="/dev/${DISK_NAMES[$(( choice - 1 ))]}" + break + fi + warn "Введите число от 1 до ${#DISK_NAMES[@]}" +done +info "Выбран диск: $DISK" + +# ── Определение наличия Windows ─────────────────────────────────────────────── + +step "Проверка наличия Windows" +HAS_WINDOWS=false +EFI_PART="" +NEED_NEW_EFI=false + +pttype=$(lsblk -d -o PTTYPE -n "$DISK" 2>/dev/null | tr -d ' ') +if [[ "$pttype" == "gpt" ]]; then + while IFS= read -r pdev; do + tmpdir=$(mktemp -d) + if mount -t vfat "$pdev" "$tmpdir" 2>/dev/null; then + if [[ -d "$tmpdir/EFI/Microsoft" ]]; then + HAS_WINDOWS=true + EFI_PART="$pdev" + info "Windows обнаружена, EFI раздел: $EFI_PART" + fi + umount "$tmpdir" + fi + rmdir "$tmpdir" + done < <(lsblk -r -o NAME,PARTTYPE -n "$DISK" 2>/dev/null \ + | awk '/c12a7328-f81f-11d2-ba4b-00a0c93ec93b/{print "/dev/"$1}') +fi +$HAS_WINDOWS || info "Windows не обнаружена" + +# ── Определение существующих разделов (идемпотентность) ─────────────────────── + +NIXOS_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "NixOS" {print "/dev/"$1}' | tail -1) +ALREADY_PARTITIONED=false + +if [[ -b "$NIXOS_PART" ]]; then + ALREADY_PARTITIONED=true + info "Найден существующий раздел NixOS: $NIXOS_PART — разметка пропускается" + + # Ищем EFI по меткам + nixos_efi=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "NixOS-EFI" {print "/dev/"$1}' | tail -1) + if [[ -b "$nixos_efi" ]]; then + # Ранее создан отдельный EFI для NixOS (Windows EFI был мал) + EFI_PART="$nixos_efi" + NEED_NEW_EFI=true + elif [[ -z "$EFI_PART" ]]; then + # Чистая установка: ищем раздел с меткой EFI + efi_clean=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "EFI" {print "/dev/"$1}' | tail -1) + [[ -b "$efi_clean" ]] && EFI_PART="$efi_clean" + fi + # Иначе EFI_PART уже содержит Windows EFI из предыдущего блока +fi + +# ── Разметка ────────────────────────────────────────────────────────────────── + +if ! $ALREADY_PARTITIONED; then + if $HAS_WINDOWS; then + step "Двойная загрузка: проверка EFI раздела" + + efi_size_b=$(lsblk -b -d -o SIZE -n "$EFI_PART" 2>/dev/null | tr -d ' ') + efi_size_mib=$(( efi_size_b / 1024 / 1024 )) + info "Размер EFI ($EFI_PART): ${efi_size_mib} MiB" + + if (( efi_size_mib < 256 )); then + warn "EFI раздел слишком мал — будет создан отдельный NixOS EFI 500 MiB." + NEED_NEW_EFI=true + else + info "EFI достаточного размера, будет использован совместно с Windows." + fi + + step "Поиск свободного места" + sector_size=$(cat "/sys/block/$(basename "$DISK")/queue/logical_block_size") + free_first=$(sgdisk -f "$DISK" 2>/dev/null) + free_last=$(sgdisk -E "$DISK" 2>/dev/null) + free_bytes=$(( (free_last - free_first + 1) * sector_size )) + free_gib=$(( free_bytes / 1024 / 1024 / 1024 )) + + if $NEED_NEW_EFI; then + required_bytes=$(( (500 * 1024 * 1024) + (NIXOS_SIZE_GIB * 1024 * 1024 * 1024) )) + required_label="${NIXOS_SIZE_GIB} ГиБ + 500 MiB EFI" + else + required_bytes=$(( NIXOS_SIZE_GIB * 1024 * 1024 * 1024 )) + required_label="${NIXOS_SIZE_GIB} ГиБ" + fi + (( free_bytes >= required_bytes )) \ + || error "Свободного места ${free_gib} ГиБ < ${required_label}. Освободите место в Windows и повторите." + info "Свободного места: ${free_gib} ГиБ" + + echo + if $NEED_NEW_EFI; then + warn "Создаётся: NixOS EFI 500 MiB + раздел NixOS ${NIXOS_SIZE_GIB} ГиБ." + warn "Windows EFI ($EFI_PART, ${efi_size_mib} MiB) не затрагивается и не используется." + else + warn "Создаётся: раздел NixOS ${NIXOS_SIZE_GIB} ГиБ." + warn "EFI Windows ($EFI_PART) будет использован совместно." + fi + warn "Данные Windows не затрагиваются." + echo + read -rp "Продолжить? (yes/no): " ans + [[ "$ans" == "yes" ]] || error "Установка отменена" + + if $NEED_NEW_EFI; then + sgdisk \ + --new=0:"${free_first}s":"+500MiB" \ + --typecode=0:ef00 \ + --change-name=0:"NixOS-EFI" \ + --new=0:0:"+${NIXOS_SIZE_GIB}GiB" \ + --typecode=0:8304 \ + --change-name=0:"NixOS" \ + "$DISK" + partprobe "$DISK"; sleep 2 + + EFI_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "NixOS-EFI" {print "/dev/"$1}' | tail -1) + else + sgdisk \ + --new=0:"${free_first}s":"+${NIXOS_SIZE_GIB}GiB" \ + --typecode=0:8304 \ + --change-name=0:"NixOS" \ + "$DISK" + partprobe "$DISK"; sleep 2 + fi + NIXOS_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "NixOS" {print "/dev/"$1}' | tail -1) + + else + step "Чистая установка" + + disk_gib=$(lsblk -d -b -o SIZE -n "$DISK" | awk '{print int($1/1024/1024/1024)}') + (( disk_gib >= NIXOS_SIZE_GIB + 1 )) \ + || error "Диск слишком мал: ${disk_gib} ГиБ (нужно минимум $((NIXOS_SIZE_GIB + 1)) ГиБ)" + + echo + warn "ВСЕ ДАННЫЕ НА ДИСКЕ $DISK (${disk_gib} ГиБ) БУДУТ УНИЧТОЖЕНЫ!" + echo + read -rp "Введите имя диска для подтверждения ($(basename "$DISK")): " ans + [[ "$ans" == "$(basename "$DISK")" ]] || error "Установка отменена" + + sgdisk --zap-all "$DISK" + sgdisk \ + --new=1:2048:"+${EFI_SIZE_MIB}MiB" \ + --typecode=1:ef00 \ + --change-name=1:"EFI" \ + --new=2:0:"+${NIXOS_SIZE_GIB}GiB" \ + --typecode=2:8304 \ + --change-name=2:"NixOS" \ + "$DISK" + partprobe "$DISK"; sleep 2 + + EFI_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "EFI" {print "/dev/"$1}' | tail -1) + NIXOS_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \ + | awk '$2 == "NixOS" {print "/dev/"$1}' | tail -1) + fi +fi + +[[ -b "$NIXOS_PART" ]] || error "Не удалось определить раздел NixOS" +[[ -b "$EFI_PART" ]] || error "Не удалось определить EFI раздел" +info "EFI раздел: $EFI_PART" +info "NixOS раздел: $NIXOS_PART" + +# ── Форматирование ──────────────────────────────────────────────────────────── + +step "Форматирование" + +if ! $HAS_WINDOWS || $NEED_NEW_EFI; then + if [[ "$(blkid_attr TYPE "$EFI_PART")" == "vfat" ]]; then + skip "EFI (FAT32)" + else + mkfs.fat -F32 -n EFI "$EFI_PART" + info "EFI отформатирован (FAT32)" + fi +else + info "EFI Windows используется как есть" +fi + +nixos_fs=$(blkid_attr TYPE "$NIXOS_PART") +nixos_label=$(blkid_attr LABEL "$NIXOS_PART") +if [[ "$nixos_fs" == "ext4" && "$nixos_label" == "nixos" ]]; then + skip "NixOS (ext4, label=nixos)" +else + mkfs.ext4 -L nixos -F "$NIXOS_PART" + info "NixOS отформатирован (ext4)" +fi + +# ── Монтирование ────────────────────────────────────────────────────────────── + +step "Монтирование" + +current_root=$(findmnt -n -o SOURCE "$MNT" 2>/dev/null || true) +if [[ "$current_root" == "$NIXOS_PART" ]]; then + skip "NixOS уже смонтирован в $MNT" +else + [[ -n "$current_root" ]] && umount -R "$MNT" + mount "$NIXOS_PART" "$MNT" +fi + +mkdir -p "$MNT/boot" +current_boot=$(findmnt -n -o SOURCE "$MNT/boot" 2>/dev/null || true) +if [[ "$current_boot" == "$EFI_PART" ]]; then + skip "EFI уже смонтирован в $MNT/boot" +else + [[ -n "$current_boot" ]] && umount "$MNT/boot" + mount "$EFI_PART" "$MNT/boot" +fi + +info "Смонтировано в $MNT" + +# ── Конфигурация ────────────────────────────────────────────────────────────── + +step "Генерация hardware-configuration.nix" +if [[ -f "$MNT/etc/nixos/hardware-configuration.nix" ]]; then + skip "hardware-configuration.nix" +else + nixos-generate-config --root "$MNT" + awk ' + /fsType = "ext4"/ { + print + print " options = [ \"noatime\" ];" + next + } + { print } + ' "$MNT/etc/nixos/hardware-configuration.nix" > /tmp/hw-patched.nix + mv /tmp/hw-patched.nix "$MNT/etc/nixos/hardware-configuration.nix" + info "noatime добавлен к ext4 разделам" +fi + +step "Загрузка конфигурации MIREA" +if [[ -f "$MNT/etc/nixos/flake.nix" ]]; then + skip "конфигурация MIREA уже загружена" +else + tmpconf=$(mktemp -d) + git clone --depth=1 "$CONFIG_REPO_URL" "$tmpconf/config" \ + || error "Не удалось клонировать $CONFIG_REPO_URL. Проверьте сетевое подключение." + cp "$tmpconf/config/flake.nix" "$MNT/etc/nixos/" + cp "$tmpconf/config/flake.lock" "$MNT/etc/nixos/" + cp "$tmpconf/config/configuration.nix" "$MNT/etc/nixos/" + rm -rf "$tmpconf" + info "Конфигурация MIREA установлена" +fi + +# ── Установка ───────────────────────────────────────────────────────────────── + +step "Установка NixOS (это займёт время)" +nixos-install \ + --root "$MNT" \ + --flake "/mnt/etc/nixos#nixos" \ + --no-root-passwd + +# ── Завершение ──────────────────────────────────────────────────────────────── + +step "Готово" +umount -R "$MNT" || true + +echo +info "NixOS установлена на $DISK" +info "Извлеките USB-носитель и перезагрузите систему" +echo +read -rp "Перезагрузить сейчас? (yes/no): " ans +[[ "$ans" == "yes" ]] && reboot || true