mirror of
https://github.com/js0ny/dotfiles.git
synced 2026-03-22 18:52:43 +00:00
chore: reorg
This commit is contained in:
parent
e0a023da4f
commit
1b5c26bc04
54 changed files with 411 additions and 227 deletions
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
188
nixcfgs/modules/lib/mergetools.nix
Normal file
188
nixcfgs/modules/lib/mergetools.nix
Normal 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;
|
||||
}
|
||||
3
nixcfgs/modules/nixos/core/binfmt.nix
Normal file
3
nixcfgs/modules/nixos/core/binfmt.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{...}: {
|
||||
boot.binfmt.emulatedSystems = ["aarch64-linux"];
|
||||
}
|
||||
82
nixcfgs/modules/nixos/services/restic.nix
Normal file
82
nixcfgs/modules/nixos/services/restic.nix
Normal 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"
|
||||
# ];
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue