Add tofu goodies
This commit is contained in:
@@ -73,7 +73,8 @@ type DeviceAssignmentsConfig = {
|
||||
};
|
||||
|
||||
const getDeviceAssignmentsConfigPath = () =>
|
||||
path.join(getWorkspaceRoot(), "orchestrator", "device-assignments.json");
|
||||
process.env.QUIXOS_DEVICE_ASSIGNMENTS_FILE ??
|
||||
path.join(getWorkspaceRoot(), "packages", "device-assignments.json");
|
||||
|
||||
const loadDeviceAssignmentsConfig = (): DeviceAssignmentsConfig => {
|
||||
const filePath = getDeviceAssignmentsConfigPath();
|
||||
@@ -935,7 +936,7 @@ const assignCommand = async (args: string[]) => {
|
||||
);
|
||||
console.log("");
|
||||
}
|
||||
console.log("result: staged in orchestrator/device-assignments.json");
|
||||
console.log(`result: staged in ${shortPath(getDeviceAssignmentsConfigPath())}`);
|
||||
console.log(`note: cutover required to apply assignment${assignments.length === 1 ? "" : "s"}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,15 +12,66 @@
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, control-cli }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
(flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
qx-control = control-cli.packages.${system}.default;
|
||||
orchestratorProject = (pkgs.callPackage ./orchestrator/yarn-project.nix { }) {
|
||||
src = ./orchestrator;
|
||||
overrideAttrs = old: {
|
||||
postInstall = (old.postInstall or "") + ''
|
||||
cp --reflink=auto --recursive ${./quixos-control-plane-protocol} \
|
||||
"$out/libexec/quixos-control-plane-protocol"
|
||||
'';
|
||||
};
|
||||
};
|
||||
quixos-orchestrator = pkgs.writeShellApplication {
|
||||
name = "quixos-orchestrator";
|
||||
runtimeInputs = [
|
||||
pkgs.git
|
||||
pkgs.nix
|
||||
pkgs.nodejs_24
|
||||
orchestratorProject.passthru.yarn
|
||||
];
|
||||
text = ''
|
||||
exec ${orchestratorProject.passthru.yarn}/bin/yarn start -- "$@"
|
||||
'';
|
||||
};
|
||||
projectVisualizerWorkspaceSrc = pkgs.runCommand "quixos-project-visualizer-workspace-src" { } ''
|
||||
mkdir -p "$out"
|
||||
cp --reflink=auto --recursive ${./project-visualizer} "$out/project-visualizer"
|
||||
cp --reflink=auto --recursive ${./quixos-control-plane-protocol} "$out/quixos-control-plane-protocol"
|
||||
'';
|
||||
projectVisualizerProject = (pkgs.callPackage ./project-visualizer/yarn-project.nix { }) {
|
||||
src = "${projectVisualizerWorkspaceSrc}/project-visualizer";
|
||||
overrideAttrs = old: {
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
yarn build
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
cp --reflink=auto --recursive dist "$out"
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
};
|
||||
project-visualizer-static = projectVisualizerProject;
|
||||
in {
|
||||
packages.qx-control = qx-control;
|
||||
packages.quixos-orchestrator = quixos-orchestrator;
|
||||
packages.project-visualizer-static = project-visualizer-static;
|
||||
packages.default = quixos-orchestrator;
|
||||
|
||||
apps.qx-control = control-cli.apps.${system}.default // {
|
||||
meta.description = "Quixos control-plane CLI";
|
||||
};
|
||||
apps.quixos-orchestrator = {
|
||||
type = "app";
|
||||
program = "${quixos-orchestrator}/bin/quixos-orchestrator";
|
||||
meta.description = "Quixos orchestrator";
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
@@ -43,5 +94,7 @@
|
||||
fi
|
||||
'';
|
||||
};
|
||||
});
|
||||
})) // {
|
||||
nixosModules.quixosHost = import ./nix/nixos/quixos-host.nix { inherit self; };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
{ self }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.quixosHost;
|
||||
quixosPackages = self.packages.${pkgs.system};
|
||||
in
|
||||
{
|
||||
options.services.quixosHost = {
|
||||
enable = lib.mkEnableOption "Quixos host";
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Public domain served by Caddy.";
|
||||
};
|
||||
|
||||
packagesPath = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/persist/srv/quixos/packages";
|
||||
description = "Mutable package-tree checkout path.";
|
||||
};
|
||||
|
||||
dataRoot = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/persist/var/lib/quixos";
|
||||
description = "Persistent Quixos runtime state root.";
|
||||
};
|
||||
|
||||
secretsEnvironmentFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/persist/secrets/quixos.env";
|
||||
description = "Environment file containing Quixos secrets.";
|
||||
};
|
||||
|
||||
packageTreeRepo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://gitea-external.egads.tutti.syntaxblitz.net/quixos-packages/packages.git";
|
||||
description = "Git URL for the mutable package-tree super-repo.";
|
||||
};
|
||||
|
||||
packageTreeRef = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "master";
|
||||
description = "Package-tree ref to clone on first boot.";
|
||||
};
|
||||
|
||||
forcePackageTreeRefOnBoot = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether each boot should force the package tree back to packageTreeRef.";
|
||||
};
|
||||
|
||||
persistDevice = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Persistent block device path mounted at /persist.";
|
||||
};
|
||||
|
||||
zfsPool = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "quixos-persist";
|
||||
description = "ZFS pool name on the persistent block device.";
|
||||
};
|
||||
|
||||
hostId = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "71756978";
|
||||
description = "Eight hex digit hostId required by ZFS.";
|
||||
};
|
||||
|
||||
enableSsh = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable inbound SSH in the NixOS host config. AWS ingress is managed by OpenTofu.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
networking.hostId = cfg.hostId;
|
||||
boot.supportedFilesystems = [ "zfs" ];
|
||||
boot.zfs.extraPools = [ cfg.zfsPool ];
|
||||
services.zfs.autoScrub = {
|
||||
enable = true;
|
||||
pools = [ cfg.zfsPool ];
|
||||
};
|
||||
|
||||
fileSystems."/nix" = {
|
||||
device = "${cfg.zfsPool}/nix";
|
||||
fsType = "zfs";
|
||||
neededForBoot = true;
|
||||
};
|
||||
|
||||
fileSystems."/persist" = {
|
||||
device = "${cfg.zfsPool}/persist";
|
||||
fsType = "zfs";
|
||||
neededForBoot = true;
|
||||
};
|
||||
|
||||
users.groups.quixos = {};
|
||||
users.users.quixos = {
|
||||
isSystemUser = true;
|
||||
group = "quixos";
|
||||
home = cfg.dataRoot;
|
||||
createHome = true;
|
||||
};
|
||||
users.users.caddy.extraGroups = [ "quixos" ];
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.git
|
||||
pkgs.nix
|
||||
pkgs.amazon-ssm-agent
|
||||
quixosPackages.qx-control
|
||||
];
|
||||
|
||||
services.amazon-ssm-agent.enable = true;
|
||||
|
||||
services.openssh = {
|
||||
enable = cfg.enableSsh;
|
||||
hostKeys = [
|
||||
{
|
||||
path = "/persist/etc/ssh/ssh_host_ed25519_key";
|
||||
type = "ed25519";
|
||||
}
|
||||
{
|
||||
path = "/persist/etc/ssh/ssh_host_rsa_key";
|
||||
type = "rsa";
|
||||
bits = 4096;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /persist/etc/ssh 0700 root root -"
|
||||
"d /persist/secrets 0700 root root -"
|
||||
"d /persist/srv/quixos 0755 quixos quixos -"
|
||||
"d ${cfg.dataRoot} 0750 quixos quixos -"
|
||||
"d /persist/var/lib/acme 0750 caddy caddy -"
|
||||
"L /var/lib/acme - - - - /persist/var/lib/acme"
|
||||
];
|
||||
|
||||
systemd.services.quixos-package-tree = {
|
||||
description = "Prepare Quixos package tree";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "quixos-orchestrator.service" ];
|
||||
after = [ "network-online.target" "persist.mount" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "quixos";
|
||||
Group = "quixos";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -eu
|
||||
mkdir -p "$(dirname ${lib.escapeShellArg cfg.packagesPath})"
|
||||
if [ ! -d ${lib.escapeShellArg cfg.packagesPath}/.git ]; then
|
||||
git clone ${lib.escapeShellArg cfg.packageTreeRepo} ${lib.escapeShellArg cfg.packagesPath}
|
||||
git -C ${lib.escapeShellArg cfg.packagesPath} checkout --detach ${lib.escapeShellArg cfg.packageTreeRef}
|
||||
git -C ${lib.escapeShellArg cfg.packagesPath} submodule update --init --recursive
|
||||
elif ${if cfg.forcePackageTreeRefOnBoot then "true" else "false"}; then
|
||||
git -C ${lib.escapeShellArg cfg.packagesPath} fetch --tags --prune origin
|
||||
git -C ${lib.escapeShellArg cfg.packagesPath} checkout --detach ${lib.escapeShellArg cfg.packageTreeRef}
|
||||
git -C ${lib.escapeShellArg cfg.packagesPath} submodule sync --recursive
|
||||
git -C ${lib.escapeShellArg cfg.packagesPath} submodule update --init --recursive
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.quixos-orchestrator = {
|
||||
description = "Quixos orchestrator";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" "quixos-package-tree.service" ];
|
||||
wants = [ "network-online.target" "quixos-package-tree.service" ];
|
||||
serviceConfig = {
|
||||
User = "quixos";
|
||||
Group = "quixos";
|
||||
RuntimeDirectory = "quixos";
|
||||
RuntimeDirectoryMode = "0770";
|
||||
WorkingDirectory = "/persist/srv/quixos";
|
||||
EnvironmentFile = cfg.secretsEnvironmentFile;
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
ExecStart = "${quixosPackages.quixos-orchestrator}/bin/quixos-orchestrator --packages-path ${cfg.packagesPath}";
|
||||
};
|
||||
environment = {
|
||||
XDG_DATA_HOME = cfg.dataRoot;
|
||||
QUIXOS_WORKSPACE_ROOT = "/persist/srv/quixos";
|
||||
QUIXOS_CONTROL_SOCKET = "/run/quixos/control.sock";
|
||||
QUIXOS_DEVICE_ASSIGNMENTS_FILE = "${cfg.packagesPath}/device-assignments.json";
|
||||
QUIXOS_REMOTE_DOM_RELAY_SOCKET = "/run/quixos/relay.sock";
|
||||
QUIXOS_REMOTE_DOM_RELAY_PUBLIC_WS_URL = "wss://${cfg.domain}/relay/";
|
||||
QUIXOS_REMOTE_DOM_RELAY_PUBLIC_HTTP_ORIGIN = "https://${cfg.domain}/relay";
|
||||
};
|
||||
};
|
||||
|
||||
services.caddy = {
|
||||
enable = true;
|
||||
virtualHosts.${cfg.domain}.extraConfig = ''
|
||||
handle_path /control/* {
|
||||
reverse_proxy unix//run/quixos/control.sock
|
||||
}
|
||||
|
||||
handle_path /relay/* {
|
||||
reverse_proxy unix//run/quixos/relay.sock {
|
||||
header_up X-Forwarded-Prefix /relay
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
root * ${quixosPackages.project-visualizer-static}
|
||||
file_server
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"assignments": []
|
||||
}
|
||||
+34
-9
@@ -330,6 +330,7 @@ if (!STATE_CONTEXT_HMAC_SECRET) {
|
||||
throw new Error("QUIXOS_STATE_CONTEXT_SECRET must be set");
|
||||
}
|
||||
const CONTROL_PLANE_SECRET = process.env.QUIXOS_CONTROL_SECRET;
|
||||
const CONTROL_PLANE_SOCKET = process.env.QUIXOS_CONTROL_SOCKET;
|
||||
const CONTROL_PLANE_HOST =
|
||||
process.env.QUIXOS_CONTROL_HOST ?? CONTROL_PLANE_DEFAULT_HOST;
|
||||
const CONTROL_PLANE_PORT = Number(
|
||||
@@ -1327,10 +1328,9 @@ const STATE_DIRECTORY_ROOT = path.join(
|
||||
"state-contexts",
|
||||
);
|
||||
const DEVICE_STATE_FILE = path.join(QUIXOS_DATA_ROOT, "orchestrator-devices.json");
|
||||
const DEVICE_ASSIGNMENTS_FILE = path.join(
|
||||
ORCHESTRATOR_DIR,
|
||||
"device-assignments.json",
|
||||
);
|
||||
const DEVICE_ASSIGNMENTS_FILE =
|
||||
process.env.QUIXOS_DEVICE_ASSIGNMENTS_FILE ??
|
||||
path.join(PACKAGES_PATH, "device-assignments.json");
|
||||
|
||||
const tryReadGitHead = (repoPath: string) => {
|
||||
try {
|
||||
@@ -1382,6 +1382,7 @@ const sortDeviceAssignmentsForSave = (
|
||||
[...entries].sort((left, right) => left.deviceId.localeCompare(right.deviceId));
|
||||
|
||||
const saveDeviceAssignmentsConfig = () => {
|
||||
fs.mkdirSync(path.dirname(DEVICE_ASSIGNMENTS_FILE), { recursive: true });
|
||||
const payload = {
|
||||
assignments: sortDeviceAssignmentsForSave(
|
||||
[...devicesById.values()]
|
||||
@@ -5309,13 +5310,37 @@ if (CONTROL_PLANE_SECRET) {
|
||||
sendJson(response, 404, { error: "Not found" });
|
||||
});
|
||||
|
||||
controlPlaneServer.listen(CONTROL_PLANE_PORT, CONTROL_PLANE_HOST, () => {
|
||||
console.log(
|
||||
`Control plane listening on http://${CONTROL_PLANE_HOST}:${CONTROL_PLANE_PORT}`,
|
||||
);
|
||||
const onControlPlaneListening = () => {
|
||||
if (CONTROL_PLANE_SOCKET) {
|
||||
try {
|
||||
fs.chmodSync(CONTROL_PLANE_SOCKET, 0o660);
|
||||
} catch {
|
||||
// Best effort; systemd/Caddy can still proxy if permissions already fit.
|
||||
}
|
||||
console.log(`Control plane listening on unix:${CONTROL_PLANE_SOCKET}`);
|
||||
} else {
|
||||
console.log(
|
||||
`Control plane listening on http://${CONTROL_PLANE_HOST}:${CONTROL_PLANE_PORT}`,
|
||||
);
|
||||
}
|
||||
ensureWorkspaceHeadChecksWarm();
|
||||
triggerRootBoot();
|
||||
});
|
||||
};
|
||||
|
||||
if (CONTROL_PLANE_SOCKET) {
|
||||
try {
|
||||
fs.rmSync(CONTROL_PLANE_SOCKET, { force: true });
|
||||
} catch {
|
||||
// If removal fails, listen() will surface the useful error.
|
||||
}
|
||||
controlPlaneServer.listen(CONTROL_PLANE_SOCKET, onControlPlaneListening);
|
||||
} else {
|
||||
controlPlaneServer.listen(
|
||||
CONTROL_PLANE_PORT,
|
||||
CONTROL_PLANE_HOST,
|
||||
onControlPlaneListening,
|
||||
);
|
||||
}
|
||||
|
||||
controlPlaneServer.on("upgrade", (request, socket, head) => {
|
||||
if (!request.url) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
||||
approvedGitRepositories:
|
||||
- "**"
|
||||
|
||||
enableScripts: true
|
||||
|
||||
generateDefaultNix: false
|
||||
|
||||
individualNixPackaging: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- checksum: e183406dae0b56ce2dcf0880f6b4f8dd4bd1a50ebc8e06f37f05c375dae351476019a3c324443c1b9fa5ed0b3ad2053fae39810d66c16d84c4fc0365f531e650
|
||||
path: .yarn/plugins/yarn-plugin-nixify.cjs
|
||||
spec: "https://raw.githubusercontent.com/stephank/yarn-plugin-nixify/main/dist/yarn-plugin-nixify.js"
|
||||
@@ -0,0 +1,289 @@
|
||||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
{ lib, stdenv, nodejs, git, cacert, fetchurl, writeShellScript, writeShellScriptBin }:
|
||||
{ src, overrideAttrs ? null, ... } @ args:
|
||||
|
||||
let
|
||||
|
||||
yarnBin = fetchurl {
|
||||
url = "https://repo.yarnpkg.com/4.14.1/packages/yarnpkg-cli/bin/yarn.js";
|
||||
hash = "sha512-ZN9EgFWy03uiadfbU1pGm42pP47xFAwl/XqDwAqPuqyyFMoOAlU7kqLFTO94u2fQtIF/qwIAHfDiT6wPrMw7Qg==";
|
||||
};
|
||||
|
||||
cacheFolder = ".yarn/cache";
|
||||
lockfile = ./yarn.lock;
|
||||
|
||||
# Call overrideAttrs on a derivation if a function is provided.
|
||||
optionalOverride = fn: drv:
|
||||
if fn == null then drv else drv.overrideAttrs fn;
|
||||
|
||||
# Simple stub that provides the global yarn command.
|
||||
yarn = writeShellScriptBin "yarn" ''
|
||||
exec '${nodejs}/bin/node' '${yarnBin}' "$@"
|
||||
'';
|
||||
|
||||
# Common attributes between Yarn derivations.
|
||||
drvCommon = {
|
||||
# Make sure the build uses the right Node.js version everywhere.
|
||||
buildInputs = [ nodejs yarn ];
|
||||
# All dependencies should already be cached.
|
||||
yarn_enable_network = "0";
|
||||
# Tell node-gyp to use the provided Node.js headers for native code builds.
|
||||
npm_config_nodedir = nodejs;
|
||||
};
|
||||
|
||||
# Comman variables that we set in a Nix build, but not in a Nix shell.
|
||||
buildVars = ''
|
||||
# Make Yarn produce friendlier logging for automated builds.
|
||||
export CI=1
|
||||
# Tell node-pre-gyp to never fetch binaries / always build from source.
|
||||
export npm_config_build_from_source=true
|
||||
# Disable Nixify plugin to save on some unnecessary processing.
|
||||
export yarn_enable_nixify=false
|
||||
'';
|
||||
|
||||
# Create derivations for fetching dependencies.
|
||||
cacheDrvs = let
|
||||
in lib.mapAttrs (locator: { filename, hash }: stdenv.mkDerivation {
|
||||
name = lib.strings.sanitizeDerivationName locator;
|
||||
buildInputs = [ yarn git cacert ];
|
||||
buildCommand = ''
|
||||
cd '${src}'
|
||||
${buildVars}
|
||||
HOME="$TMP" yarn_enable_global_cache=false yarn_cache_folder="$TMP" \
|
||||
yarn nixify fetch ${locator}
|
||||
# Because we change the cache dir, Yarn may generate a different name.
|
||||
mv "$TMP/$(sed 's/-[^-]*\.[^-]*$//' <<< "$outputFilename")"-* $out
|
||||
'';
|
||||
outputFilename = filename;
|
||||
outputHash = hash;
|
||||
}) cacheEntries;
|
||||
|
||||
# Create a shell snippet to copy dependencies from a list of derivations.
|
||||
mkCacheBuilderForDrvs = drvs:
|
||||
writeShellScript "collect-yarn-cache" (lib.concatMapStrings (drv: ''
|
||||
cp --reflink=auto ${drv} '${drv.outputFilename}'
|
||||
'') drvs);
|
||||
|
||||
# Main project derivation.
|
||||
project = stdenv.mkDerivation (drvCommon // {
|
||||
inherit src;
|
||||
name = "quixos-project-visualizer";
|
||||
|
||||
configurePhase = ''
|
||||
${buildVars}
|
||||
|
||||
# Copy over the Yarn cache.
|
||||
rm -fr '${cacheFolder}'
|
||||
mkdir -p '${cacheFolder}'
|
||||
pushd '${cacheFolder}' > /dev/null
|
||||
source ${mkCacheBuilderForDrvs (lib.attrValues cacheDrvs)}
|
||||
popd > /dev/null
|
||||
|
||||
# Yarn may need a writable home directory.
|
||||
export yarn_global_folder="$TMP"
|
||||
|
||||
# Ensure global cache is disabled. Cache must be part of our output.
|
||||
touch .yarnrc.yml
|
||||
sed -i -e '/^enableGlobalCache/d' .yarnrc.yml
|
||||
echo 'enableGlobalCache: false' >> .yarnrc.yml
|
||||
|
||||
# Some node-gyp calls may call out to npm, which could fail due to an
|
||||
# read-only home dir.
|
||||
export HOME="$TMP"
|
||||
|
||||
# running preConfigure after the cache is populated allows for
|
||||
# preConfigure to contain substituteInPlace for dependencies as well as the
|
||||
# main project. This is necessary for native bindings that maybe have
|
||||
# hardcoded values.
|
||||
runHook preConfigure
|
||||
|
||||
# Run normal Yarn install to complete dependency installation.
|
||||
yarn install --immutable --immutable-cache
|
||||
|
||||
runHook postConfigure
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
# Move the package contents to the output directory.
|
||||
if grep -q '"workspaces"' package.json; then
|
||||
# We can't use `yarn pack` in a workspace setup, because it only
|
||||
# packages the outer workspace.
|
||||
mkdir -p "$out/libexec"
|
||||
mv $PWD "$out/libexec/$name"
|
||||
else
|
||||
# - If the package.json has a `files` field, only files matching those patterns are copied
|
||||
# - Otherwise all files are copied.
|
||||
yarn pack --out package.tgz
|
||||
mkdir -p "$out/libexec/$name"
|
||||
tar xzf package.tgz --directory "$out/libexec/$name" --strip-components=1
|
||||
|
||||
cp --reflink=auto .yarnrc* "$out/libexec/$name"
|
||||
cp --reflink=auto ${lockfile} "$out/libexec/$name/yarn.lock"
|
||||
cp --reflink=auto --recursive .yarn "$out/libexec/$name"
|
||||
|
||||
# Copy the Yarn linker output into the package.
|
||||
cp --reflink=auto --recursive node_modules "$out/libexec/$name"
|
||||
fi
|
||||
|
||||
cd "$out/libexec/$name"
|
||||
|
||||
# Invoke a plugin internal command to setup binaries.
|
||||
mkdir -p "$out/bin"
|
||||
yarn nixify install-bin $out/bin
|
||||
|
||||
# A package with node_modules doesn't need the cache
|
||||
yarn cache clean
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
inherit nodejs;
|
||||
yarn-freestanding = yarn;
|
||||
yarn = writeShellScriptBin "yarn" ''
|
||||
exec '${yarn}/bin/yarn' --cwd '${overriddenProject}/libexec/${overriddenProject.name}' "$@"
|
||||
'';
|
||||
};
|
||||
});
|
||||
|
||||
overriddenProject = optionalOverride overrideAttrs project;
|
||||
|
||||
cacheEntries = {
|
||||
"@babel/code-frame@npm:7.29.0" = { filename = "@babel-code-frame-npm-7.29.0-6c4947d913-d34cc504e7.zip"; hash = "sha512-00zFBOd2XftXamY9lwZ6+2FFJYBrXK0aXMGnGDuRb+yP9X+iM1heOSb9Wp5rMarm35Gqga6Xdft6KPZY0zRvDQ=="; };
|
||||
"@babel/compat-data@npm:7.29.3" = { filename = "@babel-compat-data-npm-7.29.3-6a1cb34af5-81bddd53ce.zip"; hash = "sha512-gb3dU84bE5VXb7t8tzljGpdva0Ic0mDmzycVqWkbmg7BLKXE4buICI5g3IeHX25O9/qGdPHclq4b18NXQWYFpw=="; };
|
||||
"@babel/core@npm:7.29.0" = { filename = "@babel-core-npm-7.29.0-a74bfc561b-5127d2e8e8.zip"; hash = "sha512-USfS6OhCrkCeEby7XC3/mHSr9UFegCaSWvcwjpA/T0M5c0FGehMEkNGjmIT0YbwrZ/MGO84L5ENA24lof9hSqg=="; };
|
||||
"@babel/generator@npm:7.29.1" = { filename = "@babel-generator-npm-7.29.1-b1bf16fe79-349086e687.zip"; hash = "sha512-NJCG5odiWO8/soIwMP7g9sDrnD6+NfxXLhaZf4wDDXZfY23cYpntrmPnYOpmWPjumi7fptayTJqAyReRa5c1UQ=="; };
|
||||
"@babel/helper-compilation-targets@npm:7.28.6" = { filename = "@babel-helper-compilation-targets-npm-7.28.6-8880f389c9-3fcdf3b1b8.zip"; hash = "sha512-P83zsbhXoVeOmdIFCIWdvT8i88h7ig89xUBie0vlObrn9uYeSdkxVC/ltVdUU0cnK72s1/WKXHcCWhi3RVk6UA=="; };
|
||||
"@babel/helper-globals@npm:7.28.0" = { filename = "@babel-helper-globals-npm-7.28.0-8d79c12faf-5a0cd0c0e8.zip"; hash = "sha512-WgzQwOjHZLXyfyCV5CQ+ivb6FF2uorQbU8DBQU/m/xOeNkD04iB64rPSFToavTRvkBwmwpDufLOIHdki1O6SMg=="; };
|
||||
"@babel/helper-module-imports@npm:7.28.6" = { filename = "@babel-helper-module-imports-npm-7.28.6-5b95b9145c-b49d8d8f20.zip"; hash = "sha512-tJ2NjyBNnb/VrHDFTlM+Ummvs86pZqnZdnIrE+mSLMdzplNAX1PImsskfVrr2uRoHWMaOuPfd+wEa1jadu2irA=="; };
|
||||
"@babel/helper-module-transforms@npm:7.28.6" = { filename = "@babel-helper-module-transforms-npm-7.28.6-5923cf5a95-6f03e14fc3.zip"; hash = "sha512-bwPhT8MLKHzguDlHS18nHnKDfQyv5rFy11kYTZmPvuOQOgNegeB8LFlkSeUE9FNGPVi6pltvQKN97VvsdGILKw=="; };
|
||||
"@babel/helper-plugin-utils@npm:7.28.6" = { filename = "@babel-helper-plugin-utils-npm-7.28.6-766c984cfe-3f5f8acc15.zip"; hash = "sha512-P1+KzBUv27aahLhiQUX/T5ufbndsuYn5+Wj4YG63GFxcPPzzughTTjfh4OHBGKxnCAYQMz9WuqT3N2yZtfEUPQ=="; };
|
||||
"@babel/helper-string-parser@npm:7.27.1" = { filename = "@babel-helper-string-parser-npm-7.27.1-d1471e0598-8bda3448e0.zip"; hash = "sha512-i9o0SOB7VYNyfBA1YLz5xMJLPBBRpMUW1AUO9p3ze7mkc0pYX+EnJbjCdj3gomWqHpCbSFpOMnC3z9Pk2+S2Ag=="; };
|
||||
"@babel/helper-validator-identifier@npm:7.28.5" = { filename = "@babel-helper-validator-identifier-npm-7.28.5-1953d49d2b-42aaebed91.zip"; hash = "sha512-Qqrr7ZH3OaQfPYC3J1LR+V/XxyOU6OS9fN2IgX4HdNgKQyRRvLoXwsZCwlfEg78dQJ3UVIiDQp6pSTo7xKsIRw=="; };
|
||||
"@babel/helper-validator-option@npm:7.27.1" = { filename = "@babel-helper-validator-option-npm-7.27.1-7c563f0423-6fec5f006e.zip"; hash = "sha512-b+xfAG66QAAaIPJrHvXbvaN3t7aMitUYwFuqmvPzlueAvf3tJMTu+V0Uu3uP1WGSpu041dQ5uX0Q78XxoZHRSA=="; };
|
||||
"@babel/helpers@npm:7.29.2" = { filename = "@babel-helpers-npm-7.29.2-ec38f935cc-dab0e65b93.zip"; hash = "sha512-2rDmW5MYslAqYsWLwJE1cjGFle7ASCwx8K1Ba3JjbmaYodfFfNJ5HUUo64xUi8qI0zjcTSpVoQjcH2cC+bxVEg=="; };
|
||||
"@babel/parser@npm:7.29.3" = { filename = "@babel-parser-npm-7.29.3-1f668babfe-f06920c819.zip"; hash = "sha512-8GkgyBlVDA22ieTFtia/Vbo86/gOvpzPpDThNANs895QlR/nWfdKuy2uOBmJI5hgveRtRgAyhXitH3EUw3EabQ=="; };
|
||||
"@babel/plugin-transform-react-jsx-self@npm:7.27.1" = { filename = "@babel-plugin-transform-react-jsx-self-npm-7.27.1-bd0fa344f1-00a4f917b7.zip"; hash = "sha512-AKT5F7cKYI+ayi+zmqvgSmCqMxZafgEF/USzqFMWMOuFv1Vy6fJC9R5q0vo4wufngJAhdshjVWxYtbpvbhZAMQ=="; };
|
||||
"@babel/plugin-transform-react-jsx-source@npm:7.27.1" = { filename = "@babel-plugin-transform-react-jsx-source-npm-7.27.1-36a9716d8f-5e67b56c39.zip"; hash = "sha512-Xme1bDnE0D5Z4DuoBpKyTFqSFHIHm2OvcRsdJQ/DfBczoXBptjU391Dz6TfsRKQrHuakbNI7Gg31FjsX90H38g=="; };
|
||||
"@babel/template@npm:7.28.6" = { filename = "@babel-template-npm-7.28.6-bff3bc3923-66d87225ed.zip"; hash = "sha512-ZthyJe0Lx3+IgYGuLZeEUCGDjGGZRId/fEOYxnSLz2EfIW39a+dNOQFq9QK8qHbmzmhz2zxJ5Kw1TFbTTVfp9Q=="; };
|
||||
"@babel/traverse@npm:7.29.0" = { filename = "@babel-traverse-npm-7.29.0-85d5d916b6-f63ef6e58d.zip"; hash = "sha512-9j725Y0CqfvzwOLl8ch32j4LxX+RoZ0iI9U+NWp2hZy69RFxySEccYFtlKDmnvonMv0n/8Dhu8hLY25gkyMz6w=="; };
|
||||
"@babel/types@npm:7.29.0" = { filename = "@babel-types-npm-7.29.0-6c2fa77581-23cc3466e8.zip"; hash = "sha512-I8w0Zug7y/q4ub0O2q/bXU79uIuCs75nKLut5bovCZb4T2OxxfeowNZ+/e0oMAiYpfkwsXG7QLMRvKICnE6bTw=="; };
|
||||
"@esbuild/linux-x64@npm:0.21.5" = { filename = "@esbuild-linux-x64-npm-0.21.5-88079726c4-10c0.zip"; hash = "sha512-nlZj/KzpyEVumTSp7W50KNtAgAJO7zv+r4LUdhIL2IE4LJWL4nhUY9a0RGez0/hw1szgmpyze87xmv65eBTWdA=="; };
|
||||
"@isaacs/fs-minipass@npm:4.0.1" = { filename = "@isaacs-fs-minipass-npm-4.0.1-677026e841-c25b6dc159.zip"; hash = "sha512-wlttwVmHkNW1XAlHqbfREc+pJZTbUpbDuQfi9TPAM2ZvaSo5OerawXscfEDTYtCwY13IdMv+PnDbfCsHzJel0g=="; };
|
||||
"@jridgewell/gen-mapping@npm:0.3.13" = { filename = "@jridgewell-gen-mapping-npm-0.3.13-9bd96ac800-9a7d65fb13.zip"; hash = "sha512-mn1l+xO9muwfurdM2ghJaDm34s6zH1q5IrMj6U18SBzg/E/X4S4mEJFe2K9RF4vcYeFo6SqMi4MDsDCwNImxOw=="; };
|
||||
"@jridgewell/remapping@npm:2.3.5" = { filename = "@jridgewell-remapping-npm-2.3.5-df8dacc063-3de494219f.zip"; hash = "sha512-PeSUIZ/+ssXDhxHQ17sSgJft+RiTCQotvI7gtV0JK7c0ex/Q9HhIbF6rAQ6FXHOSexZm8hB1FtRy0kpzAX0RlA=="; };
|
||||
"@jridgewell/resolve-uri@npm:3.1.2" = { filename = "@jridgewell-resolve-uri-npm-3.1.2-5bc4245992-d502e6fb51.zip"; hash = "sha512-1QLm+1FrNQMjMUBtTpYsIf53zfHL20nGFCvL2eMFBwlLGJcneKbifLrXViCc/jSxoncp5voIouuSszlD9oDPHg=="; };
|
||||
"@jridgewell/sourcemap-codec@npm:1.5.5" = { filename = "@jridgewell-sourcemap-codec-npm-1.5.5-5189d9fc79-f9e538f302.zip"; hash = "sha512-+eU48wK2PA68Bu7LHdmRjdQontNhR6DdzjXW6k1+u9okPNp7IhO2peHYCHopjVz2MPsr05Mpzey4IBcCP2CBoA=="; };
|
||||
"@jridgewell/trace-mapping@npm:0.3.31" = { filename = "@jridgewell-trace-mapping-npm-0.3.31-1ae81d75ac-4b30ec8cd5.zip"; hash = "sha512-SzDsjNVsX9mmYfCIIwrwHgwaOIjRH/trR2OXAPcSJb4h0ffhaASNbU+USSB7l4ojXAfI8VwHcFaF0W3AYoDp2Q=="; };
|
||||
"@quixos/control-plane-protocol@file:../quixos-control-plane-protocol#../quixos-control-plane-protocol::hash=f0f687&locator=quixos-project-visualizer%40workspace%3A." = { filename = "@quixos-control-plane-protocol-file-f50f93332c-88fc86dd74.zip"; hash = "sha512-iPyG3XTXwkQlS+4WI9dhZ1VOhA5gtJfCDuOaj/yiyP2cSxr/4acbqNPG+CBlbyEUeyci8ALUtbAlN4ocNIkQ6w=="; };
|
||||
"@remote-dom/core@npm:1.11.1" = { filename = "@remote-dom-core-npm-1.11.1-8bd9a831b0-c6432934e8.zip"; hash = "sha512-xkMpNOgxLTICQyn+LGB1s6ERd8mVCfw3yVTyjK9T4NkCTMFS+42401+NiiRmCDe5VMdMlQuEGJ5wR/Q3pWz9Jw=="; };
|
||||
"@remote-dom/polyfill@npm:1.5.1" = { filename = "@remote-dom-polyfill-npm-1.5.1-02b868404f-753836a286.zip"; hash = "sha512-dTg2ooYhTprI1iRlAdTXgk/mAK5LScP+asVGeTS1oVrrD4Dnc/fHIZ+CmsV1i+B3Ro7ebaml3hQmDzYn/81uHw=="; };
|
||||
"@rolldown/pluginutils@npm:1.0.0-beta.27" = { filename = "@rolldown-pluginutils-npm-1.0.0-beta.27-108701b3b0-9658f235b3.zip"; hash = "sha512-lljyNbNFIB1Pa/sfMtqXVMoWT4ktHLaBVP5fU8HfQr1nXs1AmDbf9GiEp4R9bAC9w4r4cPfIHgW7pcJkXrSrnA=="; };
|
||||
"@rollup/rollup-linux-x64-gnu@npm:4.60.4" = { filename = "@rollup-rollup-linux-x64-gnu-npm-4.60.4-d05f6a1949-10c0.zip"; hash = "sha512-UXlav22muxAMAh6XlNM+MuN7oGw4Ny6UJm0Pq6AYVa5erRDXHH9auNMB04M12O8kuaYekhDJET9547MxFjFBFg=="; };
|
||||
"@types/babel__core@npm:7.20.5" = { filename = "@types-babel__core-npm-7.20.5-4d95f75eab-bdee3bb699.zip"; hash = "sha512-ve47tplR6DOkuBG47pNWtpph7Vt6I+GggeySSXaRF/qDqq8CO7BlYqA461hFFV/2Y+LVx13ZXB1czJHbASho/w=="; };
|
||||
"@types/babel__generator@npm:7.27.0" = { filename = "@types-babel__generator-npm-7.27.0-a5af33547a-9f9e959a87.zip"; hash = "sha512-n56VmoeS3yCKnQSAkv2n4YWL3clcYxSFeoIRqZ4g5oML3rVy41h66L5UKeN/Kpb88iKp9TrSMvVTd2TJ4TorvQ=="; };
|
||||
"@types/babel__template@npm:7.4.4" = { filename = "@types-babel__template-npm-7.4.4-f34eba762c-cc84f6c6ab.zip"; hash = "sha512-zIT2xqseqxQn6Q3St2zO5lzpQLd4qaZ74sjDnhmU5vW7yO+jCfbOqNxnVJlFJM1NKJZVjfdtkueh9G7P/ucRKw=="; };
|
||||
"@types/babel__traverse@npm:7.28.0" = { filename = "@types-babel__traverse-npm-7.28.0-44a48c1b20-b52d7d4e8f.zip"; hash = "sha512-tS19To/GqQGP5zYcQGLBwZD1d4zyRmgXy57RnWn7u1T5qF/+3rdI7YBi0s99TMCI7nOYSPR8V3QN4cSMvw0JlA=="; };
|
||||
"@types/estree@npm:1.0.8" = { filename = "@types-estree-npm-1.0.8-2195bac6d6-39d34d1afa.zip"; hash = "sha512-OdNNGvqjOKuXY/N61gZuPzSURPkFK5Z2p8wCUu+UhaQcbYHJxODSbpB3mTNU7fJe/IU/MiTdS0Rxde9ivcyGpQ=="; };
|
||||
"@types/node@npm:25.9.1" = { filename = "@types-node-npm-25.9.1-fa3ebe64ec-9a04682842.zip"; hash = "sha512-mgRoKEK+u88hoXed/quapzPXvXu8Cg7bZBqzqaPUPqxUMiWs9mnDNPRY8ZVkQ+vAcrw8coQMVDuLNWyrXILUVg=="; };
|
||||
"@types/prop-types@npm:15.7.15" = { filename = "@types-prop-types-npm-15.7.15-cefe16a1fa-b59aad1ad1.zip"; hash = "sha512-tZqtGtGb8XM89ST9TmGBlsbHaQ9I7nCjJ+tFCkKquOigY/vlnKClcBrr4tktWCKSwPuEXqV0dPahX2mUsOJgsg=="; };
|
||||
"@types/qrcode@npm:1.5.6" = { filename = "@types-qrcode-npm-1.5.6-1e3cf640f4-84844ca63e.zip"; hash = "sha512-hIRMpj5fMrxH1E3aD4pvfNzHzkTnsk8Q8Z1QeW8x0SwFj3Aqj301LJ6CoCOpq8Nvoa0B3fCiCd2O1FYup2SB/A=="; };
|
||||
"@types/react-dom@npm:18.3.7" = { filename = "@types-react-dom-npm-18.3.7-c71f2ee61f-8bd309e2c3.zip"; hash = "sha512-i9MJ4sPRYEoopzaiT5bLrfbAXVKIz++Ig7dPQFTJYbazpemX/VaG5JK+kDyPM4DbpewBfv85BrElZSnNLTlgPg=="; };
|
||||
"@types/react@npm:18.3.29" = { filename = "@types-react-npm-18.3.29-576594e3cd-b582f5c3a0.zip"; hash = "sha512-tYL1w6A0IpsuKH9eOa6/OYjWznnn/YHIJX3FW+4OMh3fcI1gFKRMXOro3r6h4VqTMmsgek/sagFwIAoD0QWt+w=="; };
|
||||
"@vitejs/plugin-react@npm:4.7.0" = { filename = "@vitejs-plugin-react-npm-4.7.0-650e714693-692f239609.zip"; hash = "sha512-aS8jlglyh5SF1kdxNmPsKZxHgiLJZWfWAoWs98fcXBeOcav+nS7v3e8e6wFRTay8LtaKrYRijev5xxFhNHNCUw=="; };
|
||||
"abbrev@npm:5.0.0" = { filename = "abbrev-npm-5.0.0-31d7ffe3c8-8e88f5c798.zip"; hash = "sha512-joj1x5jqRWLSjFo+mtaeOHmJC8XWldjy3/uGCb5MiQqsyPgO9FU/3SxqYtcMLOi8V7OAdOODvrdIe9r6ntQupQ=="; };
|
||||
"ansi-regex@npm:5.0.1" = { filename = "ansi-regex-npm-5.0.1-c963a48615-9a64bb8627.zip"; hash = "sha512-mmS7hie0NLqTJ7YMAndC5dF6xpJ3lg0EGJhZYnHZktTVK6cmemPKECMuKfYQf8ioNfbOjXGbiMX4ST+CVIE3Nw=="; };
|
||||
"ansi-styles@npm:4.3.0" = { filename = "ansi-styles-npm-4.3.0-245c7d42c7-895a23929d.zip"; hash = "sha512-iVojkp2kFvK9Pefpy06r00CUkyirhd3W5ISmN9j2gg1IX1OTNEb1KRw7dgy8SIvrjohXPdD5x9r4PczI/oGwQQ=="; };
|
||||
"baseline-browser-mapping@npm:2.10.32" = { filename = "baseline-browser-mapping-npm-2.10.32-a4230b4be5-408c93245b.zip"; hash = "sha512-QIyTJFvfHpKrD4kev5KD7GDbq/qsgb3Jog03FWWipJaw+4Ao99Yow/ZvkO4UJnCoFXXPHL1SKfe0sNNQ25EQhQ=="; };
|
||||
"browserslist@npm:4.28.2" = { filename = "browserslist-npm-4.28.2-8923c4854e-c0228b6330.zip"; hash = "sha512-wCKLYzD3hbf6WdLTYBJOxtkyL5btnz7h+HPjPsyVA6bw/8O3EZGijE/26TC3U7MAQ9ocM4RKlUjzAY1JHwnOYA=="; };
|
||||
"camelcase@npm:5.3.1" = { filename = "camelcase-npm-5.3.1-5db8af62c5-92ff9b443b.zip"; hash = "sha512-kv+bRDv+irsV8rFRPKGC0WEmNZrU+VXryD3E3cxO8/3SwHi8Ij8mc9wiNIjnXJmxbMTQVmJDdLeZ5qFVXPYbIw=="; };
|
||||
"caniuse-lite@npm:1.0.30001793" = { filename = "caniuse-lite-npm-1.0.30001793-d05254d2b9-bee8f8b55d.zip"; hash = "sha512-vuj4tV0czbIHa3NVwG/QGRaVLq3Xa4KOTV+5rGLRfsfbDit8MmuSNHi5NSatH/dPGJz0DAbeDkpe28Z3AJuX/g=="; };
|
||||
"chownr@npm:3.0.0" = { filename = "chownr-npm-3.0.0-5275e85d25-43925b8770.zip"; hash = "sha512-Q5Jbh3APfjiTKWyOnFbMWPkmQRzOOm5YmBNtqvCPCLmo63bTfTJn5wfQ3MF67S4uvfWEjAw86Vz5EKkZk1wbEA=="; };
|
||||
"cliui@npm:6.0.0" = { filename = "cliui-npm-6.0.0-488b2414c6-35229b1bb4.zip"; hash = "sha512-NSKbG7SGR+iCEEysN0yaGONLvwus4OLPAwADJrbKMFDWtZVF2R4Xv+NwX0oOKYh4eqXN5jMb9cu/AWRzLO9kkg=="; };
|
||||
"color-convert@npm:2.0.1" = { filename = "color-convert-npm-2.0.1-79730e935b-37e1150172.zip"; hash = "sha512-N+EVAXLy4xH+Gy32LGKTo0Luc4Dae5z9umfqU5kJr7102icDMgjQHW1c/GXueGiiLhjX52SOAEQlRBwPihWn1w=="; };
|
||||
"color-name@npm:1.1.4" = { filename = "color-name-npm-1.1.4-025792b0ea-a1a3f91415.zip"; hash = "sha512-oaP5FBVpYJAvRvf1a8Yu/8bJToSyyuFXpSaxwfdLZ3pH7GAr9ophq/orQtFbfFZRxtvnKkOvcgvFiN/4hbEPlQ=="; };
|
||||
"convert-source-map@npm:2.0.0" = { filename = "convert-source-map-npm-2.0.0-7ab664dc4e-8f2f7a27a1.zip"; hash = "sha512-jy96J6GgEcxsyIzE2i19DPpe4DaVCLquPZjCYLs6xSBpFGTlu+SufN8JhgwdaezG9wxjxufH9+PxjsCEhNx9mw=="; };
|
||||
"csstype@npm:3.2.3" = { filename = "csstype-npm-3.2.3-741053244e-cd29c51e70.zip"; hash = "sha512-zSnFHnD6gi8c7NhkGhRFvtcGNpdGnTVjO1FuYP6MG94EsI9sW2AiE2u2abZMY9QXOvVIZFEPu07iMoGAGEGjzg=="; };
|
||||
"debug@npm:4.4.3" = { filename = "debug-npm-4.4.3-0105c6123a-d79136ec6c.zip"; hash = "sha512-15E27GyD7L79D2pVk9pqnJHsTX3cS1TIg9bnHsmsy19noaXpbQCjKBlrW1yG02XpjYo6cIVqrxa057GYXmf1pg=="; };
|
||||
"decamelize@npm:1.2.0" = { filename = "decamelize-npm-1.2.0-c5a2fdc622-85c39fe8fb.zip"; hash = "sha512-hcOf6PvwSC1KHiJO8BGdtcGJf4UDvO+Lgmrf96GxFBSXL2/vLX3sLuC0vjhjz2SsFDkTeunmryOj2Ny+JqW0sg=="; };
|
||||
"dijkstrajs@npm:1.0.3" = { filename = "dijkstrajs-npm-1.0.3-d5b1d1b11a-2183d61ac1.zip"; hash = "sha512-IYPWGsHyUGLzw3c/PqjZ9FuhZKAOd+B/r4zFdQ2pZiItHizmKZyHWoD5aRkMcaCXMEIZLFYk1SI+TtGW/1hMmQ=="; };
|
||||
"electron-to-chromium@npm:1.5.361" = { filename = "electron-to-chromium-npm-1.5.361-59eee8ca64-5e6e9c0c12.zip"; hash = "sha512-Xm6cDBKrgjZu3fV1yLkRTVr3EKutO1khQcVs+gFpRx82M9zHk0IMMD+taN/yHI+3JLQ83n5mvpR5g/o9bVpzWQ=="; };
|
||||
"emoji-regex@npm:8.0.0" = { filename = "emoji-regex-npm-8.0.0-213764015c-b6053ad399.zip"; hash = "sha512-tgU605lRxM8zj5CS17+6RIzf1G/moqA0cAsUmsn/vBN+Nhy9PEQil/hr7S5fdXbBtUzApr+O9RBsxi9JavNQEA=="; };
|
||||
"env-paths@npm:2.2.1" = { filename = "env-paths-npm-2.2.1-7c7577428c-285325677b.zip"; hash = "sha512-KFMlZ3vwDjCEXjMO7DKJT1EFUp25dJbuP1mEeOUPAIxTUqQaMOXnLsneilQrWlcLhWmc1jvSvGRtvLnzEdg7xA=="; };
|
||||
"esbuild@npm:0.21.5" = { filename = "esbuild-npm-0.21.5-d85dfbc965-fa08508adf.zip"; hash = "sha512-+ghQit9oPD85nooBSmOCprZVQiE0MeJiBsByDlNrMcCbUHmHR8KhBaS7uh2XZ7jTYVp0wve/Hd9tg2zRHrZy3g=="; };
|
||||
"escalade@npm:3.2.0" = { filename = "escalade-npm-3.2.0-19b50dd48f-ced4dd3a78.zip"; hash = "sha512-ztTdOnjhWJftO+dOY1EQu/OwiHewpBvlDcsyXuDgtfZfwtUOmEUZTXxGM/Mn4uHGzOAKcbYXxWc98DdCAdZ/ZQ=="; };
|
||||
"exponential-backoff@npm:3.1.3" = { filename = "exponential-backoff-npm-3.1.3-28be78d98e-77e3ae682b.zip"; hash = "sha512-d+OuaCt7H0ly9WPG280rDVSsZ55i1dMvPlCF/rogSDzyi9UFVD9SDih6VtTVWijXh0KZlB+vY353mhqlmU0SZw=="; };
|
||||
"fdir@npm:6.5.0" = { filename = "fdir-npm-6.5.0-8814a0dec7-e345083c43.zip"; hash = "sha512-40UIPEMGs67Wy47FUeJsNrq1xRHpnqRXahZ1DdyNMkDmOCbMYk9a4XrU3ILmiiUyE7YNVWwRv60GS3YHhH7Qfw=="; };
|
||||
"find-up@npm:4.1.0" = { filename = "find-up-npm-4.1.0-c3ccf8d855-0406ee89eb.zip"; hash = "sha512-BAbuievu+i1Qf+sH7DZr69imFnrnSqTjT7TEq9Bs94KjziauQZTXBwb3IYKEFzPwBVHCCf5XXLAL2SEEBW54wQ=="; };
|
||||
"fsevents@npm:2.3.3" = { filename = "fsevents-npm-2.3.3-ce9fb0ffae-a1f0c44595.zip"; hash = "sha512-ofDERZUSPtcX/rvEeKqVLket/CjiCSvma4qxY1FHJUymz+HfeSqJl/InFtTLr8czCYmf97+sKsOtjPLk7MPsYA=="; };
|
||||
"gensync@npm:1.0.0-beta.2" = { filename = "gensync-npm-1.0.0-beta.2-224666d72f-782aba6cba.zip"; hash = "sha512-eCq6bLplsbta87CV2WJJ0g7b6N8y2/Rpb9Sb4lg/r2dhc79ICThliIKOTddqM1T8vrV3urHIM8zZ/EV38mED+A=="; };
|
||||
"get-caller-file@npm:2.0.5" = { filename = "get-caller-file-npm-2.0.5-80e8a86305-c6c7b60271.zip"; hash = "sha512-xse2AnGTH6dSrrkvK0fjVerBrzomc/R8lYno+KQa3HTUVVHBvFe15mqAYJ8Q/7crb1deQ3DWHMP386r/AXV83g=="; };
|
||||
"graceful-fs@npm:4.2.11" = { filename = "graceful-fs-npm-4.2.11-24bb648a68-386d011a55.zip"; hash = "sha512-OG0BGlU+ArxZSsLKC9bZ5MItf6jPv8RIptFIxZ6ogbCS252+NUeuS4jlXxsB98Si7MU7MQwEJ5PmOqRM9sJX8g=="; };
|
||||
"htm@npm:3.1.1" = { filename = "htm-npm-3.1.1-e3b831f850-0de4c8fff2.zip"; hash = "sha512-DeTI//K452wWIjWugNv5PKXu8Vdb1QWWoGzpvr8abaXvxGdBfFMDSp/6Krns/4GcvsBB3JCHiUsrkArU3ibH5w=="; };
|
||||
"is-fullwidth-code-point@npm:3.0.0" = { filename = "is-fullwidth-code-point-npm-3.0.0-1ecf4ebee5-bb11d825e0.zip"; hash = "sha512-uxHYJeBJ844EwGNzqNcngu7gIFvanZCMxVDMs8WbmddQ/5U3mC4BczwclKWONUAGYfVwQhWP9ejz6Qz5Ntrw/A=="; };
|
||||
"isexe@npm:4.0.0" = { filename = "isexe-npm-4.0.0-588229ad74-5884815115.zip"; hash = "sha512-WISBURW86sRSh3ZZqcdyY4JTFZL0PcKeXUi3xBAGYa7VQBjLkL02yy6uulIQklcHaRZ6y7lcGNOa/cy8ygbFzg=="; };
|
||||
"js-tokens@npm:4.0.0" = { filename = "js-tokens-npm-4.0.0-0ac852e9e2-e248708d37.zip"; hash = "sha512-4khwjTd6oFjqzyA3sH3thHeQ5t6JK7rT2sCrui51nLnxIbAAmaZRlWFrrctuyo0U2XXLPonrHP2mRHVkAsiu7Q=="; };
|
||||
"jsesc@npm:3.1.0" = { filename = "jsesc-npm-3.1.0-2f4f998cd7-531779df5e.zip"; hash = "sha512-Uxd5317JT0fkYtomtMvwXriKg9nwiqwroEIGUI/FmFJ6FT0IvUYrroL8eLPqoakI4aSnn4hukjhkHEze+vEYsQ=="; };
|
||||
"json5@npm:2.2.3" = { filename = "json5-npm-2.2.3-9962c55073-5a04eed948.zip"; hash = "sha512-WgTu2UgQ+lXF6hOLL3pcErl8N1C8Y9EeUR3Oy/73WAA4YVIqBwwicnZO4PTj4yOGLzhpRa61uFuH7kPwhLpYbA=="; };
|
||||
"locate-path@npm:5.0.0" = { filename = "locate-path-npm-5.0.0-46580c43e4-33a1c5247e.zip"; hash = "sha512-M6HFJH6H4CL5cT5iE6dEVXo+nsMsXQte+xCqOjgXdhW/kCIaVZJnSFcDnBoP0gY7gvKFcC03t5LZc+nnKs5sWQ=="; };
|
||||
"loose-envify@npm:1.4.0" = { filename = "loose-envify-npm-1.4.0-6307b72ccf-655d110220.zip"; hash = "sha512-ZV0RAiCYPBpLnAxnmi6AFtS2f26ce1Q1/1l57Nsg0IE/TewKCGdPy91IRqPwftu1CjaBH9N5MLlKqg2drOsBfg=="; };
|
||||
"lru-cache@npm:5.1.1" = { filename = "lru-cache-npm-5.1.1-f475882a51-89b2ef2ef4.zip"; hash = "sha512-ibLvLvRfVDAR44c3uKhiKi+JmM3fDlQ3F0748fcKi50UqRirPiMss7o0O3q93/pmfwtZB1srgOa01jw95hJ0gg=="; };
|
||||
"minipass@npm:7.1.3" = { filename = "minipass-npm-7.1.3-b73a16498d-539da88dac.zip"; hash = "sha512-U52ojayhZTMhHqWp7pjcYv9XQvUx9UZA3TRCnmIZVekcwoCpGndgJiZLf59nNZR2KfkglE6cFVg2novyLrM/uw=="; };
|
||||
"minizlib@npm:3.1.0" = { filename = "minizlib-npm-3.1.0-6680befdba-5aad75ab00.zip"; hash = "sha512-Wq11qwCQuCZgacmqvlgsAhrlPrM8bGkQVKE6Rds7T5Gn+xvXkVHmtOnpqGcntSJSfAoG7H1FIGt0XVTNMJe87A=="; };
|
||||
"ms@npm:2.1.3" = { filename = "ms-npm-2.1.3-81ff3cfac1-d924b57e73.zip"; hash = "sha512-2SS1fnMSs7Y60h/Fs9wK9eeNYaH8fPtUV+2vJjJr9ivlMHzIf/toYu8cKzOwIzzbXU8BxMlYzA1mCUi2Wih6SA=="; };
|
||||
"nanoid@npm:3.3.12" = { filename = "nanoid-npm-3.3.12-41f8e0bb94-ba142b7b39.zip"; hash = "sha512-uhQreznhHoDBbddLA2XUB4gMh8HPfhSAlWmBrpQO42Bg+ltvCSzR4xUYTdGSRMZXvQF9AzJ708YiR9aRxejt+w=="; };
|
||||
"node-gyp@npm:13.0.0" = { filename = "node-gyp-npm-13.0.0-f910600f25-e7525c427d.zip"; hash = "sha512-51JcQn2y0WqjaLiUcYfegwg9Ko3aI+PglqccIq5jesW7jtfPbIcfG5EYzScp2/7k/zpCReK3kiaQAiexWDG0kg=="; };
|
||||
"node-releases@npm:2.0.46" = { filename = "node-releases-npm-2.0.46-b0f9c0af01-04632591f9.zip"; hash = "sha512-BGMlkfl/FYSK37ErIfoBOmwZgJr89dtl/ojJWjYnHD9CPiERD9MZrVqcUCn/5l64Hz5IV+avGWIryIjZKgStIg=="; };
|
||||
"nopt@npm:10.0.1" = { filename = "nopt-npm-10.0.1-c09d426c63-980d89257f.zip"; hash = "sha512-mA2JJX+Vh/Ph93h33b+QXWqjtzjsM+SaT6GgWaDdgusoBjmCsVBlSnrp3jhvLq1g5WFy2303z1beVF9zkqKiag=="; };
|
||||
"p-limit@npm:2.3.0" = { filename = "p-limit-npm-2.3.0-94a0310039-8da01ac53e.zip"; hash = "sha512-jaAaxT7+amJwgPr8EnyHPaQMGNh7P11UktRlu4XscgfhU5SN9rnLrrEwvnAVL4dCKbgkLuK+hMB5QIJRCvl/Eg=="; };
|
||||
"p-locate@npm:4.1.0" = { filename = "p-locate-npm-4.1.0-eec6872537-1b476ad69a.zip"; hash = "sha512-G0dq1prX9gWXRPNDsm1RzgkVCJNcHbuAxOCi85f/zgyjofn1zTx84Z15KaCXGdXGX+cNjuKJw/JnzTbyiBgT6Q=="; };
|
||||
"p-try@npm:2.2.0" = { filename = "p-try-npm-2.2.0-e0390dbaf8-c36c199077.zip"; hash = "sha512-w2wZkHc0yQSxaZTmU1sCw2wiJNQz4BovGrd3I39NhuYon9X9RkhQSR6UA3nUYG7YUMA+D5q2ALDr3bURMS4Xfw=="; };
|
||||
"path-exists@npm:4.0.0" = { filename = "path-exists-npm-4.0.0-e9e4f63eb0-8c0bd3f523.zip"; hash = "sha512-jAvT9SOBiBl9x43O0VIHpHFsUcxONiTET8l6z2lVj167mir/9Ib+G07hSODBM+lsXhGpqlxIowBuNGfaBw5eGw=="; };
|
||||
"picocolors@npm:1.1.1" = { filename = "picocolors-npm-1.1.1-4fede47cf1-e2e3e8170a.zip"; hash = "sha512-4uPoFwq518dCGWmtqn4bMUNPeJr7mz8RX2uW2RlFBBrDzrAunsb+ZRD/A2vMC/keaaF3LtwLcH4SsZwPLWvPWA=="; };
|
||||
"picomatch@npm:4.0.4" = { filename = "picomatch-npm-4.0.4-e82d450244-e2c6023372.zip"; hash = "sha512-4sYCM3LMe1dkcZpf+52g+OeBIS+nykvQVi25Kd+OEXRg8A3/PLdQnaz8Brht6SSyR/UE0M4YBqN/rEYzCBRmsA=="; };
|
||||
"pngjs@npm:5.0.0" = { filename = "pngjs-npm-5.0.0-e8ba79f838-c074d8a94f.zip"; hash = "sha512-wHTYqU+3Xi3vqAIehTVr94SWiK99jOmZW3OU1XzRp3eycs+3xLzgi40Q5x5wjncXyB/VU6QT8hhAxUjsnUiTxg=="; };
|
||||
"postcss@npm:8.5.15" = { filename = "postcss-npm-8.5.15-8e6eef9b78-7f2e63ae22.zip"; hash = "sha512-fy5jriL75Dqs4b9lK9mdpOkHN8ZBlNSeUd3JzQ+eUf8oYafXNDebSU3v+gOogKXGXuxwvCnunrqnE23ePu6PMQ=="; };
|
||||
"proc-log@npm:7.0.0" = { filename = "proc-log-npm-7.0.0-d836af0493-b89c2d8626.zip"; hash = "sha512-uJwthiYE81/seVR3sMfjdv6rO6DU9NKRxOlZVnRCaXz0UaxVfQYjwcw4r0WngSi5g0EPOXoQxdOmf3bDPeR1Sw=="; };
|
||||
"qrcode@npm:1.5.4" = { filename = "qrcode-npm-1.5.4-7eaef32d9f-ae1d57c9cf.zip"; hash = "sha512-rh1Xyc/2CZY5pZC0MscbFeO9OQXOQ1Pm0AyV3ua7dpqPdz9qdXXswbjtR2v3nFE4pKZcs4DGgt47km1yBdNNEA=="; };
|
||||
"react-dom@npm:18.3.1" = { filename = "react-dom-npm-18.3.1-a805663f38-a752496c19.zip"; hash = "sha512-p1JJbBlB+Vjy6KxWI5FyKW/N3OE2XORSItBKGUfgzFVH3z6ER/hVqB1tOfAI18Muq0PbNxIHfwnj9nxIdJc+hQ=="; };
|
||||
"react-refresh@npm:0.17.0" = { filename = "react-refresh-npm-0.17.0-85b5aa925e-002cba9403.zip"; hash = "sha512-ACy6lAOEyZMACMC84mysl6nVaCvGIxEsImi6DBVRJ9nBeKmlzCIS1WAIjWDf1QPt2AhmmiX5s3fzFqMjYdCyPA=="; };
|
||||
"react@npm:18.3.1" = { filename = "react-npm-18.3.1-af38f3c1ae-283e8c5efc.zip"; hash = "sha512-KD6MXvzzeALJ0c52fzAt1Wndl6cNm7jHvnmnibmQJFHg0WM0sF1zKZsg8EjLw8fSiLu94QtwH6GU4gicI32+ow=="; };
|
||||
"require-directory@npm:2.1.1" = { filename = "require-directory-npm-2.1.1-8608aee50b-83aa76a7bc.zip"; hash = "sha512-g6p2p7wVMfaNksdaLKL1TxsBRjy1Zs8/vHh9Dei+MMnbwhHR1GvjSX2sV4X+KW8t0R1TGUWsKXMGQzV5eJZumQ=="; };
|
||||
"require-main-filename@npm:2.0.0" = { filename = "require-main-filename-npm-2.0.0-03eef65c84-db91467d9e.zip"; hash = "sha512-25FGfZ6tMRtBEcvXOk5n+ngg2u0piaMvcCN4WiZZAIxtEZdS2cSsARrgflN+uGUjrf+ZgExf2znNOgF/m0Abtg=="; };
|
||||
"rollup@npm:4.60.4" = { filename = "rollup-npm-4.60.4-e335ee554f-2734511579.zip"; hash = "sha512-JzRRFXnaIgQI7vuHe1EoF2fXkGUs7iXaj81JNslH49sUiCte2x0NXVv2DypxpYrn1ff0bBHj/fMxglOJU4hiQw=="; };
|
||||
"scheduler@npm:0.23.2" = { filename = "scheduler-npm-0.23.2-6d1dd9c2b7-26383305e2.zip"; hash = "sha512-JjgzBeJJZR1MWOZwXV+EJfFTIRrvlfFRYcFR97jeiF8kdRs3fkoLPdQszgmq0/h6Ydq3Y2hZwNibfa8aHipceA=="; };
|
||||
"semver@npm:6.3.1" = { filename = "semver-npm-6.3.1-bcba31fdbe-e3d79b6090.zip"; hash = "sha512-49ebYJBxyqeLy2zirYHHlmpGp0MdnVi4gAz6nLamNpmziZoOS8zjYWeihFeCEtmuaUK2kpukqlAVwHmmd1HULQ=="; };
|
||||
"semver@npm:7.8.4" = { filename = "semver-npm-7.8.4-9c59dc7144-81b7c296fd.zip"; hash = "sha512-gbfClv15J7gPZ/pRa3X6EBfKrIFneVMg3ijnbMvG9/AXY8MOzRDWoNj9CJcIqwVIpa67lLCHDpnCorRgCkY4mw=="; };
|
||||
"set-blocking@npm:2.0.0" = { filename = "set-blocking-npm-2.0.0-49e2cffa24-9f8c1b2d80.zip"; hash = "sha512-n4wbLYAIANC1id4Ud8dTSS3lwVSNSt5S9X8dH14Er1SBVU11zl5cQ9QAS4Cj63FDmNaQcCfcBTQXe3U5EZ9EVA=="; };
|
||||
"source-map-js@npm:1.2.1" = { filename = "source-map-js-npm-1.2.1-b9a47d7e1a-7bda1fc4c1.zip"; hash = "sha512-e9ofxMGX48b/F94biywg5gr4G2OlLLMuxaXWeiCn1CZR4ss06+k4M8WioIQ3fhdFWFT+4+IeeSXGSlG2pSsPrw=="; };
|
||||
"string-width@npm:4.2.3" = { filename = "string-width-npm-4.2.3-2c27177bae-1e525e92e5.zip"; hash = "sha512-HlJekuXq4K/XRUCG7tnIGO6EN0u4Ayj8QSF65y/18GXvHJ1/ctpB3kDHX6i7Pe5j2SNz/UkshCYKVSxjY5Kkew=="; };
|
||||
"strip-ansi@npm:6.0.1" = { filename = "strip-ansi-npm-6.0.1-caddc7cb40-1ae5f212a1.zip"; hash = "sha512-GuXyEqEm/lsWdwf3FpQkkOOTMIWl/2wAirl6svJyyAJdOqIYt71qslcpyiDMgc3bJSEC+HUeE0gqUZnoc2gJUg=="; };
|
||||
"tar@npm:7.5.16" = { filename = "tar-npm-7.5.16-628307afc6-4f37f3c4bd.zip"; hash = "sha512-TzfzxL0sonVf1zal3x1XPBqGjsGx6JM0aur6laxRD54v0UaUIL2GbMeQR5nlvUrGK11PA/4ndH1uHjc7RFBcXA=="; };
|
||||
"tinyglobby@npm:0.2.17" = { filename = "tinyglobby-npm-0.2.17-f2c3ddb917-7f7bb0f197.zip"; hash = "sha512-f3uw8ZfIi8SyDCMeDeykJAyjvzE6iPWn/uk6hyuElmpNUCIJR8BFWtB6YLOzYJYcW3/ZeSIq63Fqn5m0EgAuTA=="; };
|
||||
"typescript@npm:5.9.3" = { filename = "typescript-npm-5.9.3-48715be868-6bd7552ce3.zip"; hash = "sha512-a9dVLOOfl+cR21qgSPb5mVtT8cUvfYZnwavcFwDGinajCPV5zTCc5rU2Rt606aG+fIE6k7qvCijM1TajAnDhxQ=="; };
|
||||
"typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5" = { filename = "typescript-patch-6fda4d02cf-ad09fdf7a7.zip"; hash = "sha512-rQn996dWgU3OZbxgwWV7QNREUTRoWO6iMOEPLpWiidkYO24y5cEelazAzMIUtPNiidytS/GIawrbhNcR0zakMA=="; };
|
||||
"undici-types@npm:7.24.6" = { filename = "undici-types-npm-7.24.6-8759b28e34-d9cd8befb6.zip"; hash = "sha512-2c2L77ZDrJBGFcKAoJW6QkBTH2u0pedaIqdINjDKjT8QFtKras5s7aH2Ozotsv4Df6/hIdaRegGHVzqlSP94yg=="; };
|
||||
"undici@npm:6.27.0" = { filename = "undici-npm-6.27.0-00a86409ac-f88c3dae39.zip"; hash = "sha512-+Iw9rjlX2/nZPLSBRArO0xe9PElBtZFP6l77pRbVETiYjNtcdgBvC7EzfkHVbDRDNRBV1JLnOvJChSHDe6Knbw=="; };
|
||||
"update-browserslist-db@npm:1.2.3" = { filename = "update-browserslist-db-npm-1.2.3-de1d320326-13a00355ea.zip"; hash = "sha512-E6ADVeqCI4j2ivV0EM4yVZQdX7m3xJNCxHCaB8nyMLvvf3SZrgyn4N5TLnmoLMDE7b0SXxoyOhhFv5FO/d+L7A=="; };
|
||||
"vite@npm:5.4.21" = { filename = "vite-npm-5.4.21-12a8265f9b-468336a140.zip"; hash = "sha512-RoM2oUCfcotGQWDL8CZy5yJx+2iNDmBed2t0qJ0n4QKVCe7zo6bHVZKNgBHkdNvyNIJNBU0Hlgvl8jzRdrxy3g=="; };
|
||||
"which-module@npm:2.0.1" = { filename = "which-module-npm-2.0.1-90f889f6f6-087038e799.zip"; hash = "sha512-CHA455kmSer/psek8xWNW1OxTPW2wfDgQ9zPrLG6F50S8XVF1bhevZSkLOKApv5l0MvKtw9PxtqtHfroXg5qPg=="; };
|
||||
"which@npm:7.0.0" = { filename = "which-npm-7.0.0-638dd00e77-ca0b54f198.zip"; hash = "sha512-ygtU8Zj3i7xLfALjS9qNM1yzUuCttMvKHDexqVevOoeagsTCfKZSW8lC9UjYtk+BbvZSg2Cvnz3lX/ubl5tiDQ=="; };
|
||||
"wrap-ansi@npm:6.2.0" = { filename = "wrap-ansi-npm-6.2.0-439a7246d8-baad244e6e.zip"; hash = "sha512-uq0kTm4zM16iToblGGj+aCNibjo8iNmmZ0ZCr/8dNNmhVMkX50r42EX9JdFwxOqc9ppHEzw/NlbhJSs9Ri2fbA=="; };
|
||||
"y18n@npm:4.0.3" = { filename = "y18n-npm-4.0.3-ced95acdbc-308a2efd7c.zip"; hash = "sha512-MIou/XzClqssDzuShP1IJ74Bz+tkezuhgjDjpBbrG8iHrAUN6fjE/Z54VrLoJG4F0ZC1PJbFrY2MtW3/tvgQJA=="; };
|
||||
"yallist@npm:3.1.1" = { filename = "yallist-npm-3.1.1-a568a556b4-c66a5c46bc.zip"; hash = "sha512-xmpcRryJrxYlR29/Dy7DZTwaF5HS+UB8+0wrqBKh4cmUFBbXG6lxmHZTDjNAqZkl9pcUKYk3G3LZO57mKK/YwQ=="; };
|
||||
"yallist@npm:5.0.0" = { filename = "yallist-npm-5.0.0-8732dd9f1c-a499c81ce6.zip"; hash = "sha512-pJnIHObUodJg1OoPbUmrTaCWgeMsPwRy3uFmZ+1p0B2uY6O4F0WiS9eEduxPz4VhFMtIlqznOOAdo0ssQiNUFg=="; };
|
||||
"yargs-parser@npm:18.1.3" = { filename = "yargs-parser-npm-18.1.3-0ba9c4f088-25df918833.zip"; hash = "sha512-Jd+RiDNZKoP1Ln5Pkbp9e/qiuJHr9/6QGSPC7nl1NPI6F2kT/2/367wcwXJaBEzGplOf7Yv9ThO1sWN2h1+UmQ=="; };
|
||||
"yargs@npm:15.4.1" = { filename = "yargs-npm-15.4.1-ca1c444de1-f1ca680c97.zip"; hash = "sha512-8cpoDJdDM6WCJzKCXMp+lTBsWh53UOt7lzzm3E+XprCog3IDyLGU9GGWm/4fsRdtHUIwNmNShfYBCzkvpJirLQ=="; };
|
||||
"zod@npm:4.4.3" = { filename = "zod-npm-4.4.3-36e81f791e-7ea31b558e.zip"; hash = "sha512-fqMbVY6I+fr0TzHdGF4uHL9R/tMIF4f7lswlNHSbUMCs/G2n8JIqc1PtCS3TWMfVDCjqlslNBK9kGRvTMVLsow=="; };
|
||||
};
|
||||
|
||||
in overriddenProject
|
||||
+1551
-842
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
.terraform/
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
*.tfvars
|
||||
*.tfplan
|
||||
crash.log
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
Generated
+43
@@ -0,0 +1,43 @@
|
||||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/aws" {
|
||||
version = "5.100.0"
|
||||
constraints = "~> 5.0"
|
||||
hashes = [
|
||||
"h1:zef23ac/YWw9O2FepFWRs+my9iWWUkniL4dT4LnCKjU=",
|
||||
"zh:1a41f3ee26720fee7a9a0a361890632a1701b5dc1cf5355dc651ddbe115682ff",
|
||||
"zh:30457f36690c19307921885cc5e72b9dbeba369445815903acd5c39ac0e41e7a",
|
||||
"zh:42c22674d5f23f6309eaf3ac3a4f1f8b66b566c1efe1dcb0dd2fb30c17ce1f78",
|
||||
"zh:4cc271c795ff8ce6479ec2d11a8ba65a0a9ed6331def6693f4b9dccb6e662838",
|
||||
"zh:60932aa376bb8c87cd1971240063d9d38ba6a55502c867fdbb9f5361dc93d003",
|
||||
"zh:864e42784bde77b18393ebfcc0104cea9123da5f4392e8a059789e296952eefa",
|
||||
"zh:9750423138bb01ecaa5cec1a6691664f7783d301fb1628d3b64a231b6b564e0e",
|
||||
"zh:e5d30c4dec271ef9d6fe09f48237ec6cfea1036848f835b4e47f274b48bda5a7",
|
||||
"zh:e62bd314ae97b43d782e0841b13e68a3f8ec85cc762004f973ce5ce7b6cdbfd0",
|
||||
"zh:ea851a3c072528a4445ac6236ba2ce58ffc99ec466019b0bd0e4adde63a248e4",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/random" {
|
||||
version = "3.9.0"
|
||||
constraints = "~> 3.6"
|
||||
hashes = [
|
||||
"h1:8EQU5KSxezcjo/phRSe69rDOI0lk4pSaggj7FsskYp8=",
|
||||
"zh:03f1114cc20b8913523735ab76e0f0a2b16ce13c92923a53304bf85f07fc0dbc",
|
||||
"zh:105b678ee72322a3067f105d7e05e940f6143238f377f6e87ff4ec909246ac2a",
|
||||
"zh:55f3bbf13ea18cbace61a706566a80f25f33fe2b1780b6f3d7b582af2a05b6d2",
|
||||
"zh:63adf996db48f082f7a6351eb485e219cd88795fc71e6ec60a837263ab0d2cb1",
|
||||
"zh:7e99550738a4e3cc68b8a467714b0d69371025fe95e3326d5323d026d55653e9",
|
||||
"zh:8342b54af3a18a37e075eeae61be57f4de2ba71b35d95c5075d402dd2c1f289d",
|
||||
"zh:83ee18e32ac9dd5fc91298554b7c4cfa4c3a1db50f4c797945637cc93c0844ae",
|
||||
"zh:993ecc0adbf6bd535a59fbc9b735d8c33950e6f6eb5e621d750da9b71d65d80a",
|
||||
"zh:ad722bc59d4edbf1415e827fc007c0efe6e0e9462d5568bae20b34be1058a261",
|
||||
"zh:ae9448e1f87b2f9a6c5197a0e9862162ec6b137cb3a3835e11522995d8939e7c",
|
||||
"zh:bc9cdd3aac784f759125c6627f6f6416e8726a1c184eb9cf3e55b9edbc94c627",
|
||||
"zh:c8e35b89572ba1c40a9b20022e033a3395fb8d42e7604d50c900f193ba10382e",
|
||||
"zh:e2deaa8a9975ef81d9f62baed12c41286918b0a10908e0e031f13f69a3b730a1",
|
||||
"zh:ee39707557210a0ab1098aa357d2cdfe502e5a312d0dbdffb09d08facc4d3fc5",
|
||||
"zh:f81afe4eb63e8aa9e0ea71be6c990f0dc69cb360e7191c0742a991f4a5081b64",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# Quixos AWS Deployment
|
||||
|
||||
This stack provisions one public NixOS EC2 host for Quixos:
|
||||
|
||||
- `t4g.medium` by default
|
||||
- public IPv4 through an Elastic IP
|
||||
- Route 53 `A` record
|
||||
- Caddy on `80`/`443`
|
||||
- SSM Session Manager access by default
|
||||
- optional SSH
|
||||
- persistent EBS as a ZFS pool mounted at `/nix` and `/persist`
|
||||
- Quixos code loaded from the flake in Gitea
|
||||
- mutable package tree cloned into `/persist/srv/quixos/packages`
|
||||
|
||||
The root system is intended to stay rebuildable from NixOS config. Persistent state is explicit under `/persist`; the package tree is deployment state. The persistent EBS volume is initialized as a `quixos-persist` ZFS pool on first boot and carries `/nix` plus `/persist`.
|
||||
|
||||
Most AWS resource names include a generated deployment suffix, exposed as the `deployment_id` output. The Route 53 record uses the configured subdomain directly.
|
||||
|
||||
## Deploy
|
||||
|
||||
Create a vars file:
|
||||
|
||||
```hcl
|
||||
aws_region = "us-west-2"
|
||||
route53_zone_id = "Z..."
|
||||
subdomain = "quixos.example.com"
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
cd tofu
|
||||
tofu init
|
||||
./deploy.sh --packages-ref master
|
||||
```
|
||||
|
||||
`deploy.sh` resolves the package-tree ref. If `.quixos-package-tree.json` contains `quixos.rev`, that rev is deployed. Use `--quixos-ref master` to override it.
|
||||
|
||||
## Notes
|
||||
|
||||
The NixOS AMI lookup defaults to public owner `427812963091` and arm64 names matching `nixos/*-aarch64-linux`. If that does not match your region/account, set `nixos_ami_id`.
|
||||
|
||||
To inspect candidates manually:
|
||||
|
||||
```bash
|
||||
aws ec2 describe-images \
|
||||
--owners 427812963091 \
|
||||
--filters Name=architecture,Values=arm64 Name=virtualization-type,Values=hvm \
|
||||
--query 'Images[].{Name:Name,ImageId:ImageId,CreationDate:CreationDate}' \
|
||||
--output text | sort | tail
|
||||
```
|
||||
|
||||
The visualizer static derivation uses `project-visualizer/yarn-project.nix`, generated by `yarn-plugin-nixify`.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Quixos AWS Deployment TODO
|
||||
|
||||
- Build a Quixos-ready NixOS AMI instead of doing first-boot ZFS/NixOS bootstrapping.
|
||||
- Include ZFS kernel support in the booted generation.
|
||||
- Include SSM agent and baseline EC2 metadata handling.
|
||||
- Keep deployment-specific values in OpenTofu/user-data, not baked into the AMI.
|
||||
- Decide whether the persistent volume should be initialized by AMI boot logic, user-data, or a dedicated systemd unit.
|
||||
- Revisit the two-phase `amazon-init` bootstrap once the AMI exists; it should become much smaller or disappear.
|
||||
- Add a documented instance-debug checklist for SSM sessions.
|
||||
- Decide whether `/boot` should be persistent for this deployment model or left on replaceable root storage.
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
packages_ref="master"
|
||||
quixos_ref=""
|
||||
quixos_ref_is_rev=false
|
||||
package_tree_repo_url="https://gitea-external.egads.tutti.syntaxblitz.net/quixos-packages/packages.git"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: tofu/deploy.sh [options]
|
||||
|
||||
Options:
|
||||
--packages-ref <ref> Package-tree ref to deploy (default: master)
|
||||
--packages-repo <url> Package-tree repo URL
|
||||
--quixos-ref <ref> Override Quixos ref from package-tree metadata
|
||||
--quixos-rev <sha> Override Quixos ref and mark it as an exact rev
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--packages-ref)
|
||||
packages_ref="$2"
|
||||
shift 2
|
||||
;;
|
||||
--packages-repo)
|
||||
package_tree_repo_url="$2"
|
||||
shift 2
|
||||
;;
|
||||
--quixos-ref)
|
||||
quixos_ref="$2"
|
||||
quixos_ref_is_rev=false
|
||||
shift 2
|
||||
;;
|
||||
--quixos-rev)
|
||||
quixos_ref="$2"
|
||||
quixos_ref_is_rev=true
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
git -C "$tmpdir" init -q packages
|
||||
git -C "$tmpdir/packages" remote add origin "$package_tree_repo_url"
|
||||
if ! git -C "$tmpdir/packages" fetch --depth=1 origin "$packages_ref"; then
|
||||
git -C "$tmpdir/packages" fetch origin "$packages_ref"
|
||||
fi
|
||||
git -C "$tmpdir/packages" checkout --detach -q FETCH_HEAD
|
||||
|
||||
resolved_packages_ref="$(git -C "$tmpdir/packages" rev-parse HEAD)"
|
||||
|
||||
if [ -z "$quixos_ref" ] && [ -f "$tmpdir/packages/.quixos-package-tree.json" ]; then
|
||||
quixos_ref="$(
|
||||
node -e '
|
||||
const fs = require("fs");
|
||||
const meta = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
||||
process.stdout.write(meta?.quixos?.rev || "");
|
||||
' "$tmpdir/packages/.quixos-package-tree.json"
|
||||
)"
|
||||
if [ -n "$quixos_ref" ]; then
|
||||
quixos_ref_is_rev=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$quixos_ref" ]; then
|
||||
quixos_ref="master"
|
||||
quixos_ref_is_rev=false
|
||||
fi
|
||||
|
||||
cd "$script_dir"
|
||||
|
||||
tofu apply \
|
||||
-var "package_tree_repo_url=$package_tree_repo_url" \
|
||||
-var "package_tree_ref=$resolved_packages_ref" \
|
||||
-var "quixos_ref=$quixos_ref" \
|
||||
-var "quixos_ref_is_rev=$quixos_ref_is_rev"
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
data "aws_vpc" "default" {
|
||||
count = var.vpc_id == null ? 1 : 0
|
||||
default = true
|
||||
}
|
||||
|
||||
data "aws_subnets" "default" {
|
||||
count = var.subnet_id == null ? 1 : 0
|
||||
|
||||
filter {
|
||||
name = "vpc-id"
|
||||
values = [local.vpc_id]
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_subnet" "selected" {
|
||||
id = local.subnet_id
|
||||
}
|
||||
|
||||
data "aws_ami" "nixos" {
|
||||
count = var.nixos_ami_id == null ? 1 : 0
|
||||
most_recent = true
|
||||
owners = var.nixos_ami_owners
|
||||
|
||||
filter {
|
||||
name = "name"
|
||||
values = [var.nixos_ami_name_pattern]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "architecture"
|
||||
values = ["arm64"]
|
||||
}
|
||||
|
||||
filter {
|
||||
name = "virtualization-type"
|
||||
values = ["hvm"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "random_id" "deployment" {
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
locals {
|
||||
vpc_id = var.vpc_id == null ? data.aws_vpc.default[0].id : var.vpc_id
|
||||
subnet_id = var.subnet_id == null ? data.aws_subnets.default[0].ids[0] : var.subnet_id
|
||||
nixos_ami_id = var.nixos_ami_id == null ? data.aws_ami.nixos[0].id : var.nixos_ami_id
|
||||
domain = trimsuffix(var.subdomain, ".")
|
||||
resource_name = "${var.name}-${random_id.deployment.hex}"
|
||||
persist_device_id = replace(aws_ebs_volume.persist.id, "-", "")
|
||||
persist_device = "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_${local.persist_device_id}"
|
||||
quixos_flake_uri = "git+${var.quixos_repo_url}?${var.quixos_ref_is_rev ? "rev" : "ref"}=${urlencode(var.quixos_ref)}"
|
||||
ssh_keys = compact([var.ssh_public_key])
|
||||
tags = {
|
||||
Name = local.resource_name
|
||||
Deployment = random_id.deployment.hex
|
||||
Project = var.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "random_password" "control_secret" {
|
||||
length = 48
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "random_password" "state_context_secret" {
|
||||
length = 48
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "instance" {
|
||||
name = "${local.resource_name}-instance"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Action = "sts:AssumeRole"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "ec2.amazonaws.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "ssm" {
|
||||
role = aws_iam_role.instance.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
||||
}
|
||||
|
||||
resource "aws_iam_instance_profile" "instance" {
|
||||
name = "${local.resource_name}-instance"
|
||||
role = aws_iam_role.instance.name
|
||||
}
|
||||
|
||||
resource "aws_security_group" "instance" {
|
||||
name = "${local.resource_name}-instance"
|
||||
description = "Quixos instance ingress"
|
||||
vpc_id = local.vpc_id
|
||||
|
||||
ingress {
|
||||
description = "HTTP"
|
||||
from_port = 80
|
||||
to_port = 80
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
ingress {
|
||||
description = "HTTPS"
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
dynamic "ingress" {
|
||||
for_each = var.enable_ssh ? [1] : []
|
||||
content {
|
||||
description = "SSH"
|
||||
from_port = 22
|
||||
to_port = 22
|
||||
protocol = "tcp"
|
||||
cidr_blocks = length(var.ssh_allowed_cidrs) == 0 ? ["0.0.0.0/0"] : var.ssh_allowed_cidrs
|
||||
}
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_key_pair" "admin" {
|
||||
count = var.ssh_public_key == null ? 0 : 1
|
||||
key_name = "${local.resource_name}-admin"
|
||||
public_key = var.ssh_public_key
|
||||
}
|
||||
|
||||
resource "aws_instance" "host" {
|
||||
ami = local.nixos_ami_id
|
||||
instance_type = var.instance_type
|
||||
subnet_id = local.subnet_id
|
||||
vpc_security_group_ids = [aws_security_group.instance.id]
|
||||
iam_instance_profile = aws_iam_instance_profile.instance.name
|
||||
associate_public_ip_address = true
|
||||
key_name = var.ssh_public_key == null ? null : aws_key_pair.admin[0].key_name
|
||||
user_data_replace_on_change = true
|
||||
|
||||
user_data = templatefile("${path.module}/user-data.sh.tftpl", {
|
||||
admin_user = var.admin_user
|
||||
domain = local.domain
|
||||
enable_ssh = var.enable_ssh
|
||||
force_package_tree_ref_on_boot = var.force_package_tree_ref_on_boot
|
||||
package_tree_ref = var.package_tree_ref
|
||||
package_tree_repo_url = var.package_tree_repo_url
|
||||
persist_device = local.persist_device
|
||||
quixos_flake_uri = local.quixos_flake_uri
|
||||
ssh_authorized_keys = jsonencode(local.ssh_keys)
|
||||
control_secret = random_password.control_secret.result
|
||||
state_context_secret = random_password.state_context_secret.result
|
||||
})
|
||||
|
||||
root_block_device {
|
||||
volume_size = var.root_volume_size_gb
|
||||
volume_type = "gp3"
|
||||
}
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_ebs_volume" "persist" {
|
||||
availability_zone = data.aws_subnet.selected.availability_zone
|
||||
size = var.persist_volume_size_gb
|
||||
type = "gp3"
|
||||
|
||||
tags = merge(local.tags, {
|
||||
Name = "${local.resource_name}-persist"
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_volume_attachment" "persist" {
|
||||
device_name = "/dev/sdf"
|
||||
volume_id = aws_ebs_volume.persist.id
|
||||
instance_id = aws_instance.host.id
|
||||
}
|
||||
|
||||
resource "aws_eip" "host" {
|
||||
domain = "vpc"
|
||||
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_eip_association" "host" {
|
||||
instance_id = aws_instance.host.id
|
||||
allocation_id = aws_eip.host.id
|
||||
}
|
||||
|
||||
resource "aws_route53_record" "host" {
|
||||
zone_id = var.route53_zone_id
|
||||
name = local.domain
|
||||
type = "A"
|
||||
ttl = 60
|
||||
records = [aws_eip.host.public_ip]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
output "public_ip" {
|
||||
value = aws_eip.host.public_ip
|
||||
}
|
||||
|
||||
output "deployment_id" {
|
||||
value = random_id.deployment.hex
|
||||
}
|
||||
|
||||
output "resource_name" {
|
||||
value = local.resource_name
|
||||
}
|
||||
|
||||
output "domain" {
|
||||
value = local.domain
|
||||
}
|
||||
|
||||
output "url" {
|
||||
value = "https://${local.domain}"
|
||||
}
|
||||
|
||||
output "control_secret" {
|
||||
value = random_password.control_secret.result
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "state_context_secret" {
|
||||
value = random_password.state_context_secret.result
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ssm_start_session" {
|
||||
value = "aws ssm start-session --target ${aws_instance.host.id}"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
persist_device='${persist_device}'
|
||||
bootstrap_state_dir=/var/lib/quixos-bootstrap
|
||||
zfs_generation_marker="$bootstrap_state_dir/zfs-generation-booted"
|
||||
|
||||
mkdir -p "$bootstrap_state_dir"
|
||||
|
||||
if [ ! -e "$zfs_generation_marker" ]; then
|
||||
mkdir -p /etc/nixos
|
||||
cat > /etc/nixos/flake.nix <<'EOF'
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, ... }: {
|
||||
nixosConfigurations.quixos-bootstrap-zfs = nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
modules = [
|
||||
({ modulesPath, ... }: {
|
||||
imports = [ "$${modulesPath}/virtualisation/amazon-image.nix" ];
|
||||
|
||||
networking.hostName = "quixos";
|
||||
networking.hostId = "71756978";
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
boot.supportedFilesystems = [ "zfs" ];
|
||||
services.amazon-ssm-agent.enable = true;
|
||||
|
||||
users.users."${admin_user}" = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ];
|
||||
openssh.authorizedKeys.keys = ${ssh_authorized_keys};
|
||||
};
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
|
||||
system.stateVersion = "25.05";
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
EOF
|
||||
|
||||
nixos-rebuild boot --flake /etc/nixos#quixos-bootstrap-zfs
|
||||
touch "$zfs_generation_marker"
|
||||
systemctl reboot
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for _ in $(seq 1 600); do
|
||||
if [ -e "$persist_device" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ ! -e "$persist_device" ]; then
|
||||
echo "Persistent device did not appear: $persist_device" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat > /tmp/quixos-zfs-bootstrap.sh <<'EOS'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
persist_device="$1"
|
||||
pool="quixos-persist"
|
||||
|
||||
if ! zpool list -H "$pool" >/dev/null 2>&1; then
|
||||
if ! zpool import "$pool" >/dev/null 2>&1; then
|
||||
zpool create \
|
||||
-f \
|
||||
-o ashift=12 \
|
||||
-O acltype=posixacl \
|
||||
-O atime=off \
|
||||
-O compression=zstd \
|
||||
-O mountpoint=none \
|
||||
-O xattr=sa \
|
||||
"$pool" "$persist_device"
|
||||
|
||||
zfs create -o mountpoint=legacy "$pool/nix"
|
||||
zfs create -o mountpoint=legacy "$pool/persist"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p /mnt/quixos-nix /persist
|
||||
|
||||
if ! mountpoint -q /nix; then
|
||||
mount -t zfs "$pool/nix" /mnt/quixos-nix
|
||||
if [ ! -e /mnt/quixos-nix/store ]; then
|
||||
rsync -aHAX --numeric-ids /nix/ /mnt/quixos-nix/
|
||||
fi
|
||||
umount /mnt/quixos-nix
|
||||
mount -t zfs "$pool/nix" /nix
|
||||
fi
|
||||
|
||||
if ! mountpoint -q /persist; then
|
||||
mount -t zfs "$pool/persist" /persist
|
||||
fi
|
||||
EOS
|
||||
|
||||
nix --extra-experimental-features 'nix-command flakes' \
|
||||
shell nixpkgs#zfs nixpkgs#rsync \
|
||||
-c bash /tmp/quixos-zfs-bootstrap.sh "$persist_device"
|
||||
|
||||
mkdir -p /persist/secrets /persist/srv/quixos /persist/var/lib/quixos
|
||||
chmod 700 /persist/secrets
|
||||
|
||||
cat > /persist/secrets/quixos.env <<EOF
|
||||
QUIXOS_CONTROL_SECRET=${control_secret}
|
||||
QUIXOS_STATE_CONTEXT_SECRET=${state_context_secret}
|
||||
EOF
|
||||
chmod 600 /persist/secrets/quixos.env
|
||||
|
||||
mkdir -p /etc/nixos
|
||||
cat > /etc/nixos/flake.nix <<'EOF'
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
quixos.url = "${quixos_flake_uri}";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, quixos, ... }: {
|
||||
nixosConfigurations.quixos-host = nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
modules = [
|
||||
quixos.nixosModules.quixosHost
|
||||
({ ... }: {
|
||||
networking.hostName = "quixos";
|
||||
|
||||
users.users."${admin_user}" = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ];
|
||||
openssh.authorizedKeys.keys = ${ssh_authorized_keys};
|
||||
};
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
|
||||
services.quixosHost = {
|
||||
enable = true;
|
||||
domain = "${domain}";
|
||||
persistDevice = "${persist_device}";
|
||||
zfsPool = "quixos-persist";
|
||||
packageTreeRepo = "${package_tree_repo_url}";
|
||||
packageTreeRef = "${package_tree_ref}";
|
||||
forcePackageTreeRefOnBoot = ${force_package_tree_ref_on_boot};
|
||||
enableSsh = ${enable_ssh};
|
||||
};
|
||||
|
||||
system.stateVersion = "25.05";
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
EOF
|
||||
|
||||
nixos-rebuild switch --flake /etc/nixos#quixos-host
|
||||
@@ -0,0 +1,131 @@
|
||||
variable "aws_region" {
|
||||
type = string
|
||||
description = "AWS region."
|
||||
default = "us-west-2"
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
default = "quixos"
|
||||
description = "Name prefix for AWS resources."
|
||||
}
|
||||
|
||||
variable "route53_zone_id" {
|
||||
type = string
|
||||
description = "Existing public Route 53 hosted zone ID."
|
||||
default = "Z04472102ZT8IPD1I5PKX"
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = string
|
||||
description = "Subdomain to create inside route53_zone_id, such as quixos.example.com."
|
||||
default = "test-deployment.quixos.org"
|
||||
}
|
||||
|
||||
variable "vpc_id" {
|
||||
type = string
|
||||
default = null
|
||||
description = "VPC ID. Defaults to the account default VPC."
|
||||
}
|
||||
|
||||
variable "subnet_id" {
|
||||
type = string
|
||||
default = null
|
||||
description = "Subnet ID. Defaults to the first subnet in the selected/default VPC."
|
||||
}
|
||||
|
||||
variable "nixos_ami_id" {
|
||||
type = string
|
||||
default = null
|
||||
description = "NixOS aarch64 AMI ID. If null, OpenTofu searches for a recent NixOS AMI."
|
||||
}
|
||||
|
||||
variable "nixos_ami_owners" {
|
||||
type = list(string)
|
||||
default = ["427812963091"]
|
||||
description = "AWS account IDs to search for NixOS AMIs when nixos_ami_id is null."
|
||||
}
|
||||
|
||||
variable "nixos_ami_name_pattern" {
|
||||
type = string
|
||||
default = "nixos/*-aarch64-linux"
|
||||
description = "AMI name filter used when nixos_ami_id is null."
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
type = string
|
||||
default = "t4g.medium"
|
||||
description = "EC2 instance type."
|
||||
}
|
||||
|
||||
variable "root_volume_size_gb" {
|
||||
type = number
|
||||
default = 30
|
||||
description = "Root EBS volume size."
|
||||
}
|
||||
|
||||
variable "persist_volume_size_gb" {
|
||||
type = number
|
||||
default = 50
|
||||
description = "Persistent EBS volume size mounted at /persist."
|
||||
}
|
||||
|
||||
variable "quixos_repo_url" {
|
||||
type = string
|
||||
default = "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git"
|
||||
description = "Quixos source repository URL."
|
||||
}
|
||||
|
||||
variable "quixos_ref" {
|
||||
type = string
|
||||
default = "master"
|
||||
description = "Quixos branch, tag, or commit to deploy."
|
||||
}
|
||||
|
||||
variable "quixos_ref_is_rev" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Treat quixos_ref as an exact commit rev instead of a branch/tag ref."
|
||||
}
|
||||
|
||||
variable "package_tree_repo_url" {
|
||||
type = string
|
||||
default = "https://gitea-external.egads.tutti.syntaxblitz.net/quixos-packages/packages.git"
|
||||
description = "Package-tree super-repo URL."
|
||||
}
|
||||
|
||||
variable "package_tree_ref" {
|
||||
type = string
|
||||
default = "master"
|
||||
description = "Package-tree branch, tag, or commit to clone on first boot."
|
||||
}
|
||||
|
||||
variable "force_package_tree_ref_on_boot" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Force an existing package tree back to package_tree_ref on every boot."
|
||||
}
|
||||
|
||||
variable "enable_ssh" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Open SSH ingress and enable sshd. SSM Session Manager is enabled either way."
|
||||
}
|
||||
|
||||
variable "ssh_public_key" {
|
||||
type = string
|
||||
default = null
|
||||
description = "Optional SSH public key for the admin user."
|
||||
}
|
||||
|
||||
variable "ssh_allowed_cidrs" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "CIDRs allowed to SSH when enable_ssh is true."
|
||||
}
|
||||
|
||||
variable "admin_user" {
|
||||
type = string
|
||||
default = "quixos-admin"
|
||||
description = "Admin user created on the NixOS host."
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
terraform {
|
||||
required_version = ">= 1.8.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
}
|
||||
Reference in New Issue
Block a user