Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,19 @@ nvim-plugins-install: ## Download/build missing Neovim native plugin binaries (f
switch: nix-switch services nvim-plugins-install dotagents-sync ## Apply Nix configuration, restart services, and sync plugins.

.PHONY: clean
clean: ## Clean up old Nix generations and garbage collect.
clean: ## Clean up Nix generations older than 30 days and garbage collect.
@echo "🧹 Cleaning up old generations and garbage collecting..."
@$(SUDO) nix-collect-garbage -d
@$(SUDO) nix-collect-garbage --delete-older-than 30d
@nix store optimise

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The nix store optimise command should be run with $(SUDO) to ensure it has the necessary permissions to hardlink files across the entire Nix store. Since the preceding nix-collect-garbage command uses $(SUDO), it is consistent to use it here as well for a full system cleanup.

	@$(SUDO) nix store optimise

@echo "✅ Cleanup complete"

.PHONY: clean-all
clean-all: ## Delete ALL old Nix generations and garbage collect (nuclear).
@echo "🧹 Removing all old generations..."
@$(SUDO) nix-collect-garbage -d
@nix store optimise

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the clean target, nix store optimise in clean-all should use $(SUDO) to effectively optimize the entire store.

	@$(SUDO) nix store optimise

@echo "✅ Full cleanup complete"

