mirror of
https://github.com/Smaug123/nix-dotfiles
synced 2025-10-07 07:28:40 +00:00
Compare commits
20 Commits
chadtree-s
...
mail-on-li
Author | SHA1 | Date | |
---|---|---|---|
|
2a962e928b | ||
|
ad6a4548c6 | ||
|
b361bbcbcb | ||
|
02ceae3e22 | ||
|
e7f68f24a3 | ||
|
3208bf16c5 | ||
|
ccaa90d392 | ||
|
fd71527762 | ||
|
a210ee4301 | ||
|
d3ec6b02c3 | ||
|
aa3d08745a | ||
|
bf1dfe3d6d | ||
|
d867348640 | ||
|
7fb26eb707 | ||
|
f723b64486 | ||
|
7b94e76589 | ||
|
68d57ea7cb | ||
|
c6879ac254 | ||
|
59e1e8637c | ||
|
6256ad908f |
@@ -1,9 +1,6 @@
|
||||
This repository currently has no licence applied to it, except for the NeoVim configuration.
|
||||
That configuration is in large part derived from https://github.com/amix/vimrc and is therefore provided under the following licence.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Amir Salihefendic
|
||||
Copyright (c) 2024 Patrick Stevens
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{pkgs, ...}: let
|
||||
python = import ./python.nix {inherit pkgs;};
|
||||
mbsync = import ./mbsync.nix {inherit pkgs;};
|
||||
in {
|
||||
nix.useDaemon = true;
|
||||
|
||||
@@ -11,7 +11,7 @@ in {
|
||||
pkgs.rustup
|
||||
pkgs.libiconv
|
||||
pkgs.clang
|
||||
python
|
||||
pkgs.python3
|
||||
];
|
||||
|
||||
users.users.patrick = {
|
||||
@@ -21,16 +21,48 @@ in {
|
||||
|
||||
# This line is required; otherwise, on shell startup, you won't have Nix stuff in the PATH.
|
||||
programs.zsh.enable = true;
|
||||
programs.gnupg.agent.enable = true;
|
||||
|
||||
# Use a custom configuration.nix location.
|
||||
# $ darwin-rebuild switch -I darwin-config=$HOME/.config/nixpkgs/darwin/configuration.nix
|
||||
environment.darwinConfig = "$HOME/.nixpkgs/darwin-configuration.nix";
|
||||
|
||||
launchd.agents = {
|
||||
mbsync-btinternet = {
|
||||
command = "${mbsync}/bin/mbsync BTInternet > /tmp/mbsync.btinternet.log 2>/tmp/mbsync.btinternet.2.log";
|
||||
serviceConfig = {
|
||||
KeepAlive = false;
|
||||
UserName = "patrick";
|
||||
StartInterval = 60;
|
||||
RunAtLoad = true;
|
||||
};
|
||||
};
|
||||
|
||||
mbsync-proton = {
|
||||
command = "${mbsync}/bin/mbsync Proton > /tmp/mbsync.proton.1.log 2>/tmp/mbsync.proton.2.log";
|
||||
serviceConfig = {
|
||||
KeepAlive = false;
|
||||
UserName = "patrick";
|
||||
StartInterval = 60;
|
||||
RunAtLoad = true;
|
||||
};
|
||||
};
|
||||
|
||||
mbsync-gmail = {
|
||||
command = "${mbsync}/bin/mbsync Gmail > /tmp/mbsync.gmail.1.log 2>/tmp/mbsync.gmail.2.log";
|
||||
serviceConfig = {
|
||||
KeepAlive = false;
|
||||
UserName = "patrick";
|
||||
StartInterval = 60;
|
||||
RunAtLoad = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Auto upgrade nix package and the daemon service.
|
||||
services.nix-daemon.enable = true;
|
||||
nix.package = pkgs.nixVersions.stable;
|
||||
nix.gc.automatic = true;
|
||||
nix.nixPath = ["darwin=/nix/store/zq4v3pi2wsfsrjkpk71kcn8srhbwjabf-nix-darwin"];
|
||||
|
||||
# Sandbox causes failure: https://github.com/NixOS/nix/issues/4119
|
||||
nix.settings.sandbox = false;
|
||||
|
62
flake.lock
generated
62
flake.lock
generated
@@ -7,11 +7,11 @@
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710209440,
|
||||
"narHash": "sha256-1JwFo3u2aVrvpz12OotjCK51EQ0hEDI7xSG7CEvTSk8=",
|
||||
"lastModified": 1712279577,
|
||||
"narHash": "sha256-Bwn4rmQi2L2iX6g3ycQMA4baE3zgPHAO0xPBpr2T4/k=",
|
||||
"owner": "tpwrules",
|
||||
"repo": "nixos-apple-silicon",
|
||||
"rev": "bdc68b494d6a26c9457f4841ab1a6109b12a33e6",
|
||||
"rev": "d47afc3f0f8b3078c818da8609c41340af61a2ec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -27,11 +27,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710717205,
|
||||
"narHash": "sha256-Wf3gHh5uV6W1TV/A8X8QJf99a5ypDSugY4sNtdJDe0A=",
|
||||
"lastModified": 1711763326,
|
||||
"narHash": "sha256-sXcesZWKXFlEQ8oyGHnfk4xc9f2Ip0X/+YZOq3sKviI=",
|
||||
"owner": "lnl7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "bcc8afd06e237df060c85bad6af7128e05fd61a3",
|
||||
"rev": "36524adc31566655f2f4d55ad6b875fb5c1a4083",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -50,11 +50,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1711271005,
|
||||
"narHash": "sha256-JrhnnutZvHowEJFIrA/rQAFgGAc83WOx+BVy97teqKM=",
|
||||
"lastModified": 1712941527,
|
||||
"narHash": "sha256-wD9XQFGW0qzRW1YHj6oklCHzgKNxjwS0tZ/hFGgiHX4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "emacs-overlay",
|
||||
"rev": "d6bbd32eb3e0f167f312e1031c1beee452dc9174",
|
||||
"rev": "9f4406718ada7af83892e17355ef7fd202c20897",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -102,11 +102,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709336216,
|
||||
"narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=",
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -219,11 +219,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1711133180,
|
||||
"narHash": "sha256-WJOahf+6115+GMl3wUfURu8fszuNeJLv9qAWFQl3Vmo=",
|
||||
"lastModified": 1712759992,
|
||||
"narHash": "sha256-2APpO3ZW4idlgtlb8hB04u/rmIcKA8O7pYqxF66xbNY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "1c2c5e4cabba4c43504ef0f8cc3f3dfa284e2dbb",
|
||||
"rev": "31357486b0ef6f4e161e002b6893eeb4fafc3ca9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -254,11 +254,11 @@
|
||||
},
|
||||
"locked": {
|
||||
"dir": "contrib",
|
||||
"lastModified": 1711323947,
|
||||
"narHash": "sha256-Vc478rxwJkMuOcgBXm+brraWk9lbFqrGEdXVuST2l/A=",
|
||||
"lastModified": 1712877603,
|
||||
"narHash": "sha256-8JesAgnsv1bD+xHNoqefz0Gv243wSiCKnzh4rhZLopU=",
|
||||
"owner": "neovim",
|
||||
"repo": "neovim",
|
||||
"rev": "02d00cf3eed6681c6dde40585551c8243d7c003f",
|
||||
"rev": "18ee9f9e7dbbc9709ee9c1572870b4ad31443569",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -279,11 +279,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1711325009,
|
||||
"narHash": "sha256-c5OJdyuXYzTP+k+PN73X+0pvgXR1yYMYok+72x4SLVg=",
|
||||
"lastModified": 1712880226,
|
||||
"narHash": "sha256-2CGLzsFft8zF/gEY4qDN0uAjRCWUqvNJ9yV118NlzTg=",
|
||||
"owner": "nix-community",
|
||||
"repo": "neovim-nightly-overlay",
|
||||
"rev": "119bbc295f56b531cb87502f5d2fff13dcc35a35",
|
||||
"rev": "58d367a1924bf0d02bcc5bd2c5af8ac97f178381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -294,27 +294,27 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1709961763,
|
||||
"narHash": "sha256-6H95HGJHhEZtyYA3rIQpvamMKAGoa8Yh2rFV29QnuGw=",
|
||||
"lastModified": 1712163089,
|
||||
"narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34",
|
||||
"rev": "fd281bd6b7d3e32ddfa399853946f782553163b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34",
|
||||
"rev": "fd281bd6b7d3e32ddfa399853946f782553163b5",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1711124224,
|
||||
"narHash": "sha256-l0zlN/3CiodvWDtfBOVxeTwYSRz93muVbXWSpaMjXxM=",
|
||||
"lastModified": 1712741485,
|
||||
"narHash": "sha256-bCs0+MSTra80oXAsnM6Oq62WsirOIaijQ/BbUY59tR4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "56528ee42526794d413d6f244648aaee4a7b56c0",
|
||||
"rev": "b2cf36f43f9ef2ded5711b30b1f393ac423d8f72",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -326,11 +326,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1711231723,
|
||||
"narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=",
|
||||
"lastModified": 1712849433,
|
||||
"narHash": "sha256-flQtf/ZPJgkLY/So3Fd+dGilw2DKIsiwgMEn7BbBHL0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e1d501922fd7351da4200e1275dfcf5faaad1220",
|
||||
"rev": "f173d0881eff3b21ebb29a2ef8bedbc106c86ea5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
33
flake.nix
33
flake.nix
@@ -46,7 +46,7 @@
|
||||
};
|
||||
systems = ["aarch64-darwin" "aarch64-linux" "x86_64-linux"];
|
||||
in let
|
||||
overlays = [emacs.overlay neovim-nightly.overlay] ++ import ./overlays.nix;
|
||||
overlays = [emacs.overlay neovim-nightly.overlay];
|
||||
recursiveMerge = attrList: let
|
||||
f = attrPath:
|
||||
builtins.zipAttrsWith (n: values:
|
||||
@@ -61,6 +61,31 @@
|
||||
f [] attrList;
|
||||
in {
|
||||
nixosConfigurations = {
|
||||
capybara = let
|
||||
system = "x86_64-linux";
|
||||
in let
|
||||
pkgs = import nixpkgs {inherit system config overlays;};
|
||||
in
|
||||
nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = let
|
||||
args = {
|
||||
nixpkgs = pkgs;
|
||||
username = "patrick";
|
||||
dotnet = pkgs.dotnet-sdk_8;
|
||||
mbsync = import ./mbsync.nix {inherit pkgs;};
|
||||
secretsPath = "/home/patrick/.secrets/";
|
||||
};
|
||||
in [
|
||||
./home-manager/capybara-config.nix
|
||||
home-manager.nixosModules.home-manager
|
||||
{
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.useUserPackages = true;
|
||||
home-manager.users.patrick = recursiveMerge [(import ./home-manager/linux.nix args) (import ./home-manager/home.nix args)];
|
||||
}
|
||||
];
|
||||
};
|
||||
earthworm = let
|
||||
system = "aarch64-linux";
|
||||
in let
|
||||
@@ -73,6 +98,8 @@
|
||||
nixpkgs = pkgs;
|
||||
username = "patrick";
|
||||
dotnet = pkgs.dotnet-sdk_8;
|
||||
mbsync = import ./mbsync.nix {inherit pkgs;};
|
||||
secretsPath = "/home/patrick/.secrets/";
|
||||
};
|
||||
in [
|
||||
./home-manager/earthworm-config.nix
|
||||
@@ -81,7 +108,7 @@
|
||||
{
|
||||
home-manager.useGlobalPkgs = true;
|
||||
home-manager.useUserPackages = true;
|
||||
home-manager.users.patrick = recursiveMerge [(import ./home-manager/earthworm.nix args) (import ./home-manager/home.nix args)];
|
||||
home-manager.users.patrick = recursiveMerge [(import ./home-manager/linux.nix args) (import ./home-manager/home.nix args)];
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -100,6 +127,8 @@
|
||||
username = "patrick";
|
||||
dotnet = pkgs.dotnet-sdk_8;
|
||||
whisper = whisper.packages.${system};
|
||||
mbsync = import ./mbsync.nix {inherit pkgs;};
|
||||
secretsPath = "/Users/patrick/.secrets/";
|
||||
};
|
||||
in [
|
||||
./darwin-configuration.nix
|
||||
|
34
hardware/capybara.nix
Normal file
34
hardware/capybara.nix
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}: {
|
||||
imports = [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod"];
|
||||
boot.initrd.kernelModules = [];
|
||||
boot.kernelModules = ["kvm-intel"];
|
||||
boot.extraModulePackages = [];
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-uuid/63c5394d-55ce-48a9-8d7c-2b68f3b5f834";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/nvme0n1p2";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
swapDevices = [];
|
||||
|
||||
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
70
home-manager/capybara-config.nix
Normal file
70
home-manager/capybara-config.nix
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
imports = [
|
||||
../hardware/capybara.nix
|
||||
];
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
boot.loader.grub.useOSProber = true;
|
||||
|
||||
boot.extraModulePackages = [config.boot.kernelPackages.rtl8821au];
|
||||
|
||||
networking = {
|
||||
hostName = "capybara";
|
||||
networkmanager.enable = true;
|
||||
};
|
||||
|
||||
time.timeZone = "Europe/London";
|
||||
|
||||
programs.sway.enable = true;
|
||||
programs.zsh.enable = true;
|
||||
|
||||
# TODO: work out secrets management for password, then set mutableUsers to false
|
||||
users.mutableUsers = true;
|
||||
users.users.patrick = {
|
||||
isNormalUser = true;
|
||||
extraGroups = ["wheel" "networkManager"];
|
||||
};
|
||||
|
||||
services.syncthing = {
|
||||
enable = true;
|
||||
user = "patrick";
|
||||
dataDir = "/home/patrick/syncthing";
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.vim
|
||||
pkgs.wget
|
||||
pkgs.tmux
|
||||
pkgs.home-manager
|
||||
pkgs.firefox
|
||||
pkgs.steam-run
|
||||
];
|
||||
|
||||
environment.loginShellInit = ''
|
||||
[[ "$(tty)" == /dev/tty1 ]] && sway
|
||||
'';
|
||||
|
||||
services.openssh.enable = true;
|
||||
|
||||
system.stateVersion = "23.11";
|
||||
nix.settings.experimental-features = ["nix-command" "flakes" "ca-derivations"];
|
||||
|
||||
nix.gc.automatic = true;
|
||||
nix.extraOptions = ''
|
||||
auto-optimise-store = true
|
||||
max-jobs = auto
|
||||
keep-outputs = true
|
||||
keep-derivations = true
|
||||
'';
|
||||
|
||||
programs.steam = {
|
||||
enable = true;
|
||||
remotePlay.openFirewall = true;
|
||||
};
|
||||
}
|
@@ -1,8 +1,4 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
{pkgs, ...}: {
|
||||
imports = [
|
||||
../hardware/earthworm.nix
|
||||
];
|
||||
|
@@ -1,7 +1,9 @@
|
||||
{
|
||||
nixpkgs,
|
||||
username,
|
||||
mbsync,
|
||||
dotnet,
|
||||
secretsPath,
|
||||
...
|
||||
}: {
|
||||
# Let Home Manager install and manage itself.
|
||||
@@ -23,60 +25,40 @@
|
||||
|
||||
fonts.fontconfig.enable = true;
|
||||
|
||||
programs.tmux = {
|
||||
shell = "${nixpkgs.zsh}/bin/zsh";
|
||||
escapeTime = 50;
|
||||
mouse = false;
|
||||
prefix = "C-b";
|
||||
enable = true;
|
||||
terminal = "screen-256color";
|
||||
extraConfig = ''
|
||||
set-option -sa terminal-features ',xterm-256color:RGB'
|
||||
'';
|
||||
};
|
||||
|
||||
programs.zsh = {
|
||||
enable = true;
|
||||
autocd = true;
|
||||
autosuggestion.enable = true;
|
||||
enableCompletion = true;
|
||||
history = {
|
||||
expireDuplicatesFirst = true;
|
||||
};
|
||||
sessionVariables = {
|
||||
EDITOR = "vim";
|
||||
LC_ALL = "en_US.UTF-8";
|
||||
LC_CTYPE = "en_US.UTF-8";
|
||||
RUSTFLAGS = "-L ${nixpkgs.libiconv}/lib -L ${nixpkgs.libcxx}/lib";
|
||||
RUST_BACKTRACE = "full";
|
||||
};
|
||||
shellAliases = {
|
||||
vim = "nvim";
|
||||
view = "vim -R";
|
||||
grep = "${nixpkgs.ripgrep}/bin/rg";
|
||||
};
|
||||
sessionVariables = {
|
||||
RIPGREP_CONFIG_PATH = "/Users/${username}/.config/ripgrep/config";
|
||||
};
|
||||
initExtra = builtins.readFile ./.zshrc;
|
||||
};
|
||||
imports = [
|
||||
# ./modules/agda.nix
|
||||
# ./modules/emacs.nix
|
||||
./modules/direnv.nix
|
||||
./modules/tmux.nix
|
||||
./modules/zsh.nix
|
||||
./modules/ripgrep.nix
|
||||
./modules/alacritty.nix
|
||||
./modules/rust.nix
|
||||
(import ./modules/mail.nix
|
||||
{
|
||||
inherit mbsync secretsPath;
|
||||
pkgs = nixpkgs;
|
||||
})
|
||||
];
|
||||
|
||||
programs.fzf = {
|
||||
enable = true;
|
||||
enableZshIntegration = true;
|
||||
};
|
||||
|
||||
programs.git = {
|
||||
package = nixpkgs.gitAndTools.gitFull;
|
||||
enable = true;
|
||||
userName = "Smaug123";
|
||||
userEmail = "patrick+github@patrickstevens.co.uk";
|
||||
userEmail = "3138005+Smaug123@users.noreply.github.com";
|
||||
aliases = {
|
||||
co = "checkout";
|
||||
st = "status";
|
||||
};
|
||||
delta = {enable = true;};
|
||||
extraConfig = {
|
||||
commit.gpgsign = true;
|
||||
gpg.program = "${nixpkgs.gnupg}/bin/gpg";
|
||||
user.signingkey = "7C97D679CF3BC4F9";
|
||||
core = {
|
||||
autocrlf = "input";
|
||||
};
|
||||
@@ -138,6 +120,10 @@
|
||||
};
|
||||
};
|
||||
|
||||
services.syncthing = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
programs.neovim = let
|
||||
pynvimpp = nixpkgs.python3.pkgs.buildPythonPackage {
|
||||
pname = "pynvim-pp";
|
||||
@@ -271,30 +257,15 @@
|
||||
vimAlias = true;
|
||||
vimdiffAlias = true;
|
||||
withPython3 = true;
|
||||
withRuby = true;
|
||||
|
||||
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + builtins.readFile ./nvim/dotnet.lua + "\n" + builtins.replaceStrings ["%PYTHONENV%"] ["${pythonEnv}"] (builtins.readFile ./nvim/init.lua);
|
||||
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + builtins.readFile ./nvim/dotnet.lua + "\n" + builtins.replaceStrings ["%PYTHONENV%"] ["${pythonEnv}"] (builtins.readFile ./nvim/init.lua) + "\n" + builtins.readFile ./nvim/python.lua;
|
||||
|
||||
package = nixpkgs.neovim-nightly;
|
||||
};
|
||||
|
||||
programs.direnv = {
|
||||
enable = true;
|
||||
enableZshIntegration = true;
|
||||
nix-direnv.enable = true;
|
||||
};
|
||||
|
||||
programs.alacritty = {
|
||||
enable = true;
|
||||
settings = {
|
||||
font = {
|
||||
normal = {
|
||||
family = "FiraCode Nerd Font Mono";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
home.packages = [
|
||||
nixpkgs.syncthing
|
||||
nixpkgs.nodePackages_latest.dockerfile-language-server-nodejs
|
||||
nixpkgs.nodePackages_latest.bash-language-server
|
||||
nixpkgs.nodePackages_latest.vscode-json-languageserver
|
||||
@@ -307,8 +278,6 @@
|
||||
nixpkgs.nil
|
||||
nixpkgs.fsautocomplete
|
||||
nixpkgs.keepassxc
|
||||
nixpkgs.rust-analyzer
|
||||
nixpkgs.tmux
|
||||
nixpkgs.wget
|
||||
nixpkgs.yt-dlp
|
||||
nixpkgs.cmake
|
||||
@@ -319,32 +288,19 @@
|
||||
nixpkgs.hledger-web
|
||||
dotnet
|
||||
nixpkgs.jitsi-meet
|
||||
nixpkgs.ripgrep
|
||||
nixpkgs.elan
|
||||
nixpkgs.coreutils-prefixed
|
||||
nixpkgs.shellcheck
|
||||
nixpkgs.html-tidy
|
||||
nixpkgs.hugo
|
||||
nixpkgs.agda
|
||||
nixpkgs.pijul
|
||||
nixpkgs.universal-ctags
|
||||
nixpkgs.asciinema
|
||||
nixpkgs.git-lfs
|
||||
nixpkgs.imagemagick
|
||||
nixpkgs.nixpkgs-fmt
|
||||
nixpkgs.grpc-tools
|
||||
nixpkgs.element-desktop
|
||||
nixpkgs.ihp-new
|
||||
nixpkgs.direnv
|
||||
nixpkgs.lnav
|
||||
nixpkgs.age
|
||||
nixpkgs.nodejs
|
||||
nixpkgs.nodePackages.pyright
|
||||
nixpkgs.sqlitebrowser
|
||||
nixpkgs.typst
|
||||
nixpkgs.poetry
|
||||
nixpkgs.woodpecker-agent
|
||||
nixpkgs.alacritty
|
||||
nixpkgs.lynx
|
||||
nixpkgs.alejandra
|
||||
nixpkgs.ffmpeg
|
||||
@@ -352,28 +308,9 @@
|
||||
nixpkgs.pandoc
|
||||
nixpkgs.fd
|
||||
nixpkgs.sumneko-lua-language-server
|
||||
(nixpkgs.nerdfonts.override {fonts = ["FiraCode" "DroidSansMono"];})
|
||||
nixpkgs.gnupg
|
||||
];
|
||||
|
||||
home.file.".mailcap".source = ./mailcap;
|
||||
home.file.".ideavimrc".source = ./ideavimrc;
|
||||
home.file.".config/yt-dlp/config".source = ./youtube-dl.conf;
|
||||
home.file.".config/ripgrep/config".source = ./ripgrep.conf;
|
||||
|
||||
programs.emacs = {
|
||||
enable = true;
|
||||
package = nixpkgs.emacs;
|
||||
extraPackages = epkgs: [epkgs.evil];
|
||||
extraConfig = ''
|
||||
(load-file (let ((coding-system-for-read 'utf-8))
|
||||
(shell-command-to-string "agda-mode locate")))
|
||||
(require 'evil)
|
||||
(evil-mode 1)
|
||||
(evil-set-undo-system 'undo-redo)
|
||||
;; Allow hash to be entered
|
||||
(global set-key (kbd "M-3") '(lambda () (interactive) (insert "#")))
|
||||
'';
|
||||
};
|
||||
|
||||
home.file.".cargo/config.toml".source = ./cargo-config.toml;
|
||||
}
|
||||
|
@@ -1,9 +1,4 @@
|
||||
{
|
||||
nixpkgs,
|
||||
username,
|
||||
dotnet,
|
||||
...
|
||||
}: {
|
||||
{nixpkgs, ...}: {
|
||||
home.packages = [nixpkgs.firefox-wayland];
|
||||
nixpkgs.config.firefox.speechSynthesisSupport = true;
|
||||
|
7
home-manager/modules/agda.nix
Normal file
7
home-manager/modules/agda.nix
Normal file
@@ -0,0 +1,7 @@
|
||||
{pkgs, ...}: {
|
||||
imports = [./emacs.nix];
|
||||
|
||||
home.packages = [
|
||||
pkgs.agda
|
||||
];
|
||||
}
|
17
home-manager/modules/alacritty.nix
Normal file
17
home-manager/modules/alacritty.nix
Normal file
@@ -0,0 +1,17 @@
|
||||
{pkgs, ...}: {
|
||||
programs.alacritty = {
|
||||
enable = true;
|
||||
settings = {
|
||||
font = {
|
||||
normal = {
|
||||
family = "FiraCode Nerd Font Mono";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
home.packages = [
|
||||
pkgs.alacritty
|
||||
(pkgs.nerdfonts.override {fonts = ["FiraCode" "DroidSansMono"];})
|
||||
];
|
||||
}
|
10
home-manager/modules/direnv.nix
Normal file
10
home-manager/modules/direnv.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{pkgs, ...}: {
|
||||
home.packages = [
|
||||
pkgs.direnv
|
||||
];
|
||||
programs.direnv = {
|
||||
enable = true;
|
||||
enableZshIntegration = true;
|
||||
nix-direnv.enable = true;
|
||||
};
|
||||
}
|
14
home-manager/modules/emacs.nix
Normal file
14
home-manager/modules/emacs.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{pkgs, ...}: {
|
||||
programs.emacs = {
|
||||
enable = true;
|
||||
package = pkgs.emacs;
|
||||
extraPackages = epkgs: [epkgs.evil];
|
||||
extraConfig = ''
|
||||
(load-file (let ((coding-system-for-read 'utf-8))
|
||||
(shell-command-to-string "agda-mode locate")))
|
||||
(require 'evil)
|
||||
(evil-mode 1)
|
||||
(evil-set-undo-system 'undo-redo)
|
||||
'';
|
||||
};
|
||||
}
|
184
home-manager/modules/mail.nix
Normal file
184
home-manager/modules/mail.nix
Normal file
@@ -0,0 +1,184 @@
|
||||
{
|
||||
pkgs,
|
||||
mbsync,
|
||||
secretsPath,
|
||||
...
|
||||
}: let
|
||||
deobfuscate = str: let
|
||||
lib = pkgs.lib;
|
||||
base64Table =
|
||||
builtins.listToAttrs
|
||||
(lib.imap0 (i: c: lib.nameValuePair c i)
|
||||
(lib.stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"));
|
||||
|
||||
# Generated using python3:
|
||||
# print(''.join([ chr(n) for n in range(1, 256) ]), file=open('ascii', 'w'))
|
||||
ascii = builtins.readFile ./mail/ascii;
|
||||
|
||||
# List of base-64 numbers
|
||||
numbers64 = map (c: base64Table.${c}) (lib.lists.reverseList (lib.stringToCharacters str));
|
||||
|
||||
# List of base-256 numbers
|
||||
numbers256 = lib.concatLists (lib.genList (
|
||||
i: let
|
||||
v =
|
||||
lib.foldl'
|
||||
(acc: el: acc * 64 + el)
|
||||
0
|
||||
(lib.sublist (i * 4) 4 numbers64);
|
||||
in [
|
||||
(lib.mod (v / 256 / 256) 256)
|
||||
(lib.mod (v / 256) 256)
|
||||
(lib.mod v 256)
|
||||
]
|
||||
) (lib.length numbers64 / 4));
|
||||
in
|
||||
# Converts base-256 numbers to ascii
|
||||
lib.concatMapStrings (
|
||||
n:
|
||||
# Can't represent the null byte in Nix..
|
||||
let
|
||||
result = lib.substring (n - 1) 1 ascii;
|
||||
in
|
||||
if result == " "
|
||||
then ""
|
||||
else result
|
||||
)
|
||||
numbers256;
|
||||
in {
|
||||
accounts.email.accounts."Gmail" = let
|
||||
address = (deobfuscate "AFTN0cWdh12c") + "gmail.com";
|
||||
in {
|
||||
notmuch.enable = true;
|
||||
neomutt = {
|
||||
enable = true;
|
||||
};
|
||||
address = address;
|
||||
flavor = "gmail.com";
|
||||
mbsync = {
|
||||
enable = true;
|
||||
create = "maildir";
|
||||
extraConfig.account = {
|
||||
AuthMechs = "XOAUTH2";
|
||||
};
|
||||
};
|
||||
userName = address;
|
||||
# This is accompanied by a developer application at Google:
|
||||
# https://console.cloud.google.com/apis/credentials
|
||||
# Create an OAuth 2.0 Client ID with type `Desktop`.
|
||||
# The Google application needs the https://mail.google.com scope; mine has
|
||||
# an authorized domain `google.com` but I don't know if that's required.
|
||||
# Enter the client ID and client secret into a two-line text file
|
||||
# named gmail-client-app.txt immediately next to the intended destination
|
||||
# secret file (the arg to mutt-oauth2.py in the invocation):
|
||||
# so here it would be /path/to/gmail-client-app.txt .
|
||||
# Run `./mail/mutt-oauth2.py /path/to/secret --authorize --verbose` once manually,
|
||||
# and that will populate /path/to/secret.
|
||||
# I've left it unencrypted here; the original uses GPG to store it encrypted at rest.
|
||||
passwordCommand = ''${pkgs.python3}/bin/python ${./mail/mutt-oauth2.py} ${secretsPath}/gmail.txt'';
|
||||
realName = "Patrick Stevens";
|
||||
};
|
||||
|
||||
accounts.email.accounts."BTInternet" = let
|
||||
address = (deobfuscate "z5WZ2VGdz5yajlmc0FGc") + "@btinternet.com";
|
||||
in {
|
||||
notmuch.enable = true;
|
||||
neomutt = {
|
||||
enable = true;
|
||||
};
|
||||
address = address;
|
||||
imap = {
|
||||
host = "mail.btinternet.com";
|
||||
port = 993;
|
||||
tls = {
|
||||
enable = true;
|
||||
useStartTls = false;
|
||||
};
|
||||
};
|
||||
mbsync = {
|
||||
enable = true;
|
||||
create = "maildir";
|
||||
};
|
||||
realName = "Patrick Stevens";
|
||||
passwordCommand = "cat ${secretsPath}/btinternet.txt";
|
||||
smtp = {
|
||||
host = "mail.btinternet.com";
|
||||
port = 465;
|
||||
tls = {
|
||||
enable = true;
|
||||
useStartTls = false;
|
||||
};
|
||||
};
|
||||
userName = address;
|
||||
primary = true;
|
||||
};
|
||||
|
||||
accounts.email.accounts."Proton" = let
|
||||
address = deobfuscate "gAya15ybj5ycuVmdlR3crNWayRXYwB0ajlmc0FGc";
|
||||
in {
|
||||
notmuch.enable = true;
|
||||
neomutt = {
|
||||
enable = true;
|
||||
};
|
||||
address = address;
|
||||
# I use the ProtonMail bridge, which sits at localhost.
|
||||
imap = {
|
||||
host = "127.0.0.1";
|
||||
port = 1143; # 8125; if using hydroxide
|
||||
tls = {
|
||||
enable = false;
|
||||
useStartTls = true;
|
||||
};
|
||||
};
|
||||
mbsync = {
|
||||
enable = true;
|
||||
create = "maildir";
|
||||
extraConfig.account = {
|
||||
# Because ProtonMail Bridge is localhost, we don't
|
||||
# care that we can only auth to it in plain text.
|
||||
AuthMechs = "LOGIN";
|
||||
};
|
||||
};
|
||||
realName = "Patrick Stevens";
|
||||
passwordCommand =
|
||||
# I store the ProtonMail Bridge password here.
|
||||
# Extracting it from a keychain would be better.
|
||||
"cat ${secretsPath}/proton.txt";
|
||||
smtp = {
|
||||
host = "127.0.0.1";
|
||||
port = 1025; # 8126; if using hydroxide
|
||||
tls = {enable = false;};
|
||||
};
|
||||
userName = address;
|
||||
};
|
||||
|
||||
services.mbsync = {
|
||||
enable = pkgs.stdenv.isLinux;
|
||||
package = mbsync;
|
||||
};
|
||||
|
||||
programs.mbsync = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
CopyArrivalDate yes
|
||||
'';
|
||||
package = mbsync;
|
||||
};
|
||||
programs.neomutt = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
set use_threads=threads sort=last-date sort_aux=date
|
||||
'';
|
||||
sidebar.enable = true;
|
||||
vimKeys = true;
|
||||
};
|
||||
|
||||
programs.notmuch.enable = true;
|
||||
|
||||
home.file.".mailcap".source = ./mail/mailcap;
|
||||
|
||||
home.packages = [
|
||||
pkgs.notmuch
|
||||
pkgs.lynx
|
||||
];
|
||||
}
|
2
home-manager/modules/mail/ascii
Normal file
2
home-manager/modules/mail/ascii
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
|
402
home-manager/modules/mail/mutt-oauth2.py
Normal file
402
home-manager/modules/mail/mutt-oauth2.py
Normal file
@@ -0,0 +1,402 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Mutt OAuth2 token management script, version 2020-08-07
|
||||
# Written against python 3.7.3, not tried with earlier python versions.
|
||||
#
|
||||
# Copyright (C) 2020 Alexander Perlis
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
# Subsequently adapted by Patrick Stevens, who hacked it up to read gmail
|
||||
# client app configuration from a file called gmail-client-app.txt that
|
||||
# lives next to the secret file.
|
||||
|
||||
'''Mutt OAuth2 token management'''
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import imaplib
|
||||
import poplib
|
||||
import smtplib
|
||||
import base64
|
||||
import secrets
|
||||
import hashlib
|
||||
import time
|
||||
from datetime import timedelta, datetime
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import http.server
|
||||
import subprocess
|
||||
|
||||
ap = argparse.ArgumentParser(epilog='''
|
||||
This script obtains and prints a valid OAuth2 access token. State is maintained in an
|
||||
encrypted TOKENFILE. Run with "--verbose --authorize" to get started or whenever all
|
||||
tokens have expired, optionally with "--authflow" to override the default authorization
|
||||
flow. To truly start over from scratch, first delete TOKENFILE. Use "--verbose --test"
|
||||
to test the IMAP/POP/SMTP endpoints.
|
||||
''')
|
||||
ap.add_argument('-v', '--verbose', action='store_true', help='increase verbosity')
|
||||
ap.add_argument('-d', '--debug', action='store_true', help='enable debug output')
|
||||
ap.add_argument('tokenfile', help='persistent token storage')
|
||||
ap.add_argument('-a', '--authorize', action='store_true', help='manually authorize new tokens')
|
||||
ap.add_argument('--authflow', help='authcode | localhostauthcode | devicecode')
|
||||
ap.add_argument('-t', '--test', action='store_true', help='test IMAP/POP/SMTP endpoints')
|
||||
args = ap.parse_args()
|
||||
|
||||
token = {}
|
||||
path = Path(args.tokenfile)
|
||||
if path.exists():
|
||||
if 0o777 & path.stat().st_mode != 0o600:
|
||||
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
|
||||
try:
|
||||
token = json.loads(path.read_bytes())
|
||||
except subprocess.CalledProcessError:
|
||||
sys.exit('Difficulty decrypting token file. Is your decryption agent primed for '
|
||||
'non-interactive usage, or an appropriate environment variable such as '
|
||||
'GPG_TTY set to allow interactive agent usage from inside a pipe?')
|
||||
|
||||
|
||||
client_id, client_secret = (path.parent / "gmail-client-app.txt").read_text().strip().split('\n')
|
||||
|
||||
registrations = {
|
||||
'google': {
|
||||
'authorize_endpoint': 'https://accounts.google.com/o/oauth2/auth',
|
||||
'devicecode_endpoint': 'https://oauth2.googleapis.com/device/code',
|
||||
'token_endpoint': 'https://accounts.google.com/o/oauth2/token',
|
||||
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
|
||||
'imap_endpoint': 'imap.gmail.com',
|
||||
'pop_endpoint': 'pop.gmail.com',
|
||||
'smtp_endpoint': 'smtp.gmail.com',
|
||||
'sasl_method': 'OAUTHBEARER',
|
||||
'scope': 'https://mail.google.com/',
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def writetokenfile():
|
||||
'''Writes global token dictionary into token file.'''
|
||||
if not path.exists():
|
||||
path.touch(mode=0o600)
|
||||
if 0o777 & path.stat().st_mode != 0o600:
|
||||
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
|
||||
path.write_bytes(json.dumps(token).encode('utf-8'))
|
||||
|
||||
|
||||
if args.debug:
|
||||
print('Obtained from token file:', json.dumps(token))
|
||||
if not token:
|
||||
if not args.authorize:
|
||||
sys.exit('You must run script with "--authorize" at least once.')
|
||||
print('Available app and endpoint registrations:', *registrations)
|
||||
token['registration'] = input('OAuth2 registration: ')
|
||||
token['authflow'] = input('Preferred OAuth2 flow ("authcode" or "localhostauthcode" '
|
||||
'or "devicecode"): ')
|
||||
token['email'] = input('Account e-mail address: ')
|
||||
token['access_token'] = ''
|
||||
token['access_token_expiration'] = ''
|
||||
token['refresh_token'] = ''
|
||||
writetokenfile()
|
||||
|
||||
if token['registration'] not in registrations:
|
||||
sys.exit(f'ERROR: Unknown registration "{token["registration"]}". Delete token file '
|
||||
f'and start over.')
|
||||
registration = registrations[token['registration']]
|
||||
|
||||
authflow = token['authflow']
|
||||
if args.authflow:
|
||||
authflow = args.authflow
|
||||
|
||||
baseparams = {'client_id': registration['client_id']}
|
||||
# Microsoft uses 'tenant' but Google does not
|
||||
if 'tenant' in registration:
|
||||
baseparams['tenant'] = registration['tenant']
|
||||
|
||||
|
||||
def access_token_valid():
|
||||
'''Returns True when stored access token exists and is still valid at this time.'''
|
||||
token_exp = token['access_token_expiration']
|
||||
return token_exp and datetime.now() < datetime.fromisoformat(token_exp)
|
||||
|
||||
|
||||
def update_tokens(r):
|
||||
'''Takes a response dictionary, extracts tokens out of it, and updates token file.'''
|
||||
token['access_token'] = r['access_token']
|
||||
token['access_token_expiration'] = (datetime.now() +
|
||||
timedelta(seconds=int(r['expires_in']))).isoformat()
|
||||
if 'refresh_token' in r:
|
||||
token['refresh_token'] = r['refresh_token']
|
||||
writetokenfile()
|
||||
if args.verbose:
|
||||
print(f'NOTICE: Obtained new access token, expires {token["access_token_expiration"]}.')
|
||||
|
||||
|
||||
if args.authorize:
|
||||
p = baseparams.copy()
|
||||
p['scope'] = registration['scope']
|
||||
|
||||
if authflow in ('authcode', 'localhostauthcode'):
|
||||
verifier = secrets.token_urlsafe(90)
|
||||
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())[:-1]
|
||||
redirect_uri = registration['redirect_uri']
|
||||
listen_port = 0
|
||||
if authflow == 'localhostauthcode':
|
||||
# Find an available port to listen on
|
||||
s = socket.socket()
|
||||
s.bind(('127.0.0.1', 0))
|
||||
listen_port = s.getsockname()[1]
|
||||
s.close()
|
||||
redirect_uri = 'http://localhost:'+str(listen_port)+'/'
|
||||
# Probably should edit the port number into the actual redirect URL.
|
||||
|
||||
p.update({'login_hint': token['email'],
|
||||
'response_type': 'code',
|
||||
'redirect_uri': redirect_uri,
|
||||
'code_challenge': challenge,
|
||||
'code_challenge_method': 'S256'})
|
||||
print(registration["authorize_endpoint"] + '?' +
|
||||
urllib.parse.urlencode(p, quote_via=urllib.parse.quote))
|
||||
|
||||
authcode = ''
|
||||
if authflow == 'authcode':
|
||||
authcode = input('Visit displayed URL to retrieve authorization code. Enter '
|
||||
'code from server (might be in browser address bar): ')
|
||||
else:
|
||||
print('Visit displayed URL to authorize this application. Waiting...',
|
||||
end='', flush=True)
|
||||
|
||||
class MyHandler(http.server.BaseHTTPRequestHandler):
|
||||
'''Handles the browser query resulting from redirect to redirect_uri.'''
|
||||
|
||||
# pylint: disable=C0103
|
||||
def do_HEAD(self):
|
||||
'''Response to a HEAD requests.'''
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
'''For GET request, extract code parameter from URL.'''
|
||||
# pylint: disable=W0603
|
||||
global authcode
|
||||
querystring = urllib.parse.urlparse(self.path).query
|
||||
querydict = urllib.parse.parse_qs(querystring)
|
||||
if 'code' in querydict:
|
||||
authcode = querydict['code'][0]
|
||||
self.do_HEAD()
|
||||
self.wfile.write(b'<html><head><title>Authorizaton result</title></head>')
|
||||
self.wfile.write(b'<body><p>Authorization redirect completed. You may '
|
||||
b'close this window.</p></body></html>')
|
||||
with http.server.HTTPServer(('127.0.0.1', listen_port), MyHandler) as httpd:
|
||||
try:
|
||||
httpd.handle_request()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
if not authcode:
|
||||
sys.exit('Did not obtain an authcode.')
|
||||
|
||||
for k in 'response_type', 'login_hint', 'code_challenge', 'code_challenge_method':
|
||||
del p[k]
|
||||
p.update({'grant_type': 'authorization_code',
|
||||
'code': authcode,
|
||||
'client_secret': registration['client_secret'],
|
||||
'code_verifier': verifier})
|
||||
print('Exchanging the authorization code for an access token')
|
||||
try:
|
||||
response = urllib.request.urlopen(registration['token_endpoint'],
|
||||
urllib.parse.urlencode(p).encode())
|
||||
except urllib.error.HTTPError as err:
|
||||
print(err.code, err.reason)
|
||||
response = err
|
||||
response = response.read()
|
||||
if args.debug:
|
||||
print(response)
|
||||
response = json.loads(response)
|
||||
if 'error' in response:
|
||||
print(response['error'])
|
||||
if 'error_description' in response:
|
||||
print(response['error_description'])
|
||||
sys.exit(1)
|
||||
|
||||
elif authflow == 'devicecode':
|
||||
try:
|
||||
response = urllib.request.urlopen(registration['devicecode_endpoint'],
|
||||
urllib.parse.urlencode(p).encode())
|
||||
except urllib.error.HTTPError as err:
|
||||
print(err.code, err.reason)
|
||||
response = err
|
||||
response = response.read()
|
||||
if args.debug:
|
||||
print(response)
|
||||
response = json.loads(response)
|
||||
if 'error' in response:
|
||||
print(response['error'])
|
||||
if 'error_description' in response:
|
||||
print(response['error_description'])
|
||||
sys.exit(1)
|
||||
print(response['message'])
|
||||
del p['scope']
|
||||
p.update({'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
||||
'client_secret': registration['client_secret'],
|
||||
'device_code': response['device_code']})
|
||||
interval = int(response['interval'])
|
||||
print('Polling...', end='', flush=True)
|
||||
while True:
|
||||
time.sleep(interval)
|
||||
print('.', end='', flush=True)
|
||||
try:
|
||||
response = urllib.request.urlopen(registration['token_endpoint'],
|
||||
urllib.parse.urlencode(p).encode())
|
||||
except urllib.error.HTTPError as err:
|
||||
# Not actually always an error, might just mean "keep trying..."
|
||||
response = err
|
||||
response = response.read()
|
||||
if args.debug:
|
||||
print(response)
|
||||
response = json.loads(response)
|
||||
if 'error' not in response:
|
||||
break
|
||||
if response['error'] == 'authorization_declined':
|
||||
print(' user declined authorization.')
|
||||
sys.exit(1)
|
||||
if response['error'] == 'expired_token':
|
||||
print(' too much time has elapsed.')
|
||||
sys.exit(1)
|
||||
if response['error'] != 'authorization_pending':
|
||||
print(response['error'])
|
||||
if 'error_description' in response:
|
||||
print(response['error_description'])
|
||||
sys.exit(1)
|
||||
print()
|
||||
|
||||
else:
|
||||
sys.exit(f'ERROR: Unknown OAuth2 flow "{token["authflow"]}. Delete token file and '
|
||||
f'start over.')
|
||||
|
||||
update_tokens(response)
|
||||
|
||||
|
||||
if not access_token_valid():
|
||||
if args.verbose:
|
||||
print('NOTICE: Invalid or expired access token; using refresh token '
|
||||
'to obtain new access token.')
|
||||
if not token['refresh_token']:
|
||||
sys.exit('ERROR: No refresh token. Run script with "--authorize".')
|
||||
p = baseparams.copy()
|
||||
p.update({'client_secret': registration['client_secret'],
|
||||
'refresh_token': token['refresh_token'],
|
||||
'grant_type': 'refresh_token'})
|
||||
try:
|
||||
response = urllib.request.urlopen(registration['token_endpoint'],
|
||||
urllib.parse.urlencode(p).encode())
|
||||
except urllib.error.HTTPError as err:
|
||||
print(err.code, err.reason)
|
||||
response = err
|
||||
response = response.read()
|
||||
if args.debug:
|
||||
print(response)
|
||||
response = json.loads(response)
|
||||
if 'error' in response:
|
||||
print(response['error'])
|
||||
if 'error_description' in response:
|
||||
print(response['error_description'])
|
||||
print('Perhaps refresh token invalid. Try running once with "--authorize"')
|
||||
sys.exit(1)
|
||||
update_tokens(response)
|
||||
|
||||
|
||||
if not access_token_valid():
|
||||
sys.exit('ERROR: No valid access token. This should not be able to happen.')
|
||||
|
||||
|
||||
if args.verbose:
|
||||
print('Access Token: ', end='')
|
||||
print(token['access_token'])
|
||||
|
||||
|
||||
def build_sasl_string(user, host, port, bearer_token):
|
||||
'''Build appropriate SASL string, which depends on cloud server's supported SASL method.'''
|
||||
if registration['sasl_method'] == 'OAUTHBEARER':
|
||||
return f'n,a={user},\1host={host}\1port={port}\1auth=Bearer {bearer_token}\1\1'
|
||||
if registration['sasl_method'] == 'XOAUTH2':
|
||||
return f'user={user}\1auth=Bearer {bearer_token}\1\1'
|
||||
sys.exit(f'Unknown SASL method {registration["sasl_method"]}.')
|
||||
|
||||
|
||||
if args.test:
|
||||
errors = False
|
||||
|
||||
imap_conn = imaplib.IMAP4_SSL(registration['imap_endpoint'])
|
||||
sasl_string = build_sasl_string(token['email'], registration['imap_endpoint'], 993,
|
||||
token['access_token'])
|
||||
if args.debug:
|
||||
imap_conn.debug = 4
|
||||
try:
|
||||
imap_conn.authenticate(registration['sasl_method'], lambda _: sasl_string.encode())
|
||||
# Microsoft has a bug wherein a mismatch between username and token can still report a
|
||||
# successful login... (Try a consumer login with the token from a work/school account.)
|
||||
# Fortunately subsequent commands fail with an error. Thus we follow AUTH with another
|
||||
# IMAP command before reporting success.
|
||||
imap_conn.list()
|
||||
if args.verbose:
|
||||
print('IMAP authentication succeeded')
|
||||
except imaplib.IMAP4.error as e:
|
||||
print('IMAP authentication FAILED (does your account allow IMAP?):', e)
|
||||
errors = True
|
||||
|
||||
pop_conn = poplib.POP3_SSL(registration['pop_endpoint'])
|
||||
sasl_string = build_sasl_string(token['email'], registration['pop_endpoint'], 995,
|
||||
token['access_token'])
|
||||
if args.debug:
|
||||
pop_conn.set_debuglevel(2)
|
||||
try:
|
||||
# poplib doesn't have an auth command taking an authenticator object
|
||||
# Microsoft requires a two-line SASL for POP
|
||||
# pylint: disable=W0212
|
||||
pop_conn._shortcmd('AUTH ' + registration['sasl_method'])
|
||||
pop_conn._shortcmd(base64.standard_b64encode(sasl_string.encode()).decode())
|
||||
if args.verbose:
|
||||
print('POP authentication succeeded')
|
||||
except poplib.error_proto as e:
|
||||
print('POP authentication FAILED (does your account allow POP?):', e.args[0].decode())
|
||||
errors = True
|
||||
|
||||
# SMTP_SSL would be simpler but Microsoft does not answer on port 465.
|
||||
smtp_conn = smtplib.SMTP(registration['smtp_endpoint'], 587)
|
||||
sasl_string = build_sasl_string(token['email'], registration['smtp_endpoint'], 587,
|
||||
token['access_token'])
|
||||
smtp_conn.ehlo('test')
|
||||
smtp_conn.starttls()
|
||||
smtp_conn.ehlo('test')
|
||||
if args.debug:
|
||||
smtp_conn.set_debuglevel(2)
|
||||
try:
|
||||
smtp_conn.auth(registration['sasl_method'], lambda _=None: sasl_string)
|
||||
if args.verbose:
|
||||
print('SMTP authentication succeeded')
|
||||
except smtplib.SMTPAuthenticationError as e:
|
||||
print('SMTP authentication FAILED:', e)
|
||||
errors = True
|
||||
|
||||
if errors:
|
||||
sys.exit(1)
|
||||
|
7
home-manager/modules/ripgrep.nix
Normal file
7
home-manager/modules/ripgrep.nix
Normal file
@@ -0,0 +1,7 @@
|
||||
{pkgs, ...}: {
|
||||
home.packages = [
|
||||
pkgs.ripgrep
|
||||
];
|
||||
|
||||
home.file.".config/ripgrep/config".source = ./ripgrep/ripgrep.conf;
|
||||
}
|
10
home-manager/modules/rust.nix
Normal file
10
home-manager/modules/rust.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{pkgs, ...}: {
|
||||
programs.zsh.sessionVariables = {
|
||||
RUSTFLAGS = "-L ${pkgs.libiconv}/lib -L ${pkgs.libcxx}/lib";
|
||||
RUST_BACKTRACE = "full";
|
||||
};
|
||||
home.file.".cargo/config.toml".source = ./rust/cargo-config.toml;
|
||||
home.packages = [
|
||||
pkgs.rust-analyzer
|
||||
];
|
||||
}
|
18
home-manager/modules/tmux.nix
Normal file
18
home-manager/modules/tmux.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{pkgs, ...}: {
|
||||
imports = [./zsh.nix];
|
||||
home.packages = [
|
||||
pkgs.tmux
|
||||
];
|
||||
|
||||
programs.tmux = {
|
||||
shell = "${pkgs.zsh}/bin/zsh";
|
||||
escapeTime = 50;
|
||||
mouse = false;
|
||||
prefix = "C-b";
|
||||
enable = true;
|
||||
terminal = "screen-256color";
|
||||
extraConfig = ''
|
||||
set-option -sa terminal-features ',xterm-256color:RGB'
|
||||
'';
|
||||
};
|
||||
}
|
23
home-manager/modules/zsh.nix
Normal file
23
home-manager/modules/zsh.nix
Normal file
@@ -0,0 +1,23 @@
|
||||
{pkgs, ...}: {
|
||||
programs.zsh = {
|
||||
enable = true;
|
||||
autocd = true;
|
||||
autosuggestion.enable = true;
|
||||
enableCompletion = true;
|
||||
history = {
|
||||
expireDuplicatesFirst = true;
|
||||
};
|
||||
sessionVariables = {
|
||||
EDITOR = "vim";
|
||||
LC_ALL = "en_US.UTF-8";
|
||||
LC_CTYPE = "en_US.UTF-8";
|
||||
};
|
||||
shellAliases = {
|
||||
vim = "nvim";
|
||||
view = "vim -R";
|
||||
};
|
||||
initExtra = builtins.readFile ./zsh/zshrc;
|
||||
};
|
||||
|
||||
programs.fzf.enableZshIntegration = true;
|
||||
}
|
@@ -27,5 +27,6 @@ export WORDCHARS=''
|
||||
autoload edit-command-line
|
||||
zle -N edit-command-line
|
||||
bindkey '^X^E' edit-command-line
|
||||
bindkey -e
|
||||
|
||||
PATH="$PATH:$HOME/.cargo/bin"
|
@@ -3,6 +3,8 @@ vim.opt.mouse = ""
|
||||
vim.opt.history = 500
|
||||
vim.opt.background = "dark"
|
||||
|
||||
vim.opt.signcolumn = "yes"
|
||||
|
||||
vim.opt.wildmenu = true
|
||||
vim.opt.wildignore = vim.opt.wildignore + { "*/.git/*", "*/.hg/*", "*/.svn/*", "*/.DS_Store" }
|
||||
|
||||
|
@@ -1 +1,15 @@
|
||||
require("dap-python").setup("%PYTHONENV%/bin/python")
|
||||
|
||||
do
|
||||
local whichkey = require("which-key")
|
||||
whichkey.register({
|
||||
['pd'] = {
|
||||
"Debugger-related commands",
|
||||
t = {
|
||||
"Tests",
|
||||
f = { require("dap-python").test_class, "Run Python tests in the current file" },
|
||||
c = { require("dap-python").test_method, "Run the Python test under the cursor" },
|
||||
},
|
||||
},
|
||||
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
|
||||
end
|
||||
|
66
home-manager/nvim/python.lua
Normal file
66
home-manager/nvim/python.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
local function pytest_on_line(_, _, _) end
|
||||
local function pytest_on_complete(_, code, _)
|
||||
if code ~= 0 then
|
||||
print("Exit code " .. code)
|
||||
end
|
||||
end
|
||||
|
||||
function RunPythonTestAtCursor()
|
||||
local api = vim.api
|
||||
|
||||
-- Get the current buffer and cursor position
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local line_nr = api.nvim_win_get_cursor(0)[1]
|
||||
local filename = api.nvim_buf_get_name(bufnr)
|
||||
|
||||
-- Read the file content
|
||||
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
|
||||
-- Find the test function
|
||||
local test_name = nil
|
||||
for i = line_nr, 1, -1 do
|
||||
local line = lines[i]
|
||||
if line:match("^def test_") then
|
||||
test_name = line:match("^def (%S+)%(")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if test_name then
|
||||
-- Run pytest for the found test function
|
||||
local context = BuildUtils.create_window()
|
||||
BuildUtils.run(
|
||||
"pytest",
|
||||
{ filename .. "::" .. test_name },
|
||||
"Run PyTest (" .. test_name .. ")",
|
||||
context,
|
||||
pytest_on_line,
|
||||
pytest_on_complete
|
||||
)
|
||||
else
|
||||
print("No test function found at or above line " .. line_nr)
|
||||
end
|
||||
end
|
||||
|
||||
function RunPythonTestsInFile()
|
||||
local file_path = vim.fn.expand("%:p")
|
||||
local context = BuildUtils.create_window()
|
||||
BuildUtils.run("pytest", { file_path }, "Run PyTest", context, pytest_on_line, pytest_on_complete)
|
||||
end
|
||||
|
||||
function RunAllPythonTests()
|
||||
local context = BuildUtils.create_window()
|
||||
BuildUtils.run("pytest", {}, "Run PyTest", context, pytest_on_line, pytest_on_complete)
|
||||
end
|
||||
|
||||
do
|
||||
local whichkey = require("which-key")
|
||||
whichkey.register({
|
||||
['pt'] = {
|
||||
"Run Python tests",
|
||||
f = { RunPythonTestsInFile, "Run Python tests in the current file" },
|
||||
a = { RunAllPythonTests, "Run all Python tests" },
|
||||
c = { RunPythonTestAtCursor, "Run the Python test under the cursor" },
|
||||
},
|
||||
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
|
||||
end
|
@@ -3,6 +3,7 @@ local venv_selector = require("venv-selector")
|
||||
venv_selector.setup({
|
||||
changed_venv_hooks = { venv_selector.hooks.pyright },
|
||||
name = { "venv", ".venv" },
|
||||
search_venv_managers = true,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
@@ -84,10 +85,8 @@ end
|
||||
do
|
||||
local whichkey = require("which-key")
|
||||
whichkey.register({
|
||||
p = {
|
||||
name = "Python-related commands",
|
||||
v = {
|
||||
name = "Virtual environment-related commands",
|
||||
['pv'] = {
|
||||
name = "Python virtual environment-related commands",
|
||||
c = { CreateVenv, "Create virtual environment" },
|
||||
l = { SelectVenv, "Load virtual environment" },
|
||||
o = {
|
||||
@@ -96,7 +95,6 @@ do
|
||||
end,
|
||||
"Choose (override) new virtual environment",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
|
||||
end
|
||||
|
11
mbsync.nix
Normal file
11
mbsync.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{pkgs}:
|
||||
pkgs.buildEnv {
|
||||
name = "isync-oauth2";
|
||||
paths = [pkgs.isync];
|
||||
pathsToLink = ["/bin"];
|
||||
nativeBuildInputs = [pkgs.makeWrapper];
|
||||
postBuild = ''
|
||||
wrapProgram "$out/bin/mbsync" \
|
||||
--prefix SASL_PATH : "${pkgs.cyrus_sasl}/lib/sasl2:${pkgs.cyrus-sasl-xoauth2}/lib/sasl2"
|
||||
'';
|
||||
}
|
10
overlays.nix
10
overlays.nix
@@ -1,10 +0,0 @@
|
||||
[
|
||||
(self: super: {
|
||||
# https://github.com/NixOS/nixpkgs/issues/153304
|
||||
alacritty = super.alacritty.overrideAttrs (
|
||||
o: rec {
|
||||
doCheck = false;
|
||||
}
|
||||
);
|
||||
})
|
||||
]
|
14
python.nix
14
python.nix
@@ -1,14 +0,0 @@
|
||||
{pkgs}: let
|
||||
my-python-packages = python-packages:
|
||||
with python-packages; [
|
||||
pip
|
||||
mathlibtools
|
||||
];
|
||||
in let
|
||||
packageOverrides = self: super: {
|
||||
# Test failures on darwin ("windows-1252"); just skip pytest
|
||||
# (required for elan)
|
||||
beautifulsoup4 = super.beautifulsoup4.overridePythonAttrs (old: {pytestCheckPhase = "true";});
|
||||
};
|
||||
in
|
||||
(pkgs.python3.override {inherit packageOverrides;}).withPackages my-python-packages
|
@@ -1,2 +0,0 @@
|
||||
{nixpkgs, ...}: {
|
||||
}
|
Reference in New Issue
Block a user