updates
This commit is contained in:
1 parent
f164d8b76e
commit
5748b3409b
60 files changed
+1346
-590
No files matched your search
File renamed without changes.
@@ -0,0 +1,14 @@
|
||||
# AI tools
|
||||
|
||||
Nix installs the tools, assets, skills and system Codex defaults. Codex owns its
|
||||
writable user configuration; credentials, session history and other runtime state
|
||||
remain outside this repository. `instructions/codex.md` owns global agent guidance.
|
||||
|
||||
Herdr is optional experimentation. Reload its managed configuration with
|
||||
Ctrl+Alt+Shift+R (or Ctrl+B then Shift+R); its configuration owns the binding reference.
|
||||
Home Manager may preserve an earlier regular configuration file with `.bak`.
|
||||
|
||||
Ollama supplies `nomic-embed-text` for grounded-docs. The latter runs on localhost
|
||||
port 6280 with persistent Podman volumes. Removing another tool must not delete
|
||||
these models or volumes. No mandatory agent persistence or orchestration workflow
|
||||
is defined here.
|
||||
@@ -1,6 +1,6 @@
|
||||
{ pkgs }:
|
||||
let
|
||||
sources = import ./_sources.nix { inherit pkgs; };
|
||||
sources = import ./sources.nix { inherit pkgs; };
|
||||
context7Headers = pkgs.writeShellScript "context7-http-headers" ''
|
||||
set -euo pipefail
|
||||
CONTEXT7_KEY_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/context7/api-key"
|
||||
@@ -26,7 +26,6 @@ in
|
||||
inherit context7Headers codexHooks;
|
||||
skills = {
|
||||
context7-mcp = ../skills/context7-mcp;
|
||||
# nixos-maintainer = ../skills/nixos-maintainer;
|
||||
herdr = "${sources.herdr}/skills/herdr";
|
||||
};
|
||||
servers = {
|
||||
File renamed without changes.
@@ -7,7 +7,7 @@ in
|
||||
flake.modules.nixos.ai =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
ai = import ./ai/_assets.nix { inherit pkgs; };
|
||||
ai = import ./_support/assets.nix { inherit pkgs; };
|
||||
codexConfig = (pkgs.formats.toml { }).generate "codex-config.toml" {
|
||||
model = "gpt-5.6-sol";
|
||||
model_reasoning_effort = "high";
|
||||
@@ -15,11 +15,6 @@ in
|
||||
service_tier = "default";
|
||||
approvals_reviewer = "auto_review";
|
||||
features.hooks = true;
|
||||
notify = [
|
||||
"${pkgs.llm-agents.agent-deck}/bin/agent-deck"
|
||||
"codex-notify"
|
||||
];
|
||||
|
||||
tui = {
|
||||
status_line = [
|
||||
"model-with-reasoning"
|
||||
@@ -71,7 +66,7 @@ in
|
||||
flake.modules.homeManager.ai =
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
ai = import ./ai/_assets.nix { inherit pkgs; };
|
||||
ai = import ./_support/assets.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
services.podman = {
|
||||
@@ -105,9 +100,7 @@ in
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.codex
|
||||
pkgs.llm-agents.pi
|
||||
pkgs.llm-agents.claude-code
|
||||
pkgs.llm-agents.agent-deck
|
||||
pkgs.llm-agents.herdr
|
||||
pkgs.tmux
|
||||
pkgs.ctx7
|
||||
];
|
||||
|
||||
@@ -190,7 +183,7 @@ in
|
||||
name: source: lib.nameValuePair ".agents/skills/${name}" { inherit source; }
|
||||
) ai.skills
|
||||
// {
|
||||
".codex/AGENTS.md".text = builtins.readFile ./ai/instructions/codex.md;
|
||||
".codex/AGENTS.md".text = builtins.readFile ./instructions/codex.md;
|
||||
};
|
||||
};
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -35,16 +35,6 @@
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.vivaldi ];
|
||||
};
|
||||
|
||||
flake.modules.homeManager.nautilus =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
home.packages = [ pkgs.nautilus ];
|
||||
gtk.gtk3.bookmarks = [
|
||||
"sftp://${config.facts.topology.server.sshAlias} Orion VPS"
|
||||
];
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.nautilus ];
|
||||
};
|
||||
|
||||
flake.modules.homeManager.imv =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
@@ -75,7 +65,6 @@
|
||||
dbeaver-bin
|
||||
onlyoffice-desktopeditors
|
||||
pinta
|
||||
zellij
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem = { pkgs, ... }: {
|
||||
packages.devenv = inputs.devenv.packages.${pkgs.stdenv.hostPlatform.system}.devenv;
|
||||
};
|
||||
flake.modules.homeManager.dev-tools =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
home.sessionVariables.CARGO_HOME = "${config.xdg.dataHome}/cargo";
|
||||
home.packages = with pkgs; [
|
||||
inputs.devenv.packages.${pkgs.stdenv.hostPlatform.system}.devenv
|
||||
bruno
|
||||
kubectl
|
||||
kubelogin-oidc
|
||||
@@ -20,14 +25,8 @@
|
||||
programs.ripgrep.enable = true;
|
||||
programs.k9s.enable = true;
|
||||
|
||||
programs.direnv = {
|
||||
enable = true;
|
||||
nix-direnv.enable = true;
|
||||
|
||||
config.global = {
|
||||
hide_env_diff = true;
|
||||
log_format = "↳ %s";
|
||||
};
|
||||
};
|
||||
programs.zsh.initContent = ''
|
||||
eval "$(devenv hook zsh)"
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
flake.modules.nixos.file-management = {
|
||||
services.gvfs.enable = true;
|
||||
services.udisks2.enable = true;
|
||||
};
|
||||
|
||||
flake.modules.homeManager.file-management =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
home.packages = [ pkgs.nautilus ];
|
||||
gtk.gtk3.bookmarks = [
|
||||
"sftp://${config.facts.topology.server.sshAlias} Orion VPS"
|
||||
];
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.nautilus ];
|
||||
};
|
||||
|
||||
}
|
||||
File renamed without changes.
@@ -34,7 +34,7 @@
|
||||
Requisite = [ "handy.service" ];
|
||||
After = [ "handy.service" ];
|
||||
};
|
||||
Service.ExecStart = "${python}/bin/python ${./handy/_push_to_talk.py} ${pkgs.handy}/bin/handy";
|
||||
Service.ExecStart = "${python}/bin/python ${./push_to_talk.py} ${pkgs.handy}/bin/handy";
|
||||
};
|
||||
};
|
||||
}
|
||||
File renamed without changes.
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import _push_to_talk as ptt
|
||||
import push_to_talk as ptt
|
||||
|
||||
down = [ptt.key.KEY_LEFTMETA, ptt.key.KEY_D]
|
||||
|
||||
File renamed without changes.
@@ -13,7 +13,7 @@ in
|
||||
}:
|
||||
let
|
||||
nvimTreesitter = pkgs.vimPlugins.nvim-treesitter;
|
||||
svelteLanguageServer = import ../_svelte-language-server.nix { inherit pkgs; };
|
||||
svelteLanguageServer = import ./_support/svelte-language-server.nix { inherit pkgs; };
|
||||
svelteGrammar = nvimTreesitter.builtGrammars.svelte.overrideAttrs (_: {
|
||||
version = "0.0.0+rev=c2457b9";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
require("lz.n").load({
|
||||
{
|
||||
"mini.nvim",
|
||||
event = "VimEnter",
|
||||
-- Icons must exist before Oil opens an initial directory buffer.
|
||||
lazy = false,
|
||||
after = function()
|
||||
require("mini.ai").setup({ n_lines = 500 })
|
||||
require("mini.surround").setup()
|
||||
|
||||
+6
-2
@@ -25,9 +25,13 @@ in
|
||||
spawn = browser;
|
||||
_props.hotkey-overlay-title = "Browser";
|
||||
};
|
||||
"Mod+Shift+N" = {
|
||||
spawn-sh = "rename-workspace";
|
||||
_props.hotkey-overlay-title = "Name workspace (empty clears)";
|
||||
};
|
||||
"Mod+N" = {
|
||||
spawn-sh = "open-daily-note";
|
||||
_props.hotkey-overlay-title = "Daily Note";
|
||||
spawn-sh = "open-notes";
|
||||
_props.hotkey-overlay-title = "Find or create notes";
|
||||
};
|
||||
"Mod+Space" = {
|
||||
_props.repeat = false;
|
||||
File renamed without changes.
@@ -13,10 +13,6 @@ in
|
||||
|
||||
programs.niri.enable = true;
|
||||
|
||||
services = {
|
||||
gvfs.enable = true;
|
||||
udisks2.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.niri =
|
||||
@@ -27,7 +23,22 @@ in
|
||||
...
|
||||
}:
|
||||
let
|
||||
swapMonitorWorkspaces = import ./_shortcuts.nix { inherit pkgs; };
|
||||
swapMonitorWorkspaces = import ./_support/shortcuts.nix { inherit pkgs; };
|
||||
renameWorkspace = pkgs.writeShellApplication {
|
||||
name = "rename-workspace";
|
||||
runtimeInputs = [
|
||||
pkgs.niri
|
||||
pkgs.zenity
|
||||
];
|
||||
text = ''
|
||||
name=$(zenity --entry --title="Workspace name" --text="Name this workspace (empty clears its name):") || exit 0
|
||||
if [[ -n "$name" ]]; then
|
||||
niri msg action set-workspace-name "$name"
|
||||
else
|
||||
niri msg action unset-workspace-name
|
||||
fi
|
||||
'';
|
||||
};
|
||||
machine = config.facts.machine;
|
||||
in
|
||||
{
|
||||
@@ -41,7 +52,10 @@ in
|
||||
wireplumber
|
||||
wtype
|
||||
]
|
||||
++ [ swapMonitorWorkspaces ];
|
||||
++ [
|
||||
swapMonitorWorkspaces
|
||||
renameWorkspace
|
||||
];
|
||||
};
|
||||
|
||||
wayland.windowManager.niri = {
|
||||
@@ -121,7 +135,7 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
binds = import ./_bindings.nix {
|
||||
binds = import ./_support/bindings.nix {
|
||||
inherit (config.desktop.commands)
|
||||
browser
|
||||
launcher
|
||||
|
||||
@@ -124,7 +124,11 @@
|
||||
volume.show_label = false;
|
||||
network.show_label = false;
|
||||
battery.show_label = false;
|
||||
workspaces.show_labels = false;
|
||||
workspaces = {
|
||||
show_labels = true;
|
||||
label_source = "name";
|
||||
max_label_chars = 16;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -38,25 +38,25 @@ in
|
||||
|
||||
# {{title}}
|
||||
'';
|
||||
openDailyNote = pkgs.writeShellApplication {
|
||||
name = "open-daily-note";
|
||||
openNotes = pkgs.writeShellApplication {
|
||||
name = "open-notes";
|
||||
text = ''
|
||||
exec ${lib.getExe config.programs.kitty.package} --detach \
|
||||
--directory "${home}/sync/notes" \
|
||||
${lib.getExe pkgs.zsh} -lic 'zk daily'
|
||||
nvim '+autocmd VimEnter * ++once ZkNotes'
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
home.packages = [
|
||||
openDailyNote
|
||||
openNotes
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "notes";
|
||||
desktopName = "Notes";
|
||||
icon = "text-editor";
|
||||
exec = "nvim .";
|
||||
exec = lib.getExe openNotes;
|
||||
path = "${home}/sync/notes";
|
||||
terminal = true;
|
||||
terminal = false;
|
||||
})
|
||||
pkgs.ripgrep-all
|
||||
pkgs.zk
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,49 +0,0 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
flake.modules.homeManager.sharepoint-reference =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
rcloneConfig = "${config.xdg.configHome}/rclone/rclone.conf";
|
||||
destination = "${config.xdg.dataHome}/yookr-reference/sharepoint";
|
||||
in
|
||||
{
|
||||
systemd.user.services.sharepoint-reference-sync = {
|
||||
Unit = {
|
||||
Description = "Refresh the local SharePoint reference mirror";
|
||||
ConditionPathExists = rcloneConfig;
|
||||
# Let an in-flight sync finish; activation must not restart and wait for it.
|
||||
X-SwitchMethod = "keep-old";
|
||||
};
|
||||
|
||||
Service = {
|
||||
Type = "oneshot";
|
||||
UMask = "0077";
|
||||
ExecStart = [
|
||||
"${lib.getExe pkgs.rclone} --config ${rcloneConfig} sync company-sharepoint: ${destination}/Algemeen --max-delete 100"
|
||||
"${lib.getExe pkgs.rclone} --config ${rcloneConfig} sync company-sharepoint-projecten: ${destination}/Projecten --max-delete 100"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.timers.sharepoint-reference-sync = {
|
||||
Unit.Description = "Refresh the local SharePoint reference mirror hourly";
|
||||
|
||||
Timer = {
|
||||
OnCalendar = "hourly";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "10m";
|
||||
};
|
||||
|
||||
Install.WantedBy = [ "timers.target" ];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.nixos.sharepoint-reference = {
|
||||
home-manager.sharedModules = [ config.flake.modules.homeManager.sharepoint-reference ];
|
||||
};
|
||||
}
|
||||
@@ -52,7 +52,18 @@
|
||||
history.path = "${config.xdg.dataHome}/zsh_history";
|
||||
|
||||
initContent = ''
|
||||
source <(gardenctl rc zsh --no-completion)
|
||||
# Terminal/window switchers show the working directory and active command.
|
||||
autoload -Uz add-zsh-hook
|
||||
function terminal-title-precmd() {
|
||||
print -Pn '\e]2;%m: %~\a'
|
||||
}
|
||||
function terminal-title-preexec() {
|
||||
local command="''${1//[[:cntrl:]]/}"
|
||||
print -Pn '\e]2;%m: %~ — '
|
||||
print -rn -- "$command"$'\a'
|
||||
}
|
||||
add-zsh-hook precmd terminal-title-precmd
|
||||
add-zsh-hook preexec terminal-title-preexec
|
||||
|
||||
bindkey -v
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: nixos-maintainer
|
||||
description: Maintain this NixOS and Home Manager flake by inspecting, changing, simplifying, or validating its configuration. Use for system configuration work in the NixOS repository, not generic Nix questions or unrelated projects.
|
||||
---
|
||||
|
||||
# NixOS Maintainer
|
||||
|
||||
Read the applicable `AGENTS.md` and the complete current owner of the behavior
|
||||
before editing. Trace its imports through the host, profile, and capability
|
||||
modules so the change lands at the narrowest shared level that matches its real
|
||||
scope.
|
||||
|
||||
Prefer an existing module or native NixOS/Home Manager option. Keep mutable,
|
||||
secret, credential, and application-owned state outside the Nix store. Verify
|
||||
unfamiliar pinned interfaces against the exact package source or current primary
|
||||
documentation.
|
||||
|
||||
Preserve unrelated work. Format touched Nix files and use the narrowest useful
|
||||
evaluation for configuration changes. Build affected hosts only when needed to
|
||||
resolve a concrete concern or explicitly requested; instruction-only edits do
|
||||
not need system builds. Do not add tests or migration/compatibility code by
|
||||
default. Explain manual steps for breaking changes.
|
||||
|
||||
Do not switch or boot a generation unless the user explicitly requests activation.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
flake.modules.nixos.tailscale = {
|
||||
services.tailscale.enable = true;
|
||||
# Keep existing SSH exposure until tailnet enrollment and access are verified.
|
||||
networking.firewall.interfaces.tailscale0.allowedTCPPorts = [ 22 ];
|
||||
};
|
||||
}
|
||||
File renamed without changes.
+11
-5
@@ -1,6 +1,6 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
palette = (import ./_kanagawa-palette.nix).palette;
|
||||
palette = (import ./palette.nix).palette;
|
||||
hex = lib.removePrefix "#";
|
||||
wallpaper = ./assets/green-leaves.png;
|
||||
cursor = {
|
||||
@@ -64,8 +64,12 @@ in
|
||||
gtk4.theme = null;
|
||||
iconTheme = {
|
||||
name = "Kanagawa";
|
||||
package = pkgs.kanagawa-icon-theme.overrideAttrs (_: {
|
||||
package = pkgs.kanagawa-icon-theme.overrideAttrs (old: {
|
||||
inherit src version;
|
||||
postInstall = (old.postInstall or "") + ''
|
||||
find "$out/share/icons" -name index.theme -exec \
|
||||
sed -i 's/^Inherits=.*/Inherits=Adwaita,hicolor/' {} +
|
||||
'';
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -75,6 +79,8 @@ in
|
||||
platformTheme.name = "gtk3";
|
||||
};
|
||||
|
||||
home.packages = [ pkgs.adwaita-icon-theme ];
|
||||
|
||||
xdg.systemDirs.data = [
|
||||
"${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}"
|
||||
"${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}"
|
||||
@@ -90,7 +96,7 @@ in
|
||||
{
|
||||
boot.loader.limine.style = {
|
||||
wallpapers = [ pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath ];
|
||||
wallpaperStyle = "stretched";
|
||||
wallpaperStyle = "centered";
|
||||
backdrop = hex palette.secondaryBackground;
|
||||
|
||||
graphicalTerminal = {
|
||||
@@ -128,7 +134,7 @@ in
|
||||
};
|
||||
|
||||
interface = {
|
||||
branding = config.networking.hostName;
|
||||
branding = "NixOS - ${config.networking.hostName}";
|
||||
brandingColor = hex palette.accents.blue;
|
||||
};
|
||||
};
|
||||
@@ -297,7 +303,7 @@ in
|
||||
};
|
||||
|
||||
flake.modules.homeManager.walker = {
|
||||
programs.walker.themes.constellation.style = lib.mkAfter ''
|
||||
programs.walker.themes.desktop.style = lib.mkAfter ''
|
||||
@define-color window_bg_color ${palette.background};
|
||||
@define-color accent_bg_color ${palette.selectionBackground};
|
||||
@define-color theme_fg_color ${palette.foreground};
|
||||
File renamed without changes.
@@ -1,2 +1,2 @@
|
||||
# Select a complete theme module: plugins, assets and behavior as well as colors.
|
||||
{ imports = [ ./_themes/kanagawa.nix ]; }
|
||||
{ imports = [ ./_support/kanagawa.nix ]; }
|
||||
+2
-2
@@ -31,7 +31,7 @@
|
||||
executable = true;
|
||||
text = ''
|
||||
#!${diarizationPython}/bin/python3
|
||||
${builtins.readFile ./_diarize-transcript.py}
|
||||
${builtins.readFile ./diarize-transcript.py}
|
||||
'';
|
||||
};
|
||||
whisper = pkgs.whisper-cpp;
|
||||
@@ -72,7 +72,7 @@
|
||||
findutils
|
||||
whisper
|
||||
];
|
||||
text = builtins.readFile ./_transcribe-recordings.sh;
|
||||
text = builtins.readFile ./transcribe-recordings.sh;
|
||||
};
|
||||
home = config.home.homeDirectory;
|
||||
in
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,171 +0,0 @@
|
||||
{ config, inputs, ... }:
|
||||
{
|
||||
flake.modules.nixos.walker = {
|
||||
home-manager.sharedModules = [ config.flake.modules.homeManager.walker ];
|
||||
|
||||
nix.settings = {
|
||||
extra-substituters = [
|
||||
"https://walker.cachix.org"
|
||||
"https://walker-git.cachix.org"
|
||||
];
|
||||
extra-trusted-public-keys = [
|
||||
"walker.cachix.org-1:fG8q+uAaMqhsMxWjwvk0IMb4mFPFLqHjuvfwQxE4oJM="
|
||||
"walker-git.cachix.org-1:vmC0ocfPWh0S/vRAQGtChuiZBTAe4wiKDeyyXM0/7pM="
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.walker =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
homeDir = config.home.homeDirectory;
|
||||
projectRoots = [
|
||||
{
|
||||
scope = "work";
|
||||
path = "${homeDir}/yookr/projects";
|
||||
}
|
||||
]
|
||||
++
|
||||
map
|
||||
(scope: {
|
||||
inherit scope;
|
||||
path = "${config.xdg.userDirs.projects}/${scope}";
|
||||
})
|
||||
[
|
||||
"own"
|
||||
"ext"
|
||||
"forks"
|
||||
];
|
||||
walkerDefaults = builtins.fromTOML (builtins.readFile "${inputs.walker}/resources/config.toml");
|
||||
|
||||
launcher = pkgs.writeShellApplication {
|
||||
name = "walker-launcher";
|
||||
runtimeInputs = [ pkgs.libressl.nc ];
|
||||
text = ''
|
||||
exec nc -U "$XDG_RUNTIME_DIR/walker/walker.sock"
|
||||
'';
|
||||
};
|
||||
openProject = pkgs.writeShellApplication {
|
||||
name = "open-project";
|
||||
text = ''
|
||||
project_path="''${1:-}"
|
||||
if [[ -z "$project_path" ]]; then
|
||||
IFS= read -r project_path || [[ -n "$project_path" ]]
|
||||
fi
|
||||
|
||||
exec ${lib.getExe config.xdg.terminal-exec.package} --dir="$project_path" \
|
||||
${lib.getExe pkgs.zsh} -lic 'nvim .; exec zsh -l'
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ inputs.walker.homeManagerModules.default ];
|
||||
|
||||
desktop.commands.launcher = [ (lib.getExe launcher) ];
|
||||
home.packages = [ openProject ];
|
||||
|
||||
xdg.configFile."elephant/menus/projects.lua".text = ''
|
||||
Name = "projects"
|
||||
NamePretty = "Projects"
|
||||
Description = "Projects and workspaces"
|
||||
Icon = "folder"
|
||||
Action = "${lib.getExe openProject}"
|
||||
SearchName = true
|
||||
RefreshOnChange = {
|
||||
${lib.concatMapStringsSep "\n " (root: ''"${root.path}",'') projectRoots}
|
||||
}
|
||||
|
||||
local roots = {
|
||||
${lib.concatMapStringsSep "\n " (
|
||||
root: ''{ scope = "${root.scope}", path = "${root.path}" },''
|
||||
) projectRoots}
|
||||
}
|
||||
|
||||
local function repositories(root)
|
||||
return io.popen(
|
||||
"${lib.getExe pkgs.findutils} " .. root ..
|
||||
" -mindepth 1 \\( -type d \\( -name .archive -o -name .worktrees \\) -prune \\)" ..
|
||||
" -o \\( -name .git -print \\) 2>/dev/null"
|
||||
)
|
||||
end
|
||||
|
||||
function GetEntries()
|
||||
local entries = {
|
||||
{
|
||||
Text = "Yookr",
|
||||
Subtext = "Company workspace",
|
||||
Value = "${homeDir}/yookr",
|
||||
Keywords = { "work", "company", "workbench" },
|
||||
},
|
||||
}
|
||||
|
||||
for _, root in ipairs(roots) do
|
||||
local handle = repositories(root.path)
|
||||
if handle then
|
||||
for git_path in handle:lines() do
|
||||
local project = git_path:gsub("/%.git$", "")
|
||||
local relative = project:sub(#root.path + 2)
|
||||
table.insert(entries, {
|
||||
Text = project:match("([^/]+)$"),
|
||||
Subtext = root.scope .. " · " .. relative,
|
||||
Value = project,
|
||||
Keywords = { root.scope, relative, project },
|
||||
})
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
end
|
||||
|
||||
return entries
|
||||
end
|
||||
'';
|
||||
|
||||
programs.walker = {
|
||||
enable = true;
|
||||
runAsService = true;
|
||||
|
||||
config.theme = "constellation";
|
||||
themes.constellation = {
|
||||
layouts.layout =
|
||||
lib.replaceStrings
|
||||
[ ''<property name="height-request">570</property>'' ]
|
||||
[ ''<property name="height-request">460</property>'' ]
|
||||
(builtins.readFile "${inputs.walker}/resources/themes/default/layout.xml");
|
||||
style = lib.mkBefore (
|
||||
builtins.readFile "${inputs.walker}/resources/themes/default/style.css"
|
||||
+ ''
|
||||
.box-wrapper {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.search-container, .item-box, .preview { border-radius: 6px; }
|
||||
.input { padding: 8px 10px; }
|
||||
.item-box { padding: 5px 8px; }
|
||||
.large-icons { -gtk-icon-size: 24px; }
|
||||
.item-image-text { font-size: 24px; }
|
||||
.item-subtext { opacity: 0.75; }
|
||||
.item-quick-activation { padding: 3px 5px; }
|
||||
.keybinds { padding-top: 6px; }
|
||||
.keybind-bind { opacity: 0.65; }
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
config.providers = {
|
||||
default = walkerDefaults.providers.default ++ [ "menus:projects" ];
|
||||
prefixes = walkerDefaults.providers.prefixes ++ [
|
||||
{
|
||||
provider = "bitwarden";
|
||||
prefix = "?";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
{ config, inputs, ... }:
|
||||
{
|
||||
flake.modules.nixos.walker = {
|
||||
home-manager.sharedModules = [ config.flake.modules.homeManager.walker ];
|
||||
|
||||
nix.settings = {
|
||||
extra-substituters = [
|
||||
"https://walker.cachix.org"
|
||||
"https://walker-git.cachix.org"
|
||||
];
|
||||
extra-trusted-public-keys = [
|
||||
"walker.cachix.org-1:fG8q+uAaMqhsMxWjwvk0IMb4mFPFLqHjuvfwQxE4oJM="
|
||||
"walker-git.cachix.org-1:vmC0ocfPWh0S/vRAQGtChuiZBTAe4wiKDeyyXM0/7pM="
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.walker =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
elephantPackages = inputs.walker.inputs.elephant.packages.${pkgs.stdenv.hostPlatform.system};
|
||||
# Upstream initializes rbw at startup. Drop this patch when it supports lazy vault access.
|
||||
providers = elephantPackages.elephant-providers.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [ ./demand-driven-vault.patch ];
|
||||
});
|
||||
elephant = elephantPackages.elephant-with-providers.overrideAttrs (old: {
|
||||
installPhase =
|
||||
builtins.replaceStrings [ "${elephantPackages.elephant-providers}" ] [ "${providers}" ]
|
||||
old.installPhase;
|
||||
});
|
||||
walkerDefaults = builtins.fromTOML (builtins.readFile "${inputs.walker}/resources/config.toml");
|
||||
|
||||
launcher = pkgs.writeShellApplication {
|
||||
name = "walker-launcher";
|
||||
runtimeInputs = [ pkgs.libressl.nc ];
|
||||
text = ''
|
||||
exec nc -U "$XDG_RUNTIME_DIR/walker/walker.sock"
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ inputs.walker.homeManagerModules.default ];
|
||||
|
||||
programs.elephant.package = elephant;
|
||||
|
||||
desktop.commands.launcher = [ (lib.getExe launcher) ];
|
||||
programs.walker = {
|
||||
enable = true;
|
||||
runAsService = true;
|
||||
|
||||
config.theme = "desktop";
|
||||
themes.desktop = {
|
||||
layouts.layout =
|
||||
lib.replaceStrings
|
||||
[ ''<property name="height-request">570</property>'' ]
|
||||
[ ''<property name="height-request">460</property>'' ]
|
||||
(builtins.readFile "${inputs.walker}/resources/themes/default/layout.xml");
|
||||
style = lib.mkBefore (
|
||||
builtins.readFile "${inputs.walker}/resources/themes/default/style.css"
|
||||
+ ''
|
||||
.box-wrapper {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.search-container, .item-box, .preview { border-radius: 6px; }
|
||||
.input { padding: 8px 10px; }
|
||||
.item-box { padding: 5px 8px; }
|
||||
.large-icons { -gtk-icon-size: 24px; }
|
||||
.item-image-text { font-size: 24px; }
|
||||
.item-subtext { opacity: 0.75; }
|
||||
.item-quick-activation { padding: 3px 5px; }
|
||||
.keybinds { padding-top: 6px; }
|
||||
.keybind-bind { opacity: 0.65; }
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
config.providers = {
|
||||
default = walkerDefaults.providers.default;
|
||||
prefixes = walkerDefaults.providers.prefixes ++ [
|
||||
{
|
||||
provider = "bitwarden";
|
||||
prefix = "?";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
--- a/internal/providers/bitwarden/setup.go
|
||||
+++ b/internal/providers/bitwarden/setup.go
|
||||
@@ -34,7 +34,7 @@
|
||||
func Setup() {
|
||||
LoadConfig()
|
||||
|
||||
- initItems()
|
||||
+ // Vault access is initiated by an explicit query, never daemon startup.
|
||||
}
|
||||
|
||||
func LoadConfig() {
|
||||
--- a/internal/providers/bitwarden/query.go
|
||||
+++ b/internal/providers/bitwarden/query.go
|
||||
@@ -7,6 +7,7 @@
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
+ "sync"
|
||||
"time"
|
||||
|
||||
"github.com/abenz1267/elephant/v2/pkg/common"
|
||||
@@ -20,7 +21,24 @@
|
||||
Folder string `json:"folder"`
|
||||
}
|
||||
|
||||
+var itemsMu sync.Mutex
|
||||
+
|
||||
func initItems() {
|
||||
+ itemsMu.Lock()
|
||||
+ defer itemsMu.Unlock()
|
||||
+ initItemsLocked()
|
||||
+}
|
||||
+
|
||||
+func items() []RbwItem {
|
||||
+ itemsMu.Lock()
|
||||
+ defer itemsMu.Unlock()
|
||||
+ if cachedItems == nil {
|
||||
+ initItemsLocked()
|
||||
+ }
|
||||
+ return cachedItems
|
||||
+}
|
||||
+
|
||||
+func initItemsLocked() {
|
||||
cachedItems = nil
|
||||
cmd := exec.Command("rbw", "list", "--raw")
|
||||
output, err := cmd.CombinedOutput()
|
||||
@@ -40,7 +58,7 @@
|
||||
|
||||
entries := []*pb.QueryResponse_Item{}
|
||||
|
||||
- for k, v := range cachedItems {
|
||||
+ for k, v := range items() {
|
||||
var subtexts []string
|
||||
actions := []string{ActionCopyPassword, ActionCopyTotp, ActionSyncVault}
|
||||
if config.AutoTypeSupport {
|
||||
@@ -0,0 +1,115 @@
|
||||
# Company environment
|
||||
|
||||
This capability owns the company-root tools, human workbench environment and
|
||||
persistent read-only company mounts. Garden packaging and company certificates
|
||||
remain in `../yookr-dev/`. Requests, queries, analysis, `uv.lock`, content and
|
||||
runtime state stay in `~/yookr/workbench`.
|
||||
|
||||
Home Manager installs `devenv.nix` and `devenv.yaml` in `~/yookr/workbench`
|
||||
(human credentials), selecting the system's exact nixpkgs store path. The work
|
||||
profile also installs the tools for use at the company root without activation. The devenv CLI
|
||||
uses its own upstream dependency closure; this avoids rebuilding its dependencies
|
||||
against an unsupported pin. Its version is locked by the repository flake.
|
||||
|
||||
## Activation and trust
|
||||
|
||||
After reviewing and activating the configuration on each workstation:
|
||||
|
||||
1. Remove the obsolete `~/yookr/.envrc` and `~/yookr/workbench/.envrc`. Direnv is
|
||||
no longer installed or hooked. Product/personal `.envrc` files remain yours to
|
||||
migrate; this pass does not convert them.
|
||||
2. Run `devenv allow` in `~/yookr/workbench` only. Open a new terminal
|
||||
for the native Zsh hook. Reload/file watching uses upstream defaults.
|
||||
3. `devenv.lock` and `.devenv/` are local generated state, not new configuration
|
||||
owners. Do not commit them into the Nix repository. Retain product locks.
|
||||
4. The human workbench attempts company, live and test resolution independently.
|
||||
`workbench-reload` retries; `workbench-reload --tools-only` clears the variables
|
||||
owned by this loader. Existing processes/editors keep their original environment.
|
||||
|
||||
For a clean, credential-free check use the flake's tools-only `#workbench` shell
|
||||
from a clean parent environment. For native devenv without credential loading,
|
||||
set `WORKBENCH_TOOLS_ONLY=1` before `devenv shell`. Neither method removes arbitrary
|
||||
credentials already inherited from its parent; they are not isolation mechanisms.
|
||||
|
||||
The company root intentionally has no generated devenv environment pending review:
|
||||
upstream's hook does nothing while another devenv shell is active, so an ancestor
|
||||
shell would prevent automatic workbench entry and interfere with project environments.
|
||||
The recommendation is installed tools at the company root, native activation at the
|
||||
workbench, and product-owned environments below `projects/`.
|
||||
|
||||
## Credential provisioning (user-run only)
|
||||
|
||||
The encrypted company document belongs at `modules/secrets/company.yaml` in this
|
||||
repository. It is deliberately absent until you migrate it. Reuse the existing
|
||||
machine age identities; never copy private keys into Git. On each workstation,
|
||||
obtain its public recipient with `age-keygen -y /var/lib/sops/keys.txt`.
|
||||
|
||||
On the machine holding the old credentials, run from this repository:
|
||||
|
||||
```console
|
||||
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" python3 modules/capabilities/workbench/migrate-credentials.py \
|
||||
"$HOME/.config/yookr/credentials.yaml" modules/secrets/company.yaml \
|
||||
--age 'POLARIS_PUBLIC_AGE_RECIPIENT,ZENITH_PUBLIC_AGE_RECIPIENT'
|
||||
```
|
||||
|
||||
The helper decrypts only in memory, preserves the old file, and refuses to overwrite
|
||||
an existing destination. The new document contains root-level company login and
|
||||
live database URLs for SecretSpec, plus the existing `leafcloud` map for mounts.
|
||||
MySQL certificate verification behavior is preserved. Test credentials are not
|
||||
copied into the new document. Review the encrypted result before a local commit;
|
||||
pushing remains a separate decision. Other machines receive ciphertext through the
|
||||
existing repository. Edit it with `sops edit modules/secrets/company.yaml` using the
|
||||
machine's normal `SOPS_AGE_KEY_FILE=/var/lib/sops/keys.txt`.
|
||||
|
||||
### Test cluster: mapping and login still pending
|
||||
|
||||
No cluster query is needed to build this repository. Before enabling test loading,
|
||||
settle the explicit test context and existing application Secret/namespace/key
|
||||
mappings. The loader expects a `~/.config/workbench/test-secrets.toml` manifest with
|
||||
profile `test`, using only explicit `k8s+secret://SECRET@NAMESPACE` providers and
|
||||
`ref.item` for existing keys. Required exports are:
|
||||
|
||||
- `YOOKR_MYSQL_TEST_DATABASE_URL`, `YOOKR_MONGODB_TEST_DATABASE_URL`
|
||||
- `RABBITMQADMIN_USERNAME`, `RABBITMQADMIN_PASSWORD`
|
||||
|
||||
If applications store URL components separately, the manifest/composition must be
|
||||
adapted after confirming their keys; do not invent field names or change URL escaping.
|
||||
This manifest is a provisioning boundary pending review, not a second global secret store.
|
||||
|
||||
Test loading also requires an explicitly prepared, noninteractive kubeconfig at
|
||||
`$XDG_RUNTIME_DIR/workbench/test-kubeconfig`. No generic login helper is provided:
|
||||
the actual context and exec plugin must be inspected first, then its native or
|
||||
provider-specific cached login flow selected. Do not reimplement Kubernetes' exec
|
||||
credential protocol for an unknown plugin. Until both runtime files exist, test
|
||||
reports unavailable. Shell entry never invokes authentication or opens a login popup.
|
||||
|
||||
SecretSpec exports are captured in memory with stdin closed and a timeout. Errors
|
||||
report only the unavailable source, not provider output or secret values. Sources
|
||||
load independently; unavailable sources clear their old variables. Test never falls
|
||||
back to SOPS, and live never depends on Kubernetes. Native devenv's all-at-once secret
|
||||
resolution is intentionally not used: it would couple their failure behavior.
|
||||
|
||||
## Mounts
|
||||
|
||||
`company-obs.service` and `company-sharepoint.service` are user services on both
|
||||
workstations. Mounts live at `~/yookr/workbench/{obs,sharepoint}`. They are read-only,
|
||||
use `--vfs-cache-mode off`, and retry failures every minute. No background content
|
||||
mirror remains. The existing local SharePoint mirror is preserved, not deleted.
|
||||
Small memory buffers and filesystem metadata are still used.
|
||||
|
||||
OBS uses the company's encrypted `leafcloud` map and machine age key. SharePoint
|
||||
uses the existing native rclone remotes `company-sharepoint` and
|
||||
`company-sharepoint-projecten` in `~/.config/rclone/rclone.conf`; configure/login to
|
||||
these on each workstation with rclone. Tokens remain application-owned outside Git.
|
||||
Before first activation, unmount any old workbench-entry OBS mount. Do not empty or
|
||||
replace nonempty mount directories: move any authored content deliberately first.
|
||||
|
||||
```console
|
||||
systemctl --user restart company-obs company-sharepoint
|
||||
systemctl --user status company-obs company-sharepoint
|
||||
```
|
||||
|
||||
Do not invoke the old workspace `just mount-obs` helper once systemd owns the mount.
|
||||
The old helper/recipes can be removed after the new mounts are accepted in use.
|
||||
Shell exit does not unmount company files. All processes running as your user can
|
||||
read the mounts; these services do not isolate agents.
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
pkgs,
|
||||
garden ? import ../../yookr-dev/_support/packages.nix { inherit pkgs; },
|
||||
}:
|
||||
{
|
||||
packages = [
|
||||
garden.gardenctl
|
||||
garden.gardenlogin
|
||||
]
|
||||
++ (with pkgs; [
|
||||
k9s
|
||||
kubectl
|
||||
kubelogin-oidc
|
||||
jq
|
||||
just
|
||||
python314
|
||||
rclone
|
||||
sops
|
||||
secretspec
|
||||
util-linux
|
||||
uv
|
||||
]);
|
||||
env = {
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc.lib
|
||||
pkgs.zlib
|
||||
];
|
||||
UV_PYTHON = "${pkgs.python314}/bin/python";
|
||||
UV_PYTHON_DOWNLOADS = "never";
|
||||
RABBITMQADMIN_TARGET_HOST = "test.rabbitmq.yookr.org";
|
||||
RABBITMQADMIN_TARGET_PORT = "443";
|
||||
RABBITMQADMIN_USE_TLS = "true";
|
||||
RABBITMQADMIN_NON_INTERACTIVE_MODE = "true";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
flake.modules.homeManager.workbench =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
home = config.home.homeDirectory;
|
||||
root = "${home}/yookr/workbench";
|
||||
companySecrets = "${home}/.config/nixos/modules/secrets/company.yaml";
|
||||
environmentInputs = builtins.toJSON {
|
||||
inputs = {
|
||||
nixpkgs.url = "path:${inputs.nixpkgs}";
|
||||
git-hooks.url = "path:${inputs.devenv.inputs.git-hooks}";
|
||||
};
|
||||
};
|
||||
secretsManifest = (pkgs.formats.toml { }).generate "workbench-secrets.toml" {
|
||||
project = {
|
||||
name = "workbench";
|
||||
revision = "1";
|
||||
};
|
||||
providers.company = "sops://${companySecrets}?age_key_file=/var/lib/sops/keys.txt";
|
||||
profiles =
|
||||
lib.mapAttrs
|
||||
(
|
||||
_: names:
|
||||
lib.genAttrs names (name: {
|
||||
description = name;
|
||||
ref.item = name;
|
||||
providers = [ "company" ];
|
||||
})
|
||||
)
|
||||
{
|
||||
company = [
|
||||
"email"
|
||||
"password"
|
||||
];
|
||||
live = [
|
||||
"YOOKR_MYSQL_LIVE_DATABASE_URL"
|
||||
"YOOKR_MONGODB_LIVE_DATABASE_URL"
|
||||
];
|
||||
};
|
||||
};
|
||||
mountObs = pkgs.writeShellApplication {
|
||||
name = "mount-company-obs";
|
||||
runtimeInputs = [
|
||||
pkgs.python3
|
||||
pkgs.rclone
|
||||
pkgs.sops
|
||||
];
|
||||
text = ''
|
||||
export PATH=/run/wrappers/bin:"$PATH"
|
||||
exec python3 ${./mount-obs.py} ${lib.escapeShellArg companySecrets} ${lib.escapeShellArg "${root}/obs"}
|
||||
'';
|
||||
};
|
||||
mountSharepoint = pkgs.writeShellApplication {
|
||||
name = "mount-company-sharepoint";
|
||||
runtimeInputs = [
|
||||
pkgs.rclone
|
||||
pkgs.coreutils
|
||||
];
|
||||
text = ''
|
||||
export PATH=/run/wrappers/bin:"$PATH"
|
||||
mkdir -p ${lib.escapeShellArg "${root}/sharepoint"}
|
||||
export RCLONE_CONFIG_SHAREPOINT_TYPE=combine
|
||||
export RCLONE_CONFIG_SHAREPOINT_UPSTREAMS='Algemeen=company-sharepoint: Projecten=company-sharepoint-projecten:'
|
||||
exec rclone mount sharepoint: ${lib.escapeShellArg "${root}/sharepoint"} \
|
||||
--config ${lib.escapeShellArg "${config.xdg.configHome}/rclone/rclone.conf"} \
|
||||
--read-only --vfs-cache-mode off --buffer-size 8M
|
||||
'';
|
||||
};
|
||||
mountService = name: command: {
|
||||
Unit = {
|
||||
Description = "Read-only company ${name} mount";
|
||||
After = [ "network-online.target" ];
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
ExecStart = lib.getExe command;
|
||||
ExecStop = "/run/wrappers/bin/fusermount3 -u ${root}/${name}";
|
||||
Environment = "SOPS_AGE_KEY_FILE=/var/lib/sops/keys.txt";
|
||||
UMask = "0077";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 60;
|
||||
};
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
# devenv already installs its bundled SecretSpec CLI in the workstation profile.
|
||||
home.packages =
|
||||
lib.remove pkgs.secretspec (import ./_support/tools.nix { inherit pkgs; }).packages;
|
||||
programs.zsh.initContent = lib.mkAfter ''
|
||||
workbench-reload() {
|
||||
if [[ "''${DEVENV_ROOT:-}" != ${lib.escapeShellArg root} ]]; then
|
||||
print -u2 'Enter the workbench devenv shell before loading credentials.'
|
||||
return 1
|
||||
fi
|
||||
local exports
|
||||
exports="$(${pkgs.python3}/bin/python3 ${./load-secrets.py} ${secretsManifest} "$@")" || return
|
||||
eval "$exports"
|
||||
}
|
||||
'';
|
||||
|
||||
home.file."yookr/workbench/devenv.yaml".text = environmentInputs;
|
||||
home.file."yookr/workbench/devenv.nix".text = ''
|
||||
{ pkgs, ... }:
|
||||
let tools = import ${./_support/tools.nix} {
|
||||
inherit pkgs;
|
||||
garden = import ${../yookr-dev/_support/packages.nix} { inherit pkgs; };
|
||||
};
|
||||
in {
|
||||
inherit (tools) packages env;
|
||||
enterShell = ${lib.strings.escapeNixString ''
|
||||
if [ "''${WORKBENCH_TOOLS_ONLY:-0}" = 1 ]; then
|
||||
eval "$(python3 ${./load-secrets.py} ${secretsManifest} --tools-only)"
|
||||
else
|
||||
eval "$(python3 ${./load-secrets.py} ${secretsManifest})"
|
||||
fi
|
||||
''};
|
||||
}
|
||||
'';
|
||||
systemd.user.services = {
|
||||
company-obs = mountService "obs" mountObs;
|
||||
company-sharepoint = mountService "sharepoint" mountSharepoint;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tools = import ./_support/tools.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
# A credential-free build target for the same tools used by native devenv.
|
||||
devShells.workbench = pkgs.mkShell (tools.env // { inherit (tools) packages; });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Emit shell exports for independent sources; never print provider errors or values to stderr."""
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('manifest', type=Path)
|
||||
parser.add_argument('--tools-only', action='store_true')
|
||||
args = parser.parse_args()
|
||||
groups = {
|
||||
'company': ['email', 'password'],
|
||||
'live': ['YOOKR_MYSQL_LIVE_DATABASE_URL', 'YOOKR_MONGODB_LIVE_DATABASE_URL'],
|
||||
'test': ['YOOKR_MYSQL_TEST_DATABASE_URL', 'YOOKR_MONGODB_TEST_DATABASE_URL',
|
||||
'RABBITMQADMIN_USERNAME', 'RABBITMQADMIN_PASSWORD'],
|
||||
}
|
||||
|
||||
def load(group):
|
||||
env = os.environ.copy()
|
||||
# Explicit manifests/profiles/providers must not inherit a user's override.
|
||||
for name in ('SECRETSPEC_PROVIDER', 'SECRETSPEC_PROFILE', 'SECRETSPEC_SCOPE'):
|
||||
env.pop(name, None)
|
||||
if group == 'test':
|
||||
# A dedicated, explicitly provisioned test kubeconfig is mandatory.
|
||||
kubeconfig = Path(os.environ.get('XDG_RUNTIME_DIR', '/nonexistent')) / 'workbench/test-kubeconfig'
|
||||
manifest = Path.home() / '.config/workbench/test-secrets.toml'
|
||||
if not kubeconfig.is_file() or not manifest.is_file():
|
||||
return group, None
|
||||
env['KUBECONFIG'] = str(kubeconfig)
|
||||
else:
|
||||
manifest = args.manifest
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['secretspec', '--file', str(manifest), 'export', '--profile', group, '--format', 'json'],
|
||||
env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||
timeout=20, check=True, text=True)
|
||||
values = json.loads(result.stdout)
|
||||
if not all(isinstance(values.get(key), str) and values[key] for key in groups[group]):
|
||||
return group, None
|
||||
return group, {key: values[key] for key in groups[group]}
|
||||
except (OSError, ValueError, subprocess.SubprocessError):
|
||||
return group, None
|
||||
|
||||
# Clear inherited target values before attempting a reload, including on failure.
|
||||
for keys in groups.values():
|
||||
for key in keys:
|
||||
print('unset ' + key)
|
||||
if args.tools_only:
|
||||
for group in groups:
|
||||
print('export WORKBENCH_' + group.upper() + '_STATUS=disabled')
|
||||
else:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
|
||||
for group, values in executor.map(load, groups):
|
||||
status = 'ready' if values is not None else 'unavailable'
|
||||
print('export WORKBENCH_' + group.upper() + '_STATUS=' + status)
|
||||
if values:
|
||||
for key, value in values.items():
|
||||
print('export ' + key + '=' + shlex.quote(value))
|
||||
print(f'workbench: {group} {status}', file=sys.stderr)
|
||||
print('Retry after provisioning/login: workbench-reload. Clean tools: workbench-reload --tools-only.', file=sys.stderr)
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""User-run migration: decrypt in memory, reshape, encrypt for both machine keys."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from urllib.parse import quote
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('source', type=Path)
|
||||
parser.add_argument('destination', type=Path)
|
||||
parser.add_argument('--age', required=True, help='Comma-separated public age recipients for both machines')
|
||||
args = parser.parse_args()
|
||||
if args.destination.exists():
|
||||
parser.error('Destination exists; refusing to overwrite it')
|
||||
|
||||
def url(scheme, database):
|
||||
authority = quote(database['username'], safe='') + ':' + quote(database['password'], safe='')
|
||||
authority += '@' + database['host']
|
||||
if database.get('port'):
|
||||
authority += ':' + str(database['port'])
|
||||
return scheme + '://' + authority
|
||||
|
||||
try:
|
||||
old = json.loads(subprocess.check_output(
|
||||
['sops', 'decrypt', '--output-type', 'json', str(args.source)], stderr=subprocess.DEVNULL))
|
||||
new = {
|
||||
'email': old['company_login']['email'],
|
||||
'password': old['company_login']['password'],
|
||||
'YOOKR_MYSQL_LIVE_DATABASE_URL': url('mariadb', old['databases']['mysql']['live']) + '?ssl-verify-server-cert=0',
|
||||
'YOOKR_MONGODB_LIVE_DATABASE_URL': url('mongodb+srv', old['databases']['mongodb']['live']),
|
||||
'leafcloud': old['leafcloud'],
|
||||
}
|
||||
encrypted = subprocess.check_output(
|
||||
['sops', 'encrypt', '--input-type', 'json', '--output-type', 'yaml',
|
||||
'--age', args.age, '/dev/stdin'], input=json.dumps(new).encode(), stderr=subprocess.DEVNULL)
|
||||
os.umask(0o077)
|
||||
with args.destination.open('xb') as output:
|
||||
output.write(encrypted)
|
||||
except (subprocess.CalledProcessError, KeyError, ValueError, OSError):
|
||||
raise SystemExit('Migration failed; no plaintext was written. Check source schema, keys and recipients.')
|
||||
print('Encrypted company document created. Original credentials were preserved.')
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Foreground read-only OBS mount; systemd owns lifecycle and retries."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
projects = json.loads(subprocess.check_output(
|
||||
['sops', 'decrypt', '--extract', '["leafcloud"]', '--output-type', 'json', sys.argv[1]],
|
||||
stderr=subprocess.DEVNULL))
|
||||
if not projects:
|
||||
raise ValueError('No projects')
|
||||
env = os.environ.copy()
|
||||
upstreams = []
|
||||
for index, (project, credentials) in enumerate(projects.items()):
|
||||
if not re.fullmatch(r'[A-Za-z0-9_-]+', project):
|
||||
raise ValueError('Invalid project name')
|
||||
remote = f'obs_project_{index}'
|
||||
options = dict(type='s3', provider='Other', region='europe-nl-ams1',
|
||||
endpoint='https://leafcloud.store', force_path_style='true',
|
||||
access_key_id=credentials['access_key_id'],
|
||||
secret_access_key=credentials['secret_access_key'])
|
||||
env.update({f'RCLONE_CONFIG_{remote.upper()}_{key.upper()}': str(value)
|
||||
for key, value in options.items()})
|
||||
upstreams.append(f'{project}={remote}:')
|
||||
env['RCLONE_CONFIG_OBS_TYPE'] = 'combine'
|
||||
env['RCLONE_CONFIG_OBS_UPSTREAMS'] = ' '.join(upstreams)
|
||||
mount = Path(sys.argv[2])
|
||||
mount.mkdir(parents=True, exist_ok=True)
|
||||
os.execvpe('rclone', ['rclone', 'mount', 'obs:', str(mount), '--config', '/dev/null',
|
||||
'--read-only', '--vfs-cache-mode', 'off', '--buffer-size', '8M'], env)
|
||||
except (subprocess.CalledProcessError, KeyError, ValueError, OSError):
|
||||
raise SystemExit('OBS mount unavailable: check company SOPS provisioning and the machine age key.')
|
||||
File renamed without changes.
@@ -2,9 +2,10 @@
|
||||
flake.modules.homeManager.yookr-tools =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tools = import ./yookr-dev/_packages.nix { inherit pkgs; };
|
||||
tools = import ./_support/packages.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
programs.zsh.initContent = "source <(gardenctl rc zsh --no-completion)";
|
||||
home.packages = [
|
||||
tools.gardenctl
|
||||
tools.gardenlogin
|
||||
@@ -13,8 +14,8 @@
|
||||
|
||||
flake.modules.nixos.yookr-dev = {
|
||||
security.pki.certificateFiles = [
|
||||
./yookr-dev/zenith-rootCA.pem
|
||||
./yookr-dev/polaris-rootCA.pem
|
||||
./zenith-rootCA.pem
|
||||
./polaris-rootCA.pem
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tools = import ./_packages.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
# Tooling only: no credential loading, mounts, or Python dependency installs.
|
||||
devShells.workbench = pkgs.mkShell {
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc.lib
|
||||
pkgs.zlib
|
||||
];
|
||||
UV_PYTHON = "${pkgs.python314}/bin/python";
|
||||
UV_PYTHON_DOWNLOADS = "never";
|
||||
packages = [
|
||||
tools.gardenctl
|
||||
tools.gardenlogin
|
||||
]
|
||||
++ (with pkgs; [
|
||||
k9s
|
||||
kubectl
|
||||
kubelogin-oidc
|
||||
jq
|
||||
just
|
||||
python314
|
||||
rclone
|
||||
sops
|
||||
util-linux
|
||||
uv
|
||||
]);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -37,16 +37,13 @@ in
|
||||
flake.modules.nixos.zenith = {
|
||||
imports = [
|
||||
nixosModules.workstation-base
|
||||
nixosModules.openssh
|
||||
nixosModules.laptop-power
|
||||
nixosModules.sharepoint-reference
|
||||
./_hardware.nix
|
||||
inputs.nixos-hardware.nixosModules.lenovo-yoga-7-14ARH7-amdgpu
|
||||
];
|
||||
|
||||
services.fwupd.enable = true;
|
||||
services.teamviewer.enable = true;
|
||||
# Temporary Xorg session for remote support.
|
||||
# services.xserver.enable = true;
|
||||
# services.xserver.desktopManager.xfce.enable = true;
|
||||
services.openssh.openFirewall = false;
|
||||
};
|
||||
}
|
||||
@@ -13,6 +13,7 @@ in
|
||||
nixosModules.facts
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
nixosModules.nix
|
||||
nixosModules.tailscale
|
||||
nixosModules.locale
|
||||
nixosModules.sops
|
||||
];
|
||||
|
||||
@@ -16,6 +16,7 @@ in
|
||||
flake.modules.homeManager.work = {
|
||||
imports = [
|
||||
homeModules.yookr-tools
|
||||
homeModules.workbench
|
||||
homeModules.work-git
|
||||
];
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ in
|
||||
nixosModules.adb
|
||||
nixosModules.audio
|
||||
nixosModules.bluetooth
|
||||
nixosModules.file-management
|
||||
nixosModules.flatpak
|
||||
nixosModules.fonts
|
||||
nixosModules.network-manager
|
||||
@@ -49,7 +50,7 @@ in
|
||||
homeModules.xdg
|
||||
homeModules.workstation-apps
|
||||
homeModules.vivaldi
|
||||
homeModules.nautilus
|
||||
homeModules.file-management
|
||||
homeModules.imv
|
||||
homeModules.sioyek
|
||||
homeModules.discord
|
||||
|
||||
Reference in new issue
Block a user