.PHONY: reset
reset: git-submodule-sync ## Reset git status to clean (restore all changes and reinitialize submodules).
@echo "🔄 Resetting git status to clean..."
Expand Down
13 changes: 7 additions & 6 deletions config/mempalace/default.nix
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{config, ...}: let
{ config, ... }:
let
homeDir = config.home.homeDirectory;
configText = builtins.replaceStrings
["__HOME_DIR__"]
[homeDir]
(builtins.readFile ./config.json);
in {
configText = builtins.replaceStrings [ "__HOME_DIR__" ] [ homeDir ] (
builtins.readFile ./config.json
);
in
{
home.file.".mempalace/config.json" = {
text = configText;
force = true;
Expand Down
2 changes: 2 additions & 0 deletions home-manager/nix/nix.nix
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
};
gc = {
automatic = true;
frequency = "weekly";
options = "--delete-older-than 30d";
};
};
}
2 changes: 2 additions & 0 deletions nix-darwin/services/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
pkgs,
}:
let
nixGcModule = import ./nix-gc { inherit lib isRunner; };
pmsetBatteryPolicyModule = import ./pmset-battery-policy { inherit lib isRunner pkgs; };
in
[
nixGcModule
pmsetBatteryPolicyModule
]
22 changes: 22 additions & 0 deletions nix-darwin/services/nix-gc/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
lib,
isRunner,
...
}:
lib.mkIf (!isRunner) {
launchd.daemons."com.shunkakinoki.nix-gc" = {
script = ''
/nix/var/nix/profiles/default/bin/nix-collect-garbage --delete-older-than 30d
'';
serviceConfig = {
RunAtLoad = false;
StartCalendarInterval = [
{
Weekday = 0;
Hour = 3;
Minute = 0;
}
];
};
};
Comment on lines +7 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of manually defining a launchd daemon, it is more idiomatic to use the built-in nix.gc module provided by nix-darwin. This simplifies the configuration, uses the configured Nix package automatically, and follows established patterns for managing garbage collection on macOS.

  nix.gc = {
    automatic = true;
    interval = {
      Weekday = 0;
      Hour = 3;
      Minute = 0;
    };
    options = "--delete-older-than 30d";
  };

}
25 changes: 11 additions & 14 deletions spec/clipboard_copy_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ setup() {
mock_bin_setup xclip
# Restrict PATH so higher-priority backends (real xclip, pbcopy, etc.) are hidden
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
unset WAYLAND_DISPLAY
}
Expand All @@ -64,7 +64,7 @@ setup() {
mock_bin_setup xsel
# Restrict PATH so higher-priority backends (real xclip, pbcopy, etc.) are hidden
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
unset WAYLAND_DISPLAY
}
Expand All @@ -86,15 +86,13 @@ setup() {
MOCK_ORIGINAL_PATH="${PATH:-}"
MOCK_ORIGINAL_WAYLAND="${WAYLAND_DISPLAY:-}"
MOCK_ORIGINAL_SSH_TTY="${SSH_TTY:-}"
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
local base64_dir
base64_dir="$(dirname "$(readlink -f "$(command -v base64)")")"
local tr_dir
tr_dir="$(dirname "$(readlink -f "$(command -v tr)")")"
local printf_dir
printf_dir="$(dirname "$(readlink -f "$(command -v printf)")")"
export PATH="$MOCK_BIN:$bash_dir:$base64_dir:$tr_dir:$printf_dir"
# Symlink only the commands the script needs into MOCK_BIN to avoid
# leaking pbcopy/xclip/etc. from shared directories like /usr/bin
local cmd
for cmd in bash base64 tr printf; do
ln -sf "$(command -v "$cmd")" "$MOCK_BIN/$cmd"
done
export PATH="$MOCK_BIN"
unset WAYLAND_DISPLAY
export SSH_TTY=/dev/pts/0
export MOCK_BIN MOCK_ORIGINAL_PATH MOCK_ORIGINAL_WAYLAND MOCK_ORIGINAL_SSH_TTY
Expand Down Expand Up @@ -128,9 +126,8 @@ setup() {
MOCK_ORIGINAL_PATH="${PATH:-}"
MOCK_ORIGINAL_WAYLAND="${WAYLAND_DISPLAY:-}"
MOCK_ORIGINAL_SSH_TTY="${SSH_TTY:-}"
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
export PATH="$MOCK_BIN:$bash_dir"
ln -sf "$(command -v bash)" "$MOCK_BIN/bash"
export PATH="$MOCK_BIN"
unset WAYLAND_DISPLAY SSH_TTY TMUX
export MOCK_BIN MOCK_ORIGINAL_PATH MOCK_ORIGINAL_WAYLAND MOCK_ORIGINAL_SSH_TTY
}
Expand Down
8 changes: 4 additions & 4 deletions spec/clipboard_paste_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ printf 'hello'
EOF
chmod +x "$MOCK_BIN/wl-paste"
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
}
cleanup() {
Expand Down Expand Up @@ -73,7 +73,7 @@ printf 'hello'
EOF
chmod +x "$MOCK_BIN/xclip"
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
}
cleanup() {
Expand Down Expand Up @@ -103,7 +103,7 @@ printf 'hello'
EOF
chmod +x "$MOCK_BIN/xsel"
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
unset WAYLAND_DISPLAY
export MOCK_BIN MOCK_ORIGINAL_PATH MOCK_ORIGINAL_WAYLAND
Expand Down Expand Up @@ -132,7 +132,7 @@ setup() {
MOCK_ORIGINAL_PATH="${PATH:-}"
MOCK_ORIGINAL_WAYLAND="${WAYLAND_DISPLAY:-}"
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
unset WAYLAND_DISPLAY
export MOCK_BIN MOCK_ORIGINAL_PATH MOCK_ORIGINAL_WAYLAND
Expand Down
10 changes: 5 additions & 5 deletions spec/notify_local_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ setup() {
# Hide osascript so notify-send path is taken
# Use readlink -f to resolve to nix store path (avoids NixOS profile bin with all packages)
local bash_dir cat_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
cat_dir="$(dirname "$(readlink -f "$(command -v cat)")")"
bash_dir="$(resolve_cmd_dir bash)"
cat_dir="$(resolve_cmd_dir cat)"
export PATH="$MOCK_BIN:$bash_dir:$cat_dir"
}
cleanup() {
Expand All @@ -80,8 +80,8 @@ setup() {
# Hide osascript and notify-send so terminal-notifier path is taken
# Use readlink -f to resolve to nix store path (avoids NixOS profile bin with all packages)
local bash_dir cat_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
cat_dir="$(dirname "$(readlink -f "$(command -v cat)")")"
bash_dir="$(resolve_cmd_dir bash)"
cat_dir="$(resolve_cmd_dir cat)"
export PATH="$MOCK_BIN:$bash_dir:$cat_dir"
}
cleanup() {
Expand All @@ -105,7 +105,7 @@ setup() {
MOCK_ORIGINAL_PATH="${PATH:-}"
# Use readlink -f to resolve to nix store path (avoids NixOS profile bin with all packages)
local bash_dir
bash_dir="$(dirname "$(readlink -f "$(command -v bash)")")"
bash_dir="$(resolve_cmd_dir bash)"
export PATH="$MOCK_BIN:$bash_dir"
export MOCK_BIN MOCK_ORIGINAL_PATH
}
Expand Down
29 changes: 24 additions & 5 deletions spec/secure_dotenv_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,34 @@ setup() {
# Create a symlink (should be skipped)
ln -s "$TEST_HOME/.env" "$TEST_HOME/.env.link"

# Create a portable stat wrapper that accepts GNU stat -c '%a' syntax
STAT_WRAPPER="$TEST_HOME/portable-stat.sh"
cat >"$STAT_WRAPPER" <<'WRAPPER'
#!/usr/bin/env bash
if stat -c '%a' "$0" >/dev/null 2>&1; then
exec stat "$@"
fi
# macOS: translate -c '%a' to -f '%Lp'
args=()
while [[ $# -gt 0 ]]; do
case "$1" in
-c) shift; args+=(-f "$(echo "$1" | sed "s/%a/%Lp/g")"); shift ;;
*) args+=("$1"); shift ;;
esac
done
exec stat "${args[@]}"
WRAPPER
chmod +x "$STAT_WRAPPER"

# Preprocess the script, replacing placeholders with real commands
PROCESSED_SCRIPT="$TEST_HOME/secure-dotenv-test.sh"
sed \
-e "s|@find@|$(command -v find)|g" \
-e "s|@stat@|$(command -v stat)|g" \
-e "s|@stat@|$STAT_WRAPPER|g" \
"$SCRIPT" >"$PROCESSED_SCRIPT"
chmod +x "$PROCESSED_SCRIPT"

export TEST_HOME PROCESSED_SCRIPT
export TEST_HOME PROCESSED_SCRIPT STAT_WRAPPER
}
cleanup() {
rm -rf "$TEST_HOME"
Expand All @@ -84,17 +103,17 @@ The status should be success
End

It 'changes .env.local from 755 to 600'
When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && stat -c '%a' '$TEST_HOME/subdir/.env.local'"
When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && '$STAT_WRAPPER' -c '%a' '$TEST_HOME/subdir/.env.local'"
The output should equal '600'
End

It 'changes app.env from 644 to 600'
When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && stat -c '%a' '$TEST_HOME/app.env'"
When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && '$STAT_WRAPPER' -c '%a' '$TEST_HOME/app.env'"
The output should equal '600'
End

It 'leaves already-600 files unchanged'
When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && stat -c '%a' '$TEST_HOME/.env.safe'"
When run bash -c "bash '$PROCESSED_SCRIPT' '$TEST_HOME' && '$STAT_WRAPPER' -c '%a' '$TEST_HOME/.env.safe'"
The output should equal '600'
End

Expand Down
30 changes: 26 additions & 4 deletions spec/test_helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

set -euo pipefail

# Portable alternative to `dirname "$(readlink -f "$(command -v cmd)")"`.
# macOS BSD readlink lacks -f; this uses Python as fallback.
resolve_cmd_dir() {
local cmd_path
cmd_path="$(command -v "$1")"
if readlink -f "$cmd_path" >/dev/null 2>&1; then
dirname "$(readlink -f "$cmd_path")"
else
dirname "$(python3 -c "import os; print(os.path.realpath('$cmd_path'))")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using $cmd_path directly inside the Python command string can lead to syntax errors or unexpected behavior if the path contains single quotes. It is safer to pass the path as an argument to the Python script and access it via sys.argv.

Suggested change
dirname "$(python3 -c "import os; print(os.path.realpath('$cmd_path'))")"
dirname "$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$cmd_path")"

fi
}

# Portable file permission query (octal). macOS stat uses -f, GNU uses -c.
portable_stat_perms() {
if stat -c '%a' "$1" 2>/dev/null; then
return
fi
stat -f '%Lp' "$1"
}

mock_bin_setup() {
MOCK_BIN="$(mktemp -d)"
MOCK_LOG="$MOCK_BIN/mock.log"
Expand Down Expand Up @@ -48,10 +68,12 @@ nix_script_preprocess() {

# Replace @placeholder@ patterns with actual commands
sed \
-e 's|@aws@|aws|g' \
-e 's|@rsync@|rsync|g' \
-e 's|@bash@|bash|g' \
-e 's|@sed@|sed|g' \
-e "s|@aws@|aws|g" \
-e "s|@rsync@|rsync|g" \
-e "s|@bash@|bash|g" \
-e "s|@sed@|sed|g" \
-e "s|@find@|$(command -v find)|g" \
-e "s|@stat@|$(command -v stat)|g" \
"$script" >"$processed"

chmod +x "$processed"
Expand Down
Loading