Make Emacs flake standalone

This commit is contained in:
Tobias Ostner 2026-07-18 10:57:45 +02:00
parent bad1727674
commit bbd082947e
7 changed files with 293 additions and 126 deletions

54
flakes/emacs/README.md Normal file
View file

@ -0,0 +1,54 @@
# Tobmacs flake
This flake builds the complete Emacs configuration without Home Manager. It
contains Emacs, all Emacs packages, tree-sitter grammars, snippets, Hunspell,
and dictionaries.
## Run or install directly
```console
nix run 'git+https://codeberg.org/oibot/dotfiles?dir=flakes/emacs'
nix profile install 'git+https://codeberg.org/oibot/dotfiles?dir=flakes/emacs'
```
From this repository, use `nix run path:./flakes/emacs`.
## NixOS
Add the flake as an input and make its nixpkgs follow the system nixpkgs:
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
tobs-emacs = {
url = "git+https://codeberg.org/oibot/dotfiles?dir=flakes/emacs";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, tobs-emacs, ... }: {
nixosConfigurations.server = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
tobs-emacs.nixosModules.default
{
# Installs Tobmacs and starts an Emacs user service. No Home Manager
# module is involved.
services.emacs.enable = true;
services.emacs.startWithGraphical = false;
}
];
};
};
}
```
If no daemon is wanted, either set `services.emacs.install = true` instead of
`enable`, or install the package directly:
```nix
environment.systemPackages = [
tobs-emacs.packages.${pkgs.stdenv.hostPlatform.system}.default
];
```

27
flakes/emacs/flake.lock generated Normal file
View file

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1784120854,
"narHash": "sha256-KesHgItiZPgGX740axSiQLcIQ8D24MDqNpkKYWIek8k=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "753cc8a3a87467296ddd1fa93f0cc3e81120ee46",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,15 +1,55 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager/";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};
description = "Standalone Tobmacs package and NixOS integration";
outputs = inputs:
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
systems = [
"aarch64-darwin"
"aarch64-linux"
"x86_64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs systems;
in
{
packages = forAllSystems (system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfreePredicate = package:
builtins.elem (nixpkgs.lib.getName package) [
"copilot-language-server"
];
};
in
{
default = import ./package.nix { inherit pkgs; };
tobs-emacs = self.packages.${system}.default;
});
apps = forAllSystems (system: {
default = {
type = "app";
program = "${self.packages.${system}.default}/bin/emacs";
meta.description = "Run Tobmacs";
};
});
# Importing this module selects Tobmacs as services.emacs.package. The
# caller still decides whether to enable or merely install the service.
nixosModules.default = { pkgs, ... }: {
services.emacs.package =
self.packages.${pkgs.stdenv.hostPlatform.system}.default;
};
# Optional compatibility integration for this dotfiles repository. The
# standalone package and NixOS module above do not depend on Home Manager.
homeManagerModules = {
aarch64-darwin = import ./home.nix;
x86_64-linux = import ./home.nix;
default = import ./home.nix { inherit self; };
aarch64-darwin = self.homeManagerModules.default;
aarch64-linux = self.homeManagerModules.default;
x86_64-linux = self.homeManagerModules.default;
};
};
}

View file

@ -1,125 +1,17 @@
{ config, pkgs, lib, ... }:
{ self }:
{ pkgs, lib, ... }:
let
treeSitterLibraryExtension =
if pkgs.stdenv.hostPlatform.isDarwin then "dylib" else "so";
treeSitterGrammars = {
swift = pkgs.tree-sitter-grammars.tree-sitter-swift;
python = pkgs.tree-sitter-grammars.tree-sitter-python;
typescript = pkgs.tree-sitter-grammars.tree-sitter-typescript;
tsx = pkgs.tree-sitter-grammars.tree-sitter-tsx;
javascript = pkgs.tree-sitter-grammars.tree-sitter-javascript;
json = pkgs.tree-sitter-grammars.tree-sitter-json;
};
emacs = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
in
{
home.packages = with pkgs; [
enchant
hunspell
hunspellDicts.de_DE
hunspellDicts.en_US
];
home.packages = [ emacs ];
home.file =
(lib.mapAttrs'
(language: grammar:
lib.nameValuePair
".emacs.d/tree-sitter/libtree-sitter-${language}.${treeSitterLibraryExtension}"
{ source = "${grammar}/parser"; })
treeSitterGrammars)
// {
".emacs.d/early-init.el".source = ./config/early-init.el;
".emacs.d/snippets".source = ./config/snippets;
};
programs.emacs = {
enable = true;
extraConfig = builtins.readFile ./config/init.el +
builtins.readFile ./config/lisp/utils.el;
extraPackages = epkgs:
let
myEpkgs = epkgs.overrideScope (import ./pkgs { inherit pkgs; });
in
with myEpkgs; [
agent-shell
apheleia
avy
cape
catppuccin-theme
cider
clojure-mode
consult
consult-denote
copilot
corfu
denote
diff-hl
dockerfile-mode
ef-themes
eglot
envrc
exec-path-from-shell
flycheck
flycheck-clj-kondo
flycheck-swiftlint
flymake-collection
git-timemachine
graphql-mode
helpful
htmlize
jinx
js2-mode
json-mode
ledger-mode
ligature
markdown-mode
minions
multiple-cursors
nerd-icons
nerd-icons-completion
nerd-icons-corfu
nerd-icons-dired
nerd-icons-ibuffer
ob-restclient
ob-swift
olivetti
orderless
org
org-modern
ox-gfm
ox-reveal
pulsar
rainbow-delimiters
rainbow-mode
restclient
smartparens
swift-mode
swift-ts-mode
super-save
terraform-mode
treesit-auto
ob-typescript
yaml-mode
vterm
fontaine
magit
magit-todos
marginalia
modus-themes
nix-mode
vertico
web-mode
winpulse
yasnippet
];
};
launchd.agents.emacs-server = {
launchd.agents.emacs-server = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin {
enable = true;
config = {
KeepAlive = true;
ProgramArguments = [
"${config.programs.emacs.finalPackage}/bin/emacs"
"${emacs}/bin/emacs"
"--fg-daemon"
];
RunAtLoad = true;

158
flakes/emacs/package.nix Normal file
View file

@ -0,0 +1,158 @@
{ pkgs }:
let
inherit (pkgs) lib;
treeSitterLibraryExtension =
if pkgs.stdenv.hostPlatform.isDarwin then "dylib" else "so";
treeSitterGrammars = {
swift = pkgs.tree-sitter-grammars.tree-sitter-swift;
python = pkgs.tree-sitter-grammars.tree-sitter-python;
typescript = pkgs.tree-sitter-grammars.tree-sitter-typescript;
tsx = pkgs.tree-sitter-grammars.tree-sitter-tsx;
javascript = pkgs.tree-sitter-grammars.tree-sitter-javascript;
json = pkgs.tree-sitter-grammars.tree-sitter-json;
};
treeSitterBundle = pkgs.runCommand "tobmacs-tree-sitter-grammars" { } (
''
mkdir -p "$out/lib"
''
+ lib.concatStringsSep "\n" (lib.mapAttrsToList
(language: grammar: ''
ln -s "${grammar}/parser" \
"$out/lib/libtree-sitter-${language}.${treeSitterLibraryExtension}"
'')
treeSitterGrammars)
);
epkgs = (pkgs.emacsPackagesFor pkgs.emacs).overrideScope (
import ./pkgs { inherit pkgs; }
);
emacsPackages = with epkgs; [
agent-shell
apheleia
avy
cape
catppuccin-theme
cider
clojure-mode
consult
consult-denote
copilot
corfu
denote
diff-hl
dockerfile-mode
ef-themes
eglot
envrc
exec-path-from-shell
flycheck
flycheck-clj-kondo
flycheck-swiftlint
flymake-collection
fontaine
git-timemachine
graphql-mode
helpful
htmlize
jinx
js2-mode
json-mode
ledger-mode
ligature
magit
magit-todos
marginalia
markdown-mode
minions
modus-themes
multiple-cursors
nerd-icons
nerd-icons-completion
nerd-icons-corfu
nerd-icons-dired
nerd-icons-ibuffer
nix-mode
ob-restclient
ob-swift
ob-typescript
olivetti
orderless
org
org-modern
ox-gfm
ox-reveal
pulsar
rainbow-delimiters
rainbow-mode
restclient
smartparens
super-save
swift-mode
swift-ts-mode
terraform-mode
treesit-auto
vertico
vterm
web-mode
winpulse
yaml-mode
yasnippet
];
configDirectory = pkgs.runCommand "tobmacs-config" { } ''
mkdir -p "$out"
cp ${./config/early-init.el} "$out/early-init.el"
cat > "$out/init.el" <<'EOF'
;; -*- lexical-binding: t; -*-
;; --init-directory points at the immutable Nix store. Reset this after
;; startup has found init.el so mutable state remains in the user's home.
(setq user-emacs-directory (expand-file-name "~/.emacs.d/"))
(make-directory user-emacs-directory t)
(setq yas-snippet-dirs '("${./config/snippets}"))
${builtins.readFile ./config/init.el}
${builtins.readFile ./config/lisp/utils.el}
EOF
'';
emacsWithPackages = epkgs.emacsWithPackages (
_: emacsPackages ++ [ treeSitterBundle ]
);
# Make this package own its init directory, including a real early-init.el.
# The inner emacsWithPackages wrapper still supplies all package load paths.
emacs = pkgs.symlinkJoin {
name = "tobmacs-emacs";
paths = [ emacsWithPackages ];
nativeBuildInputs = [ pkgs.makeBinaryWrapper ];
postBuild = ''
wrapProgramBinary "$out/bin/emacs" \
--add-flags "--init-directory=${configDirectory}"
if [ -x "$out/Applications/Emacs.app/Contents/MacOS/Emacs" ]; then
wrapProgramBinary "$out/Applications/Emacs.app/Contents/MacOS/Emacs" \
--add-flags "--init-directory=${configDirectory}"
fi
'';
meta = emacsWithPackages.meta;
};
in
pkgs.buildEnv {
name = "tobmacs";
paths = [
emacs
pkgs.enchant
pkgs.hunspell
pkgs.hunspellDicts.de_DE
pkgs.hunspellDicts.en_US
];
meta = emacs.meta // {
description = "Emacs with Tobias Ostner's configuration and packages";
mainProgram = "emacs";
};
}