chore: reorg

This commit is contained in:
js0ny 2026-03-15 21:07:36 +00:00
parent e0a023da4f
commit 1b5c26bc04
54 changed files with 411 additions and 227 deletions

View file

@ -1,122 +0,0 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.programs.cider-2;
ciderConfigDir = "sh.cider.genten";
# --- 1. 辅助函数Cider Marketplace Fetcher ---
fetchCiderMarketplace = {
projectId,
version,
sha256,
}:
pkgs.runCommand "cider-extension-${toString projectId}" {
nativeBuildInputs = [pkgs.unzip];
src = pkgs.fetchurl {
url = "https://api.connect.cider.sh/marketplace/projects/${toString projectId}/versions/${version}/download";
inherit sha256;
};
} ''
mkdir -p $out
unzip $src -d $out
'';
# --- 2. 类型定义Entry Submodule ---
# 这个 submodule 定义了单一 Theme 或 Plugin 的配置结构
entryType = types.submodule ({
name,
config,
...
}: {
options = {
src = mkOption {
type = types.nullOr types.path;
default = null;
description = "Direct path or derivation to the theme/plugin directory.";
};
marketplace = mkOption {
default = null;
description = "Download from Cider Marketplace.";
type = types.nullOr (types.submodule {
options = {
id = mkOption {
type = types.int;
description = "Project ID (e.g. 10)";
};
version = mkOption {
type = types.str;
description = "Version string (e.g. 1.1.0)";
};
sha256 = mkOption {
type = types.str;
description = "SRI Hash or SHA256";
};
};
});
};
};
});
# --- 3. 逻辑转换函数 ---
# 将用户的 submodule 配置转换为最终的 store path
resolveEntry = name: entryCfg:
if entryCfg.src != null
then entryCfg.src
else if entryCfg.marketplace != null
then
fetchCiderMarketplace {
projectId = entryCfg.marketplace.id;
inherit (entryCfg.marketplace) version sha256;
}
else throw "programs.cider-2: Theme/Plugin '${name}' must have either 'src' or 'marketplace' defined.";
in {
# --- Options 定义 ---
options.programs.cider-2 = {
enable = mkEnableOption "Cider 2";
package = mkOption {
type = types.package;
default = pkgs.cider-2;
defaultText = literalExpression "pkgs.cider-2";
};
themes = mkOption {
# Key 是目录名 (对于 Marketplace theme 通常是 ID如 "12")
type = types.attrsOf entryType;
default = {};
description = "Themes configuration.";
};
plugins = mkOption {
# Key 是 literalId (如 "ch.kaifa.listenbrainz")
type = types.attrsOf entryType;
default = {};
description = "Plugins configuration.";
};
};
# --- Config 实现 ---
config = mkIf cfg.enable {
home.packages = [cfg.package];
xdg.configFile = let
# 生成 Theme 路径: .../themes/<Key>
mkTheme = name: entry:
nameValuePair
"${ciderConfigDir}/themes/${name}"
{source = resolveEntry name entry;};
# 生成 Plugin 路径: .../plugins/<Key (LiteralID)>
mkPlugin = name: entry:
nameValuePair
"${ciderConfigDir}/plugins/${name}"
{source = resolveEntry name entry;};
in
(mapAttrs' mkTheme cfg.themes) // (mapAttrs' mkPlugin cfg.plugins);
};
}

View file

