caesium: accounts and yubikey
This commit is contained in:
+141
-7
@@ -1,26 +1,160 @@
|
||||
{ config, pkgs, ... }: {
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
# Wraps the interactive pamu2fcfg ceremony (touch required, can't be
|
||||
# scripted away) so the repo's yubikey.u2f-mappings file always ends up
|
||||
# well-formed: one line per user, credentials colon-separated. See
|
||||
# module.yubikey.nix for how the file is consumed.
|
||||
yubikeyEnroll = pkgs.writeShellApplication {
|
||||
name = "yubikey-enroll";
|
||||
runtimeInputs = [ pkgs.pam_u2f pkgs.gawk pkgs.gnugrep ];
|
||||
text = ''
|
||||
REPO="''${YUBIKEY_REPO:-$HOME/nixos}"
|
||||
MAP_FILE="$REPO/yubikey.u2f-mappings"
|
||||
USERNAME="''${1:-$(whoami)}"
|
||||
|
||||
if [[ ! -f "$MAP_FILE" ]]; then
|
||||
echo "error: $MAP_FILE not found (expected the nixos repo checkout at $REPO)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
existing_line="$(grep "^''${USERNAME}:" "$MAP_FILE" || true)"
|
||||
|
||||
echo "Insert the YubiKey to enroll, then touch it when it blinks."
|
||||
if [[ -z "$existing_line" ]]; then
|
||||
new_line="$(pamu2fcfg -u "$USERNAME" -o pam://mbessette -i pam://mbessette)"
|
||||
else
|
||||
cred="$(pamu2fcfg -n -o pam://mbessette -i pam://mbessette)"
|
||||
new_line="''${existing_line}:''${cred}"
|
||||
fi
|
||||
|
||||
if [[ "$(printf '%s\n' "$new_line" | wc -l)" -ne 1 ]]; then
|
||||
echo "error: unexpected multi-line credential output, aborting without writing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
if [[ -z "$existing_line" ]]; then
|
||||
{ grep -v "^''${USERNAME}:" "$MAP_FILE" || true; printf '%s\n' "$new_line"; } > "$tmp"
|
||||
else
|
||||
awk -F: -v u="$USERNAME" -v line="$new_line" 'BEGIN{OFS=":"} $1==u {print line; next} {print}' "$MAP_FILE" > "$tmp"
|
||||
fi
|
||||
|
||||
n=$(grep -c "^''${USERNAME}:" "$tmp")
|
||||
if [[ "$n" -ne 1 ]]; then
|
||||
echo "error: expected exactly one line for $USERNAME, got $n; not writing" >&2
|
||||
rm -f "$tmp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$tmp" "$MAP_FILE"
|
||||
count=$(awk -F: -v u="$USERNAME" '$1==u{print NF-1}' "$MAP_FILE")
|
||||
echo "Enrolled. $USERNAME now has $count credential(s) in $MAP_FILE."
|
||||
echo "Review the diff, then run nix-update to install it to /etc/u2f_mappings."
|
||||
'';
|
||||
};
|
||||
|
||||
yubikeyUnenroll = pkgs.writeShellApplication {
|
||||
name = "yubikey-unenroll";
|
||||
runtimeInputs = [ pkgs.gawk pkgs.gnugrep pkgs.coreutils ];
|
||||
text = ''
|
||||
REPO="''${YUBIKEY_REPO:-$HOME/nixos}"
|
||||
MAP_FILE="$REPO/yubikey.u2f-mappings"
|
||||
|
||||
if [[ ! -f "$MAP_FILE" ]]; then
|
||||
echo "error: $MAP_FILE not found (expected the nixos repo checkout at $REPO)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
list_credentials() {
|
||||
local user="$1" line
|
||||
line="$(grep "^''${user}:" "$MAP_FILE" || true)"
|
||||
if [[ -z "$line" ]]; then
|
||||
echo "$user: no credentials enrolled"
|
||||
return
|
||||
fi
|
||||
awk -F: -v u="$user" '$1==u {
|
||||
for (i=2;i<=NF;i++) { split($i, f, ","); printf " %d: %s...\n", i-1, substr(f[1],1,16) }
|
||||
}' <<<"$line"
|
||||
}
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "usage: yubikey-unenroll <index> [username] (no args: list credentials)"
|
||||
while IFS= read -r user; do
|
||||
list_credentials "$user"
|
||||
done < <(awk -F: '{print $1}' "$MAP_FILE" | sort -u)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
INDEX="$1"
|
||||
USERNAME="''${2:-$(whoami)}"
|
||||
|
||||
if ! [[ "$INDEX" =~ ^[0-9]+$ ]]; then
|
||||
echo "error: index must be a positive integer" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
line="$(grep "^''${USERNAME}:" "$MAP_FILE" || true)"
|
||||
if [[ -z "$line" ]]; then
|
||||
echo "error: no entry for $USERNAME in $MAP_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
count=$(( $(awk -F: '{print NF}' <<<"$line") - 1 ))
|
||||
if [[ "$INDEX" -lt 1 || "$INDEX" -gt "$count" ]]; then
|
||||
echo "error: $USERNAME has $count credential(s); index must be 1..$count" >&2
|
||||
list_credentials "$USERNAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
new_line="$(awk -F: -v OFS=: -v idx="$INDEX" '{ out=$1; for (i=2;i<=NF;i++) if (i-1!=idx) out=out OFS $i; print out }' <<<"$line")"
|
||||
|
||||
tmp="$(mktemp)"
|
||||
if [[ "$count" -eq 1 ]]; then
|
||||
awk -F: -v u="$USERNAME" '$1!=u' "$MAP_FILE" > "$tmp"
|
||||
else
|
||||
awk -F: -v u="$USERNAME" -v line="$new_line" 'BEGIN{OFS=":"} $1==u {print line; next} {print}' "$MAP_FILE" > "$tmp"
|
||||
fi
|
||||
mv "$tmp" "$MAP_FILE"
|
||||
|
||||
echo "Removed credential #$INDEX for $USERNAME. $((count - 1)) credential(s) remain."
|
||||
if [[ "$count" -eq 1 ]]; then
|
||||
echo "Note: $USERNAME now has zero credentials -- pam_u2f falls through to password (control=sufficient), not a lockout."
|
||||
fi
|
||||
echo "Review the diff in $MAP_FILE, then run nix-update to apply it."
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
imports = [
|
||||
./programs.vscode.nix
|
||||
./programs.thunderbird.nix
|
||||
./module.accounts.nix
|
||||
./module.evolution.nix
|
||||
];
|
||||
|
||||
programs.firefox.enable = true;
|
||||
|
||||
home.packages = with pkgs; [
|
||||
firefox
|
||||
wowup-cf
|
||||
faugus-launcher
|
||||
yubikeyEnroll
|
||||
yubikeyUnenroll
|
||||
];
|
||||
|
||||
systemd.user.sessionVariables = {
|
||||
## Required to fix firefox unrendered addressbar and tabs
|
||||
XDG_DATA_DIRS = "${config.home.homeDirectory}/.nix-profile/share:/usr/local/share:/usr/share";
|
||||
};
|
||||
|
||||
services.flatpak = {
|
||||
# nix-flatpak's home-manager module always manages the "user" flatpak
|
||||
# installation, independent of NixOS's services.flatpak.enable (system
|
||||
# scope) below in caesium.nixos.nix. Standalone home-manager has no
|
||||
# osConfig to inherit from, so this must be set explicitly.
|
||||
enable = true;
|
||||
uninstallUnmanaged = true;
|
||||
packages = [
|
||||
"com.spotify.Client"
|
||||
"com.discordapp.Discord"
|
||||
"org.signal.Signal"
|
||||
"io.openrct2.OpenRCT2"
|
||||
"io.github.enginkirmaci.lumux"
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
imports = [
|
||||
./caesium.hardware.nix # Crucial: Imports your UUIDs
|
||||
./module.firewall.nix
|
||||
./module.yubikey.nix
|
||||
];
|
||||
|
||||
time.timeZone = "America/New_York";
|
||||
@@ -161,9 +162,25 @@
|
||||
services.displayManager.sddm.enable = true;
|
||||
services.desktopManager.plasma6.enable = true;
|
||||
|
||||
# Registers the evolution-data-server D-Bus services/systemd user units so
|
||||
# GNOME Calendar, GNOME Contacts and Planify can share calendars/contacts
|
||||
# (see module.evolution.nix). Deliberately NOT programs.evolution.enable,
|
||||
# which force-enables services.gnome.gnome-keyring and would fight kwallet
|
||||
# for ownership of org.freedesktop.secrets.
|
||||
services.gnome.evolution-data-server.enable = true;
|
||||
|
||||
# Tell SDDM to use Wayland for the login screen itself (Ultra Modern)
|
||||
services.displayManager.sddm.wayland.enable = true;
|
||||
|
||||
# GTK apps (Firefox, Thunderbird) read their theme through GtkSettings, which needs the
|
||||
# org.gtk.Settings.* / org.gnome.desktop.interface GSettings schemas. Plasma is Qt-based so
|
||||
# nothing pulls them into the system profile, and the Mozilla wrappers don't add them
|
||||
# themselves — without this their chrome renders black.
|
||||
environment.sessionVariables.XDG_DATA_DIRS = [
|
||||
"${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}"
|
||||
"${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}"
|
||||
];
|
||||
|
||||
fonts.packages = with pkgs; [
|
||||
noto-fonts
|
||||
noto-fonts-cjk-sans
|
||||
@@ -219,6 +236,7 @@
|
||||
python3
|
||||
nodejs_26
|
||||
home-manager
|
||||
glib
|
||||
];
|
||||
|
||||
programs.appimage = {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{ ... }:
|
||||
let
|
||||
davHost = "cloud.thiccdata.io";
|
||||
davUser = "mbessette";
|
||||
|
||||
mkGmailAccount = address: realName: {
|
||||
inherit address realName;
|
||||
flavor = "gmail.com";
|
||||
thunderbird.enable = true;
|
||||
};
|
||||
|
||||
mkCalendar = collection: {
|
||||
remote = {
|
||||
type = "caldav";
|
||||
url = "https://${davHost}/remote.php/dav/calendars/${davUser}/${collection}/";
|
||||
userName = davUser;
|
||||
};
|
||||
};
|
||||
|
||||
mkAddressBook = book: {
|
||||
remote = {
|
||||
type = "carddav";
|
||||
url = "https://${davHost}/remote.php/dav/addressbooks/users/${davUser}/${book}/";
|
||||
userName = davUser;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
accounts.calendar.basePath = ".calendars";
|
||||
accounts.contact.basePath = ".contacts";
|
||||
|
||||
accounts.email.accounts = {
|
||||
personal = mkGmailAccount "blade30912@gmail.com" "Matthew Bessette" // {
|
||||
primary = true;
|
||||
};
|
||||
bessette = mkGmailAccount "bessette.matthew94@gmail.com" "Matthew Bessette";
|
||||
house = mkGmailAccount "mbessette.house@gmail.com" "Matthew Bessette";
|
||||
joseph = mkGmailAccount "joseph.lamothe55@gmail.com" "Joseph Lamothe";
|
||||
};
|
||||
|
||||
# Calendars and the address book are consumed via evolution-data-server
|
||||
# (see module.evolution.nix) rather than Thunderbird's own calendar/address
|
||||
# book code. Only email accounts opt in to Thunderbird.
|
||||
accounts.calendar.accounts = {
|
||||
family = mkCalendar "test";
|
||||
matt = mkCalendar "matt-calendar" // {
|
||||
primary = true;
|
||||
};
|
||||
joseph-cal = mkCalendar "joseph-calendar";
|
||||
};
|
||||
|
||||
accounts.contact.accounts = {
|
||||
contacts = mkAddressBook "contacts";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
calendarAccounts = config.accounts.calendar.accounts;
|
||||
contactAccounts = config.accounts.contact.accounts;
|
||||
|
||||
# Nextcloud stores VTODOs in the same CalDAV collections as VEVENTs, so a
|
||||
# calendar account can also be exposed to task-list clients (Planify) by
|
||||
# emitting a second EDS source against the same collection.
|
||||
tasksFrom = [ "matt" ];
|
||||
|
||||
# Per-calendar accent colors, previously set on the (now removed)
|
||||
# `thunderbird.color` attrs in module.accounts.nix.
|
||||
calendarColors = {
|
||||
family = "#FED3D9";
|
||||
matt = "#FDF0D7";
|
||||
joseph-cal = "#CEF4F8";
|
||||
};
|
||||
|
||||
splitUrl = url:
|
||||
let
|
||||
stripped = lib.removePrefix "https://" url;
|
||||
host = lib.head (lib.splitString "/" stripped);
|
||||
in
|
||||
{
|
||||
inherit host;
|
||||
resourcePath = lib.removePrefix host stripped;
|
||||
};
|
||||
|
||||
mkAuth = remote:
|
||||
let
|
||||
s = splitUrl remote.url;
|
||||
in
|
||||
{
|
||||
"Security" = {
|
||||
Method = "tls";
|
||||
};
|
||||
"Authentication" = {
|
||||
Host = s.host;
|
||||
Port = "443";
|
||||
User = remote.userName;
|
||||
Method = "plain/password";
|
||||
RememberPassword = "true";
|
||||
};
|
||||
"WebDAV Backend" = {
|
||||
ResourcePath = s.resourcePath;
|
||||
};
|
||||
"Offline" = {
|
||||
StaySynchronized = "true";
|
||||
};
|
||||
"Refresh" = {
|
||||
Enabled = "true";
|
||||
IntervalMinutes = "30";
|
||||
};
|
||||
};
|
||||
|
||||
toIni = sections:
|
||||
lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList
|
||||
(section: keys: ''
|
||||
[${section}]
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k}=${v}") keys)}
|
||||
'')
|
||||
sections
|
||||
);
|
||||
|
||||
mkCalendarSource = name: account:
|
||||
let
|
||||
uid = "nextcloud-${name}";
|
||||
in
|
||||
{
|
||||
inherit uid;
|
||||
text = toIni ({
|
||||
"Data Source" = {
|
||||
DisplayName = name;
|
||||
Enabled = "true";
|
||||
Parent = "caldav-stub";
|
||||
};
|
||||
"Calendar" = {
|
||||
BackendName = "caldav";
|
||||
Color = calendarColors.${name} or "#3584E4";
|
||||
Selected = "true";
|
||||
};
|
||||
} // mkAuth account.remote);
|
||||
};
|
||||
|
||||
mkTaskListSource = name: account:
|
||||
let
|
||||
uid = "nextcloud-${name}-tasks";
|
||||
in
|
||||
{
|
||||
inherit uid;
|
||||
text = toIni ({
|
||||
"Data Source" = {
|
||||
DisplayName = "${name} (Tasks)";
|
||||
Enabled = "true";
|
||||
Parent = "caldav-stub";
|
||||
};
|
||||
"Task List" = {
|
||||
BackendName = "caldav";
|
||||
Color = calendarColors.${name} or "#3584E4";
|
||||
Selected = "true";
|
||||
};
|
||||
} // mkAuth account.remote);
|
||||
};
|
||||
|
||||
mkContactSource = name: account:
|
||||
let
|
||||
uid = "nextcloud-${name}";
|
||||
in
|
||||
{
|
||||
inherit uid;
|
||||
text = toIni ({
|
||||
"Data Source" = {
|
||||
DisplayName = name;
|
||||
Enabled = "true";
|
||||
Parent = "carddav-stub";
|
||||
};
|
||||
"Address Book" = {
|
||||
BackendName = "carddav";
|
||||
};
|
||||
} // mkAuth account.remote);
|
||||
};
|
||||
|
||||
calendarSources = lib.mapAttrsToList mkCalendarSource calendarAccounts;
|
||||
taskListSources = lib.mapAttrsToList mkTaskListSource
|
||||
(lib.filterAttrs (name: _: builtins.elem name tasksFrom) calendarAccounts);
|
||||
contactSources = lib.mapAttrsToList mkContactSource contactAccounts;
|
||||
|
||||
allSources = calendarSources ++ taskListSources ++ contactSources;
|
||||
|
||||
sourceFile = src: pkgs.writeText "${src.uid}.source" src.text;
|
||||
in
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
gnome-calendar
|
||||
gnome-contacts
|
||||
planify
|
||||
libsecret
|
||||
];
|
||||
|
||||
# evolution-data-server treats files under ~/.config/evolution/sources/ as
|
||||
# writable/removable and rewrites them on connect and on any in-app tweak
|
||||
# (color, visibility, ...). A /nix/store symlink can't be written back to,
|
||||
# so these are copied into place as real files on every activation instead
|
||||
# of managed via xdg.configFile.
|
||||
home.activation.evolutionSources = lib.hm.dag.entryAfter [ "writeBoundary" ] (
|
||||
lib.concatMapStringsSep "\n"
|
||||
(src: ''
|
||||
install -Dm644 ${sourceFile src} "$HOME/.config/evolution/sources/${src.uid}.source"
|
||||
'')
|
||||
allSources
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# YubiKey (FIDO2/U2F) login via pam_u2f.
|
||||
#
|
||||
# `security.pam.u2f.enable = true` defaults every PAM service's `u2fAuth` to
|
||||
# true, so this covers sudo, sddm/sddm-greeter, the Plasma lock screen (the
|
||||
# `kde` service, added by services.desktopManager.plasma6), login, and su
|
||||
# without listing them individually. `control = "sufficient"` means a touch
|
||||
# skips the password, but the password always still works — losing both
|
||||
# enrolled keys can never lock this account out.
|
||||
#
|
||||
# /etc/u2f_mappings starts empty. Enroll keys after the first switch with the
|
||||
# `yubikey-enroll` / `yubikey-unenroll` scripts (defined in caesium.home.nix),
|
||||
# which wrap the interactive pamu2fcfg touch ceremony and keep
|
||||
# yubikey.u2f-mappings well-formed; then re-run nix-update. An empty/missing
|
||||
# mapping just falls through to the password, so this is safe to deploy
|
||||
# before enrollment.
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
services.pcscd.enable = true;
|
||||
services.udev.packages = [
|
||||
pkgs.yubikey-personalization
|
||||
pkgs.libfido2
|
||||
];
|
||||
|
||||
security.pam.u2f = {
|
||||
enable = true;
|
||||
control = "sufficient";
|
||||
settings = {
|
||||
cue = true;
|
||||
authfile = "/etc/u2f_mappings";
|
||||
origin = "pam://mbessette";
|
||||
appid = "pam://mbessette";
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc."u2f_mappings".source = ./yubikey.u2f-mappings;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
pam_u2f
|
||||
yubikey-manager
|
||||
libfido2
|
||||
];
|
||||
}
|
||||
+19
-63
@@ -1,31 +1,12 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tbPkg = if pkgs.stdenv.isDarwin then pkgs.thunderbird-bin else pkgs.thunderbird;
|
||||
|
||||
mkGmailAccount = address: realName: {
|
||||
inherit address realName;
|
||||
flavor = "gmail.com";
|
||||
thunderbird.enable = true;
|
||||
};
|
||||
|
||||
mkCalendar = collection: {
|
||||
remote = {
|
||||
type = "caldav";
|
||||
url = "https://cloud.thiccdata.io/remote.php/dav/calendars/mbessette/${collection}/";
|
||||
userName = "mbessette";
|
||||
};
|
||||
};
|
||||
|
||||
mkAddressBook = book: {
|
||||
remote = {
|
||||
type = "carddav";
|
||||
url = "https://cloud.thiccdata.io/remote.php/dav/addressbooks/users/mbessette/${book}/";
|
||||
userName = "mbessette";
|
||||
};
|
||||
thunderbird.enable = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
# Account definitions (email/calendar/contact) live in module.accounts.nix,
|
||||
# shared with evolution-data-server (module.evolution.nix). Only email
|
||||
# accounts opt in to Thunderbird; calendars and the address book are
|
||||
# consumed via GNOME/EDS instead, so Thunderbird is mail-only.
|
||||
programs.thunderbird = {
|
||||
enable = true;
|
||||
package = tbPkg;
|
||||
@@ -33,48 +14,23 @@ in
|
||||
profiles.default = {
|
||||
isDefault = true;
|
||||
accountsOrder = [ "personal" ];
|
||||
};
|
||||
};
|
||||
|
||||
accounts.calendar.basePath = ".calendars";
|
||||
accounts.contact.basePath = ".contacts";
|
||||
|
||||
accounts.email.accounts = {
|
||||
personal = mkGmailAccount "blade30912@gmail.com" "Matthew Bessette" // {
|
||||
primary = true;
|
||||
};
|
||||
bessette = mkGmailAccount "bessette.matthew94@gmail.com" "Matthew Bessette";
|
||||
house = mkGmailAccount "mbessette.house@gmail.com" "Matthew Bessette";
|
||||
joseph = mkGmailAccount "joseph.lamothe55@gmail.com" "Joseph Lamothe";
|
||||
};
|
||||
|
||||
accounts.calendar.accounts = {
|
||||
family = mkCalendar "test" // {
|
||||
thunderbird = {
|
||||
enable = true;
|
||||
color = "#FED3D9";
|
||||
settings = {
|
||||
"mail.chat.enabled" = false;
|
||||
"calendar.itip.showImipBar" = false;
|
||||
"toolkit.legacyUserProfileCustomizations.stylesheets" = true;
|
||||
};
|
||||
};
|
||||
matt = mkCalendar "matt-calendar" // {
|
||||
primary = true;
|
||||
thunderbird = {
|
||||
enable = true;
|
||||
color = "#FDF0D7";
|
||||
settings = id: {
|
||||
"calendar.registry.${id}.imip.identity.key" =
|
||||
"id_${builtins.hashString "sha256" "personal"}";
|
||||
};
|
||||
};
|
||||
};
|
||||
joseph-cal = mkCalendar "joseph-calendar" // {
|
||||
thunderbird = {
|
||||
enable = true;
|
||||
color = "#CEF4F8";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
accounts.contact.accounts = {
|
||||
contacts = mkAddressBook "contacts";
|
||||
# Hide the calendar/tasks/chat spaces so Thunderbird presents as an
|
||||
# email-only client. The address book space stays (local/collected
|
||||
# addresses + autocomplete still live there).
|
||||
userChrome = ''
|
||||
#calendarButton,
|
||||
#tasksButton,
|
||||
#chatButton {
|
||||
display: none !important;
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
{ config, pkgs, ... }: {
|
||||
|
||||
xdg.configFile."oxlint/oxlintrc.json".source =
|
||||
config.lib.file.mkOutOfStoreSymlink "/Users/matthew.bessette/nixos/config.oxlintrc.json";
|
||||
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nixos/config.oxlintrc.json";
|
||||
xdg.configFile."biome/biome.json".source =
|
||||
config.lib.file.mkOutOfStoreSymlink "/Users/matthew.bessette/nixos/config.biome.json";
|
||||
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nixos/config.biome.json";
|
||||
|
||||
programs.vscode = {
|
||||
enable = true;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
mbessette:PusBTtTXC1flzOLV/pqIiz0xoiYLfbd8sMiLPdMuqbvg/IM0pylvB1kNYYJYadlivlshkITYB41h6yv2ouiPHw==,wkf3QAvo+NMZecX4KZO2urNeKB2086cYx0k5vGzvwYxFAS4SD92POq3uBjNg8kMwLlqgQbyJS0aEAdClyvzchQ==,es256,+presence::ttMJESYFH+swvoq3DXA23U9gnvKi12yleSz3GoKZ1sb/+pusgz6rc2B2LE3AJ5o9L/Pj5eEMjFFv0VUjfBuv4w==,m/SxBY1LuX//kxIA3RcnfjX7cnfVkQMRyNGbplhtUnHVfivx4zGLdIqWPUWVXJHV/pXax1IGxwVZeyz2EuQr/A==,es256,+presence
|
||||
Reference in New Issue
Block a user