Author SHA1 Message Date
github-actions[bot] d767b95281 chore(nix): update ghostty to 66fed652 2026-07-28 20:00:00 +00:00
moons-14andgithub-actions[bot] 28620c1d03 chore(nix): update nixpkgs to d2f1d98b (#54)
NixOS CI / Validate flake (push) Has been cancelled
NixOS CI / Build ${{ matrix.host }} (push) Has been cancelled
NixOS CI / Report main status (push) Has been cancelled
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-28 05:04:25 +09:00
moons-14andgithub-actions[bot] 1971108a7f chore(nix): update noctalia to 0cd9b22e (#52)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-28 05:04:14 +09:00
moons-14andgithub-actions[bot] 849e79e124 chore(nix): update niri-flake to ef7a2a3d (#51)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-28 05:03:37 +09:00
moons-14andgithub-actions[bot] 057c3cc5a3 chore(nix): update ghostty to 4c725242 (#49)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-28 05:03:16 +09:00
moons-14andgithub-actions[bot] 6cb15f3a54 chore(nix): update nixpkgs-unstable to d4221905 (#44)
NixOS CI / Validate flake (push) Has been cancelled
NixOS CI / Build ${{ matrix.host }} (push) Has been cancelled
NixOS CI / Report main status (push) Has been cancelled
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-27 04:43:36 +09:00
moons-14andgithub-actions[bot] 99132ef43f chore(nix): update nixpkgs to c76cb9d6 (#46)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-27 04:43:35 +09:00
moons-14andgithub-actions[bot] d40b5b4b00 chore(nix): update nixpkgs to 8db7e9c9 (#45)
NixOS CI / Validate flake (push) Has been cancelled
NixOS CI / Build ${{ matrix.host }} (push) Has been cancelled
NixOS CI / Report main status (push) Has been cancelled
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-26 04:42:32 +09:00
277 changed files with 8422 additions and 1494 deletions
@@ -0,0 +1,260 @@
name: Update Flake Input
description: Update one GitHub-backed Nix flake input and create a pull request
inputs:
input-name:
description: Name of the flake input to update
required: true
github-token:
description: Token used to query GitHub, push the update branch, and manage the pull request
required: true
base-branch:
description: Branch targeted by the pull request
required: false
default: main
minimum-release-age-days:
description: Minimum age of the target commit in days
required: false
default: "3"
skip-delay:
description: Update to the latest revision without applying the minimum age
required: false
default: "false"
auto-merge:
description: Enable squash auto-merge on the pull request
required: false
default: "true"
pr-labels:
description: Comma-separated labels to add when they already exist in the repository
required: false
default: dependencies,automated
outputs:
updated:
description: Whether flake.lock changed
value: ${{ steps.update.outputs.updated }}
current-version:
description: Previous locked revision
value: ${{ steps.update.outputs.current_version }}
new-version:
description: New locked revision
value: ${{ steps.update.outputs.new_version }}
pr-url:
description: URL of the created or updated pull request
value: ${{ steps.pull-request.outputs.pr_url }}
runs:
using: composite
steps:
- name: Update flake input
id: update
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
INPUT_NAME: ${{ inputs.input-name }}
MINIMUM_RELEASE_AGE_DAYS: ${{ inputs.minimum-release-age-days }}
SKIP_DELAY: ${{ inputs.skip-delay }}
run: |
set -euo pipefail
if [[ ! "$MINIMUM_RELEASE_AGE_DAYS" =~ ^[0-9]+$ ]]; then
echo "::error::minimum-release-age-days must be a non-negative integer"
exit 1
fi
node_key="$(
jq -er --arg input "$INPUT_NAME" '
.nodes.root.inputs[$input]
| if type == "array" then .[0] else . end
' flake.lock
)"
input_type="$(jq -r --arg node "$node_key" '.nodes[$node].locked.type // ""' flake.lock)"
input_owner="$(jq -r --arg node "$node_key" '.nodes[$node].locked.owner // ""' flake.lock)"
input_repo="$(jq -r --arg node "$node_key" '.nodes[$node].locked.repo // ""' flake.lock)"
input_ref="$(jq -r --arg node "$node_key" '.nodes[$node].original.ref // ""' flake.lock)"
current_rev="$(jq -er --arg node "$node_key" '.nodes[$node].locked.rev' flake.lock)"
if [ "$input_type" != "github" ] || [ -z "$input_owner" ] || [ -z "$input_repo" ]; then
echo "::error::${INPUT_NAME} is not a GitHub-backed flake input"
exit 1
fi
echo "Input: $INPUT_NAME"
echo "Repository: ${input_owner}/${input_repo}"
echo "Current revision: $current_rev"
if [ "$SKIP_DELAY" = "true" ]; then
nix flake update "$INPUT_NAME"
else
cutoff="$(date --utc --date="${MINIMUM_RELEASE_AGE_DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ)"
api_args=(
--method GET
"repos/${input_owner}/${input_repo}/commits"
-f "until=$cutoff"
-f per_page=1
)
if [ -n "$input_ref" ]; then
api_args+=(-f "sha=$input_ref")
fi
echo "Selecting the newest commit no later than $cutoff"
target_data="$(gh api "${api_args[@]}" --jq '.[0] | {sha: .sha, date: .commit.committer.date}')"
target_rev="$(jq -er '.sha' <<< "$target_data")"
target_date="$(jq -er '.date' <<< "$target_data")"
if [ "$target_rev" = "$current_rev" ]; then
echo "The input is already at the newest eligible revision"
{
echo "updated=false"
echo "current_version=$current_rev"
echo "new_version=$current_rev"
} >> "$GITHUB_OUTPUT"
exit 0
fi
current_date="$(
gh api "repos/${input_owner}/${input_repo}/commits/${current_rev}" \
--jq '.commit.committer.date'
)"
current_timestamp="$(date --date="$current_date" +%s)"
target_timestamp="$(date --date="$target_date" +%s)"
if [ "$target_timestamp" -lt "$current_timestamp" ]; then
echo "The newest eligible revision is older than the current revision; skipping"
{
echo "updated=false"
echo "current_version=$current_rev"
echo "new_version=$current_rev"
} >> "$GITHUB_OUTPUT"
exit 0
fi
nix flake update "$INPUT_NAME" \
--override-input "$INPUT_NAME" "github:${input_owner}/${input_repo}/${target_rev}"
fi
if git diff --quiet -- flake.lock; then
echo "No lock file changes were produced"
{
echo "updated=false"
echo "current_version=$current_rev"
echo "new_version=$current_rev"
} >> "$GITHUB_OUTPUT"
exit 0
fi
new_node_key="$(
jq -er --arg input "$INPUT_NAME" '
.nodes.root.inputs[$input]
| if type == "array" then .[0] else . end
' flake.lock
)"
new_rev="$(jq -er --arg node "$new_node_key" '.nodes[$node].locked.rev' flake.lock)"
echo "New revision: $new_rev"
{
echo "updated=true"
echo "current_version=$current_rev"
echo "new_version=$new_rev"
echo "input_owner=$input_owner"
echo "input_repo=$input_repo"
} >> "$GITHUB_OUTPUT"
- name: Create or update pull request
id: pull-request
if: steps.update.outputs.updated == 'true'
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
INPUT_NAME: ${{ inputs.input-name }}
BASE_BRANCH: ${{ inputs.base-branch }}
CURRENT_REV: ${{ steps.update.outputs.current_version }}
NEW_REV: ${{ steps.update.outputs.new_version }}
INPUT_OWNER: ${{ steps.update.outputs.input_owner }}
INPUT_REPO: ${{ steps.update.outputs.input_repo }}
MINIMUM_RELEASE_AGE_DAYS: ${{ inputs.minimum-release-age-days }}
SKIP_DELAY: ${{ inputs.skip-delay }}
AUTO_MERGE: ${{ inputs.auto-merge }}
PR_LABELS: ${{ inputs.pr-labels }}
run: |
set -euo pipefail
branch_suffix="$(tr -c 'A-Za-z0-9._-' '-' <<< "$INPUT_NAME" | sed 's/-$//')"
branch="update-flake-${branch_suffix}"
current_short="${CURRENT_REV:0:8}"
new_short="${NEW_REV:0:8}"
title="chore(nix): update ${INPUT_NAME} to ${new_short}"
if [ "$SKIP_DELAY" = "true" ]; then
age_note="The minimum release age check was skipped for this manually requested update."
else
age_note="The target commit is at least ${MINIMUM_RELEASE_AGE_DAYS} days old."
fi
body="$(
printf '%s\n' \
"Automated update of the \`${INPUT_NAME}\` flake input." \
"" \
"- Previous revision: [\`${current_short}\`](https://github.com/${INPUT_OWNER}/${INPUT_REPO}/commit/${CURRENT_REV})" \
"- New revision: [\`${new_short}\`](https://github.com/${INPUT_OWNER}/${INPUT_REPO}/commit/${NEW_REV})" \
"- Changes: [compare](https://github.com/${INPUT_OWNER}/${INPUT_REPO}/compare/${CURRENT_REV}...${NEW_REV})" \
"" \
"$age_note"
)"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add flake.lock
git switch -C "$branch"
git commit -m "$title"
git fetch origin "refs/heads/${branch}:refs/remotes/origin/${branch}" || true
git push --force-with-lease origin "HEAD:refs/heads/${branch}"
label_args=()
available_labels="$(gh label list --limit 100 --json name --jq '.[].name')"
IFS=',' read -ra requested_labels <<< "$PR_LABELS"
for label in "${requested_labels[@]}"; do
label="$(xargs <<< "$label")"
if [ -n "$label" ] && grep -Fxq "$label" <<< "$available_labels"; then
label_args+=(--add-label "$label")
elif [ -n "$label" ]; then
echo "::warning::Skipping missing pull request label: $label"
fi
done
pr_number="$(
gh pr list \
--state open \
--head "$branch" \
--json number \
--jq '.[0].number // empty'
)"
if [ -n "$pr_number" ]; then
gh pr edit "$pr_number" \
--title "$title" \
--body "$body" \
"${label_args[@]}"
else
gh pr create \
--base "$BASE_BRANCH" \
--head "$branch" \
--title "$title" \
--body "$body"
pr_number="$(
gh pr list \
--state open \
--head "$branch" \
--json number \
--jq '.[0].number'
)"
if [ "${#label_args[@]}" -gt 0 ]; then
gh pr edit "$pr_number" "${label_args[@]}"
fi
fi
if [ "$AUTO_MERGE" = "true" ]; then
gh pr merge "$pr_number" --auto --squash ||
echo "::warning::Auto-merge could not be enabled; check the repository merge settings"
fi
pr_url="$(gh pr view "$pr_number" --json url --jq '.url')"
echo "pr_url=$pr_url" >> "$GITHUB_OUTPUT"
echo "Pull request: $pr_url"
+150
View File
@@ -0,0 +1,150 @@
name: NixOS CI
on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
concurrency:
group: nixos-ci-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
validate:
name: Validate flake
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
hosts: ${{ steps.hosts.outputs.hosts }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Nix
uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
with:
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
access-tokens = github.com=${{ github.token }}
- name: Check flake and evaluate all outputs
run: nix flake check --all-systems --no-build --show-trace
- name: Discover NixOS hosts
id: hosts
run: |
hosts=$(nix eval --json '.#nixosConfigurations' --apply 'configs: builtins.attrNames configs')
echo "hosts=$hosts" >> "$GITHUB_OUTPUT"
echo "Discovered hosts: $hosts"
build:
name: Build ${{ matrix.host }}
needs: validate
if: ${{ needs.validate.outputs.hosts != '[]' }}
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
host: ${{ fromJSON(needs.validate.outputs.hosts) }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Nix
uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
with:
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
access-tokens = github.com=${{ github.token }}
- name: Build NixOS system
run: |
nix build ".#nixosConfigurations.${{ matrix.host }}.config.system.build.toplevel" \
--no-link \
--print-build-logs \
--show-trace
report-main-status:
name: Report main status
needs:
- validate
- build
if: ${{ always() && !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
issues: write
env:
CI_FAILED: ${{ needs.validate.result == 'failure' || needs.build.result == 'failure' }}
JOB_RESULTS: ${{ toJSON(needs) }}
steps:
- name: Create or resolve failure issue
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const title = "NixOS CI is failing on main";
const marker = "<!-- nixos-ci-main-failure -->";
const failed = process.env.CI_FAILED === "true";
const jobs = JSON.parse(process.env.JOB_RESULTS);
const failedJobs = Object.entries(jobs)
.filter(([, job]) => job.result === "failure")
.map(([name]) => `\`${name}\``)
.join(", ");
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
const commitUrl = `${context.serverUrl}/${owner}/${repo}/commit/${context.sha}`;
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
});
const existing = issues.find(
(issue) => !issue.pull_request && issue.title === title && issue.body?.includes(marker),
);
if (failed) {
const body = [
marker,
"The NixOS CI workflow failed after a push to `main`.",
"",
`- Failed jobs: ${failedJobs || "unknown"}`,
`- Commit: [\`${context.sha.slice(0, 7)}\`](${commitUrl})`,
`- Workflow run: [${context.runId}](${runUrl})`,
"",
"This issue is updated on subsequent failures and closed automatically after CI recovers.",
].join("\n");
if (existing) {
await github.rest.issues.update({
owner,
repo,
issue_number: existing.number,
body,
});
} else {
await github.rest.issues.create({ owner, repo, title, body });
}
return;
}
if (existing) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: existing.number,
body: `CI recovered in [workflow run ${context.runId}](${runUrl}).`,
});
await github.rest.issues.update({
owner,
repo,
issue_number: existing.number,
state: "closed",
state_reason: "completed",
});
}
+31
View File
@@ -0,0 +1,31 @@
name: Renovate
on:
schedule:
# Every day at 03:00 JST (18:00 UTC on the previous day).
- cron: "0 18 * * *"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: renovate
cancel-in-progress: false
jobs:
renovate:
name: Update GitHub Actions dependencies
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Use a PAT or GitHub App token so Renovate PRs trigger the other workflows.
- name: Run Renovate
uses: renovatebot/github-action@3064367f740a1a91cca218698a63902689cce200 # v46.1.20
with:
renovate-version: 43.262.1
token: ${{ secrets.RENOVATE_TOKEN }}
env:
LOG_LEVEL: info
RENOVATE_PLATFORM: github
RENOVATE_REPOSITORIES: ${{ github.repository }}
+106
View File
@@ -0,0 +1,106 @@
name: Update Flake Inputs
on:
schedule:
# Every day at 03:30 JST (18:30 UTC on the previous day).
- cron: "30 18 * * *"
workflow_dispatch:
inputs:
input:
description: Update only this flake input (empty updates all inputs)
required: false
type: string
skip-delay:
description: Update to the latest revision without the three-day delay
required: false
default: false
type: boolean
auto-merge:
description: Enable auto-merge after required checks pass
required: false
default: true
type: boolean
permissions:
contents: write
pull-requests: write
concurrency:
group: update-flake-inputs
cancel-in-progress: false
jobs:
discover:
name: Discover flake inputs
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.inputs.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build update matrix
id: inputs
env:
REQUESTED_INPUT: ${{ inputs.input }}
run: |
set -euo pipefail
github_inputs="$(
jq -c '
. as $lock
| [
$lock.nodes.root.inputs
| to_entries[]
| .key as $name
| (
.value
| if type == "array" then .[0] else . end
) as $node
| select($lock.nodes[$node].locked.type == "github")
| $name
]
| sort
' flake.lock
)"
if [ -n "$REQUESTED_INPUT" ]; then
if ! jq -e --arg input "$REQUESTED_INPUT" 'index($input) != null' <<< "$github_inputs" >/dev/null; then
echo "::error::Unknown or unsupported flake input: $REQUESTED_INPUT"
exit 1
fi
matrix="$(jq -cn --arg input "$REQUESTED_INPUT" '{input: [$input]}')"
else
matrix="$(jq -cn --argjson inputs "$github_inputs" '{input: $inputs}')"
fi
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "Update matrix: $matrix"
update:
name: Update ${{ matrix.input }}
needs: discover
if: ${{ needs.discover.outputs.matrix != '{"input":[]}' }}
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 4
matrix: ${{ fromJSON(needs.discover.outputs.matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
token: ${{ secrets.RENOVATE_TOKEN }}
- name: Install Nix
uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
with:
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
access-tokens = github.com=${{ secrets.RENOVATE_TOKEN }}
- name: Update input
uses: ./.github/actions/update-flake-input
with:
input-name: ${{ matrix.input }}
github-token: ${{ secrets.RENOVATE_TOKEN }}
skip-delay: ${{ github.event_name == 'workflow_dispatch' && inputs.skip-delay }}
auto-merge: ${{ github.event_name != 'workflow_dispatch' || inputs.auto-merge }}
+10 -13
View File
@@ -14,18 +14,15 @@
!/flake.nix
!/flake.lock
!/renovate.json
!shells/
!hosts/
!overlays/
!profiles/
!modules/
!docs/
!images/
!secrets/
!/shells/
!/flake/
!/overlays/
!/images/
!/secrets/
!/hosts/
!/libs/
!/modules/
!/tests/
+18
View File
@@ -0,0 +1,18 @@
keys:
- &admin_yubikey1 age1yubikey1qvy5y8kxqc63y7fk0tfv43u499a8z8q332ff087lythry9cc6hdxxkznc6t
- &host_x1g13 age12g85cuvg4kjfr79lqf5fx2k0d82tchrgv88xgkt7ukk2cfcsw98s2rjyat
- &host_ops age18rtm2dq2r62zvnhwdq0gkm24hu85r7zyleyk3jqv22zpdtw064eq7ay7dl
- &host_internal-app-01 age1mcp5gma7y0k59equhzqfsnn0ed335ljjtn0k08ua77htlxf0x54qsravch
creation_rules:
- path_regex: ^secrets/common/[^/]+\.ya?ml$
key_groups:
- age:
- *admin_yubikey1
- *host_x1g13
- *host_ops
- *host_internal-app-01
- path_regex: ^secrets/hosts/x1g13/[^/]+\.ya?ml$
key_groups:
- age:
- *admin_yubikey1
- *host_x1g13
+352 -503
View File
@@ -1,574 +1,423 @@
# Repository Guidelines
# AGENTS.md
## Project Structure and Ownership
NixOS + Home Manager flake (v2)。flake-parts ベース。
This repository manages NixOS, nix-darwin, and Home Manager configurations as a
flake. `flake.nix` defines inputs and delegates flake outputs through
flake-parts. Keep configuration with the component that owns it, rather than in
the root flake or an unrelated host.
## Commands
| Path | Responsibility |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `modules/applications/` | One software component, including GUI applications, window managers, desktop environments, CLI tools, and editors |
| `modules/systems/` | OS foundations such as Nix, boot, locale, Wayland, and networking |
| `modules/services/` | Daemons, long-running services, and configuration that involves permissions or user groups |
| `modules/hardwares/` | Reusable drivers, hardware families, and VM or WSL guest configuration |
| `modules/users/` | User identity and the user's NixOS-, nix-darwin-, and Home Manager-specific definitions |
| `modules/profiles/` | Purpose- or form-factor-oriented compositions of multiple units |
| `hosts/` | Machine-specific facts and the profiles or applications selected for each machine |
| `libs/` | Registry, unit discovery, and host construction logic |
| `overlays/` | Package replacements and additions |
| `shells/` | Development shells |
| `flake/` | Supporting flake outputs such as formatters, checks, and Git hooks |
```sh
nix flake update # flake の更新
nix fmt # フォーマット (treefmt: nixfmt, deadnix, statix, shfmt, shellcheck, prettier, yamlfmt, taplo, oxfmt)
nix develop .#dotnix # 開発シェル (pre-commit hooks, sops, age 入り)
sudo nixos-rebuild build --flake .#<host> # ビルド確認
sudo nixos-rebuild switch --flake .#<host> # 適用
```
Use **unit** as the generic internal term for a Registry-managed component and
**profile** for a unit that composes multiple units. Do not introduce a
`features/` layer. Window managers and desktop environments such as niri and
GNOME belong in `modules/applications/`; do not create a separate `desktop/`
module category.
## Conventions
Before adding configuration, decide whether it is owned by an application,
system foundation, service, hardware family, user, profile, or individual host.
Prefer the following placements:
- User: `moons`, locale: `ja_JP.UTF-8`, timezone: `Asia/Tokyo`
- Commits: conventional commits (`feat:`, `chore:`, `fix:`, etc.)
- リモート: `[email protected]:moons-14/dotfiles.git`
- `environment.systemPackages` にパッケージを追加する際はパッケージ名の横に簡単な説明をコメントで追加する
- 警告を抑制する設定は書かない。根本原因を調査して修正する
- home-manager の `sharedModules` 内で `lib.hm.*` を使う場合は、そのモジュール関数の引数で `lib` を受け取る必要がある(NixOSモジュールの `lib` とは別スコープ)
- `useGlobalPkgs = true` なので、home-manager 内で `nixpkgs.config` を設定しない(NixOSレベルで一括設定)
- `allowUnfree` は `hosts/default.nix` でグローバルに設定済み。各モジュールで個別設定しない
- pre-commit hooks が `git-hooks.nix` で設定済み(treefmt, gitleaks, deadnix, statix, shellcheck)。dev shell で自動有効化
| Configuration | Placement |
| ---------------------------------------------------- | ------------------------------------------------ |
| Nix settings shared by every system host | `modules/systems/nix/common.nix` |
| NixOS-only boot configuration | `modules/systems/boot/.../nixos.nix` |
| Ghostty-specific configuration | `modules/applications/ghostty/` |
| niri-specific configuration | `modules/applications/niri/` |
| Applications selected for the niri environment | `modules/profiles/interface/niri/meta.nix` |
| GNOME itself | `modules/applications/gnome/` |
| Docker daemon and Docker group membership | `modules/services/docker/nixos.nix` |
| Reusable ThinkPad-family configuration | `modules/hardwares/thinkpad/` |
| The laptop unit composition | `modules/profiles/platform/laptop/meta.nix` |
| The development-environment unit composition | `modules/profiles/workload/development/meta.nix` |
| A user's OS- and Home Manager-specific configuration | `modules/users/<name>/` |
| x1g13-specific monitor layout | `hosts/x1g13/home.nix` |
| x1g13-specific disk UUID | `hosts/x1g13/nixos.nix` |
| Package replacement or addition | `overlays/` |
| Formatter, checks, or Git hooks | `flake/` |
## Secrets
## Unit Discovery and Identity
- **sops-nix** + **age** + **YubiKey** で秘密管理
- `.sops.yaml` で暗号化ルール定義、`secrets/` に暗号化済み YAML を配置
- `modules/system/sops.nix` で sops-nix を import、`services.pcscd` (YubiKey用) を有効化
- `modules/system/secret.nix` で `sops.secrets` を宣言
- 平文の秘密をコミットしない(gitleaks が pre-commit で検出)
A directory below `modules/` is a unit if, and only if, it directly contains at
least one reserved file. Directories used only for classification, such as
`modules/applications/` or `modules/profiles/interface/`, are namespaces rather
than units when they have no reserved file of their own.
## Architecture
The Registry recognizes exactly these five reserved filenames:
```
flake.nix
├── hosts/default.nix # mkSystem でホスト構成を生成
│ ├── modules/ # 全モジュール(常にインポートされる)
│ │ ├── applications/ # アプリケーション設定
│ │ ├── system/ # システム設定
│ │ ├── drivers/ # ドライバ設定
│ │ ├── features/ # 機能バンドル(application/systemを束ねる)
│ │ └── integrations/ # home-manager 統合
│ └── profiles/ # ホストごとに有効化するfeaturesの組み合わせ
│ ├── interfaces/ # 操作インターフェース (CLI/GUI)
│ ├── platforms/ # ハードウェア (desktop/laptop/thinkpad/vm)
│ └── workloads/ # 用途 (dev/personal/srv/remote/secure-storage)
├── overlays/ # nixpkgs オーバーレイ
├── shells/ # devShells (dotnix)
└── flake/ # formatter.nix, git-hooks.nix
```
| File | Target and responsibility |
| ------------ | -------------------------------------------------------------------------------- |
| `common.nix` | System-side configuration fragment shared by NixOS and nix-darwin |
| `nixos.nix` | NixOS-only configuration fragment |
| `darwin.nix` | nix-darwin-only configuration fragment |
| `home.nix` | Home Manager configuration fragment |
| `meta.nix` | Registry descriptor for dependencies, external modules, and descriptive metadata |
### 評価の流れ
`common.nix` is never applied to Home Manager. OS-independent Home Manager
configuration still belongs in `home.nix`.
```
profile (featuresの有効化)
→ features (application/systemの有効化 + パッケージ追加)
→ applications (system.nix + home.nix)
→ system (NixOS設定)
```
The Registry derives a unit ID from the path relative to `modules/`, joining
path components with dots. Category names remain plural. It also derives the
enable option by prefixing the same components with `my` and appending `enable`.
## Layer Design
| Unit directory | Unit ID | Enable option |
| ---------------------------------- | ------------------------- | ----------------------------------- |
| `modules/applications/ghostty/` | `applications.ghostty` | `my.applications.ghostty.enable` |
| `modules/applications/niri/` | `applications.niri` | `my.applications.niri.enable` |
| `modules/systems/boot/uefi/` | `systems.boot.uefi` | `my.systems.boot.uefi.enable` |
| `modules/services/docker/` | `services.docker` | `my.services.docker.enable` |
| `modules/hardwares/qemu-guest/` | `hardwares.qemu-guest` | `my.hardwares.qemu-guest.enable` |
| `modules/users/moons/` | `users.moons` | `my.users.moons.enable` |
| `modules/profiles/interface/niri/` | `profiles.interface.niri` | `my.profiles.interface.niri.enable` |
### `modules/system/` — NixOS システム設定
Represent option paths as attribute-path lists, never as Nix source encoded in
strings or evaluated dynamically. Generate and read attributes with helpers such
as `lib.setAttrByPath` and `lib.getAttrFromPath`:
OS全体に影響する設定。`config.my.system.*` namespace。
- audio, boot, camera, disko, fingerprint, fonts, gc, hardware, locale, network, nix, power, secure-boot, sops, user, version, secret
- 常にインポートされるが、`enable` オプションで実効性を制御
- home-manager の設定は含めない
### `modules/applications/` — アプリケーション設定
個別に有効/無効を切り替えたいアプリケーション。`config.my.applications.*` namespace。
- NixOS設定のみ、または NixOS + Home Manager の両方
- Complex Module は system.nix と home.nix に分離
### `modules/drivers/` — ドライバ設定
ハードウェア固有のドライバ。`config.my.drivers.*` namespace。
### `modules/features/` — 機能バンドル
application や system より抽象度の高い「機能」単位で、複数の application/system を束ねて有効化する層。`config.my.features.*` namespace。
**features がやること:**
1. 複数の `my.applications.*.enable` / `my.system.*.enable` をまとめて有効化
2. application/system に属さないパッケージや設定を直接記述(`environment.systemPackages`、`home.activation` 等)
3. 追加オプションの受け渡し(例: tailscale の `acceptDns` を feature から application に passthrough)
**features がやらないこと:**
- 個別アプリケーションの詳細設定(これは applications 層の責務)
### `profiles/` — ホスト構成
features の `enable` を指定するだけの薄い層。ロジックは書かない。
`profiles/` から `my.system.*.enable` / `my.applications.*.enable` を直接指定しない。
必要な場合は必ず `modules/features/` に feature 層を作り、profile では `my.features.*.enable` のみ指定する。
| カテゴリ | 役割 | 例 |
| ------------- | -------------------- | ------------------------------------------ |
| `interfaces/` | 操作インターフェース | cli-minimal, cli-interactive, gui |
| `platforms/` | ハードウェア固有設定 | desktop, laptop, thinkpad, vm |
| `workloads/` | 用途・ワークロード | dev, personal, srv, remote, secure-storage |
profiles は継承可能:
```nix
# cli-interactive.nix
{
id = "applications.ghostty";
optionPath = [
"my"
"applications"
"ghostty"
"enable"
];
kind = "applications";
name = "ghostty";
relativePath = [
"applications"
"ghostty"
];
imports = [ ./cli-minimal.nix ];
my.features.cli.interactive.enable = true;
}
```
For `modules/profiles/interface/niri/`, path inference additionally gives
`kind = "profiles"`, `group = "interface"`, and `name = "niri"`. The path is
always authoritative for identity. `meta.nix` may provide display metadata such
as `description`, but it must not override or alias the unit ID.
### `hosts/` — ホスト定義
## Unit Files and Fragment Contract
Only reserved files that a unit actually needs should exist. The Registry
registers present fragments and does not require empty or placeholder files. All
of the following are valid units:
```text
# Home Manager only
modules/applications/ghostty/
├── home.nix
└── settings.nix
# NixOS only
modules/applications/gnome/
└── nixos.nix
# nix-darwin only
modules/systems/macos-defaults/
└── darwin.nix
# NixOS and Home Manager, with metadata and helpers
modules/applications/niri/
├── nixos.nix
├── home.nix
├── meta.nix
├── settings.nix
└── keybindings.nix
# Metadata only, commonly a composition profile
modules/profiles/interface/niri/
└── meta.nix
# System configuration shared by NixOS and nix-darwin
modules/systems/nix/
└── common.nix
```
If a unit has no `home.nix`, do not generate or apply a Home Manager module for
it. The same rule applies independently to `common.nix`, `nixos.nix`, and
`darwin.nix`.
### Configuration fragments
`common.nix`, `nixos.nix`, `darwin.nix`, and `home.nix` are configuration
fragments to which the Registry adds the enable condition. They return the
configuration for their class directly and must not define top-level `imports`,
`options`, or `config` attributes:
`mkSystem` でホストを定義。profiles のリストを指定:
```nix
# modules/services/docker/nixos.nix
{ primaryUser, ... }:
{
virtualisation.docker = {
enable = true;
autoPrune.enable = true;
nix-example = mkSystem {
host = "nix-example";
system = "x86_64-linux";
profiles = [
"interfaces/cli-interactive"
"platforms/vm"
"workloads/dev"
];
};
users.users.${primaryUser}.extraGroups = [
"docker"
];
}
```
Conceptually, the Registry supplies a wrapper like this:
`specialArgs` で `inputs`, `username`, `unstable`, `host` が全モジュールに渡される。
**注意:** `installer` ホストは `mkSystem` を使わず直接 `nixosSystem` で定義(インストーラ用)。
## Module Patterns
### Simple Module(NixOS のみ)
home-manager の設定を含まない。1ファイルで完結:
```nix
{ config, lib, ... }@args:
{
config =
lib.mkIf
config.my.services.docker.enable
(import dockerNixosPath args);
}
```
Do not add hand-written `mkEnableOption`, `cfg`, or `mkIf` boilerplate to each
unit. The Registry generates the enable option from the unit path and guards the
fragment.
### Helper files and directories
Every filename other than the five reserved names is an ordinary helper,
regardless of its extension. The Registry neither discovers nor automatically
imports helper files such as `settings.nix`, `keybindings.nix`, `packages.nix`,
`colors.nix`, `rules.nix`, or `helpers.nix`. Import a helper explicitly from the
reserved fragment that uses it:
```nix
# modules/applications/niri/home.nix
{ lib, ... }:
# modules/system/audio.nix
{ lib, config, ... }:
let
settings = import ./settings.nix;
keybindings = import ./keybindings.nix;
cfg = config.my.system.audio;
in
{
programs.niri.settings = lib.recursiveUpdate settings {
binds = keybindings;
options.my.system.audio = {
enable = lib.mkEnableOption "Audio support (PipeWire)";
};
config = lib.mkIf cfg.enable {
# NixOS設定をここに書く
};
}
```
Do not use a leading underscore to mark a file private; `_settings.nix` has no
special meaning. Give helper files descriptive names instead. When helpers are
configuration functions, pass the module arguments explicitly and combine them
with normal Nix expressions:
### Simple Module(Home Manager のみ)
NixOS設定を含まず、home-manager のみ:
```nix
# modules/applications/example/home.nix
{ lib, ... }@args:
lib.mkMerge [
(import ./packages.nix args)
(import ./settings.nix args)
]
```
The same discovery rule applies recursively to helper directories:
```text
modules/applications/niri/
├── home.nix
└── parts/
├── appearance.nix
└── keybindings.nix
```
Here `parts/` is not a unit because it directly contains no reserved file. A
helper directory that directly contains `home.nix` or another reserved file is
itself discovered as a unit, so never use reserved filenames inside a directory
that is intended to contain helpers only.
### Registry metadata
`meta.nix` is a Registry descriptor, not a NixOS, nix-darwin, or Home Manager
module. It may declare `description`, `includes`, and class-specific external
module imports:
```nix
# modules/applications/niri/meta.nix
{ inputs, ... }:
# modules/applications/zoom.nix
{ pkgs, lib, config, ... }:
let
cfg = config.my.applications.zoom;
in
{
description = "Niri Wayland compositor";
includes = [
"systems.wayland"
"services.xdg-portal"
];
imports.nixos = [
inputs.niri-flake.nixosModules.niri
];
imports.home = [
inputs.niri-flake.homeModules.niri
];
}
```
External modules, including modules supplied by flake inputs, define Nix module
options and therefore belong in `meta.nix` under `imports.nixos`,
`imports.darwin`, or `imports.home`. Do not place them in a configuration
fragment's top-level `imports`: the Nix module system resolves imports before a
configuration-level enable condition.
`includes` lists units to enable whenever the declaring unit is enabled. Always
use fully qualified unit IDs:
```nix
# modules/profiles/interface/niri/meta.nix
{
includes = [
"applications.niri"
"applications.ghostty"
"applications.noctalia"
"applications.vicinae"
"applications.nautilus"
"services.xdg-portal"
];
}
```
Never omit a prefix such as `applications.` merely because the including unit is
a profile. Fully qualified IDs make ownership explicit and allow moves, name
collisions, and missing dependencies to be detected. Do not enable another unit
by assigning to its enable option from a class fragment; declare the dependency
in `meta.includes`.
Application metadata should include only dependencies technically required for
the application to work. A profile owns the user's choice to adopt several
otherwise independent applications together. For example, niri may include the
Wayland foundation and xdg-desktop-portal as technical dependencies, while
`profiles.interface.niri` selects Ghostty, Vicinae, Noctalia, and Nautilus.
Ghostty must not depend on niri, and niri-specific keybindings remain owned by
the niri unit.
## Profiles
Profiles compose units by purpose or form factor; they do not replace clear
application, system, service, or hardware ownership. Suitable profile namespaces
include:
```text
modules/profiles/
├── base/
├── interface/
│ ├── niri/
│ ├── gnome/
│ ├── cli-minimal/
│ └── cli-interactive/
├── platform/
│ ├── laptop/
│ ├── thinkpad/
│ ├── desktop/
│ ├── vm/
│ └── wsl/
├── workload/
│ ├── development/
│ ├── personal/
│ ├── server/
│ └── remote/
└── security/
└── secure-boot/
```
The `desktop/` name above is a form-factor profile under `profiles/platform/`,
not a top-level module category.
A profile may consist only of `meta.includes`. Small settings that belong only
to the composition and have no useful independent identity may go directly in
the profile's `nixos.nix`, `darwin.nix`, or `home.nix`. Extract configuration to
an appropriate application, system, service, or hardware unit when any of these
conditions holds:
- It should be independently enableable.
- Multiple profiles reuse it.
- It owns separate configuration files.
- Its NixOS, nix-darwin, and Home Manager implementations differ.
- Other units depend on it.
- It involves a daemon, permissions, or user groups.
## Registry Responsibilities
Implement unit discovery with Nix standard functionality such as
`builtins.readDir`. Do not depend on an external indiscriminate auto-import
mechanism, and do not design the repository around `import-tree`. Registry logic
has these responsibilities:
1. Recursively visit directories below `modules/`.
2. Check only the five reserved filenames directly within each directory.
3. Register a directory as a unit when at least one reserved file exists there.
4. Derive the unit ID from the path relative to `modules/`.
5. Record only class fragments that exist.
6. Evaluate `meta.nix` as a descriptor only when it exists.
7. Exclude non-reserved files from discovery and implicit imports.
8. Generate every unit's `my.<unit path>.enable` option.
9. Enable included units from `meta.includes`.
10. Raise a clear evaluation error for a reference to a missing unit ID.
11. Apply only the fragments appropriate to the current host class.
12. Pass `home.nix` to Home Manager only for hosts that enable Home Manager.
A unit record may conceptually look like this; the implementation need not use
this exact representation:
```nix
{
id = "applications.ghostty";
directory = ./applications/ghostty;
fragments = {
common = null;
nixos = null;
darwin = null;
home = ./applications/ghostty/home.nix;
options.my.applications.zoom = {
enable = lib.mkEnableOption "Zoom video conferencing";
};
meta = { };
}
```
Keep the custom Registry limited to unit discovery, enable-option generation,
`includes`, and class dispatch. Do not reimplement general Nix imports or Nix
module evaluation. In particular, never infer a unit ID from metadata or
implicitly load a non-reserved file.
## Hosts and Class Dispatch
`hosts/` is outside Registry discovery. A host contains machine-specific facts,
differences, and unit selection, not reusable shared configuration. Appropriate
host-owned data includes:
- Generated `hardware-configuration.nix`.
- Disk UUIDs and disko target devices.
- Monitor identifiers, layout, and scale.
- MAC addresses and static IP addresses.
- Kernel parameters required by one machine only.
- `system.stateVersion`.
- Host-specific secret references.
- The profiles, applications, and other units enabled on that host.
A host registry may use a specification like this:
```nix
# hosts/default.nix
config = lib.mkIf cfg.enable {
home-manager.sharedModules = [
{
x1g13 = {
system = "x86_64-linux";
user = "moons";
path = ./x1g13;
profiles = [
"base"
"platform.thinkpad"
"interface.niri"
"interface.gnome"
"workload.development"
"workload.personal"
"security.secure-boot"
home.packages = with pkgs; [
zoom-us # Video conferencing application
];
applications = [
"codex-desktop"
];
units = [
"services.tailscale"
];
};
macbook = {
system = "aarch64-darwin";
user = "moons";
path = ./macbook;
profiles = [
"base"
"platform.laptop"
"workload.development"
"workload.personal"
];
applications = [
"ghostty"
}
];
};
}
```
Treat entries in `profiles` and `applications` as IDs relative to their
respective category roots. Add the category prefixes during host construction:
### Complex Module(NixOS + Home Manager)
ディレクトリ構造で system と home を分離:
```
modules/applications/<app>/
├── default.nix # マスター enable + imports
├── system.nix # NixOS 設定
├── home.nix # Home Manager 設定
└── (other files) # 設定ファイル等
```
**default.nix** — `enable` のみ宣言。`system.enable`/`homeManager.enable` は sub-file に任せる:
```nix
selectedUnits =
[ "users.${spec.user}" ]
++ map (name: "profiles.${name}") spec.profiles
++ map (name: "applications.${name}") spec.applications
++ spec.units or [ ];
{
lib,
config,
...
}:
let
cfg = config.my.applications.<name>;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.<name> = {
enable = lib.mkEnableOption "<description>";
};
config = lib.mkIf cfg.enable {
my.applications.<name>.system.enable = lib.mkDefault true;
my.applications.<name>.homeManager.enable = lib.mkDefault true;
};
}
```
`units` is an escape hatch for fully qualified service, system, hardware, or
other unit IDs. Prefer profiles for the main composition; do not make hosts list
large numbers of low-level units directly.
**system.nix:**
Host modules use normal Nix module semantics and are not Registry-guarded
configuration fragments. For example:
```nix
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.<name>.system;
in
{
options.my.applications.<name>.system = {
enable = lib.mkEnableOption "<name> system configuration";
};
```text
hosts/x1g13/
├── nixos.nix
├── home.nix
├── hardware-configuration.nix
└── disko.nix
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
<package> # Description
];
};
}
```
`hosts/x1g13/nixos.nix` may explicitly load `hardware-configuration.nix` and
`disko.nix` with the normal top-level Nix module `imports`. Do not confuse these
host imports with the prohibition on top-level `imports` in unit configuration
fragments.
**home.nix** — `home-manager.sharedModules` を使用:
Derive the system class from the host's `system`:
```nix
{
lib,
config,
...
}:
let
cfg = config.my.applications.<name>;
hmCfg = config.my.applications.<name>.homeManager;
in
{
options.my.applications.<name>.homeManager = {
enable = lib.mkEnableOption "<name> home-manager configuration";
};
- A Linux NixOS host receives `common.nix` and `nixos.nix`.
- A nix-darwin host receives `common.nix` and `darwin.nix`.
- A host with integrated Home Manager additionally receives `home.nix`.
config.home-manager.sharedModules = [
{
config = lib.mkIf hmCfg.enable {
# home-manager 設定をここに書く
};
}
];
}
```
Home Manager is additive, not a system class mutually exclusive with NixOS or
nix-darwin. The supported combinations are NixOS plus Home Manager and
nix-darwin plus Home Manager. If standalone Home Manager is supported later, add
an explicit host kind because `system` alone cannot distinguish it from NixOS.
**注意点:**
Do not duplicate reusable settings in hosts, but do not force genuinely
machine-specific values into a common unit merely to remove a host-local line.
- `default.nix` では `enable` のみ宣言。`system.enable`/`homeManager.enable` は `system.nix`/`home.nix` で宣言する(重複宣言エラー回避)
- home.nix で親の `cfg` を参照する場合は `cfg` と `hmCfg` の両方を let で定義
- Complex Module の home-manager 設定は `home-manager.sharedModules` で記述(`home-manager.users.<user>` は使わない)
## Coding Style and Implementation Rules
### Feature Module
Use two-space indentation in Nix files and let `nixfmt` decide layout. Prefer
small units, explicit imports, and descriptive kebab-case names, for example
`modules/services/media-server/nixos.nix`. Use camelCase for Nix attributes
unless an upstream option dictates otherwise. Shell snippets must pass `shfmt`
and `shellcheck`; YAML, TOML, and Markdown are formatted by the configured
treefmt tools.
**application/system を束ねる場合:**
When implementing or modifying modules:
```nix
# modules/features/services/container.nix
{ lib, config, ... }:
let
cfg = config.my.features.services.container;
in
{
options.my.features.services.container = {
enable = lib.mkEnableOption "Container runtime (Docker)";
};
- Do not create `features/` or a top-level `desktop/` module category.
- Do not add per-unit `mkEnableOption`, `cfg`, or `mkIf` boilerplate; the Registry
derives and guards enable options from paths.
- Put unit dependencies in `meta.includes`, not in direct assignments to another
unit's enable option from a class fragment.
- Do not assume any non-reserved file is discovered or loaded automatically.
- Do not require an `_` prefix for helper or private files.
- Do not create unused `common.nix`, `nixos.nix`, `darwin.nix`, `home.nix`, or
`meta.nix` files.
- Do not override a path-derived unit ID from `meta.nix`.
- Keep technical application dependencies separate from the applications a
personal environment chooses to combine in a profile.
- Do not rely on module-list ordering to override values. Use Nix module
priorities such as `lib.mkDefault`, `lib.mkForce`, `lib.mkBefore`, or
`lib.mkAfter` explicitly when required.
- Keep Registry responsibilities narrow; use normal Nix imports and module
evaluation for everything outside discovery, generated enables, includes, and
class dispatch.
config = lib.mkIf cfg.enable {
my.applications.docker.enable = true;
};
}
```
## Build, Test, and Development Commands
**パッケージを直接追加する場合(application/system に属さない):**
- `nix develop .#dotnix` enters the main development shell and installs the
repository's pre-commit hooks.
- `nix develop .#android` provides Android platform tools such as `adb` and
`fastboot`.
- `nix fmt` formats all supported files through treefmt.
- `nix flake check` evaluates flake outputs and runs configured checks.
- `pre-commit run --all-files` runs formatting, dead-code and static Nix checks,
shell linting, and secret scanning.
- `nix flake update` refreshes pinned inputs in `flake.lock`; review lockfile
changes before committing.
```nix
# modules/features/gui/capture.nix
{ pkgs, lib, config, ... }:
let
cfg = config.my.features.gui.capture;
in
{
options.my.features.gui.capture = {
enable = lib.mkEnableOption "Screen capture tools";
};
If direnv is installed, `direnv allow` activates the `dotnix` shell from `.envrc`
automatically.
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
slurp # Tool for selecting a region of the screen
grim # Screenshot tool for Wayland
];
};
}
```
## Testing Guidelines
**オプションを passthrough する場合:**
There is no separate unit-test suite. Before submitting changes, run
`nix flake check` and `pre-commit run --all-files`. For system-specific changes,
also build or evaluate the affected NixOS, nix-darwin, or Home Manager
configuration without switching the live machine. Never commit generated
secrets, `.age` plaintext, or local `.direnv/` state.
```nix
# modules/features/network/tailscale.nix
{
options.my.features.network.tailscale = {
enable = lib.mkEnableOption "Tailscale VPN";
acceptDns = lib.mkOption { type = lib.types.bool; default = false; };
};
For Registry changes, test discovery of each supported fragment combination,
dependency closure through `meta.includes`, missing-unit errors, and class
dispatch. Verify that helper files are ignored until explicitly imported and
that directories without a directly contained reserved file remain namespaces.
config = lib.mkIf cfg.enable {
my.applications.tailscale = {
enable = true;
inherit (cfg) acceptDns;
};
};
}
```
## Commit and Pull Request Guidelines
**home.activation を使う場合(identity 等):**
Recent history favors short, lowercase, imperative subjects such as `fix` and
`update action`; automated dependency commits use `chore(deps): ...`. Prefer a
specific summary that states the affected area, such as
`shells: add deployment tools`. Keep commits focused. Pull requests should
explain the motivation, list affected hosts or profiles, report validation
commands, and note any manual migration or secret-management steps. Include
screenshots only for visible desktop or application configuration changes.
```nix
# modules/features/identity/ssh-default-key.nix
config.home-manager.sharedModules = [
(
{ lib, ... }:
{
config = lib.mkIf cfg.enable {
home.activation.generateSshKey = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
# shell script here
'';
};
}
)
];
```
### Profile
features の有効化のみを記述:
```nix
# profiles/platforms/laptop.nix
{
my.features = {
boot.power.enable = true;
connect = {
wifi.enable = true;
bluetooth.enable = true;
};
gui.camera.enable = true;
identity.fingerprint.enable = true;
network.tailscale.enable = true;
};
}
```
## Adding New Features — Checklist
### 新しいアプリケーションを追加する場合
1. `modules/applications/` にモジュール作成(Simple or Complex)
2. `modules/applications/default.nix` の `imports` に追加
3. `modules/features/` の適切なカテゴリに feature を作成(既存の feature に追記でも可)
4. `modules/features/<category>/default.nix` の `imports` に追加
5. `profiles/` の適切な profile で feature を有効化
### 新しいシステム設定を追加する場合
1. `modules/system/` にモジュール作成(Simple Module)
2. `modules/system/default.nix` の `imports` に追加
3. `modules/features/` の適切なカテゴリに feature を作成
4. `modules/features/<category>/default.nix` の `imports` に追加
5. `profiles/` の適切な profile で feature を有効化
### 新しいホストを追加する場合
1. `hosts/<hostname>/default.nix` を作成(`hardware-configuration.nix` を import)
2. `hosts/default.nix` の `flake.nixosConfigurations` に `mkSystem` で追加
3. profiles のリストを指定
## Key Technical Notes
- **nixpkgs channel**: `nixos-26.05` (stable) + `nixpkgs-unstable`
- **unstable パッケージ**: `specialArgs.unstable` 経由で参照(`unstable.<pkg>`)
- **llm-agents**: `inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.<name>` で参照。overlay も `hosts/default.nix` でグローバルに適用
- **nixvim**: `nixpkgs.source = pkgs.path` と `nixpkgs.config.allowUnfree = true` を vim/home/default.nix で設定
- **home-manager**: `useGlobalPkgs = true`, `useUserPackages = true`, `backupFileExtension = "backup"`
- **hostname**: `specialArgs.host` から `modules/system/network/default.nix` で `networking.hostName` に設定
- **stateVersion**: `config.my.stateVersions.nixos` / `config.my.stateVersions.homeManager` で管理(`modules/system/version.nix`)
- **disko**: `modules/system/disko.nix` で disk パーティション管理。ホスト固有の `disko.nix` を import
- **stylix**: `inputs.stylix` でテーマ管理
+190 -2
View File
@@ -1,3 +1,191 @@
# moons14 dotfiles
# dotfiles
My NixOS + Home Manager configurations build with flake.
My NixOS + Home Manager configurations built with flake-parts.
## Overview
- **OS**: NixOS 26.05 (stable) + nixpkgs-unstable
- **Window Manager**: Niri (Wayland)
- **Shell**: Zsh
- **Terminal**: Ghostty
- **Editor**: Neovim (nixvim), VSCode
- **Launcher**: Vicinae
- **Theme**: Stylix (Dracula)
- **Secrets**: sops-nix + age + YubiKey
## Hosts
| Host | Description | Profiles |
| ------------- | --------------- | -------------------------------------------- |
| `x1g13` | ThinkPad laptop | gui, thinkpad, dev, personal, secure-storage |
| `nix-example` | VM | cli-interactive, vm, dev, remote |
| `installer` | NixOS installer | (standalone) |
## Directory Structure
```
.
├── flake.nix # Flake inputs and outputs
├── flake/
│ ├── formatter.nix # treefmt configuration (nixfmt, deadnix, statix, etc.)
│ └── git-hooks.nix # pre-commit hooks
├── hosts/
│ ├── default.nix # mkSystem helper and host definitions
│ ├── x1g13/ # ThinkPad host config
│ ├── nix-example/ # VM host config
│ └── installer/ # Installer ISO config
├── modules/
│ ├── applications/ # Application configs (NixOS + Home Manager)
│ │ ├── niri/ # Wayland compositor
│ │ ├── ghostty/ # Terminal emulator
│ │ ├── vim/ # Neovim (nixvim)
│ │ ├── vscode/ # VSCode
│ │ ├── zsh/ # Shell
│ │ ├── zellij/ # Terminal multiplexer
│ │ ├── git/ # Git config
│ │ ├── docker.nix # Container runtime
│ │ ├── tailscale.nix # VPN
│ │ ├── claude/ # Claude Code
│ │ ├── opencode.nix # OpenCode
│ │ └── ... # chrome, discord, zoom, slack, etc.
│ ├── system/ # NixOS system configs
│ │ ├── audio.nix # PipeWire
│ │ ├── boot/ # Bootloader (systemd-boot, lanzaboote)
│ │ ├── disko.nix # Disk partitioning
│ │ ├── fonts.nix # Fonts
│ │ ├── network/ # Networking
│ │ ├── sops.nix # Secrets management
│ │ ├── user/ # User accounts
│ │ └── ...
│ ├── features/ # Feature bundles (abstraction layer)
│ │ ├── application/ # browser, communication
│ │ ├── boot/ # UEFI
│ │ ├── cli/ # base, interactive, shell
│ │ ├── connect/ # WiFi, Bluetooth
│ │ ├── dev/ # agent, nix, python, bun, java, arduino
│ │ ├── gui/ # desktop, terminal, audio, editor, capture
│ │ ├── identity/ # SSH key, fingerprint
│ │ ├── network/ # Tailscale
│ │ ├── services/ # container, KDE
│ │ └── storage/ # disko
│ ├── drivers/ # Hardware drivers (Intel)
│ └── integrations/ # Home Manager integration
├── profiles/
│ ├── interfaces/ # cli-minimal, cli-interactive, gui
│ ├── platforms/ # desktop, laptop, thinkpad, vm
│ └── workloads/ # dev, personal, srv, remote, secure-storage
├── overlays/ # nixpkgs overlays
├── shells/ # devShells (pre-commit hooks, sops, age)
├── secrets/ # Encrypted secrets (sops)
└── docs/ # Documentation
```
## Architecture
```
profile (enable features)
→ features (bundle applications/system + add packages)
→ applications (system.nix + home.nix)
→ system (NixOS config)
```
### Module Patterns
**Simple Module** — Single file for NixOS-only or Home Manager-only configs:
```nix
{ lib, config, ... }:
let cfg = config.my.system.audio;
in {
options.my.system.audio.enable = lib.mkEnableOption "Audio";
config = lib.mkIf cfg.enable { ... };
}
```
**Complex Module** — Directory with `default.nix`, `system.nix`, `home.nix`:
```
modules/applications/<app>/
├── default.nix # Master enable + imports
├── system.nix # NixOS config
└── home.nix # Home Manager config (sharedModules)
```
**Feature Module** — Bundles multiple applications/system modules:
```nix
{ lib, config, ... }:
let cfg = config.my.features.gui.desktop;
in {
options.my.features.gui.desktop.enable = lib.mkEnableOption "Desktop";
config = lib.mkIf cfg.enable {
my.applications = { niri.enable = true; gtk.enable = true; ... };
};
}
```
**Profile** — Thin layer that only enables features:
```nix
{
my.features = {
gui.desktop.enable = true;
dev.agent.enable = true;
};
}
```
## Packages
### CLI
- **Shell**: Zsh with zoxide, direnv
- **Terminal multiplexer**: Zellij
- **Editor**: Neovim (nixvim)
- **Tools**: ripgrep, curl, wget, htop, btop, fastfetch, unzip, unrar
### GUI
- **Compositor**: Niri
- **Terminal**: Ghostty, Alacritty
- **Editor**: VSCode
- **Browser**: Chrome
- **Launcher**: Vicinae
- **File manager**: Nautilus
- **Communication**: Discord, Zoom, Slack
### Development
- **AI agents**: Claude Code, Codex, OpenCode, Grok
- **Languages**: Python, Bun (JavaScript/TypeScript), Java, Arduino
- **Container**: Docker
- **Nix**: nh, nixfmt, deadnix, statix
### System
- **VPN**: Tailscale
- **Secrets**: sops-nix, age
- **Boot**: systemd-boot, lanzaboote (Secure Boot)
- **Disk**: disko
- **Theme**: Stylix
## Commands
```sh
nix flake update # Update flake inputs
nix fmt # Format code
nix develop .#dotnix # Enter dev shell
sudo nixos-rebuild switch --flake .#<host> # Apply config
sudo nixos-rebuild build --flake .#<host> # Build without applying
```
## Inspired
- [Zaney/zaneyos](https://gitlab.com/Zaney/zaneyos)
- [fa0311/.zshrc](https://gist.github.com/fa0311/d37d53ff39c73c54c883379e8e3732df)
- [AsianLovesLinux/Niri](https://github.com/AsianLovesLinux/Niri)
- [natsukium/dotfiles](https://github.com/natsukium/dotfiles)
- [dracula](https://github.com/dracula)
- [akazdayo/nix-configs](https://github.com/akazdayo/nix-configs)
- [yutakobayashidev/dotnix](https://github.com/yutakobayashidev/dotnix)
- [kawaemon/dotfiles](https://github.com/kawaemon/dotfiles)
+68
View File
@@ -0,0 +1,68 @@
# Fingerprint Commands
This note covers the basic `fprintd` commands used by the fingerprint module.
## Enroll a new fingerprint
Register a fingerprint for the current user:
```sh
fprintd-enroll $USER
```
To enroll a specific finger, pass the finger name:
```sh
fprintd-enroll -f right-index-finger $USER
```
Common finger names include:
- `left-thumb`
- `left-index-finger`
- `right-thumb`
- `right-index-finger`
Follow the prompts and swipe or touch the sensor until enrollment completes.
## List enrolled fingerprints
Show fingerprints registered for the current user:
```sh
fprintd-list $USER
```
You can also list fingerprints for another user:
```sh
fprintd-list <username>
```
## Delete fingerprints
Delete one enrolled fingerprint for the current user:
```sh
fprintd-delete
```
Delete all enrolled fingerprints for the current user:
```sh
fprintd-delete $USER
```
Delete fingerprints for another user:
```sh
fprintd-delete <username>
```
## Verify authentication
Test fingerprint authentication for the current user:
```sh
fprintd-verify
```
+157
View File
@@ -0,0 +1,157 @@
# NixOSインストール手順
## 事前準備
1. [ISOビルド](iso-build.md)を参照してISOを作成
2. USBに書き込んで対象マシンでブート
## ネットワーク接続
### 有線LAN
DHCPで自動設定される。
### WiFi(有線が使えない場合)
```bash
nmcli device wifi connect <SSID> --ask
```
## SSH接続
コンソールに表示されたIPアドレスに接続:
```bash
ssh root@<ip-address>
```
## インストール手順
### 1. dotfilesのクローン
```bash
git clone [email protected]:moons-14/dotfiles.git ~/dotfiles
```
### 2. SSHホストキーの生成
新しいホスト用のSSHホストキーを生成:
```bash
ssh-keygen -t ed25519 -f /tmp/ssh_host_ed25519_key -N ""
```
### 3. age公開鍵の取得
SSHホストキーからage公開鍵を取得:
```bash
ssh-to-age -i /tmp/ssh_host_ed25519_key.pub
```
出力されたage公開鍵をコピー。
### 4. .sops.yamlの編集
```bash
cd ~/dotfiles
vim .sops.yaml
```
以下を追加:
```yaml
keys:
- &host_<hostname> <age公開鍵>
creation_rules:
- path_regex: ^secrets/hosts/<hostname>/[^/]+\.ya?ml$
key_groups:
- age:
- *admin_yubikey1
- *host_<hostname>
```
### 5. シークレットの再暗号化
```bash
sops updatekeys secrets/common/system.yaml
sops updatekeys secrets/hosts/<hostname>/*.yaml
```
### 6. disko設定の作成
新しいホスト用の`hosts/<hostname>/disko.nix`を作成。
#### シンプル構成(暗号化なし)
```nix
_:
{
disko.enableConfig = true;
disko.devices.disk.main = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
}
```
#### LUKS暗号化 + btrfs
`hosts/x1g13/disko.nix`を参照。
### 7. ディスクのパーティション
```bash
cd ~/dotfiles
nix run github:nix-community/disko -- --mode disko hosts/<hostname>/disko.nix
```
### 8. ホストキーのコピー
```bash
mkdir -p /mnt/etc/ssh
cp /tmp/ssh_host_ed25519_key* /mnt/etc/ssh/
chmod 600 /mnt/etc/ssh/ssh_host_ed25519_key
```
### 9. NixOSインストール
```bash
nixos-install --flake ~/dotfiles#<hostname>
```
### 10. 再起動
```bash
reboot
```
## インストール後の確認
- SSHでログインできるか
- sopsシークレットが復号できるか
- diskoでパーティションが正しく設定されているか
+26
View File
@@ -0,0 +1,26 @@
# カスタムISOビルド
## ビルド
```bash
nix build .#nixosConfigurations.installer.config.system.build.isoImage
```
## ISO書き込み
```bash
# USBデバイスの確認
lsblk
# 書き込み(/dev/sdXは実際のデバイスに置き換える)
sudo dd if=./result/nixos-minimal-*.iso of=/dev/sdX bs=4M status=progress
sync
```
## ISOの特徴
- SSH鍵認証でrootログイン可能
- 有線LANはDHCPで自動設定
- WiFiは`nmcli`で手動設定可能
- disko/sops/ageなどのツールを内蔵
- ブート時にIPアドレスとヘルプを表示
+17
View File
@@ -0,0 +1,17 @@
# Generate Sops key file
```bash
mkdir -p ~/.config/sops/age
chmod 700 ~/.config/sops/age
age-plugin-yubikey --identity --slot 1 \
> ~/.config/sops/age/yubikey-identity.txt
chmod 600 ~/.config/sops/age/yubikey-identity.txt
```
## Edit sops file
```bash
sops secrets/common/system.yaml
```
Generated
+61 -40
View File
@@ -107,11 +107,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1785087211,
"narHash": "sha256-lmIZA1LPcCB7vPagN3lS5mcSwVgN5GAvOxHS7CjqWzI=",
"lastModified": 1784823421,
"narHash": "sha256-/EM7Cr2Ai0VjNbKv+eIW0+iXT/NBW6WbPkCbf9w+u/o=",
"owner": "ilysenko",
"repo": "codex-desktop-linux",
"rev": "bc7b92cf38c74b49dbff568257542eabbc62cf55",
"rev": "efcf40b5ab41323c8fa8eef5526c6a50c45fd8cd",
"type": "github"
},
"original": {
@@ -376,11 +376,11 @@
"zon2nix": "zon2nix"
},
"locked": {
"lastModified": 1785080636,
"narHash": "sha256-ACiF5qaL4yiLSMesIPi4+4Aftw5QiMSR2qzp+5hlMvo=",
"lastModified": 1784994897,
"narHash": "sha256-JYe8CS+vFY63dE1yYXKgO8a2D7Rt4Wi4JsS7rxamZRE=",
"owner": "moons-14",
"repo": "ghostty",
"rev": "f11e5199a6bd2a2e1a536d3c225a7d9a39827ea4",
"rev": "66fed652a148cda9d8ea90b1b34ae9768871dbd9",
"type": "github"
},
"original": {
@@ -469,6 +469,27 @@
"type": "github"
}
},
"home-manager_3": {
"inputs": {
"nixpkgs": [
"nix-hazkey",
"nixpkgs"
]
},
"locked": {
"lastModified": 1778444552,
"narHash": "sha256-f18pIiR9q/p1vHY93gmAum7aHhQOG49oGvAB9+lptRo=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "dcebe66f958673729896eec2de4abfd86ef22d21",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"lanzaboote": {
"inputs": {
"crane": "crane",
@@ -501,11 +522,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1785094920,
"narHash": "sha256-RPhNusC9jaFUkdgb6COZofPz1QLqmm8k2k9Wh7KcQII=",
"lastModified": 1784838505,
"narHash": "sha256-v9wgz4KSm259C+Yb9/Y/tPyylXto/bTyOQyiV599Ads=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "56dea2a2de7d50461c502db975e766c2c2d71e84",
"rev": "b358b1d5b458d6bd9814d02b70fcd3c0cd61886f",
"type": "github"
},
"original": {
@@ -570,24 +591,24 @@
"type": "github"
}
},
"nix-darwin": {
"nix-hazkey": {
"inputs": {
"home-manager": "home-manager_3",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1783744694,
"narHash": "sha256-2cp6N3rrwnGYLTx9l6N+NI+kwrCWxvJUbj5WJhvB29A=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "c3e90c89649b07d1a96e4b9dd6cd0d6e44b91a74",
"lastModified": 1779529080,
"narHash": "sha256-CHa5L3I71NbPUNJp1gmmwbh91tKsZPyuRK50mLHjAVY=",
"owner": "aster-void",
"repo": "nix-hazkey",
"rev": "24cb2926666836988e78ceebeb67ad6c5a387ac3",
"type": "github"
},
"original": {
"owner": "nix-darwin",
"ref": "nix-darwin-26.05",
"repo": "nix-darwin",
"owner": "aster-void",
"repo": "nix-hazkey",
"type": "github"
}
},
@@ -598,11 +619,11 @@
]
},
"locked": {
"lastModified": 1785046085,
"narHash": "sha256-UiK+mmZJuLWQVhJ5b2wDzogIYWAesyRm6LA3h3Ulh3Y=",
"lastModified": 1784440659,
"narHash": "sha256-Q5kNLlWngt7TaIIZoxDKWMHjiSaNRVqr70FqWCRRfr4=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "11665045df8b9938ef811a3bfdc65cffb02b4b70",
"rev": "4f8d52a3598b0dc7db7a5e7b419e3edd9d1ecfdb",
"type": "github"
},
"original": {
@@ -714,11 +735,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1784963784,
"narHash": "sha256-IZAjgNI19TdwYus6QUIMvU0cw0vWVb/5oYwpyxQXL1E=",
"lastModified": 1784835677,
"narHash": "sha256-99n/+kX5HRtEouTsNNyz5pMfIpNMp4Um/6nCEDQ9YDs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "38affae6a5768f9b61f81355c7558ee971b2afb1",
"rev": "d4221905ef70b7f4a40701ce98e3e25c3d95d082",
"type": "github"
},
"original": {
@@ -791,11 +812,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1784872115,
"narHash": "sha256-THPEF2po0fsoH8gNtp+Ae0XFDJH3N/ol7xO3v6VMTJU=",
"lastModified": 1784555310,
"narHash": "sha256-/FCliTPgiuV1owejZFNx3Ch9irdvkOfOFl+HHZ+DrtM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "335f0738cb2fa9708f3f428e39d2eae975d1338d",
"rev": "421eebfd0ec7bccd4abe826ce62d7e6e83129493",
"type": "github"
},
"original": {
@@ -852,11 +873,11 @@
},
"nixpkgs_8": {
"locked": {
"lastModified": 1784856561,
"narHash": "sha256-J+Bx1Z6Oeoj2FgnBhRMKyUhhtDoOpTgXYaVLZpDjW4A=",
"lastModified": 1784923315,
"narHash": "sha256-2e5BnQ0YLJMIRII1BdGsLiBcJ1fRVauvZypwZIR2JLw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "597283ad8aa0b331c788e97c4c262d58877074ef",
"rev": "d2f1d98be0573ebf42b96630bc8d7001ff62af43",
"type": "github"
},
"original": {
@@ -912,11 +933,11 @@
]
},
"locked": {
"lastModified": 1785099099,
"narHash": "sha256-hwmc/ov72HfpuZvLX7KvaHFw3cI4KTD+O5znR9a7pcI=",
"lastModified": 1784917398,
"narHash": "sha256-lBVMJgBWbC3H6UGlCYiu8pbgUJqWWyJ70n/xIljk/1I=",
"owner": "noctalia-dev",
"repo": "noctalia",
"rev": "02a846f5c947da00f3d4acdf7cf00f056d92fe3d",
"rev": "0cd9b22e19fb71455c950a70c11324b7e245a790",
"type": "github"
},
"original": {
@@ -983,7 +1004,7 @@
"lanzaboote": "lanzaboote",
"llm-agents": "llm-agents",
"niri-flake": "niri-flake",
"nix-darwin": "nix-darwin",
"nix-hazkey": "nix-hazkey",
"nix-index-database": "nix-index-database",
"nixos-hardware": "nixos-hardware",
"nixos-wsl": "nixos-wsl",
@@ -1339,11 +1360,11 @@
"systems": "systems_7"
},
"locked": {
"lastModified": 1785027961,
"narHash": "sha256-F8yaTqRGGKzl5QTtMM+4I2zUCpOq4or7zuzTmXSiTMA=",
"lastModified": 1784839524,
"narHash": "sha256-B87U5HDB/6JPxSnNQwnIiwtqUWvyxzqs7lV/GPzva68=",
"owner": "vicinaehq",
"repo": "vicinae",
"rev": "65c973b55df4b8f9a80e1663feeca570e3bf016c",
"rev": "632ca79e9f8fc9721384921f28ec069ff48aaf53",
"type": "github"
},
"original": {
@@ -1362,11 +1383,11 @@
"vicinae": "vicinae_2"
},
"locked": {
"lastModified": 1785084836,
"narHash": "sha256-iww/OcxGK8isAgNh8k4E1IJkR5bGUvEK/K+q86g2ISk=",
"lastModified": 1784504910,
"narHash": "sha256-fzPBEJZiRvc/FNMdpbdcfaZzF01U4IQenHW9IQFzhos=",
"owner": "vicinaehq",
"repo": "extensions",
"rev": "2d5176bcb19498ff862ca1caa5eb97f03f60faac",
"rev": "ca74eede9a778a9373c8f5fd221b0a5026dcd1ef",
"type": "github"
},
"original": {
+9 -6
View File
@@ -11,11 +11,6 @@
inputs.nixpkgs.follows = "nixpkgs";
};
nix-darwin = {
url = "github:nix-darwin/nix-darwin/nix-darwin-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
# Hardware / Platform
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
nixos-wsl.url = "github:nix-community/NixOS-WSL";
@@ -97,6 +92,12 @@
# Systems
systems.url = "github:nix-systems/default-linux";
# Japanese Input Method
nix-hazkey = {
url = "github:aster-void/nix-hazkey";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
@@ -110,8 +111,10 @@
imports = [
./overlays
./hosts
./shells
./flake
./flake/formatter.nix
./flake/git-hooks.nix
];
};
}
-7
View File
@@ -1,7 +0,0 @@
{
imports = [
./formatter.nix
./git-hooks.nix
./registry.nix
];
}
-42
View File
@@ -1,42 +0,0 @@
{
inputs,
lib,
...
}:
let
dotfilesLib = import ../libs {
inherit inputs lib;
root = ../.;
};
hostSpecsPath = ../hosts/default.nix;
hostSpecs =
if builtins.pathExists hostSpecsPath then
let
value = import hostSpecsPath;
in
if builtins.isFunction value then
value (
builtins.intersectAttrs (builtins.functionArgs value) {
inherit inputs lib;
}
)
else
value
else
{ };
configurations = dotfilesLib.hosts.mkConfigurations hostSpecs;
in
{
flake = {
inherit (configurations) darwinConfigurations nixosConfigurations;
lib = dotfilesLib;
};
perSystem =
{ pkgs, ... }:
{
checks.registry = import ../tests/registry.nix {
inherit inputs lib pkgs;
};
};
}
+107 -7
View File
@@ -1,13 +1,113 @@
{
x1g9 = {
system = "x86_64-linux";
user = "moons";
path = ./x1g9;
inputs,
config,
lib,
...
}:
let
inherit (inputs.nixpkgs.lib) nixosSystem;
username = "moons";
mkSystem =
{
host,
system,
profiles ? [ ],
extraModules ? [ ],
}:
let
unstable = import inputs.nixpkgs-unstable {
inherit system;
config = {
allowUnfree = true;
};
};
in
assert lib.assertMsg (lib.elem system config.systems)
"mkSystem: system '${system}' not in valid systems: ${lib.generators.toPretty { } config.systems}";
nixosSystem {
inherit system;
modules = [
{
nixpkgs.config.allowUnfree = true;
nixpkgs.overlays = builtins.attrValues inputs.self.overlays;
}
../modules
./${host}/default.nix
]
++ map (p: ../profiles/${p}.nix) profiles
++ extraModules;
specialArgs = {
inherit
inputs
username
unstable
host
;
};
};
nixosConfigurations = {
nix-example = mkSystem {
host = "nix-example";
system = "x86_64-linux";
profiles = [
"base"
"interface.cli-minimal"
"platform.laptop"
"interfaces/cli-interactive"
"platforms/vm"
"workloads/dev"
"workloads/remote"
];
};
ops = mkSystem {
host = "ops";
system = "x86_64-linux";
profiles = [
"interfaces/cli-interactive"
"platforms/vm"
"workloads/remote"
];
};
internal-app-01 = mkSystem {
host = "internal-app-01";
system = "x86_64-linux";
profiles = [
"interfaces/cli-interactive"
"platforms/vm"
"workloads/srv"
];
};
x1g13 = mkSystem {
host = "x1g13";
system = "x86_64-linux";
profiles = [
"interfaces/gui"
"platforms/thinkpad"
"workloads/dev"
"workloads/personal"
"workloads/secure-storage"
"workloads/tailscale/client"
];
};
installer = nixosSystem {
system = "x86_64-linux";
modules = [
./installer/default.nix
];
specialArgs = {
inherit inputs;
};
};
};
in
{
flake = {
inherit nixosConfigurations;
checks.x86_64-linux = lib.mapAttrs' (
name: nixos: lib.nameValuePair "nixos-${name}" nixos.config.system.build.toplevel
) nixosConfigurations;
};
}
+193
View File
@@ -0,0 +1,193 @@
{
pkgs,
lib,
modulesPath,
...
}:
{
imports = [
"${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix"
];
boot.zfs.forceImportRoot = false;
networking = {
hostName = "nixos-installer";
networkmanager = {
enable = true;
wifi.powersave = false;
};
};
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "prohibit-password";
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PubkeyAuthentication = "yes";
};
};
users.users.root.openssh.authorizedKeys.keys = [
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIKhxDkucmeCor6CKoXAua7DgDSzuXrZOtpdkyzQxz5+aAAAABHNzaDo= moons@moons14.com"
"sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIN6hZJyng/5LgFKPjR6uZAd/00UkO0vN0uQOoIvfSELdAAAABHNzaDo= moons@moons14.com"
];
environment.systemPackages = with pkgs; [
git # Clone dotfiles repository
disko # Disk partitioning
sops # Secrets management
age # Age encryption
ssh-to-age # Convert SSH keys to age
age-plugin-yubikey # YubiKey support
yubikey-manager # YubiKey management
pcsc-tools # Smart card tools
mkpasswd # Password hash generation
rsync # File synchronization
vim # Text editor
wget # Download files
curl # HTTP client
jq # JSON processor
parted # Partition tools
cryptsetup # LUKS encryption
btrfs-progs # Btrfs filesystem tools
];
services.pcscd.enable = true;
environment.etc."installer-help.txt".text = ''
╔══════════════════════════════════════════════════════════════╗
║ NixOS Installer ISO ║
╠══════════════════════════════════════════════════════════════╣
║ ║
║ SSH Access: ║
║ ssh root@<ip-address> ║
║ ║
║ Network Setup: ║
║ Wired: Auto-configured via DHCP ║
║ WiFi: nmcli device wifi connect <SSID> --ask ║
║ ║
║ Installation Workflow: ║
║ ║
║ 1. Clone dotfiles: ║
║ git clone git@github.com:moons-14/dotfiles.git ~/dotfiles║
║ ║
║ 2. Generate SSH host key for new host: ║
║ ssh-keygen -t ed25519 -f /tmp/ssh_host_ed25519_key -N "" ║
║ ║
║ 3. Get age public key from SSH host key: ║
║ ssh-to-age -i /tmp/ssh_host_ed25519_key.pub ║
║ ║
║ 4. Add age key to .sops.yaml: ║
║ cd ~/dotfiles ║
║ # Edit .sops.yaml and add the age key ║
║ # Add new host entry to creation_rules ║
║ ║
║ 5. Re-encrypt secrets: ║
║ sops updatekeys secrets/common/system.yaml ║
║ sops updatekeys secrets/hosts/<host>/*.yaml ║
║ ║
║ 6. Create disko.nix for new host: ║
║ # Check disk devices ║
║ lsblk -f ║
║ ║
║ # Create hosts/<host>/disko.nix ║
║ # Example: LUKS + btrfs ║
║ # See hosts/x1g13/disko.nix for reference ║
║ ║
║ 7. Partition disk with disko: ║
║ nix run github:nix-community/disko -- \ ║
║ --mode disko hosts/<host>/disko.nix ║
║ ║
║ 8. Copy host key to installed system: ║
║ mkdir -p /mnt/etc/ssh ║
║ cp /tmp/ssh_host_ed25519_key* /mnt/etc/ssh/ ║
║ chmod 600 /mnt/etc/ssh/ssh_host_ed25519_key ║
║ ║
║ 9. Install NixOS: ║
║ nixos-install --flake ~/dotfiles#<host> ║
║ ║
║ Disko Configuration Examples: ║
║ ║
║ Simple (no encryption): ║
║ disko.devices.disk.main = { ║
║ type = "disk"; ║
║ device = "/dev/sda"; ║
║ content = { ║
║ type = "gpt"; ║
║ partitions = { ║
║ ESP = { size = "512M"; type = "EF00"; ║
║ content = { type = "filesystem"; ║
║ format = "vfat"; mountpoint = "/boot"; }; }; ║
║ root = { size = "100%"; ║
║ content = { type = "filesystem"; ║
║ format = "ext4"; mountpoint = "/"; }; }; ║
║ }; ║
║ }; ║
║ }; ║
║ ║
║ LUKS + btrfs (see hosts/x1g13/disko.nix): ║
║ - Use partuuid for device path ║
║ - Set askPassword = true for LUKS ║
║ - Configure btrfs subvolumes ║
║ ║
╚══════════════════════════════════════════════════════════════╝
'';
systemd.services.installer-banner = {
description = "Display installer help on console";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.coreutils}/bin/cat /etc/installer-help.txt";
StandardOutput = "tty";
TTYPath = "/dev/tty1";
};
};
systemd.services.display-ip = {
description = "Display IP address on console";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "display-ip" ''
sleep 2
echo ""
echo "=== Network Interfaces ==="
${pkgs.iproute2}/bin/ip -4 addr show | ${pkgs.gnugrep}/bin/grep inet
echo ""
echo "=== SSH Access ==="
for ip in $(${pkgs.iproute2}/bin/ip -4 addr show | ${pkgs.gnugrep}/bin/grep -oP 'inet \K[\d.]+' | ${pkgs.gnugrep}/bin/grep -v '127.0.0.1'); do
echo " ssh root@$ip"
done
echo ""
'';
StandardOutput = "tty";
TTYPath = "/dev/tty1";
};
};
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
trusted-users = [ "root" ];
};
extraOptions = ''
experimental-features = nix-command flakes
'';
};
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
system.stateVersion = "26.05";
}
+6
View File
@@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./hardware-configuration.nix
];
}
@@ -0,0 +1,40 @@
# Do not modify this file! It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ lib, modulesPath, ... }:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [
"ata_piix"
"uhci_hcd"
"virtio_pci"
"virtio_scsi"
"sd_mod"
"sr_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/1b12ab98-2537-4207-a3f4-bb8ba7b53b00";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/8365-C778";
fsType = "vfat";
options = [
"fmask=0077"
"dmask=0077"
];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
+6
View File
@@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./hardware-configuration.nix
];
}
@@ -0,0 +1,43 @@
# Do not modify this file! It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
lib,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [
"ata_piix"
"uhci_hcd"
"virtio_pci"
"virtio_scsi"
"sd_mod"
"sr_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/8f0eaec6-5dc9-4821-aa8d-fb6809b5a5bf";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/201C-961B";
fsType = "vfat";
options = [
"fmask=0077"
"dmask=0077"
];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
+56
View File
@@ -0,0 +1,56 @@
{ ... }:
{
imports = [
./hardware-configuration.nix
];
networking = {
useDHCP = false;
interfaces = {
ens18 = {
useDHCP = false;
ipv4.addresses = [
{
address = "10.50.128.20";
prefixLength = 24;
}
];
};
ens19 = {
useDHCP = false;
ipv4.addresses = [
{
address = "10.50.7.101";
prefixLength = 24;
}
];
};
ens20 = {
useDHCP = false;
ipv4.routes = [
{
address = "10.50.64.0";
prefixLength = 24;
via = "10.50.82.1";
}
];
ipv4.addresses = [
{
address = "10.50.82.10";
prefixLength = 24;
}
];
};
};
defaultGateway = {
address = "10.50.128.1";
interface = "ens18";
};
};
}
+44
View File
@@ -0,0 +1,44 @@
# Do not modify this file! It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
lib,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [
"ata_piix"
"uhci_hcd"
"virtio_pci"
"virtio_scsi"
"sd_mod"
"sr_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/69fa2193-1e4f-438a-8898-5de8a3f36e5b";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/D09B-4277";
fsType = "vfat";
options = [
"fmask=0077"
"dmask=0077"
];
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
+10
View File
@@ -0,0 +1,10 @@
{ ... }:
{
imports = [
./hardware-configuration.nix
./disko.nix
];
boot.initrd.luks.devices.cryptroot.device =
"/dev/disk/by-partuuid/311d0f9c-f35f-42e6-b6fc-a4d67dd21b2e";
}
+99
View File
@@ -0,0 +1,99 @@
_:
let
espPart = "/dev/disk/by-partuuid/a53e3b19-67de-40de-9ded-3eac3117689a";
nixosPart = "/dev/disk/by-partuuid/311d0f9c-f35f-42e6-b6fc-a4d67dd21b2e";
btrfsMountOptions = [
"compress=zstd"
"noatime"
"ssd"
"space_cache=v2"
];
in
{
disko.enableConfig = true;
disko.devices.disk = {
esp = {
type = "disk";
device = espPart;
destroy = false;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [
"umask=0077"
];
};
};
nixos = {
type = "disk";
device = nixosPart;
destroy = false;
content = {
type = "luks";
name = "cryptroot";
askPassword = true;
settings = {
allowDiscards = true;
};
extraFormatArgs = [
"--type"
"luks2"
"--pbkdf"
"argon2id"
"--label"
"NixOS-LUKS"
];
content = {
type = "btrfs";
extraArgs = [
"-f"
"-L"
"NixOS"
];
subvolumes = {
"@root" = {
mountpoint = "/";
mountOptions = btrfsMountOptions;
};
"@home" = {
mountpoint = "/home";
mountOptions = btrfsMountOptions;
};
"@nix" = {
mountpoint = "/nix";
mountOptions = btrfsMountOptions;
};
"@log" = {
mountpoint = "/var/log";
mountOptions = btrfsMountOptions;
};
"@swap" = {
mountpoint = "/.swapvol";
mountOptions = [
"noatime"
];
swap.swapfile.size = "32G";
};
};
};
};
};
};
}
+33
View File
@@ -0,0 +1,33 @@
# Do not modify this file! It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
modulesPath,
...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [
"xhci_pci"
"thunderbolt"
"nvme"
"usb_storage"
"sd_mod"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
swapDevices = [ ];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
-1
View File
@@ -1 +0,0 @@
{ }
-7
View File
@@ -1,7 +0,0 @@
{
imports = [
./hardware.nix
];
system.stateVersion = "26.05";
}
-17
View File
@@ -1,17 +0,0 @@
{
inputs,
lib ? inputs.nixpkgs.lib,
root,
}:
let
registry = import ./registry.nix {
inherit inputs lib;
modulesRoot = root + "/modules";
};
hosts = import ./hosts.nix {
inherit inputs lib registry;
};
in
{
inherit hosts registry;
}
-168
View File
@@ -1,168 +0,0 @@
{
inputs,
lib,
registry,
}:
let
ensure =
condition: message: value:
if condition then value else throw "host registry: ${message}";
isLinux = system: lib.hasSuffix "-linux" system;
isDarwin = system: lib.hasSuffix "-darwin" system;
hostFile =
spec: name:
let
path = spec.path + "/${name}";
in
if builtins.pathExists path then path else null;
selectedUnits =
spec:
[ "users.${spec.user}" ]
++ map (name: "profiles.${name}") (spec.profiles or [ ])
++ map (name: "applications.${name}") (spec.applications or [ ])
++ (spec.units or [ ]);
validateSpec =
name: spec:
ensure (builtins.isAttrs spec) "${name}: host specification must be an attribute set" (
ensure (spec ? system && builtins.isString spec.system) "${name}: system is required" (
ensure (isLinux spec.system || isDarwin spec.system)
"${name}: unsupported system '${spec.system}'; expected a Linux NixOS or Darwin system"
(
ensure (spec ? user && builtins.isString spec.user && spec.user != "") "${name}: user is required" (
ensure (spec ? path && builtins.pathExists spec.path)
"${name}: path must name an existing host directory"
(
ensure
(lib.all
(field: builtins.isList (spec.${field} or [ ]) && lib.all builtins.isString (spec.${field} or [ ]))
[
"profiles"
"applications"
"units"
]
)
"${name}: profiles, applications, and units must be lists of strings"
(ensure (builtins.isBool (spec.homeManager or true)) "${name}: homeManager must be a boolean" spec)
)
)
)
)
);
mkSpecialArgs = name: spec: {
inherit inputs registry;
inherit (spec) system;
hostName = name;
primaryUser = spec.user;
};
mkHomeManagerModule =
name: spec: selected:
let
homePath = hostFile spec "home.nix";
homeModules = [
(registry.mkModule { class = "home"; })
(registry.mkSelectionModule selected)
]
++ lib.optional (homePath != null) homePath;
in
{
imports = [ inputs.home-manager.nixosModules.home-manager ];
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
extraSpecialArgs = mkSpecialArgs name spec;
users.${spec.user}.imports = homeModules;
};
};
mkDarwinHomeManagerModule =
name: spec: selected:
let
homePath = hostFile spec "home.nix";
homeModules = [
(registry.mkModule { class = "home"; })
(registry.mkSelectionModule selected)
]
++ lib.optional (homePath != null) homePath;
in
{
imports = [ inputs.home-manager.darwinModules.home-manager ];
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
extraSpecialArgs = mkSpecialArgs name spec;
users.${spec.user}.imports = homeModules;
};
};
mkNixos =
name: rawSpec:
let
spec = validateSpec name rawSpec;
selected = registry.validateUnitIds (selectedUnits spec);
nixosPath = hostFile spec "nixos.nix";
modules = [
(registry.mkModule { class = "nixos"; })
(registry.mkSelectionModule selected)
{ networking.hostName = lib.mkDefault name; }
]
++ lib.optional (spec.homeManager or true) (mkHomeManagerModule name spec selected)
++ lib.optional (nixosPath != null) nixosPath;
in
inputs.nixpkgs.lib.nixosSystem {
inherit (spec) system;
specialArgs = mkSpecialArgs name spec;
inherit modules;
};
mkDarwin =
name: rawSpec:
let
spec = validateSpec name rawSpec;
selected = registry.validateUnitIds (selectedUnits spec);
darwinPath = hostFile spec "darwin.nix";
modules = [
(registry.mkModule { class = "darwin"; })
(registry.mkSelectionModule selected)
{ networking.hostName = lib.mkDefault name; }
]
++ lib.optional (spec.homeManager or true) (mkDarwinHomeManagerModule name spec selected)
++ lib.optional (darwinPath != null) darwinPath;
in
ensure (inputs ? nix-darwin) "${name}: the nix-darwin input is required" (
inputs.nix-darwin.lib.darwinSystem {
inherit (spec) system;
specialArgs = mkSpecialArgs name spec;
inherit modules;
}
);
mkConfigurations =
hostSpecs:
let
validated = lib.mapAttrs validateSpec hostSpecs;
in
{
nixosConfigurations = lib.mapAttrs mkNixos (
lib.filterAttrs (_: spec: isLinux spec.system) validated
);
darwinConfigurations = lib.mapAttrs mkDarwin (
lib.filterAttrs (_: spec: isDarwin spec.system) validated
);
};
in
{
inherit
mkConfigurations
mkDarwin
mkNixos
selectedUnits
;
}
-324
View File
@@ -1,324 +0,0 @@
{
inputs,
lib,
modulesRoot,
}:
let
reservedFiles = {
common = "common.nix";
nixos = "nixos.nix";
darwin = "darwin.nix";
home = "home.nix";
meta = "meta.nix";
};
isFile = kind: kind == "regular" || kind == "symlink";
ensure =
condition: message: value:
if condition then value else throw "unit registry: ${message}";
callWithAvailableArgs =
value: availableArgs:
if builtins.isFunction value then
value (builtins.intersectAttrs (builtins.functionArgs value) availableArgs)
else
value;
pathFor =
relativePath:
if relativePath == [ ] then
modulesRoot
else
modulesRoot + "/${lib.concatStringsSep "/" relativePath}";
entryIsFile = entries: name: builtins.hasAttr name entries && isFile entries.${name};
normalizeMeta =
unit:
let
metaPath = unit.fragments.meta;
importedValue =
if metaPath == null then
{ }
else
callWithAvailableArgs (import metaPath) {
inherit inputs lib unit;
};
imported =
ensure (builtins.isAttrs importedValue) "${unit.id}: meta.nix must return an attribute set"
importedValue;
allowedKeys = [
"description"
"includes"
"imports"
];
unknownKeys = lib.filter (name: !(builtins.elem name allowedKeys)) (builtins.attrNames imported);
description = imported.description or null;
includes = imported.includes or [ ];
imports = imported.imports or { };
allowedImportKeys = [
"nixos"
"darwin"
"home"
];
unknownImportKeys =
if builtins.isAttrs imports then
lib.filter (name: !(builtins.elem name allowedImportKeys)) (builtins.attrNames imports)
else
[ ];
normalized = {
inherit description includes;
imports = {
nixos = imports.nixos or [ ];
darwin = imports.darwin or [ ];
home = imports.home or [ ];
};
};
in
ensure (unknownKeys == [ ])
"${unit.id}: meta.nix has unsupported keys: ${lib.concatStringsSep ", " unknownKeys}"
(
ensure (description == null || builtins.isString description)
"${unit.id}: meta.description must be a string"
(
ensure (builtins.isList includes && lib.all builtins.isString includes)
"${unit.id}: meta.includes must be a list of fully qualified unit IDs"
(
ensure (lib.unique includes == includes) "${unit.id}: meta.includes contains duplicate unit IDs" (
ensure (builtins.isAttrs imports) "${unit.id}: meta.imports must be an attribute set" (
ensure (unknownImportKeys == [ ])
"${unit.id}: meta.imports has unsupported classes: ${lib.concatStringsSep ", " unknownImportKeys}"
(
ensure (lib.all builtins.isList [
normalized.imports.nixos
normalized.imports.darwin
normalized.imports.home
]) "${unit.id}: every meta.imports.<class> value must be a list" normalized
)
)
)
)
)
);
makeUnit =
relativePath: entries:
let
directory = pathFor relativePath;
id = lib.concatStringsSep "." relativePath;
fragments = lib.mapAttrs (
_class: fileName: if entryIsFile entries fileName then directory + "/${fileName}" else null
) reservedFiles;
baseUnit = {
inherit
id
directory
fragments
relativePath
;
optionPath = [ "my" ] ++ relativePath ++ [ "enable" ];
kind = builtins.head relativePath;
name = lib.last relativePath;
}
//
lib.optionalAttrs (builtins.length relativePath > 2 && builtins.head relativePath == "profiles")
{
group = builtins.elemAt relativePath 1;
};
in
ensure (relativePath != [ ]) "the modules root cannot itself be a unit" (
ensure (lib.all (component: component != "" && !(lib.hasInfix "." component)) relativePath)
"${id}: path components must be non-empty and must not contain dots"
(baseUnit // { meta = normalizeMeta baseUnit; })
);
walk =
relativePath:
let
directory = pathFor relativePath;
entries = builtins.readDir directory;
hasReservedFile = lib.any (fileName: entryIsFile entries fileName) (
builtins.attrValues reservedFiles
);
childDirectories = lib.filter (name: entries.${name} == "directory") (builtins.attrNames entries);
current = lib.optional hasReservedFile (makeUnit relativePath entries);
children = lib.concatMap (name: walk (relativePath ++ [ name ])) childDirectories;
in
current ++ children;
discoveredUnits =
ensure (builtins.pathExists modulesRoot) "modules root does not exist: ${toString modulesRoot}"
(walk [ ]);
unitsById = builtins.listToAttrs (map (unit: lib.nameValuePair unit.id unit) discoveredUnits);
dependencyValidation = lib.foldl' (
valid: unit:
lib.foldl' (
inner: includedId:
if builtins.hasAttr includedId unitsById then
inner
else
throw "unit registry: ${unit.id} includes missing unit '${includedId}'"
) valid unit.meta.includes
) true discoveredUnits;
units = builtins.seq dependencyValidation unitsById;
unitIds = builtins.attrNames units;
getUnit =
id: if builtins.hasAttr id units then units.${id} else throw "unit registry: unknown unit '${id}'";
validateUnitIds =
ids:
ensure (
builtins.isList ids && lib.all builtins.isString ids
) "selected units must be a list of strings" (map (id: builtins.seq (getUnit id) id) ids);
optionDefinitions = lib.foldl' lib.recursiveUpdate { } (
map (
unit:
lib.setAttrByPath unit.optionPath (
lib.mkOption {
type = lib.types.bool;
default = false;
description =
if unit.meta.description == null then
"Whether to enable the ${unit.id} unit."
else
"Whether to enable ${unit.meta.description}.";
}
)
) discoveredUnits
);
enabled = config: unit: lib.getAttrFromPath unit.optionPath config;
enableUnit = id: lib.setAttrByPath (getUnit id).optionPath true;
includeConfig =
config: unit: lib.mkIf (enabled config unit) (lib.mkMerge (map enableUnit unit.meta.includes));
fragmentClasses = {
nixos = [
"common"
"nixos"
];
darwin = [
"common"
"darwin"
];
home = [ "home" ];
};
applyFragment =
{
config,
fragmentPath,
options,
specialArgs,
unit,
}:
let
fragment = import fragmentPath;
directArgs = specialArgs // {
inherit
config
lib
options
specialArgs
unit
;
};
fragmentArgSpec = builtins.functionArgs fragment;
fragmentArgs = builtins.listToAttrs (
lib.concatMap (
name:
if builtins.hasAttr name directArgs then
[ (lib.nameValuePair name directArgs.${name}) ]
else if fragmentArgSpec.${name} then
[ ]
else
[ (lib.nameValuePair name config._module.args.${name}) ]
) (builtins.attrNames fragmentArgSpec)
);
resultValue = if builtins.isFunction fragment then fragment fragmentArgs else fragment;
result =
ensure (builtins.isAttrs resultValue)
"${unit.id}: ${builtins.baseNameOf fragmentPath} must return an attribute set"
resultValue;
forbiddenKeys = lib.filter (name: builtins.hasAttr name result) [
"imports"
"options"
"config"
];
in
ensure (forbiddenKeys == [ ])
"${unit.id}: ${builtins.baseNameOf fragmentPath} is a configuration fragment and cannot define top-level ${lib.concatStringsSep ", " forbiddenKeys}"
result;
externalImports = class: lib.concatMap (unit: unit.meta.imports.${class}) discoveredUnits;
mkModule =
{ class }:
ensure (builtins.hasAttr class fragmentClasses) "unsupported module class '${class}'" (
builtins.seq dependencyValidation (
{
config,
lib,
options,
specialArgs,
...
}:
let
fragmentConfigs = lib.concatMap (
unit:
lib.filter (value: value != null) (
map (
fragmentClass:
let
fragmentPath = unit.fragments.${fragmentClass};
in
if fragmentPath == null then
null
else
lib.mkIf (enabled config unit) (applyFragment {
inherit
config
fragmentPath
options
specialArgs
unit
;
})
) fragmentClasses.${class}
)
) discoveredUnits;
in
{
imports = externalImports class;
options = optionDefinitions;
config = lib.mkMerge ((map (includeConfig config) discoveredUnits) ++ fragmentConfigs);
}
)
);
mkSelectionModule =
selectedIds:
let
checkedIds = validateUnitIds (lib.unique selectedIds);
in
{
config = lib.mkMerge (map enableUnit checkedIds);
};
in
{
inherit
getUnit
mkModule
mkSelectionModule
unitIds
units
validateUnitIds
;
}
+26
View File
@@ -0,0 +1,26 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications."1password";
in
{
options.my.applications."1password" = {
enable = lib.mkEnableOption "1Password password manager";
};
config = lib.mkIf cfg.enable {
programs._1password.enable = true;
programs._1password-gui = {
enable = true;
polkitPolicyOwners = [ "moons" ];
};
programs.ssh.startAgent = lib.mkForce false;
programs.gnupg.agent.enableSSHSupport = lib.mkForce false;
services.gnome.gcr-ssh-agent.enable = lib.mkForce false;
};
}
+29
View File
@@ -0,0 +1,29 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.arduino;
arduinoIdeX11 = pkgs.arduino-ide.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ pkgs.makeWrapper ];
postFixup = (old.postFixup or "") + ''
wrapProgram $out/bin/arduino-ide \
--add-flags "--ozone-platform=x11"
'';
});
in
{
options.my.applications.arduino = {
enable = lib.mkEnableOption "Arduino development tools";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
arduino-cli # Arduino command-line interface
arduinoIdeX11 # Arduino IDE with X11 support
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.btop;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.btop = {
enable = lib.mkEnableOption "btop system monitor";
};
config = lib.mkIf cfg.enable {
my.applications.btop.system.enable = lib.mkDefault true;
my.applications.btop.homeManager.enable = lib.mkDefault true;
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
# Bashtop theme with Nord palette (https://www.nordtheme.com)
#Bashtop theme with nord palette (https://www.nordtheme.com)
#by Justin Zobel <[email protected]>
# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255"
@@ -18,7 +18,7 @@ theme[main_fg]="#BD93F9"
# Title color for boxes
theme[title]="#f8f8f2"
# Highlight color for keyboard shortcuts
# Higlight color for keyboard shortcuts
theme[hi_fg]="#ff79c6"
# Background color of selected item in processes box
+18 -1
View File
@@ -1,8 +1,25 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.btop.homeManager;
in
{
options.my.applications.btop.homeManager = {
enable = lib.mkEnableOption "btop home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf cfg.enable {
programs.btop = {
enable = true;
settings.color_theme = "dracula";
themes.dracula = builtins.readFile ./dracula.theme;
};
};
}
];
}
+20
View File
@@ -0,0 +1,20 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.btop.system;
in
{
options.my.applications.btop.system = {
enable = lib.mkEnableOption "btop system configuration";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
btop # Resource monitor that shows usage and stats
];
};
}
+44
View File
@@ -0,0 +1,44 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.chrome;
in
{
options.my.applications.chrome = {
enable = lib.mkEnableOption "Google Chrome browser";
};
config = lib.mkIf cfg.enable {
home-manager.sharedModules = [
{
home.packages = with pkgs; [
google-chrome # Popular web browser from Google
];
xdg.desktopEntries."google-chrome" = {
name = "Google Chrome";
genericName = "Web Browser";
exec = "${pkgs.google-chrome}/bin/google-chrome-stable --enable-features=TouchpadOverscrollHistoryNavigation %U";
terminal = false;
icon = "google-chrome";
categories = [
"Network"
"WebBrowser"
];
startupNotify = true;
type = "Application";
};
xdg.mimeApps.defaultApplications = {
"text/html" = "google-chrome.desktop";
"x-scheme-handler/http" = "google-chrome.desktop";
"x-scheme-handler/https" = "google-chrome.desktop";
};
}
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.claude;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.claude = {
enable = lib.mkEnableOption "Claude Code AI assistant";
};
config = lib.mkIf cfg.enable {
my.applications.claude.system.enable = lib.mkDefault true;
my.applications.claude.homeManager.enable = lib.mkDefault true;
};
}
+14 -5
View File
@@ -1,13 +1,19 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.claude.homeManager;
in
{
home.packages = [
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
];
options.my.applications.claude.homeManager = {
enable = lib.mkEnableOption "Claude Code home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf cfg.enable {
home.file.".claude/settings.json".text = builtins.toJSON {
statusLine = {
type = "command";
@@ -15,4 +21,7 @@
padding = 0;
};
};
};
}
];
}
+20
View File
@@ -0,0 +1,20 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.claude.system;
in
{
options.my.applications.claude.system = {
enable = lib.mkEnableOption "Claude Code system configuration";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
pkgs.llm-agents.claude-code # AI coding assistant
];
};
}
+65
View File
@@ -0,0 +1,65 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.codexDesktop;
codexCliPackage = pkgs.llm-agents.codex;
codexDesktopPackage =
inputs.codex-desktop-linux.packages.${pkgs.stdenv.hostPlatform.system}.codex-desktop-computer-use-ui;
codexDesktopLauncher = pkgs.makeDesktopItem {
name = "codex";
desktopName = "Codex";
genericName = "ChatGPT Desktop";
comment = "Run Codex Desktop on Linux";
exec = "env CODEX_CLI_PATH=${lib.getExe' codexCliPackage "codex"} BAMF_DESKTOP_FILE_HINT=codex.desktop CHROME_DESKTOP=codex.desktop ${lib.getExe' codexDesktopPackage "codex-desktop"} %u";
icon = "codex-desktop";
terminal = false;
categories = [ "Development" ];
keywords = [
"codex"
"chatgpt"
"openai"
"ai"
"assistant"
];
startupNotify = true;
startupWMClass = "codex-desktop";
actions = {
new-window = {
name = "New Window";
exec = "env CODEX_CLI_PATH=${lib.getExe' codexCliPackage "codex"} BAMF_DESKTOP_FILE_HINT=codex.desktop CHROME_DESKTOP=codex.desktop CODEX_MULTI_LAUNCH=1 ${lib.getExe' codexDesktopPackage "codex-desktop"} --new-instance";
};
};
extraConfig = {
X-GNOME-WMClass = "codex-desktop";
};
};
in
{
imports = [
inputs.codex-desktop-linux.nixosModules.default
];
options.my.applications.codexDesktop = {
enable = lib.mkEnableOption "ChatGPT Desktop for Linux";
};
config = lib.mkIf cfg.enable {
my.applications.codex.enable = true;
programs.codexDesktopLinux = {
enable = true;
package = codexDesktopPackage;
cliPackage = codexCliPackage;
computerUseUi.enable = true;
};
environment.systemPackages = [
codexDesktopLauncher # Vicinae-searchable Codex Desktop launcher alias
];
};
}
+20
View File
@@ -0,0 +1,20 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.codex;
in
{
options.my.applications.codex = {
enable = lib.mkEnableOption "Codex AI coding assistant";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
pkgs.llm-agents.codex # OpenAI Codex CLI
];
};
}
+47
View File
@@ -0,0 +1,47 @@
{
imports = [
./1password.nix
./arduino.nix
./btop
./chrome.nix
./claude
./codex-desktop.nix
./codex.nix
./direnv.nix
./discord.nix
./docker.nix
./fcitx5
./ghostty
./git
./gnupg
./grok.nix
./gnome.nix
./greetd.nix
./ly
./gtk
./java
./kde.nix
./nautilus.nix
./nh.nix
./niri
./nix-index
./noctalia
./opencode.nix
./openssh.nix
./slack.nix
./ssh
./swayidle.nix
./swaylock
./tailscale.nix
./vicinae.nix
./vim
./vscode
./wayland.nix
./yazi.nix
./zed
./zellij
./zoom.nix
./zoxide.nix
./zsh
];
}
+16
View File
@@ -0,0 +1,16 @@
{ lib, config, ... }:
let
cfg = config.my.applications.direnv;
in
{
options.my.applications.direnv = {
enable = lib.mkEnableOption "direnv environment variable manager";
};
config = lib.mkIf cfg.enable {
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.discord;
in
{
options.my.applications.discord = {
enable = lib.mkEnableOption "Discord (Vesktop)";
};
config = lib.mkIf cfg.enable {
home-manager.sharedModules = [
{
programs.vesktop = {
enable = true;
};
}
];
};
}
+34
View File
@@ -0,0 +1,34 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.docker;
in
{
options.my.applications.docker = {
enable = lib.mkEnableOption "Docker container runtime";
};
config = lib.mkIf cfg.enable {
virtualisation.docker = {
enable = true;
autoPrune = {
enable = true;
dates = "weekly";
};
daemon.settings = {
ipv6 = true;
"fixed-cidr-v6" = "fd00:30::/64";
ip6tables = true;
};
};
environment.systemPackages = with pkgs; [
docker # Container runtime
oxker # Docker TUI Tool
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.fcitx5;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.fcitx5 = {
enable = lib.mkEnableOption "fcitx5 input method";
};
config = lib.mkIf cfg.enable {
my.applications.fcitx5.system.enable = lib.mkDefault true;
my.applications.fcitx5.homeManager.enable = lib.mkDefault true;
};
}
+24
View File
@@ -0,0 +1,24 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.fcitx5.homeManager;
in
{
options.my.applications.fcitx5.homeManager = {
enable = lib.mkEnableOption "fcitx5 home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf cfg.enable {
home.file.".config/fcitx5/config" = {
recursive = true;
source = ./config;
};
};
}
];
}
+68
View File
@@ -0,0 +1,68 @@
{
pkgs,
lib,
config,
inputs,
...
}:
let
system = pkgs.stdenv.hostPlatform.system;
cfg = config.my.applications.fcitx5.system;
in
{
options.my.applications.fcitx5.system = {
enable = lib.mkEnableOption "fcitx5 system configuration";
};
imports = [
inputs.nix-hazkey.nixosModules.hazkey
];
config = lib.mkIf cfg.enable {
services.hazkey = {
enable = true;
server.package = inputs.nix-hazkey.packages.${system}.hazkey-server.override {
enableVulkan = true;
};
installHazkeySettings = false;
installFcitx5Addon = false;
};
environment.systemPackages = [ inputs.nix-hazkey.packages.${system}.hazkey-settings ];
i18n.inputMethod = {
enable = true;
type = "fcitx5";
fcitx5 = {
waylandFrontend = true;
addons = with pkgs; [
inputs.nix-hazkey.packages.${system}.fcitx5-hazkey
fcitx5-mozc-ut
fcitx5-gtk
kdePackages.fcitx5-qt
qt6Packages.fcitx5-configtool
];
settings.inputMethod = {
GroupOrder = {
"0" = "Default";
};
"Groups/0" = {
Name = "Default";
"Default Layout" = "jp";
DefaultIM = "mozc";
};
"Groups/0/Items/0" = {
Name = "keyboard-jp";
Layout = "";
};
"Groups/0/Items/1" = {
Name = "mozc";
Layout = "";
};
};
};
};
};
}
+40
View File
@@ -0,0 +1,40 @@
theme = dracula
background-blur-radius = 20
background-opacity = 0.9
font-family = BlexMono Nerd Font Mono
mouse-hide-while-typing = true
window-decoration = true
# keybind
# Copy/Paste
keybind = performable:ctrl+shift+c=copy_to_clipboard
keybind = ctrl+shift+v=paste_from_clipboard
# create new tab
keybind = ctrl+shift+t=new_tab
# move tabs
keybind = ctrl+alt+left_bracket=previous_tab
keybind = ctrl+alt+right_bracket=next_tab
# close tab
keybind = ctrl+alt+q=close_window
# font size
keybind = ctrl+shift+semicolon=increase_font_size:1
keybind = ctrl+shift+minus=increase_font_size:1
# quick terminal
keybind = global:super+space=toggle_quick_terminal
quick-terminal-position = top
quick-terminal-size = 100%
gtk-quick-terminal-layer = overlay
quick-terminal-keyboard-interactivity = exclusive
quick-terminal-autohide = false
quit-after-last-window-closed = false
shell-integration-features = ssh-terminfo,ssh-env
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.ghostty;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.ghostty = {
enable = lib.mkEnableOption "ghostty terminal emulator";
};
config = lib.mkIf cfg.enable {
my.applications.ghostty.system.enable = lib.mkDefault true;
my.applications.ghostty.homeManager.enable = lib.mkDefault true;
};
}
@@ -0,0 +1,45 @@
# MIT License
#
# Copyright (c) 2023 Dracula Theme
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
palette = 0=#21222c
palette = 1=#ff5555
palette = 2=#50fa7b
palette = 3=#f1fa8c
palette = 4=#bd93f9
palette = 5=#ff79c6
palette = 6=#8be9fd
palette = 7=#f8f8f2
palette = 8=#6272a4
palette = 9=#ff6e6e
palette = 10=#69ff94
palette = 11=#ffffa5
palette = 12=#d6acff
palette = 13=#ff92df
palette = 14=#a4ffff
palette = 15=#ffffff
background = #282a36
foreground = #f8f8f2
cursor-color = #f8f8f2
cursor-text = #282a36
selection-foreground = #f8f8f2
selection-background = #44475a
+83
View File
@@ -0,0 +1,83 @@
{
pkgs,
lib,
config,
inputs,
...
}:
let
cfg = config.my.applications.ghostty.homeManager;
system = pkgs.stdenv.hostPlatform.system;
ghosttyPkg = inputs.ghostty.packages.${system}.ghostty-releasefast;
in
{
options.my.applications.ghostty.homeManager = {
enable = lib.mkEnableOption "ghostty home-manager configuration";
};
config.home-manager.sharedModules = [
(
{ lib, ... }:
{
config = lib.mkIf cfg.enable {
programs.ghostty = {
enable = true;
package = ghosttyPkg;
systemd.enable = true;
settings = {
theme = "dracula";
background-blur-radius = 20;
background-opacity = 0.9;
font-family = "BlexMono Nerd Font Mono";
mouse-hide-while-typing = true;
window-decoration = "auto";
keybind = [
# Copy/Paste
"performable:ctrl+shift+c=copy_to_clipboard"
"ctrl+shift+v=paste_from_clipboard"
# Create new tab
"ctrl+shift+t=new_tab"
# Move tabs
"ctrl+alt+left_bracket=previous_tab"
"ctrl+alt+right_bracket=next_tab"
# Close window
"ctrl+alt+q=close_window"
# Font size
"ctrl+shift+semicolon=increase_font_size:1"
"ctrl+shift+minus=decrease_font_size:1"
];
# Quick terminal
quick-terminal-position = "top";
quick-terminal-size = "98%,100%";
quick-terminal-autohide = false;
quick-terminal-keyboard-interactivity = "on-demand";
gtk-quick-terminal-layer = "top";
quit-after-last-window-closed = false;
shell-integration-features = "no-ssh-env,no-ssh-terminfo";
};
};
xdg.configFile."ghostty/themes/dracula".source = ./dracula.theme;
};
}
)
];
}
+26
View File
@@ -0,0 +1,26 @@
{
pkgs,
lib,
config,
inputs,
...
}:
let
cfg = config.my.applications.ghostty.system;
system = pkgs.stdenv.hostPlatform.system;
ghosttyPkg = inputs.ghostty.packages.${system}.ghostty-releasefast;
in
{
options.my.applications.ghostty.system = {
enable = lib.mkEnableOption "ghostty system configuration";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
ghosttyPkg # A fast and minimal terminal emulator for Wayland
ghosttyPkg.terminfo # Terminfo database for ghostty
];
};
}
+35
View File
@@ -0,0 +1,35 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.git;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.git = {
enable = lib.mkEnableOption "git version control";
userName = lib.mkOption {
type = lib.types.singleLineStr;
default = "moons";
description = "Default Git user.name.";
};
userEmail = lib.mkOption {
type = lib.types.singleLineStr;
default = "moons@moons14.com";
description = "Default Git user.email.";
};
};
config = lib.mkIf cfg.enable {
my.applications.git.system.enable = lib.mkDefault true;
my.applications.git.homeManager.enable = lib.mkDefault true;
};
}
+111
View File
@@ -0,0 +1,111 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.git;
hmCfg = config.my.applications.git.homeManager;
signingKeyPath = ".ssh/1password-git-signing.pub";
signingKeyFile = "~/${signingKeyPath}";
gitSshSign = pkgs.writeShellScript "git-ssh-sign" ''
one_password_sock="$HOME/.1password/agent.sock"
if { [ -n "''${SSH_CONNECTION:-}" ] || [ -n "''${SSH_CLIENT:-}" ]; } \
&& [ -n "''${SSH_AUTH_SOCK:-}" ] \
&& [ -S "$SSH_AUTH_SOCK" ]; then
exec ${pkgs.openssh}/bin/ssh-keygen "$@"
fi
if [ -S "$one_password_sock" ]; then
export SSH_AUTH_SOCK="$one_password_sock"
exec ${pkgs.openssh}/bin/ssh-keygen "$@"
fi
if [ -n "''${SSH_AUTH_SOCK:-}" ] && [ -S "$SSH_AUTH_SOCK" ]; then
exec ${pkgs.openssh}/bin/ssh-keygen "$@"
fi
echo "git ssh signing failed: no forwarded SSH agent or 1Password agent socket found" >&2
echo "expected: forwarded SSH_AUTH_SOCK or $one_password_sock" >&2
exit 1
'';
in
{
options.my.applications.git.homeManager = {
enable = lib.mkEnableOption "git home-manager configuration";
signingPublicKey = lib.mkOption {
type = lib.types.nullOr lib.types.singleLineStr;
default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLwReAiwhXoO34S2+MrvqUhi8IWp4IzUq4OSp3niJdq 1password-git-signing";
example = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPLwReAiwhXoO34S2+MrvqUhi8IWp4IzUq4OSp3niJdq 1password-git-signing";
description = "SSH public key copied from the 1Password SSH key item used for Git signing.";
};
signingKey = lib.mkOption {
type = lib.types.str;
default = signingKeyFile;
readOnly = true;
description = "SSH public key path used for Git commit and tag signing.";
};
};
config = lib.mkIf hmCfg.enable {
assertions = [
{
assertion = hmCfg.signingPublicKey != null && hmCfg.signingPublicKey != "";
message = "my.applications.git.homeManager.signingPublicKey must be set to the public key copied from 1Password.";
}
];
home-manager.sharedModules = [
{
home.file.${signingKeyPath}.text = hmCfg.signingPublicKey + "\n";
programs.git = {
enable = true;
ignores = [
".direnv/"
".envrc"
"!.envrc.example"
];
signing = {
key = hmCfg.signingKey;
format = "ssh";
signByDefault = true;
};
settings = {
user.name = cfg.userName;
user.email = cfg.userEmail;
push.default = "simple";
credential.helper = "cache --timeout=7200";
init.defaultBranch = "main";
log.decorate = "full";
log.date = "iso";
merge.conflictStyle = "diff3";
gpg.ssh.program = "${gitSshSign}";
alias = {
br = "branch --sort=-committerdate";
co = "checkout";
df = "diff";
com = "commit -a";
gs = "stash";
gp = "pull";
lg = "log --graph --pretty=format:'%Cred%h%Creset - %C(yellow)%d%Creset %s %C(green)(%cr)%C(bold blue) <%an>%Creset' --abbrev-commit";
st = "status";
};
};
};
}
];
};
}
+21
View File
@@ -0,0 +1,21 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.git.system;
in
{
options.my.applications.git.system = {
enable = lib.mkEnableOption "git system configuration";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
git # Distributed version control system
gh # GitHub CLI
];
};
}
+94
View File
@@ -0,0 +1,94 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.gnome;
in
{
options.my.applications.gnome = {
enable = lib.mkEnableOption "GNOME desktop environment";
};
config = lib.mkIf cfg.enable {
services.desktopManager.gnome.enable = true;
# ly is the display manager for switching between installed sessions.
services.displayManager.gdm.enable = lib.mkForce false;
home-manager.sharedModules = [
{
dconf.settings = {
"org/gnome/desktop/sound" = {
event-sounds = false;
input-feedback-sounds = false;
};
"org/gnome/desktop/wm/keybindings" = {
close = [ "<Super>q" ];
show-desktop = [ ];
};
"org/gnome/settings-daemon/plugins/media-keys" = {
home = [ ];
screensaver = [ "<Super>l" ];
custom-keybindings = [
"/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/"
"/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/"
"/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom2/"
"/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom3/"
"/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom4/"
];
};
"org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0" = {
name = "Open Terminal";
command = "ghostty";
binding = "<Super>t";
};
"org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1" = {
name = "Run Application";
command = "vicinae toggle";
binding = "<Super>d";
};
"org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom2" = {
name = "Open File Manager";
command = "nautilus --new-window";
binding = "<Super>e";
};
"org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom3" = {
name = "Clipboard History";
command = "vicinae vicinae://extensions/vicinae/clipboard/history";
binding = "<Super>v";
};
"org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom4" = {
name = "Log Out";
command = "gnome-session-quit --logout --no-prompt";
binding = "<Super><Shift>e";
};
"org/gnome/shell" = {
favorite-apps = [
"google-chrome.desktop"
"code.desktop"
"com.mitchellh.ghostty.desktop"
"slack.desktop"
"vesktop.desktop"
];
};
};
}
];
environment.systemPackages = with pkgs; [
gnome-tweaks # GNOME desktop customization tool
gnome-extension-manager # GNOME Shell extension manager
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.gnupg;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.gnupg = {
enable = lib.mkEnableOption "GnuPG agent";
};
config = lib.mkIf cfg.enable {
my.applications.gnupg.system.enable = lib.mkDefault true;
my.applications.gnupg.homeManager.enable = lib.mkDefault true;
};
}
+22
View File
@@ -0,0 +1,22 @@
{
lib,
config,
...
}:
let
hmCfg = config.my.applications.gnupg.homeManager;
in
{
options.my.applications.gnupg.homeManager = {
enable = lib.mkEnableOption "GnuPG home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf hmCfg.enable {
services.gpg-agent.enable = false;
services.gpg-agent.enableSshSupport = false;
};
}
];
}
+20
View File
@@ -0,0 +1,20 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.gnupg.system;
in
{
options.my.applications.gnupg.system = {
enable = lib.mkEnableOption "GnuPG system configuration";
};
config = lib.mkIf cfg.enable {
programs.gnupg.agent = {
enable = true;
enableSSHSupport = false;
};
};
}
+26
View File
@@ -0,0 +1,26 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.greetd;
in
{
options.my.applications.greetd = {
enable = lib.mkEnableOption "greetd login manager";
};
config = lib.mkIf cfg.enable {
services.greetd = {
enable = true;
settings = {
default_session = {
user = "greeter";
command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd niri-session";
};
};
};
};
}
+24
View File
@@ -0,0 +1,24 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.grok;
grok = pkgs.llm-agents.grok.overrideAttrs (_old: {
versionCheckProgram = "${placeholder "out"}/libexec/grok/grok-launcher";
});
in
{
options.my.applications.grok = {
enable = lib.mkEnableOption "Grok AI assistant";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
grok
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.gtk;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.gtk = {
enable = lib.mkEnableOption "GTK theme configuration";
};
config = lib.mkIf cfg.enable {
my.applications.gtk.system.enable = lib.mkDefault true;
my.applications.gtk.homeManager.enable = lib.mkDefault true;
};
}
+36
View File
@@ -0,0 +1,36 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.gtk.homeManager;
in
{
options.my.applications.gtk.homeManager = {
enable = lib.mkEnableOption "GTK home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf cfg.enable {
gtk = {
enable = true;
theme = {
name = "Dracula";
package = pkgs.dracula-theme;
};
cursorTheme = {
package = pkgs.adwaita-icon-theme;
name = "Adwaita";
};
iconTheme = {
package = pkgs.papirus-icon-theme;
name = "Papirus-Dark";
};
};
};
}
];
}
+20
View File
@@ -0,0 +1,20 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.gtk.system;
in
{
options.my.applications.gtk.system = {
enable = lib.mkEnableOption "GTK system configuration";
};
config = lib.mkIf cfg.enable {
programs.dconf.enable = true;
programs.seahorse.enable = true;
services.gnome.gnome-keyring.enable = true;
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.java;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.java = {
enable = lib.mkEnableOption "Java runtime";
};
config = lib.mkIf cfg.enable {
my.applications.java.system.enable = lib.mkDefault true;
my.applications.java.homeManager.enable = lib.mkDefault true;
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.java.homeManager;
in
{
options.my.applications.java.homeManager = {
enable = lib.mkEnableOption "Java home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf cfg.enable {
programs.java = {
enable = true;
};
};
}
];
}
+27
View File
@@ -0,0 +1,27 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.java.system;
in
{
options.my.applications.java.system = {
enable = lib.mkEnableOption "Java system configuration";
};
config = lib.mkIf cfg.enable {
programs.java = {
enable = true;
package = pkgs.jdk25;
};
environment.systemPackages = with pkgs; [
jdk25 # Java Development Kit 25
maven # Java Build Tool
gradle # Java Build Tool
];
};
}
+19
View File
@@ -0,0 +1,19 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.kde;
in
{
options.my.applications.kde = {
enable = lib.mkEnableOption "KDE Connect";
};
config = lib.mkIf cfg.enable {
programs.kdeconnect = {
enable = true;
};
};
}
+36
View File
@@ -0,0 +1,36 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.my.applications.ly;
indyzLinuxfire = pkgs.fetchurl {
url = "https://codeberg.org/attachments/f336d6ac-8331-4323-91fc-0e4619803401";
hash = "sha256-fRm0wlkq9/GdLrVBOzMEnQG/i2ng+uGIzq0u9hu3m9g=";
};
in
{
options.my.applications.ly = {
enable = lib.mkEnableOption "ly TUI display manager";
};
config = lib.mkIf cfg.enable {
services.displayManager.defaultSession = lib.mkDefault "niri";
services.displayManager.ly = {
enable = true;
settings = {
default_session = "niri";
animate = true;
animation = "dur_file";
dur_file_path = "${indyzLinuxfire}";
dur_offset_alignment = "center";
full_color = true;
};
};
};
}
+22
View File
@@ -0,0 +1,22 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.nautilus;
in
{
options.my.applications.nautilus = {
enable = lib.mkEnableOption "Nautilus file manager";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
nautilus # GNOME file manager
gvfs # GNOME virtual file system
sushi # Nautilus file previewer
];
};
}
+27
View File
@@ -0,0 +1,27 @@
{
username,
lib,
config,
...
}:
let
cfg = config.my.applications.nh;
in
{
options.my.applications.nh = {
enable = lib.mkEnableOption "nh Nix CLI helper";
};
config = lib.mkIf cfg.enable {
programs.nh = {
enable = true;
flake = "/home/${username}/dotfiles";
clean = {
enable = true;
dates = "weekly";
extraArgs = "--keep-since 14d --keep 10";
};
};
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.niri;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.niri = {
enable = lib.mkEnableOption "niri window manager";
};
config = lib.mkIf cfg.enable {
my.applications.niri.system.enable = lib.mkDefault true;
my.applications.niri.homeManager.enable = lib.mkDefault true;
};
}
@@ -0,0 +1,166 @@
{
"Mod+Shift+Slash".action.show-hotkey-overlay = { };
"XF86AudioRaiseVolume" = {
allow-when-locked = true;
action.spawn = [
"wpctl"
"set-volume"
"@DEFAULT_AUDIO_SINK@"
"0.1+"
];
};
"XF86AudioLowerVolume" = {
allow-when-locked = true;
action.spawn = [
"wpctl"
"set-volume"
"@DEFAULT_AUDIO_SINK@"
"0.1-"
];
};
"XF86AudioMute" = {
allow-when-locked = true;
action.spawn = [
"wpctl"
"set-mute"
"@DEFAULT_AUDIO_SINK@"
"toggle"
];
};
"XF86AudioMicMute" = {
allow-when-locked = true;
action.spawn = [
"wpctl"
"set-mute"
"@DEFAULT_AUDIO_SOURCE@"
"toggle"
];
};
"Mod+Q".action.close-window = { };
"Mod+Left".action.focus-column-left = { };
"Mod+Down".action.focus-window-down = { };
"Mod+Up".action.focus-window-up = { };
"Mod+Right".action.focus-column-right = { };
"Mod+H".action.focus-column-left = { };
"Mod+J".action.focus-window-down = { };
"Mod+K".action.focus-window-up = { };
"Mod+L".action.focus-column-right = { };
"Mod+Ctrl+Left".action.move-column-left = { };
"Mod+Ctrl+Down".action.move-window-down = { };
"Mod+Ctrl+Up".action.move-window-up = { };
"Mod+Ctrl+Right".action.move-column-right = { };
"Mod+Ctrl+H".action.move-column-left = { };
"Mod+Ctrl+J".action.move-window-down = { };
"Mod+Ctrl+K".action.move-window-up = { };
"Mod+Ctrl+L".action.move-column-right = { };
"Mod+Home".action.focus-column-first = { };
"Mod+End".action.focus-column-last = { };
"Mod+Ctrl+Home".action.move-column-to-first = { };
"Mod+Ctrl+End".action.move-column-to-last = { };
"Mod+Shift+Left".action.focus-monitor-left = { };
"Mod+Shift+Down".action.focus-monitor-down = { };
"Mod+Shift+Up".action.focus-monitor-up = { };
"Mod+Shift+Right".action.focus-monitor-right = { };
"Mod+Shift+H".action.focus-monitor-left = { };
"Mod+Shift+J".action.focus-monitor-down = { };
"Mod+Shift+K".action.focus-monitor-up = { };
"Mod+Shift+L".action.focus-monitor-right = { };
"Mod+Shift+Ctrl+Left".action.move-column-to-monitor-left = { };
"Mod+Shift+Ctrl+Down".action.move-column-to-monitor-down = { };
"Mod+Shift+Ctrl+Up".action.move-column-to-monitor-up = { };
"Mod+Shift+Ctrl+Right".action.move-column-to-monitor-right = { };
"Mod+Shift+Ctrl+H".action.move-column-to-monitor-left = { };
"Mod+Shift+Ctrl+J".action.move-column-to-monitor-down = { };
"Mod+Shift+Ctrl+K".action.move-column-to-monitor-up = { };
"Mod+Shift+Ctrl+L".action.move-column-to-monitor-right = { };
"Mod+Page_Down".action.focus-workspace-down = { };
"Mod+Page_Up".action.focus-workspace-up = { };
"Mod+U".action.focus-workspace-down = { };
"Mod+I".action.focus-workspace-up = { };
"Mod+Ctrl+Page_Down".action.move-column-to-workspace-down = { };
"Mod+Ctrl+Page_Up".action.move-column-to-workspace-up = { };
"Mod+Ctrl+U".action.move-column-to-workspace-down = { };
"Mod+Ctrl+I".action.move-column-to-workspace-up = { };
"Mod+Shift+Page_Down".action.move-workspace-down = { };
"Mod+Shift+Page_Up".action.move-workspace-up = { };
"Mod+Shift+U".action.move-workspace-down = { };
"Mod+Shift+I".action.move-workspace-up = { };
"Mod+WheelScrollDown" = {
cooldown-ms = 150;
action.focus-workspace-down = { };
};
"Mod+WheelScrollUp" = {
cooldown-ms = 150;
action.focus-workspace-up = { };
};
"Mod+Ctrl+WheelScrollDown" = {
cooldown-ms = 150;
action.move-column-to-workspace-down = { };
};
"Mod+Ctrl+WheelScrollUp" = {
cooldown-ms = 150;
action.move-column-to-workspace-up = { };
};
"Mod+WheelScrollRight".action.focus-column-right = { };
"Mod+WheelScrollLeft".action.focus-column-left = { };
"Mod+Ctrl+WheelScrollRight".action.move-column-right = { };
"Mod+Ctrl+WheelScrollLeft".action.move-column-left = { };
"Mod+Shift+WheelScrollDown".action.focus-column-right = { };
"Mod+Shift+WheelScrollUp".action.focus-column-left = { };
"Mod+Ctrl+Shift+WheelScrollDown".action.move-column-right = { };
"Mod+Ctrl+Shift+WheelScrollUp".action.move-column-left = { };
"Mod+1".action.focus-workspace = 1;
"Mod+2".action.focus-workspace = 2;
"Mod+3".action.focus-workspace = 3;
"Mod+4".action.focus-workspace = 4;
"Mod+5".action.focus-workspace = 5;
"Mod+6".action.focus-workspace = 6;
"Mod+7".action.focus-workspace = 7;
"Mod+8".action.focus-workspace = 8;
"Mod+9".action.focus-workspace = 9;
"Mod+Ctrl+1".action.move-column-to-workspace = 1;
"Mod+Ctrl+2".action.move-column-to-workspace = 2;
"Mod+Ctrl+3".action.move-column-to-workspace = 3;
"Mod+Ctrl+4".action.move-column-to-workspace = 4;
"Mod+Ctrl+5".action.move-column-to-workspace = 5;
"Mod+Ctrl+6".action.move-column-to-workspace = 6;
"Mod+Ctrl+7".action.move-column-to-workspace = 7;
"Mod+Ctrl+8".action.move-column-to-workspace = 8;
"Mod+Ctrl+9".action.move-column-to-workspace = 9;
"Mod+Comma".action.consume-window-into-column = { };
"Mod+Period".action.expel-window-from-column = { };
"Mod+R".action.switch-preset-column-width = { };
"Mod+Shift+R".action.reset-window-height = { };
"Mod+F".action.maximize-column = { };
"Mod+Shift+F".action.fullscreen-window = { };
"Mod+C".action.center-column = { };
"Mod+Minus".action.set-column-width = "-10%";
"Mod+Equal".action.set-column-width = "+10%";
"Mod+Shift+Minus".action.set-window-height = "-10%";
"Mod+Shift+Equal".action.set-window-height = "+10%";
"Print".action.screenshot = { };
"Ctrl+Print".action.screenshot-screen = { };
"Alt+Print".action.screenshot-window = { };
"Mod+Shift+E".action.quit = { };
"Mod+Shift+P".action.power-off-monitors = { };
}
+273
View File
@@ -0,0 +1,273 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.niri.homeManager;
defaultKeyBind = import ./defaultKeyBind.nix;
in
{
options.my.applications.niri.homeManager = {
enable = lib.mkEnableOption "niri home-manager configuration";
};
config.home-manager.sharedModules = [
inputs.niri-flake.homeModules.niri
{
config = lib.mkIf cfg.enable {
programs.niri.package = pkgs.niri;
programs.niri.settings = {
input.touchpad = {
natural-scroll = true;
scroll-factor = 4.0;
scroll-method = "two-finger";
click-method = "clickfinger";
drag = true;
drag-lock = true;
};
input.keyboard.xkb = {
layout = "jp";
options = "ctrl:nocaps";
};
input.mouse = {
accel-profile = "flat";
accel-speed = -0.1;
};
input.warp-mouse-to-focus.enable = true;
input.focus-follows-mouse = {
enable = true;
max-scroll-amount = "0%";
};
spawn-at-startup = [
{ command = [ "noctalia" ]; }
{
command = [
"${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"
];
}
];
cursor.size = 16;
outputs = {
# x1g13 monitor
"eDP-1" = {
scale = 1.2;
position = {
x = 2240;
y = 1440;
};
};
# LG WQHD monitor
"HDMI-A-1" = {
scale = 1.0;
position = {
x = 2560;
y = 0;
};
};
# DELL monitor
"DP-3" = {
scale = 1.5;
position = {
x = 0;
y = 0;
};
};
};
layout = {
focus-ring = {
active.color = "#bd93f9";
inactive.color = "#6272a4";
};
border = {
active.color = "#ffc87f";
inactive.color = "#505050";
urgent.color = "#9b0000";
};
shadow = {
color = "#0007";
};
background-color = "transparent";
};
binds = defaultKeyBind // {
"Mod+T" = {
action.spawn = "ghostty";
hotkey-overlay.title = "Open a Terminal: ghostty";
};
"Mod+D" = {
action.spawn = [
"vicinae"
"toggle"
];
hotkey-overlay.title = "Run an Application: vicinae";
};
"Mod+E" = {
action.spawn = [
"nautilus"
"--new-window"
];
hotkey-overlay.title = "Open File Manager: nautilus";
};
"Mod+L" = {
action.spawn = [
(lib.getExe' pkgs.systemd "loginctl")
"lock-session"
];
hotkey-overlay.title = "Lock the Screen";
};
"Mod+V" = {
action.spawn = [
"vicinae"
"vicinae://launch/clipboard/history?toggle=true"
];
hotkey-overlay.title = "Clipboard History";
};
"Mod+Space" = {
action.spawn = [
"ghostty"
"+toggle-quick-terminal"
];
hotkey-overlay.title = "Toggle Quick Terminal: ghostty";
};
# Focus monitor
"Mod+Shift+Left" = {
action.focus-monitor-left = [ ];
hotkey-overlay.title = "Focus Monitor Left";
};
"Mod+Shift+Right" = {
action.focus-monitor-right = [ ];
hotkey-overlay.title = "Focus Monitor Right";
};
"Mod+Shift+Up" = {
action.focus-monitor-up = [ ];
hotkey-overlay.title = "Focus Monitor Up";
};
"Mod+Shift+Down" = {
action.focus-monitor-down = [ ];
hotkey-overlay.title = "Focus Monitor Down";
};
# Move focused window to monitor
"Mod+Shift+Ctrl+Left" = {
action.move-window-to-monitor-left = [ ];
hotkey-overlay.title = "Move Window to Monitor Left";
};
"Mod+Shift+Ctrl+Right" = {
action.move-window-to-monitor-right = [ ];
hotkey-overlay.title = "Move Window to Monitor Right";
};
"Mod+Shift+Ctrl+Up" = {
action.move-window-to-monitor-up = [ ];
hotkey-overlay.title = "Move Window to Monitor Up";
};
"Mod+Shift+Ctrl+Down" = {
action.move-window-to-monitor-down = [ ];
hotkey-overlay.title = "Move Window to Monitor Down";
};
"XF86AudioRaiseVolume" = {
action.spawn = [
"noctalia"
"msg"
"volume-up"
];
};
"XF86AudioLowerVolume" = {
action.spawn = [
"noctalia"
"msg"
"volume-down"
];
};
"XF86AudioMute" = {
action.spawn = [
"noctalia"
"msg"
"volume-mute"
];
};
"XF86AudioMicMute" = {
action.spawn = [
"noctalia"
"msg"
"mic-mute"
];
};
"XF86MonBrightnessUp" = {
action.spawn = [
"noctalia"
"msg"
"brightness-up"
];
};
"XF86MonBrightnessDown" = {
action.spawn = [
"noctalia"
"msg"
"brightness-down"
];
};
"XF86Favorites" = {
action.spawn = [
"noctalia"
"msg"
"caffeine-toggle"
];
};
};
window-rules = [
{
geometry-corner-radius = {
top-left = 20.0;
top-right = 20.0;
bottom-left = 20.0;
bottom-right = 20.0;
};
clip-to-geometry = true;
}
{
matches = [ { app-id = "^dev\\.noctalia\\.Noctalia$"; } ];
open-floating = true;
default-column-width = {
fixed = 1080;
};
default-window-height = {
fixed = 920;
};
}
];
layer-rules = [
{
matches = [ { namespace = "^noctalia-wallpaper"; } ];
place-within-backdrop = true;
}
];
overview = {
workspace-shadow = {
enable = false;
};
};
};
};
}
];
}
+24
View File
@@ -0,0 +1,24 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.niri.system;
in
{
options.my.applications.niri.system = {
enable = lib.mkEnableOption "niri system configuration";
};
config = lib.mkIf cfg.enable {
programs.niri.enable = true;
environment.systemPackages = with pkgs; [
wdisplays # Wayland display configuration GUI
wlr-randr # Wayland output management CLI
polkit_gnome # Polkit authentication agent for GNOME
];
};
}
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.nix-index;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.nix-index = {
enable = lib.mkEnableOption "nix-index and comma command runner";
};
config = lib.mkIf cfg.enable {
my.applications.nix-index.system.enable = lib.mkDefault true;
my.applications.nix-index.homeManager.enable = lib.mkDefault true;
};
}
+21
View File
@@ -0,0 +1,21 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.nix-index.homeManager;
in
{
options.my.applications.nix-index.homeManager = {
enable = lib.mkEnableOption "nix-index home-manager configuration";
};
config.home-manager.sharedModules = [
{
config = lib.mkIf cfg.enable {
programs.nix-index.enable = true;
};
}
];
}
+20
View File
@@ -0,0 +1,20 @@
{
inputs,
lib,
config,
...
}:
let
cfg = config.my.applications.nix-index.system;
in
{
imports = [ inputs.nix-index-database.nixosModules.default ];
options.my.applications.nix-index.system = {
enable = lib.mkEnableOption "nix-index system configuration";
};
config = lib.mkIf cfg.enable {
programs.nix-index-database.comma.enable = true;
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.noctalia;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.noctalia = {
enable = lib.mkEnableOption "noctalia";
};
config = lib.mkIf cfg.enable {
my.applications.noctalia.system.enable = lib.mkDefault true;
my.applications.noctalia.homeManager.enable = lib.mkDefault true;
};
}
+225
View File
@@ -0,0 +1,225 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.noctalia.homeManager;
walls = pkgs.fetchFromGitHub {
owner = "moons-14";
repo = "wallpapers";
rev = "cc3256f4aaf2c8e7d16fb000b1ee251af54085db";
hash = "sha256-emQ/FqKqMq3YI5bLx8gBZg/ZE72OG9Ilh71ggq78WdQ=";
};
in
{
options.my.applications.noctalia.homeManager = {
enable = lib.mkEnableOption "noctalia home-manager configuration";
};
config.home-manager.sharedModules = [
inputs.noctalia.homeModules.default
{
config = lib.mkIf cfg.enable {
home.file.".face" = {
recursive = true;
source = ../../../images/avatar.jpg;
};
home.file.".wallpapers" = {
source = walls;
recursive = true;
};
home.file.".cache/noctalia/wallpapers.json" = {
text = builtins.toJSON {
defaultWallpaper = "~/.wallpapers/1.jpg";
wallpapers = {
"DP-1" = "~/.wallpapers/1.jpg";
};
};
};
programs.noctalia = {
enable = true;
settings = {
theme = {
mode = "auto";
source = "wallpaper";
templates = {
enable_builtin_templates = false;
enable_community_templates = false;
};
};
desktop_widgets.enabled = false;
lockscreen = {
enabled = false;
fingerprint = false;
};
widget = {
cpu = {
type = "sysmon";
stat = "cpu_usage";
};
cpu-graph = {
type = "sysmon";
stat = "cpu_usage";
display = "graph";
show_label = false;
};
ram = {
type = "sysmon";
stat = "ram_used";
};
media = {
type = "media";
hide_when_no_media = true;
title_scroll = "always";
};
battery = {
type = "battery";
display_mode = "graphic";
warning_threshold = 30;
};
brightness = {
type = "brightness";
show_label = false;
};
input-volume = {
type = "volume";
device = "input";
};
output-volume = {
type = "volume";
device = "output";
};
clock = {
type = "clock";
format = "{:%Y/%m/%d %H:%M}";
vertical_format = "{:%Y/%m/%d\n%H:%M}";
tooltip_format = "{:%Y/%m/%d %H:%M (%a)}";
};
network-connection = {
type = "custom_button";
glyph = "access-point";
command = "nm-connection-editor";
};
tray = {
type = "tray";
pinned = [ "org.fcitx.Fcitx5" ];
};
};
bar = {
order = [ "main" ];
main = {
margin_ends = 15;
position = "top";
start = [
"network"
"bluetooth"
"network-connection"
"spacer"
"cpu"
"cpu-graph"
"ram"
];
center = [ "workspaces" ];
end = [
"media"
"spacer"
"notifications"
"tray"
"spacer"
"battery"
"input-volume"
"output-volume"
"privacy"
"brightness"
"clock"
"control-center"
];
};
};
colorSchemes.predefinedScheme = "dracula";
general = {
avatarImage = "~/.face";
radiusRatio = 0.2;
};
location = {
monthBeforeDay = true;
weatherEnabled = false;
name = "Tokyo";
};
wallpaper = {
enabled = true;
directory = "~/.wallpapers/";
fillMode = "crop";
automation = {
enabled = true;
order = "random";
interval_seconds = 60;
};
default.path = "~/.wallpapers/1.jpg";
};
dock = {
enabled = true;
position = "left";
auto_hide = true;
show_dots = true;
background_opacity = 0.8;
size = 1;
onlySameOutput = true;
reserve_space = false;
monitors = [ "eDP-1" ];
pinned = [
"com.mitchellh.ghostty"
"google-chrome"
"code"
];
colorizeIcons = false;
};
shell = {
clipboard_enabled = false;
};
controlCenter = {
position = "close_to_bar_button";
shortcuts = {
left = [
{ id = "WiFi"; }
{ id = "Bluetooth"; }
{ id = "ScreenRecorder"; }
{ id = "WallpaperSelector"; }
];
right = [
{ id = "Notifications"; }
{ id = "NightLight"; }
];
};
cards = [
{
enabled = true;
id = "profile-card";
}
{
enabled = true;
id = "shortcuts-card";
}
{
enabled = true;
id = "audio-card";
}
{
enabled = true;
id = "media-sysmon-card";
}
];
};
};
};
};
}
];
}
+32
View File
@@ -0,0 +1,32 @@
{
pkgs,
inputs,
lib,
config,
...
}:
let
cfg = config.my.applications.noctalia.system;
in
{
options.my.applications.noctalia.system = {
enable = lib.mkEnableOption "noctalia system package";
};
imports = [
inputs.noctalia.nixosModules.default
];
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
inputs.noctalia.packages.${pkgs.stdenv.hostPlatform.system}.default
];
programs.noctalia = {
enable = true;
recommendedServices.enable = true;
systemd.enable = true;
};
};
}
+20
View File
@@ -0,0 +1,20 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.opencode;
in
{
options.my.applications.opencode = {
enable = lib.mkEnableOption "OpenCode AI coding assistant";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
pkgs.llm-agents.opencode # OpenCode CLI
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{ lib, config, ... }:
let
cfg = config.my.applications.openssh;
in
{
options.my.applications.openssh = {
enable = lib.mkEnableOption "OpenSSH server";
};
config = lib.mkIf cfg.enable {
services.openssh = {
enable = true;
openFirewall = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PubkeyAuthentication = "yes";
};
};
};
}
+24
View File
@@ -0,0 +1,24 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.slack;
in
{
options.my.applications.slack = {
enable = lib.mkEnableOption "Slack messaging client";
};
config = lib.mkIf cfg.enable {
home-manager.sharedModules = [
{
home.packages = with pkgs; [
slack # Team messaging and collaboration platform
];
}
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.ssh;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.ssh = {
enable = lib.mkEnableOption "OpenSSH client";
};
config = lib.mkIf cfg.enable {
my.applications.ssh.system.enable = lib.mkDefault true;
my.applications.ssh.homeManager.enable = lib.mkDefault true;
};
}
+63
View File
@@ -0,0 +1,63 @@
{
pkgs,
lib,
config,
...
}:
let
hmCfg = config.my.applications.ssh.homeManager;
in
{
options.my.applications.ssh.homeManager = {
enable = lib.mkEnableOption "SSH home-manager configuration";
matchBlocks = lib.mkOption {
type = lib.types.attrs;
default = { };
description = "SSH match blocks";
};
};
config.home-manager.sharedModules = [
{
config = lib.mkIf hmCfg.enable {
home.packages = [
pkgs.openssh
];
systemd.user.sockets.gcr-ssh-agent.Install.WantedBy = lib.mkForce [ ];
services.ssh-agent.enable = lib.mkForce false;
programs.ssh = {
enable = true;
enableDefaultConfig = false;
settings = hmCfg.matchBlocks // {
"github.com" = {
HostName = "github.com";
User = "git";
AddKeysToAgent = "no";
};
"*.sfc.wide.ad.jp" = {
identityFile = "~/.ssh/id_ed25519_sk_rk";
identitiesOnly = true;
};
"*" = {
AddKeysToAgent = "no";
SetEnv = {
TERM = "xterm-256color";
};
};
};
extraConfig = ''
Match exec "test -S %d/.1password/agent.sock"
IdentityAgent %d/.1password/agent.sock
'';
};
};
}
];
}
+23
View File
@@ -0,0 +1,23 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.ssh.system;
in
{
options.my.applications.ssh.system = {
enable = lib.mkEnableOption "SSH system configuration";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
openssh # OpenSSH client and server
];
programs.ssh.startAgent = false;
services.gnome.gcr-ssh-agent.enable = false;
};
}
+96
View File
@@ -0,0 +1,96 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.swayidle;
brightnessctl = lib.getExe pkgs.brightnessctl;
niri = lib.getExe pkgs.niri;
rm = "${pkgs.coreutils}/bin/rm";
systemctl = lib.getExe' pkgs.systemd "systemctl";
brightnessState = "$XDG_RUNTIME_DIR/swayidle-brightness";
# AC 接続中は何もしない。
# つまり、以下のアイドル処理をバッテリー駆動時のみにする。
skipIfOnAC = ''
for supply in /sys/class/power_supply/*; do
if [ -f "$supply/type" ] \
&& [ "$(< "$supply/type")" = "Mains" ] \
&& [ -f "$supply/online" ] \
&& [ "$(< "$supply/online")" = "1" ]; then
exit 0
fi
done
'';
dimScreen = pkgs.writeShellScript "swayidle-dim-screen" ''
${skipIfOnAC}
${brightnessctl} get > "${brightnessState}" 2>/dev/null || exit 0
${brightnessctl} set 10% >/dev/null 2>&1 || true
'';
restoreBrightness = pkgs.writeShellScript "swayidle-restore-brightness" ''
if [ -f "${brightnessState}" ]; then
saved=$(< "${brightnessState}")
${brightnessctl} set "$saved" >/dev/null 2>&1 || true
${rm} -f "${brightnessState}"
fi
'';
suspendOnBattery = pkgs.writeShellScript "swayidle-suspend-on-battery" ''
${skipIfOnAC}
exec ${systemctl} suspend
'';
afterResume = pkgs.writeShellScript "swayidle-after-resume" ''
${niri} msg action power-on-monitors
${restoreBrightness}
'';
in
{
options.my.applications.swayidle = {
enable = lib.mkEnableOption "swayidle idle manager";
};
config = lib.mkIf cfg.enable {
home-manager.sharedModules = [
{
services.swayidle = {
enable = true;
systemdTargets = [
"graphical-session.target"
];
timeouts = [
{
timeout = 300;
command = "${dimScreen}";
resumeCommand = "${restoreBrightness}";
}
{
timeout = 360;
command = "${suspendOnBattery}";
}
];
events = {
after-resume = "${afterResume}";
};
};
systemd.user.services.swayidle.Service.PassEnvironment = [
"WAYLAND_DISPLAY"
"XDG_RUNTIME_DIR"
"DBUS_SESSION_BUS_ADDRESS"
];
}
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.swaylock;
in
{
imports = [
./home.nix
./system.nix
];
options.my.applications.swaylock = {
enable = lib.mkEnableOption "swaylock screen locker";
};
config = lib.mkIf cfg.enable {
my.applications.swaylock.system.enable = lib.mkDefault true;
my.applications.swaylock.homeManager.enable = lib.mkDefault true;
};
}
+48
View File
@@ -0,0 +1,48 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.swaylock;
hmCfg = config.my.applications.swaylock.homeManager;
in
{
options.my.applications.swaylock.homeManager = {
enable = lib.mkEnableOption "swaylock home-manager configuration";
};
config.home-manager.sharedModules = [
(
{ lib, pkgs, ... }:
{
config = lib.mkIf (cfg.enable && hmCfg.enable) {
home.packages = with pkgs; [
swaylock # Wayland screen locker using ext-session-lock-v1
];
# systemd-lock-handler holds a sleep inhibitor until this service is
# ready. swaylock forks only after the compositor confirms the lock.
systemd.user.services.swaylock = {
Unit = {
Description = "Screen locker for Wayland";
Documentation = [ "man:swaylock(1)" ];
OnSuccess = [ "unlock.target" ];
PartOf = [ "lock.target" ];
Before = [ "lock.target" ];
};
Service = {
Type = "forking";
ExecStart = "${lib.getExe pkgs.swaylock} -f -i %h/.wallpapers/28.jpg";
Restart = "on-failure";
RestartSec = 0;
};
Install.WantedBy = [ "lock.target" ];
};
};
}
)
];
}
+26
View File
@@ -0,0 +1,26 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.swaylock.system;
in
{
options.my.applications.swaylock.system = {
enable = lib.mkEnableOption "swaylock system configuration";
};
config = lib.mkIf cfg.enable {
services.systemd-lock-handler.enable = true;
security.pam.services.swaylock.fprintAuth = true;
# PAM authentication is serial. Let a supplied password succeed before
# starting fprintd, whose scan otherwise blocks password verification until
# its timeout expires. Submit an empty password to start fingerprint
# authentication in upstream swaylock.
security.pam.services.swaylock.rules.auth.fprintd.order =
config.security.pam.services.swaylock.rules.auth.unix.order + 50;
};
}
+118
View File
@@ -0,0 +1,118 @@
{ lib, config, ... }:
let
cfg = config.my.applications.tailscale;
hasAdvertiseRoutes = cfg.advertiseRoutes != [ ];
computedRoutingFeatures =
if cfg.routingFeatures != "auto" then
cfg.routingFeatures
else if hasAdvertiseRoutes && cfg.acceptRoutes then
"both"
else if hasAdvertiseRoutes then
"server"
else if cfg.acceptRoutes then
"client"
else
"none";
computedOpenFirewall = if cfg.openFirewall != null then cfg.openFirewall else hasAdvertiseRoutes;
computedSetFlags = [
"--accept-dns=${lib.boolToString cfg.acceptDns}"
"--accept-routes=${lib.boolToString cfg.acceptRoutes}"
]
++ lib.optionals hasAdvertiseRoutes [
"--advertise-routes=${lib.concatStringsSep "," cfg.advertiseRoutes}"
]
++ cfg.extraSetFlags;
in
{
options.my.applications.tailscale = {
enable = lib.mkEnableOption "Tailscale";
acceptDns = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Accept DNS configuration from Tailscale.";
};
acceptRoutes = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Accept subnet routes advertised by other Tailscale nodes.";
};
advertiseRoutes = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "10.50.0.0/16" ];
description = "Subnet routes to advertise through this Tailscale node.";
};
routingFeatures = lib.mkOption {
type = lib.types.enum [
"auto"
"none"
"client"
"server"
"both"
];
default = "auto";
description = ''
Routing feature mode for Tailscale.
auto:
- advertiseRoutes only -> server
- acceptRoutes only -> client
- both -> both
- neither -> none
'';
};
openFirewall = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
default = null;
description = ''
Whether to open the firewall for Tailscale's UDP port.
null means automatic:
- true when advertiseRoutes is non-empty
- false otherwise
'';
};
extraSetFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Additional flags to pass to `tailscale set`.";
};
extraUpFlags = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Additional flags to pass to `tailscale up`.
Note: on current NixOS this is only applied by the built-in
autoconnect service when services.tailscale.authKeyFile is set.
'';
};
};
config = lib.mkIf cfg.enable {
services.tailscale = {
enable = true;
openFirewall = computedOpenFirewall;
useRoutingFeatures = computedRoutingFeatures;
# 常時反映したい設定は tailscale set に寄せる
extraSetFlags = computedSetFlags;
# authKeyFile を使う場合だけ効くものとして残す
inherit (cfg) extraUpFlags;
};
};
}
+66
View File
@@ -0,0 +1,66 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
cfg = config.my.applications.vicinae;
in
{
options.my.applications.vicinae = {
enable = lib.mkEnableOption "Vicinae application launcher";
};
config = lib.mkIf cfg.enable {
home-manager.sharedModules = [
inputs.vicinae.homeManagerModules.default
{
programs.vicinae = {
enable = true;
systemd = {
enable = true;
autoStart = true;
environment = {
USE_LAYER_SHELL = 1;
};
};
settings = {
font.size = 11;
close_on_focus_loss = true;
consider_preedit = true;
pop_to_root_on_close = true;
favicon_service = "twenty";
search_files_in_root = true;
theme = {
light = {
name = "dracula";
icon_theme = "default";
};
dark = {
name = "vicinae-light";
icon_theme = "default";
};
};
window = {
csd = true;
opacity = 0.95;
rounding = 10;
};
};
extensions = with inputs.vicinae-extensions.packages.${pkgs.stdenv.hostPlatform.system}; [
nix
power-profile
niri
zoxide-recent-directories
ssh
port-killer
noctalia-shell-wallpaper-selector
];
};
}
];
};
}
+23
View File
@@ -0,0 +1,23 @@
{
lib,
config,
...
}:
let
cfg = config.my.applications.vim;
in
{
imports = [
./home
./system.nix
];
options.my.applications.vim = {
enable = lib.mkEnableOption "vim text editor";
};
config = lib.mkIf cfg.enable {
my.applications.vim.system.enable = lib.mkDefault true;
my.applications.vim.homeManager.enable = lib.mkDefault true;
};
}
@@ -0,0 +1,8 @@
_: {
programs.nixvim.autoCmd = [
{
event = "TextYankPost";
callback.__raw = "function() vim.highlight.on_yank() end";
}
];
}
@@ -0,0 +1,6 @@
_: {
programs.nixvim.colorschemes.catppuccin = {
enable = true;
settings.transparent_background = true;
};
}

Some files were not shown because too many files have changed in this diff Show More