@ -0,0 +1,188 @@
# mergetools.nix — Dual-mode config merge library
#
# Supports both Home Manager ("home") and NixOS system-level ("system") modes.
# All targets are ABSOLUTE paths regardless of mode.
#
# Usage (home mode — default):
# mergetools = import ../../modules/lib/mergetools.nix { inherit pkgs lib config; };
# myConfig = mergetools.mkMergedJson {
# name = "my-config";
# target = "${config.home.homeDirectory}/.config/app/config.json";
# settings = { key = "value"; };
# };
# # Then: imports = [ myConfig ];
#
# Usage (system mode):
# mergetools = import ../../modules/lib/mergetools.nix { inherit pkgs lib config; };
# myConfig = mergetools.mkMergedJson {
# name = "my-config";
# target = "/var/lib/myapp/config.json";
# settings = { key = "value"; };
# mode = "system";
# # owner = "myapp"; # default: "root"
# # group = "myapp"; # default: "root"
# # permissions = "0640"; # default: "0644"
# };
# # Then: imports = [ myConfig ];
{
config,
lib,
pkgs,
...
}: let
mkForceVar = force:
if force
then "true"
else "false";
# Derive a safe relative path for home.file from an absolute target.
# Strips the leading $HOME/ to get the relative portion.
# e.g., "/home/js0ny/.config/foo" -> ".config/foo"
stripHomePrefix = target: let
homeDir = config.home.homeDirectory;
homeDirSlash = homeDir + "/";
len = builtins.stringLength homeDirSlash;
in
if lib.hasPrefix homeDirSlash target
then builtins.substring len (builtins.stringLength target - len) target
else builtins.abort "mergetools (home mode): target '${target}' must start with '${homeDirSlash}'";
# ── Home mode ──────────────────────────────────────────────────────
mkHomeMerge = {
name,
target,
patchContent,
mergeCmdStr,
force,
emptyInit,
}: let
relTarget = stripHomePrefix target;
patchFile = "${relTarget}.nix-managed";
in {
home.file."${patchFile}".text = patchContent;
home.activation."merge-${name}" = lib.hm.dag.entryAfter ["writeBoundary"] ''
TARGET="${target}"
PATCH="$HOME/${patchFile}"
FORCE="${mkForceVar force}"
if [ -f "$TARGET" ] || [ "$FORCE" = "true" ]; then
if [ -f "$PATCH" ]; then
verboseEcho "Merging Nix managed config into: $TARGET"
run mkdir -p "$(dirname "$TARGET")"
if [ ! -f "$TARGET" ]; then
echo '${emptyInit}' > "$TARGET"
fi
run ${mergeCmdStr}
fi
else
verboseEcho "Skipping merge for $TARGET: file missing and force=false"
fi
'';
};
# ── System mode ────────────────────────────────────────────────────
mkSystemMerge = {
name,
target,
patchContent,
mergeCmdStr,
force,
emptyInit,
owner ? "root",
group ? "root",
permissions ? "0644",
}: let
patchFile = pkgs.writeText "${name}.nix-managed" patchContent;
in {
system.activationScripts."merge-${name}" = lib.stringAfter ["etc"] ''
TARGET="${target}"
PATCH="${patchFile}"
FORCE="${mkForceVar force}"
if [ -f "$TARGET" ] || [ "$FORCE" = "true" ]; then
echo "mergetools: Merging Nix managed config into: $TARGET"
mkdir -p "$(dirname "$TARGET")"
if [ ! -f "$TARGET" ]; then
echo '${emptyInit}' > "$TARGET"
fi
${mergeCmdStr}
chown ${owner}:${group} "$TARGET"
chmod ${permissions} "$TARGET"
else
echo "mergetools: Skipping merge for $TARGET: file missing and force=false"
fi
'';
};
# ── Dispatch ───────────────────────────────────────────────────────
mkMerge = {mode ? "home", ...} @ args: let
# Strip mode-irrelevant attrs before passing
homeArgs = builtins.removeAttrs args ["mode" "owner" "group" "permissions"];
systemArgs = builtins.removeAttrs args ["mode"];
in
if mode == "home"
then mkHomeMerge homeArgs
else if mode == "system"
then mkSystemMerge systemArgs
else builtins.abort "mergetools: unknown mode '${mode}', expected 'home' or 'system'";
# ── Public API ─────────────────────────────────────────────────────
mkMergedYaml = {
name,
target,
settings,
force ? false,
mode ? "home",
owner ? "root",
group ? "root",
permissions ? "0644",
}:
mkMerge {
inherit name target mode force owner group permissions;
patchContent = lib.generators.toYAML {} settings;
# $TARGET and $PATCH are shell variables set in the activation script
mergeCmdStr = ''${pkgs.yq-go}/bin/yq -i -oy -P ". *= load(\"$PATCH\")" "$TARGET"'';
emptyInit = "{}";
};
mkMergedJson = {
name,
target,
settings,
force ? false,
mode ? "home",
owner ? "root",
group ? "root",
permissions ? "0644",
}:
mkMerge {
inherit name target mode force owner group permissions;
patchContent = builtins.toJSON settings;
mergeCmdStr = ''${pkgs.yq-go}/bin/yq -i -o json -P --indent 2 ". *= load(\"$PATCH\")" "$TARGET"'';
emptyInit = "{}";
};
mkMergedIni = {
name,
target,
settings,
force ? false,
mode ? "home",
owner ? "root",
group ? "root",
permissions ? "0644",
}:
mkMerge {
inherit name target mode force owner group permissions;
patchContent = lib.generators.toINI {} settings;
mergeCmdStr = ''${pkgs.crudini}/bin/crudini --merge "$TARGET" < "$PATCH"'';
emptyInit = "";
};
in {
inherit mkMergedYaml mkMergedJson mkMergedIni;
}

View file

@ -0,0 +1,3 @@
{...}: {
boot.binfmt.emulatedSystems = ["aarch64-linux"];
}

View file

@ -0,0 +1,82 @@
# TODO: Configure restic backup
#
# This is a skeleton for services.restic.backups.
# NixOS provides `services.restic.backups.<name>` which creates systemd
# timers that run restic backup on a schedule.
#
# Steps to complete:
# 1. Add a restic repository password to sops secrets:
# In secrets/secrets.yaml, add:
# restic_repo_password: "your-secure-password"
# Then reference it below via sops.secrets."RESTIC_REPO_PASSWORD"
#
# 2. Choose a backup repository backend and set `repository`:
# - Local: "/mnt/backup/restic-repo"
# - SFTP: "sftp:user@host:/path/to/repo"
# - S3: "s3:https://s3.amazonaws.com/bucket-name"
# - B2: "b2:bucket-name:/"
# - Rclone: "rclone:remote:path"
#
# 3. For cloud backends, add credential env vars:
# - S3: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
# - B2: B2_ACCOUNT_ID, B2_ACCOUNT_KEY
# Store these in sops and pass them via `environmentFile`
#
# 4. Set `paths` to directories you want to back up
#
# 5. Set `exclude` patterns for files to skip
#
# 6. Set `timerConfig` for backup schedule (systemd timer syntax)
#
# 7. Optionally configure `pruneOpts` for automatic old snapshot cleanup
#
# 8. Initialize the repo: `restic -r <repository> init`
#
{config, ...}: {
services.restic.backups = {
# TODO: Rename this backup job as needed
main = {
# TODO: Set your restic repository URL (see step 2 above)
repository = "/mnt/backup/restic-repo";
# TODO: Add "RESTIC_REPO_PASSWORD" to sops secrets, then uncomment:
# passwordFile = config.sops.secrets."RESTIC_REPO_PASSWORD".path;
# TODO: For cloud backends, create an env file with credentials:
# environmentFile = config.sops.secrets."RESTIC_ENV".path;
# TODO: Set directories to back up
paths = [
# "/home/js0ny/Documents"
# "/home/js0ny/Projects"
# "/home/js0ny/Academia"
];
# TODO: Set exclude patterns
exclude = [
".cache"
".local/share/Trash"
"node_modules"
".venv"
"__pycache__"
"target" # Rust/Maven build artifacts
"result" # Nix build results
];
# TODO: Set backup schedule (default: daily at 3am)
timerConfig = {
OnCalendar = "daily";
Persistent = true; # Run missed backups after sleep/shutdown
RandomizedDelaySec = "1h";
};
# TODO: Uncomment to enable automatic snapshot pruning
# pruneOpts = [
# "--keep-daily 7"
# "--keep-weekly 4"
# "--keep-monthly 6"
# "--keep-yearly 2"
# ];
};
};
}