mirror of
https://github.com/tw93/Mole.git
synced 2026-02-05 00:59:41 +00:00
Merge branch 'main' into dev
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<!-- {"contributors":["tw93","JackPhallen","amanthanvi","alexandear","rubnogueira","bsisduck","jimmystridh","fte-jjmartres","Else00","carolyn-sun","purofle","huyixi","bunizao","zeldrisho","yuzeguitarist","thijsvanhal","Sizk","ndbroadbent","MohammedEsafi","Schlauer-Hax","anonymort","khipu-luke","LmanTW","kwakubiney","kowyo","jalen0x","Hensell","ClathW","andmev"],"collaborators":["tw93"],"bots":["dependabot[bot]","github-actions[bot]"]} -->
|
||||
|
||||
<!-- {"contributors":["tw93","JackPhallen","amanthanvi","alexandear","rubnogueira","bsisduck","jimmystridh","fte-jjmartres","Else00","carolyn-sun","purofle","huyixi","bunizao","zeldrisho","yuzeguitarist","thijsvanhal","Sizk","ndbroadbent","MohammedEsafi","Schlauer-Hax","anonymort","khipu-luke","LmanTW","kwakubiney","kowyo","jalen0x","Hensell","ClathW","andmev"],"collaborators":["tw93"],"bots":["github-actions[bot]","dependabot[bot]"]} -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1000" height="548">
|
||||
<style>.contributor-link { cursor: pointer; }</style>
|
||||
<g transform="translate(45, 45)">
|
||||
@@ -320,4 +321,4 @@
|
||||
<text x="36" y="86" text-anchor="middle" alignment-baseline="middle" font-size="10" fill="#666" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif">andmev</text>
|
||||
</a>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 255 KiB |
133
bin/touchid.sh
133
bin/touchid.sh
@@ -14,10 +14,17 @@ LIB_DIR="$(cd "$SCRIPT_DIR/../lib" && pwd)"
|
||||
source "$LIB_DIR/core/common.sh"
|
||||
|
||||
readonly PAM_SUDO_FILE="${MOLE_PAM_SUDO_FILE:-/etc/pam.d/sudo}"
|
||||
readonly PAM_SUDO_LOCAL_FILE="${MOLE_PAM_SUDO_LOCAL_FILE:-/etc/pam.d/sudo_local}"
|
||||
readonly PAM_TID_LINE="auth sufficient pam_tid.so"
|
||||
|
||||
# Check if Touch ID is already configured
|
||||
is_touchid_configured() {
|
||||
# Check sudo_local first
|
||||
if [[ -f "$PAM_SUDO_LOCAL_FILE" ]]; then
|
||||
grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" 2> /dev/null && return 0
|
||||
fi
|
||||
|
||||
# Fallback to standard sudo file
|
||||
if [[ ! -f "$PAM_SUDO_FILE" ]]; then
|
||||
return 1
|
||||
fi
|
||||
@@ -74,7 +81,74 @@ enable_touchid() {
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check if already configured
|
||||
# Check if we should use sudo_local (Sonoma+)
|
||||
if grep -q "sudo_local" "$PAM_SUDO_FILE"; then
|
||||
# Check if already correctly configured in sudo_local
|
||||
if [[ -f "$PAM_SUDO_LOCAL_FILE" ]] && grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE"; then
|
||||
# It is in sudo_local, but let's check if it's ALSO in sudo (incomplete migration)
|
||||
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
|
||||
# Clean up legacy config
|
||||
temp_file=$(mktemp)
|
||||
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
|
||||
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
|
||||
echo -e "${GREEN}${ICON_SUCCESS} Cleanup legacy configuration${NC}"
|
||||
fi
|
||||
fi
|
||||
echo -e "${GREEN}${ICON_SUCCESS} Touch ID is already enabled${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Not configured in sudo_local yet.
|
||||
# Check if configured in sudo (Legacy)
|
||||
local is_legacy_configured=false
|
||||
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
|
||||
is_legacy_configured=true
|
||||
fi
|
||||
|
||||
# Function to write to sudo_local
|
||||
local write_success=false
|
||||
if [[ ! -f "$PAM_SUDO_LOCAL_FILE" ]]; then
|
||||
# Create the file
|
||||
echo "# sudo_local: local customizations for sudo" | sudo tee "$PAM_SUDO_LOCAL_FILE" > /dev/null
|
||||
echo "$PAM_TID_LINE" | sudo tee -a "$PAM_SUDO_LOCAL_FILE" > /dev/null
|
||||
sudo chmod 444 "$PAM_SUDO_LOCAL_FILE"
|
||||
sudo chown root:wheel "$PAM_SUDO_LOCAL_FILE"
|
||||
write_success=true
|
||||
else
|
||||
# Append if not present
|
||||
if ! grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE"; then
|
||||
temp_file=$(mktemp)
|
||||
cp "$PAM_SUDO_LOCAL_FILE" "$temp_file"
|
||||
echo "$PAM_TID_LINE" >> "$temp_file"
|
||||
sudo mv "$temp_file" "$PAM_SUDO_LOCAL_FILE"
|
||||
sudo chmod 444 "$PAM_SUDO_LOCAL_FILE"
|
||||
sudo chown root:wheel "$PAM_SUDO_LOCAL_FILE"
|
||||
write_success=true
|
||||
else
|
||||
write_success=true # Already there (should be caught by first check, but safe fallback)
|
||||
fi
|
||||
fi
|
||||
|
||||
if $write_success; then
|
||||
# If we migrated from legacy, clean it up now
|
||||
if $is_legacy_configured; then
|
||||
temp_file=$(mktemp)
|
||||
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
|
||||
sudo mv "$temp_file" "$PAM_SUDO_FILE"
|
||||
log_success "Touch ID migrated to sudo_local"
|
||||
else
|
||||
log_success "Touch ID enabled (via sudo_local) - try: sudo ls"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to write to sudo_local"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Legacy method: Modify sudo file directly
|
||||
|
||||
# Check if already configured (Legacy)
|
||||
if is_touchid_configured; then
|
||||
echo -e "${GREEN}${ICON_SUCCESS} Touch ID is already enabled${NC}"
|
||||
return 0
|
||||
@@ -129,26 +203,55 @@ disable_touchid() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create backup only if it doesn't exist
|
||||
if [[ ! -f "${PAM_SUDO_FILE}.mole-backup" ]]; then
|
||||
if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then
|
||||
log_error "Failed to create backup"
|
||||
# Check sudo_local first
|
||||
if [[ -f "$PAM_SUDO_LOCAL_FILE" ]] && grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE"; then
|
||||
# Remove from sudo_local
|
||||
temp_file=$(mktemp)
|
||||
grep -v "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" > "$temp_file"
|
||||
|
||||
if sudo mv "$temp_file" "$PAM_SUDO_LOCAL_FILE" 2> /dev/null; then
|
||||
# Since we modified sudo_local, we should also check if it's in sudo file (legacy cleanup)
|
||||
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
|
||||
temp_file=$(mktemp)
|
||||
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
|
||||
sudo mv "$temp_file" "$PAM_SUDO_FILE"
|
||||
fi
|
||||
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled (removed from sudo_local)${NC}"
|
||||
echo ""
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to disable Touch ID from sudo_local"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove pam_tid.so line
|
||||
temp_file=$(mktemp)
|
||||
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
|
||||
# Fallback to sudo file (legacy)
|
||||
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
|
||||
# Create backup only if it doesn't exist
|
||||
if [[ ! -f "${PAM_SUDO_FILE}.mole-backup" ]]; then
|
||||
if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then
|
||||
log_error "Failed to create backup"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
|
||||
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled${NC}"
|
||||
echo ""
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to disable Touch ID"
|
||||
return 1
|
||||
# Remove pam_tid.so line
|
||||
temp_file=$(mktemp)
|
||||
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
|
||||
|
||||
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
|
||||
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled${NC}"
|
||||
echo ""
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to disable Touch ID"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Should not reach here if is_touchid_configured was true
|
||||
log_error "Could not find Touch ID configuration to disable"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Interactive menu
|
||||
|
||||
@@ -30,6 +30,7 @@ func snapshotFromModel(m model) historyEntry {
|
||||
Entries: cloneDirEntries(m.entries),
|
||||
LargeFiles: cloneFileEntries(m.largeFiles),
|
||||
TotalSize: m.totalSize,
|
||||
TotalFiles: m.totalFiles,
|
||||
Selected: m.selected,
|
||||
EntryOffset: m.offset,
|
||||
LargeSelected: m.largeSelected,
|
||||
@@ -250,6 +251,7 @@ func saveCacheToDisk(path string, result scanResult) error {
|
||||
Entries: result.Entries,
|
||||
LargeFiles: result.LargeFiles,
|
||||
TotalSize: result.TotalSize,
|
||||
TotalFiles: result.TotalFiles,
|
||||
ModTime: info.ModTime(),
|
||||
ScanTime: time.Now(),
|
||||
}
|
||||
@@ -264,6 +266,29 @@ func saveCacheToDisk(path string, result scanResult) error {
|
||||
return encoder.Encode(entry)
|
||||
}
|
||||
|
||||
// peekCacheTotalFiles attempts to read the total file count from cache,
|
||||
// ignoring expiration. Used for initial scan progress estimates.
|
||||
func peekCacheTotalFiles(path string) (int64, error) {
|
||||
cachePath, err := getCachePath(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
file, err := os.Open(cachePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close() //nolint:errcheck
|
||||
|
||||
var entry cacheEntry
|
||||
decoder := gob.NewDecoder(file)
|
||||
if err := decoder.Decode(&entry); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return entry.TotalFiles, nil
|
||||
}
|
||||
|
||||
func invalidateCache(path string) {
|
||||
cachePath, err := getCachePath(path)
|
||||
if err == nil {
|
||||
|
||||
@@ -35,12 +35,14 @@ type scanResult struct {
|
||||
Entries []dirEntry
|
||||
LargeFiles []fileEntry
|
||||
TotalSize int64
|
||||
TotalFiles int64
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
Entries []dirEntry
|
||||
LargeFiles []fileEntry
|
||||
TotalSize int64
|
||||
TotalFiles int64
|
||||
ModTime time.Time
|
||||
ScanTime time.Time
|
||||
}
|
||||
@@ -50,6 +52,7 @@ type historyEntry struct {
|
||||
Entries []dirEntry
|
||||
LargeFiles []fileEntry
|
||||
TotalSize int64
|
||||
TotalFiles int64
|
||||
Selected int
|
||||
EntryOffset int
|
||||
LargeSelected int
|
||||
@@ -114,6 +117,8 @@ type model struct {
|
||||
height int // Terminal height
|
||||
multiSelected map[string]bool // Track multi-selected items by path (safer than index)
|
||||
largeMultiSelected map[string]bool // Track multi-selected large files by path (safer than index)
|
||||
totalFiles int64 // Total files found in current/last scan
|
||||
lastTotalFiles int64 // Total files from previous scan (for progress bar)
|
||||
}
|
||||
|
||||
func (m model) inOverviewMode() bool {
|
||||
@@ -195,6 +200,13 @@ func newModel(path string, isOverview bool) model {
|
||||
}
|
||||
}
|
||||
|
||||
// Try to peek last total files for progress bar, even if cache is stale
|
||||
if !isOverview {
|
||||
if total, err := peekCacheTotalFiles(path); err == nil && total > 0 {
|
||||
m.lastTotalFiles = total
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -355,6 +367,7 @@ func (m model) scanCmd(path string) tea.Cmd {
|
||||
Entries: cached.Entries,
|
||||
LargeFiles: cached.LargeFiles,
|
||||
TotalSize: cached.TotalSize,
|
||||
TotalFiles: 0, // Cache doesn't store file count currently, minor UI limitation
|
||||
}
|
||||
return scanResultMsg{result: result, err: nil}
|
||||
}
|
||||
@@ -441,6 +454,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.entries = filteredEntries
|
||||
m.largeFiles = msg.result.LargeFiles
|
||||
m.totalSize = msg.result.TotalSize
|
||||
m.totalFiles = msg.result.TotalFiles
|
||||
m.status = fmt.Sprintf("Scanned %s", humanizeBytes(m.totalSize))
|
||||
m.clampEntrySelection()
|
||||
m.clampLargeSelection()
|
||||
@@ -685,6 +699,9 @@ func (m model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
invalidateCache(m.path)
|
||||
m.status = "Refreshing..."
|
||||
m.scanning = true
|
||||
if m.totalFiles > 0 {
|
||||
m.lastTotalFiles = m.totalFiles
|
||||
}
|
||||
atomic.StoreInt64(m.filesScanned, 0)
|
||||
atomic.StoreInt64(m.dirsScanned, 0)
|
||||
atomic.StoreInt64(m.bytesScanned, 0)
|
||||
@@ -968,6 +985,7 @@ func (m model) enterSelectedDir() (tea.Model, tea.Cmd) {
|
||||
m.entries = cloneDirEntries(cached.Entries)
|
||||
m.largeFiles = cloneFileEntries(cached.LargeFiles)
|
||||
m.totalSize = cached.TotalSize
|
||||
m.totalFiles = cached.TotalFiles
|
||||
m.selected = cached.Selected
|
||||
m.offset = cached.EntryOffset
|
||||
m.largeSelected = cached.LargeSelected
|
||||
@@ -978,6 +996,10 @@ func (m model) enterSelectedDir() (tea.Model, tea.Cmd) {
|
||||
m.scanning = false
|
||||
return m, nil
|
||||
}
|
||||
m.lastTotalFiles = 0
|
||||
if total, err := peekCacheTotalFiles(m.path); err == nil && total > 0 {
|
||||
m.lastTotalFiles = total
|
||||
}
|
||||
return m, tea.Batch(m.scanCmd(m.path), tickCmd())
|
||||
}
|
||||
m.status = fmt.Sprintf("File: %s (%s)", selected.Name, humanizeBytes(selected.Size))
|
||||
|
||||
@@ -251,6 +251,7 @@ func scanPathConcurrent(root string, filesScanned, dirsScanned, bytesScanned *in
|
||||
Entries: entries,
|
||||
LargeFiles: largeFiles,
|
||||
TotalSize: total,
|
||||
TotalFiles: atomic.LoadInt64(filesScanned),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -75,10 +75,25 @@ func (m model) View() string {
|
||||
if m.scanning {
|
||||
filesScanned, dirsScanned, bytesScanned := m.getScanProgress()
|
||||
|
||||
fmt.Fprintf(&b, "%s%s%s%s Scanning: %s%s files%s, %s%s dirs%s, %s%s%s\n",
|
||||
progressPrefix := ""
|
||||
if m.lastTotalFiles > 0 {
|
||||
percent := float64(filesScanned) / float64(m.lastTotalFiles) * 100
|
||||
// Cap at 100% generally
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
// While strictly scanning, cap at 99% to avoid "100% but still working" confusion
|
||||
if m.scanning && percent >= 100 {
|
||||
percent = 99
|
||||
}
|
||||
progressPrefix = fmt.Sprintf(" %s(%.0f%%)%s", colorCyan, percent, colorReset)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&b, "%s%s%s%s Scanning%s: %s%s files%s, %s%s dirs%s, %s%s%s\n",
|
||||
colorCyan, colorBold,
|
||||
spinnerFrames[m.spinner],
|
||||
colorReset,
|
||||
progressPrefix,
|
||||
colorYellow, formatNumber(filesScanned), colorReset,
|
||||
colorYellow, formatNumber(dirsScanned), colorReset,
|
||||
colorGreen, humanizeBytes(bytesScanned), colorReset)
|
||||
|
||||
@@ -88,6 +88,7 @@ clean_productivity_apps() {
|
||||
safe_clean ~/Library/Caches/com.orabrowser.app/* "Ora browser cache"
|
||||
safe_clean ~/Library/Caches/com.filo.client/* "Filo cache"
|
||||
safe_clean ~/Library/Caches/com.flomoapp.mac/* "Flomo cache"
|
||||
safe_clean ~/Library/Application\ Support/Quark/Cache/videoCache/* "Quark video cache"
|
||||
}
|
||||
# Music/media players (protect Spotify offline music).
|
||||
clean_media_players() {
|
||||
|
||||
@@ -85,8 +85,9 @@ readonly DATA_PROTECTED_BUNDLES=(
|
||||
"com.lastpass.*" # LastPass
|
||||
"com.dashlane.*" # Dashlane
|
||||
"com.bitwarden.*" # Bitwarden
|
||||
"com.keepassx.*" # KeePassXC
|
||||
"com.keepassx.*" # KeePassXC (Legacy)
|
||||
"org.keepassx.*" # KeePassX
|
||||
"org.keepassxc.*" # KeePassXC
|
||||
"com.authy.*" # Authy
|
||||
"com.yubico.*" # YubiKey Manager
|
||||
|
||||
|
||||
@@ -9,6 +9,13 @@ set -euo pipefail
|
||||
# ============================================================================
|
||||
|
||||
check_touchid_support() {
|
||||
# Check sudo_local first (Sonoma+)
|
||||
if [[ -f /etc/pam.d/sudo_local ]]; then
|
||||
grep -q "pam_tid.so" /etc/pam.d/sudo_local 2> /dev/null
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Fallback to checking sudo directly
|
||||
if [[ -f /etc/pam.d/sudo ]]; then
|
||||
grep -q "pam_tid.so" /etc/pam.d/sudo 2> /dev/null
|
||||
return $?
|
||||
|
||||
Reference in New Issue
Block a user