mirror of
https://github.com/neoromantique/dotfiles.git
synced 2026-03-13 21:53:20 +03:00
update dotfiles
This commit is contained in:
57
.chezmoi.toml.tmpl
Normal file
57
.chezmoi.toml.tmpl
Normal file
@@ -0,0 +1,57 @@
|
||||
{{- /* Detect hostname and suggest default profile */ -}}
|
||||
{{- $hostname := .chezmoi.hostname -}}
|
||||
{{- $defaultProfile := "desktop" -}}
|
||||
{{- if eq $hostname "box" -}}
|
||||
{{- $defaultProfile = "desktop" -}}
|
||||
{{- else if eq $hostname "bluefin" -}}
|
||||
{{- $defaultProfile = "laptop" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Prompt for device profile */ -}}
|
||||
{{- $deviceProfile := promptStringOnce . "deviceProfile" (printf "Device profile (desktop/laptop) [%s]" $defaultProfile) -}}
|
||||
{{- if eq $deviceProfile "" -}}
|
||||
{{- $deviceProfile = $defaultProfile -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Detect distro */ -}}
|
||||
{{- $distro := "unknown" -}}
|
||||
{{- if stat "/etc/arch-release" -}}
|
||||
{{- $distro = "arch" -}}
|
||||
{{- else if lookPath "rpm-ostree" -}}
|
||||
{{- $distro = "fedora-atomic" -}}
|
||||
{{- else if stat "/etc/fedora-release" -}}
|
||||
{{- $distro = "fedora" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Prompt for secrets path */ -}}
|
||||
{{- $secretsPath := promptStringOnce . "secretsPath" "Path to secrets directory [~/secrets]" -}}
|
||||
{{- if eq $secretsPath "" -}}
|
||||
{{- $secretsPath = "~/secrets" -}}
|
||||
{{- end -}}
|
||||
|
||||
[data]
|
||||
deviceProfile = {{ $deviceProfile | quote }}
|
||||
hostname = {{ $hostname | quote }}
|
||||
distro = {{ $distro | quote }}
|
||||
secretsPath = {{ $secretsPath | quote }}
|
||||
|
||||
# Device-specific configuration
|
||||
{{- if eq $deviceProfile "desktop" }}
|
||||
primaryMonitor = "DP-1"
|
||||
secondaryMonitor = "DP-2"
|
||||
primaryResolution = "2560x1440@165"
|
||||
secondaryResolution = "2560x1440@60"
|
||||
hasMultipleMonitors = true
|
||||
hasTouchpad = false
|
||||
hasBattery = false
|
||||
idleTimeout = 300
|
||||
{{- else if eq $deviceProfile "laptop" }}
|
||||
primaryMonitor = "eDP-1"
|
||||
secondaryMonitor = ""
|
||||
primaryResolution = "preferred"
|
||||
secondaryResolution = ""
|
||||
hasMultipleMonitors = false
|
||||
hasTouchpad = true
|
||||
hasBattery = true
|
||||
idleTimeout = 180
|
||||
{{- end }}
|
||||
20
.chezmoiignore
Normal file
20
.chezmoiignore
Normal file
@@ -0,0 +1,20 @@
|
||||
# Ignore README and install script in home directory
|
||||
README.md
|
||||
install.sh
|
||||
|
||||
# Ignore backup files
|
||||
*.pre-ai
|
||||
*.bak
|
||||
*.orig
|
||||
*.backup
|
||||
|
||||
# Ignore git and editor files
|
||||
.git/
|
||||
.gitignore
|
||||
*.swp
|
||||
*~
|
||||
.claude/
|
||||
|
||||
# OS-specific ignores
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -1,3 +1,8 @@
|
||||
# dotfiles
|
||||
|
||||
init script tba
|
||||
Dotfiles managed with [chezmoi](https://chezmoi.io/). Supports CachyOS (desktop) and Bluefin (laptop) with device-specific configs for Hyprland, Waybar, and related tools. Templates handle monitor setup, touchpad/gestures, battery modules, and idle behavior automatically based on hostname detection.
|
||||
|
||||
```bash
|
||||
# Install
|
||||
./install.sh
|
||||
```
|
||||
|
||||
BIN
home/Pictures/wlpp/wallhaven-9dkeqd.png
Normal file
BIN
home/Pictures/wlpp/wallhaven-9dkeqd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 MiB |
27
home/private_dot_config/hypr/autostart.conf.tmpl
Normal file
27
home/private_dot_config/hypr/autostart.conf.tmpl
Normal file
@@ -0,0 +1,27 @@
|
||||
# Autostart applications
|
||||
# Device: {{ .deviceProfile }}
|
||||
|
||||
# Polkit agent (GUI password prompts)
|
||||
exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
|
||||
|
||||
# System tray & bar
|
||||
exec-once = nm-applet &
|
||||
exec-once = waybar
|
||||
|
||||
# Notifications
|
||||
exec-once = swaync
|
||||
|
||||
# Wallpaper & idle
|
||||
exec-once = hyprpaper
|
||||
exec-once = hypridle
|
||||
|
||||
# Clipboard
|
||||
exec-once = copyq --start-server
|
||||
|
||||
{{- if eq .deviceProfile "desktop" }}
|
||||
# Desktop: Hyprland plugins
|
||||
exec-once = hyprpm reload -n
|
||||
{{- else }}
|
||||
# Laptop: Power management
|
||||
exec-once = power-profiles-daemon || true
|
||||
{{- end }}
|
||||
18
home/private_dot_config/hypr/colors.conf
Normal file
18
home/private_dot_config/hypr/colors.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
$background = rgb(150E11)
|
||||
$foreground = rgb(FEEFF0)
|
||||
$color0 = rgb(3D373A)
|
||||
$color1 = rgb(220615)
|
||||
$color2 = rgb(08432C)
|
||||
$color3 = rgb(CE2E9E)
|
||||
$color4 = rgb(0881BC)
|
||||
$color5 = rgb(C1E177)
|
||||
$color6 = rgb(FCD0D3)
|
||||
$color7 = rgb(F5DFE1)
|
||||
$color8 = rgb(AC9C9D)
|
||||
$color9 = rgb(220615)
|
||||
$color10 = rgb(08432C)
|
||||
$color11 = rgb(CE2E9E)
|
||||
$color12 = rgb(0881BC)
|
||||
$color13 = rgb(C1E177)
|
||||
$color14 = rgb(FCD0D3)
|
||||
$color15 = rgb(F5DFE1)
|
||||
30
home/private_dot_config/hypr/hypridle.conf.tmpl
Normal file
30
home/private_dot_config/hypr/hypridle.conf.tmpl
Normal file
@@ -0,0 +1,30 @@
|
||||
# Hypridle Configuration
|
||||
# Device: {{ .deviceProfile }} ({{ .hostname }})
|
||||
# Idle timeout: {{ .idleTimeout }} seconds
|
||||
|
||||
general {
|
||||
lock_cmd = pidof hyprlock || hyprlock
|
||||
before_sleep_cmd = loginctl lock-session
|
||||
after_sleep_cmd = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = {{ .idleTimeout }}
|
||||
on-timeout = hyprctl dispatch dpms off
|
||||
on-resume = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
{{- if .hasBattery }}
|
||||
# Laptop: dim screen before turning off
|
||||
listener {
|
||||
timeout = {{ sub .idleTimeout 60 }}
|
||||
on-timeout = brightnessctl -s set 30%
|
||||
on-resume = brightnessctl -r
|
||||
}
|
||||
|
||||
# Laptop: suspend after extended idle
|
||||
listener {
|
||||
timeout = {{ mul .idleTimeout 2 }}
|
||||
on-timeout = systemctl suspend
|
||||
}
|
||||
{{- end }}
|
||||
293
home/private_dot_config/hypr/hyprland.conf.tmpl
Normal file
293
home/private_dot_config/hypr/hyprland.conf.tmpl
Normal file
@@ -0,0 +1,293 @@
|
||||
# Hyprland Configuration
|
||||
# Device: {{ .deviceProfile }} ({{ .hostname }})
|
||||
# Managed by chezmoi - edit source at ~/dotfiles
|
||||
|
||||
source = ~/.config/hypr/colors.conf
|
||||
source = ~/.config/hypr/autostart.conf
|
||||
source = ~/.config/hypr/workspaces.conf
|
||||
source = ~/.config/hypr/monitors.conf
|
||||
|
||||
################
|
||||
### MONITORS ###
|
||||
################
|
||||
|
||||
# Monitor configuration is in monitors.conf (device-specific)
|
||||
|
||||
###################
|
||||
### MY PROGRAMS ###
|
||||
###################
|
||||
|
||||
$terminal = ghostty
|
||||
$fileManager = thunar
|
||||
$menu = wofi --show drun
|
||||
$run_menu = wofi --show run
|
||||
|
||||
#############################
|
||||
### ENVIRONMENT VARIABLES ###
|
||||
#############################
|
||||
|
||||
env = XCURSOR_SIZE,24
|
||||
env = HYPRCURSOR_SIZE,24
|
||||
|
||||
#####################
|
||||
### LOOK AND FEEL ###
|
||||
#####################
|
||||
|
||||
general {
|
||||
gaps_in = 2
|
||||
gaps_out = 5
|
||||
border_size = 2
|
||||
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
|
||||
col.inactive_border = rgba(595959aa)
|
||||
resize_on_border = false
|
||||
allow_tearing = false
|
||||
layout = dwindle
|
||||
}
|
||||
|
||||
decoration {
|
||||
rounding = 10
|
||||
rounding_power = 2
|
||||
active_opacity = 1.0
|
||||
inactive_opacity = 1.0
|
||||
|
||||
shadow {
|
||||
enabled = true
|
||||
range = 4
|
||||
render_power = 3
|
||||
color = rgba(1a1a1aee)
|
||||
}
|
||||
|
||||
blur {
|
||||
enabled = true
|
||||
size = 3
|
||||
passes = 1
|
||||
vibrancy = 0.1696
|
||||
}
|
||||
}
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = easeOutQuint,0.23,1,0.32,1
|
||||
bezier = easeInOutCubic,0.65,0.05,0.36,1
|
||||
bezier = linear,0,0,1,1
|
||||
bezier = almostLinear,0.5,0.5,0.75,1.0
|
||||
bezier = quick,0.15,0,0.1,1
|
||||
bezier = snappy,0.2,0.8,0.2,1
|
||||
bezier = overshoot,0.34,1.56,0.64,1
|
||||
bezier = bounce,0.68,-0.55,0.27,1.55
|
||||
bezier = whip,0.1,0.9,0.2,1
|
||||
|
||||
animation = global, 1, 5, default
|
||||
animation = border, 1, 2.5, snappy
|
||||
animation = windows, 1, 2.8, easeOutQuint
|
||||
animation = windowsIn, 1, 2.2, bounce, popin 95%
|
||||
animation = windowsOut, 1, 0.9, whip, popin 85%
|
||||
animation = fadeIn, 1, 0.9, snappy
|
||||
animation = fadeOut, 1, 0.8, snappy
|
||||
animation = fade, 1, 1.6, quick
|
||||
animation = layers, 1, 2.0, easeOutQuint
|
||||
animation = layersIn, 1, 1.8, bounce, fade
|
||||
animation = layersOut, 1, 0.8, whip, fade
|
||||
animation = fadeLayersIn, 1, 0.9, snappy
|
||||
animation = fadeLayersOut, 1, 0.7, snappy
|
||||
animation = workspaces, 1, 1.2, bounce, fade
|
||||
animation = workspacesIn, 1, 0.9, overshoot, fade
|
||||
animation = workspacesOut, 1, 1.0, whip, fade
|
||||
}
|
||||
|
||||
# Smart gaps window rules
|
||||
windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
|
||||
windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
|
||||
windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
|
||||
windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
|
||||
|
||||
binds {
|
||||
hide_special_on_workspace_change = true
|
||||
workspace_back_and_forth = true
|
||||
}
|
||||
|
||||
ecosystem {
|
||||
enforce_permissions = true
|
||||
}
|
||||
|
||||
dwindle {
|
||||
pseudotile = true
|
||||
preserve_split = true
|
||||
}
|
||||
|
||||
master {
|
||||
new_status = master
|
||||
}
|
||||
|
||||
misc {
|
||||
force_default_wallpaper = -1
|
||||
disable_hyprland_logo = false
|
||||
enable_anr_dialog = false
|
||||
}
|
||||
|
||||
#############
|
||||
### INPUT ###
|
||||
#############
|
||||
|
||||
input {
|
||||
kb_layout = us, ru
|
||||
kb_variant =
|
||||
kb_model =
|
||||
kb_options = caps:escape
|
||||
kb_rules =
|
||||
numlock_by_default = true
|
||||
follow_mouse = 1
|
||||
sensitivity = 0
|
||||
|
||||
{{- if .hasTouchpad }}
|
||||
touchpad {
|
||||
natural_scroll = true
|
||||
tap-to-click = true
|
||||
disable_while_typing = true
|
||||
}
|
||||
}
|
||||
|
||||
gestures {
|
||||
workspace_swipe = true
|
||||
workspace_swipe_fingers = 3
|
||||
}
|
||||
{{- else }}
|
||||
touchpad {
|
||||
natural_scroll = false
|
||||
}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
device {
|
||||
name = epic-mouse-v1
|
||||
sensitivity = -0.5
|
||||
}
|
||||
|
||||
###################
|
||||
### KEYBINDINGS ###
|
||||
###################
|
||||
|
||||
$mainMod = SUPER
|
||||
|
||||
# Keyboard layout toggle
|
||||
bind = SUPER, SPACE, exec, bash -c "current=$(hyprctl getoption input:kb_layout -j | jq -r '.str'); if [[ $current == \"us,ru\" ]]; then hyprctl keyword input:kb_layout 'ru,us'; else hyprctl keyword input:kb_layout 'us,ru'; fi"
|
||||
|
||||
# Wallpapers
|
||||
bind = SUPER, bracketright, exec, ~/Scripts/change_wallpaper.sh next
|
||||
bind = SUPER, bracketleft, exec, ~/Scripts/change_wallpaper.sh prev
|
||||
|
||||
# Core bindings
|
||||
bind = $mainMod, Q, exec, $terminal
|
||||
bind = $mainMod, K, killactive,
|
||||
bind = $mainMod, M, exit,
|
||||
bind = $mainMod, E, exec, ~/.config/hypr/scripts/toggle_expo_on_primary.sh
|
||||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, R, exec, $menu
|
||||
bind = $mainMod SHIFT, R, exec, hyprctl reload
|
||||
bind = $mainMod, P, pseudo,
|
||||
bind = $mainMod, J, togglesplit,
|
||||
bind = $mainMod, L, exec, pactl set-sink-mute @DEFAULT_SINK@ 1 && hyprlock
|
||||
bind = $mainMod, t, togglegroup
|
||||
|
||||
# VPN switcher
|
||||
bind = , F6, exec, ~/.config/hypr/scripts/vpn-switcher.sh
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
bind = $mainMod, right, movefocus, r
|
||||
bind = $mainMod, up, movefocus, u
|
||||
bind = $mainMod, down, movefocus, d
|
||||
|
||||
# Switch workspaces with mainMod + [0-9]
|
||||
bind = $mainMod, 1, workspace, 1
|
||||
bind = $mainMod, 2, workspace, 2
|
||||
bind = $mainMod, 3, workspace, 3
|
||||
bind = $mainMod, 4, workspace, 4
|
||||
bind = $mainMod, 5, workspace, 5
|
||||
bind = $mainMod, 6, workspace, 6
|
||||
bind = $mainMod, 7, workspace, 7
|
||||
bind = $mainMod, 8, workspace, 8
|
||||
bind = $mainMod, 9, workspace, 9
|
||||
|
||||
# Special workspaces
|
||||
bind = SUPER, F12, exec, ~/.config/hypr/scripts/workspace-pin.sh 1337
|
||||
bind = , F12, togglespecialworkspace, org
|
||||
bind = SUPER, A, togglespecialworkspace, org
|
||||
bind = SUPER SHIFT, F12, movetoworkspace, special:org
|
||||
bind = SUPER, X, workspace, name:media
|
||||
bind = SUPER SHIFT, X, movetoworkspace, name:media
|
||||
bind = SUPER SHIFT, C, movetoworkspace, name:tg
|
||||
bind = SUPER, c, workspace, name:tg
|
||||
bind = $mainMod, S, togglespecialworkspace, termius
|
||||
bind = $mainMod SHIFT, S, movetoworkspacesilent, special:termius
|
||||
bind = $mainMod, G, togglespecialworkspace, llm
|
||||
bind = $mainMod SHIFT, G, movetoworkspacesilent, special:llm
|
||||
|
||||
# Move active window to workspace
|
||||
bind = $mainMod SHIFT, 1, movetoworkspacesilent, 1
|
||||
bind = $mainMod SHIFT, 2, movetoworkspacesilent, 2
|
||||
bind = $mainMod SHIFT, 3, movetoworkspacesilent, 3
|
||||
bind = $mainMod SHIFT, 4, movetoworkspacesilent, 4
|
||||
bind = $mainMod SHIFT, 5, movetoworkspacesilent, 5
|
||||
bind = $mainMod SHIFT, 6, movetoworkspacesilent, 6
|
||||
bind = $mainMod SHIFT, 7, movetoworkspacesilent, 7
|
||||
bind = $mainMod SHIFT, 8, movetoworkspacesilent, 8
|
||||
bind = $mainMod SHIFT, 9, movetoworkspacesilent, 9
|
||||
bind = $mainMod SHIFT, 0, movetoworkspacesilent, 10
|
||||
|
||||
# Screenshot
|
||||
bind = , Print, exec, ~/.config/hypr/scripts/screenshot.sh
|
||||
|
||||
# Scroll through existing workspaces
|
||||
bind = $mainMod, mouse_down, workspace, e+1
|
||||
bind = $mainMod, mouse_up, workspace, e-1
|
||||
|
||||
# Move/resize windows with mainMod + LMB/RMB
|
||||
bindm = $mainMod, mouse:272, movewindow
|
||||
bindm = $mainMod, mouse:273, resizewindow
|
||||
|
||||
# Media keys
|
||||
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
|
||||
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
|
||||
{{- if .hasBattery }}
|
||||
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
|
||||
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
|
||||
{{- end }}
|
||||
bindl = , XF86AudioNext, exec, playerctl next
|
||||
bindl = , XF86AudioPause, exec, playerctl play-pause
|
||||
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
||||
bindl = , XF86AudioPrev, exec, playerctl previous
|
||||
|
||||
##############################
|
||||
### WINDOWS AND WORKSPACES ###
|
||||
##############################
|
||||
|
||||
# Window rules for apps
|
||||
windowrulev2 = workspace name:tg, class:^(org.telegram.desktop)$
|
||||
windowrulev2 = workspace name:tg, class:^(discord)$
|
||||
windowrulev2 = suppressevent maximize, class:.*
|
||||
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
|
||||
|
||||
#############
|
||||
### PLUGINS ###
|
||||
#############
|
||||
|
||||
plugin {
|
||||
hyprexpo {
|
||||
columns = 3
|
||||
gap_size = 5
|
||||
bg_col = rgb(111111)
|
||||
workspace_method = first 1
|
||||
{{- if .hasTouchpad }}
|
||||
enable_gesture = true
|
||||
gesture_fingers = 3
|
||||
gesture_distance = 300
|
||||
gesture_positive = true
|
||||
{{- else }}
|
||||
enable_gesture = false
|
||||
{{- end }}
|
||||
}
|
||||
}
|
||||
124
home/private_dot_config/hypr/hyprlock.conf
Normal file
124
home/private_dot_config/hypr/hyprlock.conf
Normal file
@@ -0,0 +1,124 @@
|
||||
# Hyprlock Configuration
|
||||
# Sourcing colors
|
||||
source = $HOME/.config/hypr/colors.conf
|
||||
$Scripts = $HOME/.config/hypr/scripts
|
||||
|
||||
general {
|
||||
grace = 1
|
||||
}
|
||||
|
||||
background {
|
||||
monitor =
|
||||
path = screenshot
|
||||
blur_size = 5
|
||||
blur_passes = 1
|
||||
noise = 0.0117
|
||||
contrast = 1.3000
|
||||
brightness = 0.8000
|
||||
vibrancy = 0.2100
|
||||
vibrancy_darkness = 0.0
|
||||
}
|
||||
|
||||
input-field {
|
||||
monitor =
|
||||
size = 250, 50
|
||||
outline_thickness = 3
|
||||
dots_size = 0.33
|
||||
dots_spacing = 0.15
|
||||
dots_center = true
|
||||
outer_color = $color5
|
||||
inner_color = $color0
|
||||
font_color = $color12
|
||||
placeholder_text = <i>Password...</i>
|
||||
hide_input = false
|
||||
position = 0, 200
|
||||
halign = center
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
# Date
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:18000000] date +'%A, %-d %B %Y'
|
||||
color = $color12
|
||||
font_size = 34
|
||||
font_family = JetBrains Mono Nerd Font 10
|
||||
position = 0, -150
|
||||
halign = center
|
||||
valign = top
|
||||
}
|
||||
|
||||
# Week
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:18000000] date +'Week %U'
|
||||
color = $color5
|
||||
font_size = 24
|
||||
font_family = JetBrains Mono Nerd Font 10
|
||||
position = 0, -250
|
||||
halign = center
|
||||
valign = top
|
||||
}
|
||||
|
||||
# Time
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1000] date +"%H:%M:%S"
|
||||
color = $color15
|
||||
font_size = 94
|
||||
font_family = JetBrains Mono Nerd Font 10
|
||||
position = 0, 0
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
# User
|
||||
label {
|
||||
monitor =
|
||||
text = $USER
|
||||
color = $color12
|
||||
font_size = 18
|
||||
font_family = Inter Display Medium
|
||||
position = 0, 100
|
||||
halign = center
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
# Uptime
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:60000] uptime -p
|
||||
color = $color12
|
||||
font_size = 24
|
||||
font_family = JetBrains Mono Nerd Font 10
|
||||
position = 0, 0
|
||||
halign = right
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
# Weather
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:3600000] [ -f ~/.cache/.weather_cache ] && cat ~/.cache/.weather_cache
|
||||
color = $color12
|
||||
font_size = 24
|
||||
font_family = JetBrains Mono Nerd Font 10
|
||||
position = 50, 0
|
||||
halign = left
|
||||
valign = bottom
|
||||
}
|
||||
|
||||
# Wallpaper image
|
||||
image {
|
||||
monitor =
|
||||
path = $HOME/.config/hypr/wallpaper_effects/.wallpaper_current
|
||||
size = 230
|
||||
rounding = -1
|
||||
border_size = 2
|
||||
border_color = $color11
|
||||
rotate = 0
|
||||
reload_time = -1
|
||||
position = 0, 300
|
||||
halign = center
|
||||
valign = bottom
|
||||
}
|
||||
13
home/private_dot_config/hypr/hyprpaper.conf.tmpl
Normal file
13
home/private_dot_config/hypr/hyprpaper.conf.tmpl
Normal file
@@ -0,0 +1,13 @@
|
||||
# Hyprpaper Configuration
|
||||
# Device: {{ .deviceProfile }} ({{ .hostname }})
|
||||
|
||||
preload = ~/Pictures/wlpp/wallhaven-9dkeqd.png
|
||||
|
||||
{{- if eq .deviceProfile "desktop" }}
|
||||
wallpaper = {{ .primaryMonitor }}, ~/Pictures/wlpp/wallhaven-9dkeqd.png
|
||||
{{- if .secondaryMonitor }}
|
||||
wallpaper = {{ .secondaryMonitor }}, ~/Pictures/wlpp/wallhaven-9dkeqd.png
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
wallpaper = {{ .primaryMonitor }}, ~/Pictures/wlpp/wallhaven-9dkeqd.png
|
||||
{{- end }}
|
||||
21
home/private_dot_config/hypr/monitors.conf.tmpl
Normal file
21
home/private_dot_config/hypr/monitors.conf.tmpl
Normal file
@@ -0,0 +1,21 @@
|
||||
# Monitor Configuration
|
||||
# Device: {{ .deviceProfile }} ({{ .hostname }})
|
||||
|
||||
{{- if eq .deviceProfile "desktop" }}
|
||||
# Desktop dual-monitor setup
|
||||
# Primary: {{ .primaryMonitor }} at {{ .primaryResolution }}
|
||||
monitor = {{ .primaryMonitor }},{{ .primaryResolution }},0x0,1
|
||||
|
||||
{{- if .secondaryMonitor }}
|
||||
# Secondary: {{ .secondaryMonitor }} at {{ .secondaryResolution }}
|
||||
# Positioned to the right of primary
|
||||
monitor = {{ .secondaryMonitor }},{{ .secondaryResolution }},2560x0,1
|
||||
{{- end }}
|
||||
|
||||
{{- else }}
|
||||
# Laptop single monitor
|
||||
monitor = {{ .primaryMonitor }},{{ .primaryResolution }},auto,1
|
||||
|
||||
# External displays (when docked)
|
||||
monitor = ,preferred,auto,1
|
||||
{{- end }}
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
if [ -z "$XDG_PICTURES_DIR" ] ; then
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
fi
|
||||
|
||||
save_dir="${2:-$XDG_PICTURES_DIR/Screenshots}"
|
||||
save_file=$(date +'%Y-%m-%d_%H-%M-%S_screenshot.png')
|
||||
|
||||
gtkMode=`gsettings get org.gnome.desktop.interface color-scheme | sed "s/'//g" | awk -F '-' '{print $2}'`
|
||||
ncolor="-h string:bgcolor:#191724 -h string:fgcolor:#faf4ed -h string:frcolor:#56526e"
|
||||
|
||||
if [ "${gtkMode}" == "light" ] ; then
|
||||
ncolor="-h string:bgcolor:#f4ede8 -h string:fgcolor:#9893a5 -h string:frcolor:#908caa"
|
||||
fi
|
||||
|
||||
if [ ! -d "$save_dir" ] ; then
|
||||
mkdir -p $save_dir
|
||||
fi
|
||||
|
||||
case $1 in
|
||||
p) grim $save_dir/$save_file ;;
|
||||
s) grim -g "$(slurp)" - | satty --filename - --fullscreen --output-filename ~/Pictures/Screenshots/satty-$(date '+%Y%m%d-%H:%M:%S').png;;
|
||||
*) echo "...valid options are..."
|
||||
echo "p : print screen to $save_dir"
|
||||
echo "s : snip current screen to $save_dir"
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
if [ -f "$save_dir/$save_file" ] ; then
|
||||
notify-send $ncolor "theme" -a "saved in $save_dir" -i "$save_dir/$save_file" -r 91190 -t 2200
|
||||
fi
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get a list of sink names
|
||||
sinks=($(pactl list sinks short | awk '{print $2}'))
|
||||
|
||||
# Get the current default sink name
|
||||
current_sink=$(pactl info | grep "Default Sink:" | awk '{print $3}')
|
||||
|
||||
# Check if we found the current sink
|
||||
if [ -z "$current_sink" ]; then
|
||||
echo "Error: Could not determine the current default sink." >&2
|
||||
# Attempt to set the first sink as default if we can't find the current one
|
||||
if [ ${#sinks[@]} -gt 0 ]; then
|
||||
pactl set-default-sink "${sinks[0]}"
|
||||
echo "Attempting to set default sink to first available: ${sinks[0]}" >&2
|
||||
exit 0
|
||||
else
|
||||
echo "Error: No sinks found." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Find the index of the current sink in the list
|
||||
current_sink_index=-1
|
||||
for i in "${!sinks[@]}"; do
|
||||
if [[ "${sinks[$i]}" == "$current_sink" ]]; then
|
||||
current_sink_index=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Check if the current sink was found in the list
|
||||
if [ "$current_sink_index" -eq -1 ]; then
|
||||
echo "Error: Current default sink '$current_sink' not found in the list of sinks." >&2
|
||||
# Reset to the first sink as a fallback
|
||||
if [ ${#sinks[@]} -gt 0 ]; then
|
||||
pactl set-default-sink "${sinks[0]}"
|
||||
echo "Resetting to first available sink: ${sinks[0]}" >&2
|
||||
exit 0
|
||||
else
|
||||
echo "Error: No sinks found to reset to." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Determine the direction
|
||||
direction=$1
|
||||
|
||||
# Calculate the index of the next sink
|
||||
if [ "$direction" == "up" ]; then
|
||||
next_sink_index=$(( (current_sink_index - 1 + ${#sinks[@]}) % ${#sinks[@]} ))
|
||||
else # default to down
|
||||
next_sink_index=$(( (current_sink_index + 1) % ${#sinks[@]} ))
|
||||
fi
|
||||
|
||||
# Get the name of the next sink
|
||||
next_sink="${sinks[$next_sink_index]}"
|
||||
|
||||
# Set the new default sink
|
||||
pactl set-default-sink "$next_sink"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
# Just open expo, no special handling
|
||||
hyprctl dispatch hyprexpo:expo toggle
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# Helper script for vpn-switcher to run privileged commands
|
||||
# This gets called via pkexec
|
||||
|
||||
ACTION="$1"
|
||||
shift
|
||||
|
||||
case "$ACTION" in
|
||||
wg-up)
|
||||
wg-quick up "$1"
|
||||
;;
|
||||
wg-down)
|
||||
wg-quick down "$1"
|
||||
;;
|
||||
systemctl-start)
|
||||
systemctl start "$1"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown action: $ACTION" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
# Secrets directory - configurable via chezmoi
|
||||
SECRETS_DIR = os.path.expanduser("{{ .secretsPath }}")
|
||||
VPN_DIR = os.path.join(SECRETS_DIR, "vpn") if os.path.isdir(os.path.join(os.path.expanduser("{{ .secretsPath }}"), "vpn")) else os.path.expanduser("~/cfg/vpn")
|
||||
HELPER = os.path.expanduser("~/.config/hypr/scripts/vpn-switcher-helper.sh")
|
||||
WOFI_CMD = ["wofi", "--dmenu", "--width", "450", "--height", "350", "--prompt", "VPN Switcher", "--cache-file", "/dev/null"]
|
||||
|
||||
def run(cmd, check=False):
|
||||
"""Run command and return stdout, or None on failure."""
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=check)
|
||||
return result.stdout.strip()
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
def notify(msg):
|
||||
subprocess.run(["notify-send", "VPN Switcher", msg, "-r", "91191", "-t", "2000"])
|
||||
|
||||
def get_active_wg():
|
||||
"""Get list of active WireGuard interfaces."""
|
||||
out = run(["wg", "show", "interfaces"])
|
||||
return out.split() if out else []
|
||||
|
||||
def is_tailscale_up():
|
||||
"""Check if tailscale is running."""
|
||||
result = subprocess.run(["tailscale", "status"], capture_output=True)
|
||||
return result.returncode == 0
|
||||
|
||||
def get_ts_exit_node():
|
||||
"""Get current tailscale exit node if any."""
|
||||
if not is_tailscale_up():
|
||||
return None
|
||||
out = run(["tailscale", "status", "--json"])
|
||||
if out:
|
||||
import json
|
||||
try:
|
||||
data = json.loads(out)
|
||||
return data.get("ExitNodeStatus", {}).get("ID")
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_wg_configs():
|
||||
"""Get list of WireGuard config files."""
|
||||
configs = []
|
||||
if os.path.isdir(VPN_DIR):
|
||||
for f in os.listdir(VPN_DIR):
|
||||
if f.endswith(".conf"):
|
||||
configs.append(f[:-5]) # Remove .conf
|
||||
return sorted(configs)
|
||||
|
||||
def get_ts_accounts():
|
||||
"""Get list of tailscale accounts."""
|
||||
out = run(["tailscale", "switch", "--list"])
|
||||
if not out:
|
||||
return []
|
||||
|
||||
accounts = []
|
||||
for line in out.splitlines()[1:]: # Skip header
|
||||
parts = line.split()
|
||||
if len(parts) >= 3:
|
||||
id_, tailnet, account = parts[0], parts[1], parts[2]
|
||||
is_active = account.endswith("*")
|
||||
if is_active:
|
||||
account = account[:-1]
|
||||
accounts.append({"id": id_, "tailnet": tailnet, "account": account, "active": is_active})
|
||||
return accounts
|
||||
|
||||
def get_ts_exit_nodes():
|
||||
"""Get list of available tailscale exit nodes."""
|
||||
if not is_tailscale_up():
|
||||
return []
|
||||
|
||||
out = run(["tailscale", "exit-node", "list"])
|
||||
if not out:
|
||||
return []
|
||||
|
||||
nodes = []
|
||||
for line in out.splitlines():
|
||||
line = line.strip()
|
||||
# Skip header, empty lines, and comment lines
|
||||
if not line or line.startswith("IP") or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) >= 4:
|
||||
ip, hostname = parts[0], parts[1]
|
||||
country = parts[2] if len(parts) > 2 else "-"
|
||||
city = parts[3] if len(parts) > 3 else "-"
|
||||
status = " ".join(parts[4:]) if len(parts) > 4 else ""
|
||||
# Skip offline nodes
|
||||
if "offline" in status.lower():
|
||||
continue
|
||||
# Extract short name (before first dot)
|
||||
short_name = hostname.split(".")[0]
|
||||
nodes.append({"hostname": hostname, "short": short_name, "ip": ip, "country": country, "city": city})
|
||||
return nodes
|
||||
|
||||
def build_menu():
|
||||
"""Build the menu entries."""
|
||||
lines = []
|
||||
active_wg = get_active_wg()
|
||||
ts_exit = get_ts_exit_node()
|
||||
|
||||
# WireGuard section
|
||||
lines.append("━━━ WireGuard ━━━")
|
||||
for name in get_wg_configs():
|
||||
status = "●" if name in active_wg else "○"
|
||||
lines.append(f" {status} {name}")
|
||||
|
||||
# Tailscale accounts section
|
||||
lines.append("━━━ Tailscale Accounts ━━━")
|
||||
for acc in get_ts_accounts():
|
||||
status = "●" if acc["active"] else "○"
|
||||
if acc["active"]:
|
||||
lines.append(f" {status} {acc['tailnet']} ({acc['account']})")
|
||||
else:
|
||||
lines.append(f" {status} {acc['tailnet']} ({acc['account']}) [{acc['id']}]")
|
||||
|
||||
# Tailscale exit nodes section
|
||||
lines.append("━━━ Tailscale Exit Nodes ━━━")
|
||||
if is_tailscale_up():
|
||||
if ts_exit:
|
||||
lines.append(" ● Exit Node Active - Disconnect")
|
||||
for node in get_ts_exit_nodes():
|
||||
lines.append(f" ○ {node['hostname']} ({node['country']}/{node['city']})")
|
||||
else:
|
||||
lines.append(" ○ Start Tailscale")
|
||||
|
||||
# Actions
|
||||
lines.append("━━━ Actions ━━━")
|
||||
lines.append(" ⊘ Disconnect All")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def pkexec_helper(action, arg):
|
||||
"""Run helper script via pkexec."""
|
||||
subprocess.run(["pkexec", HELPER, action, arg], capture_output=True)
|
||||
|
||||
def disconnect_all_wg():
|
||||
"""Disconnect all WireGuard interfaces."""
|
||||
for iface in get_active_wg():
|
||||
conf_path = os.path.join(VPN_DIR, f"{iface}.conf")
|
||||
if os.path.exists(conf_path):
|
||||
pkexec_helper("wg-down", conf_path)
|
||||
else:
|
||||
pkexec_helper("wg-down", iface)
|
||||
|
||||
def handle_selection(selection):
|
||||
"""Handle the user's menu selection."""
|
||||
# Skip headers and empty
|
||||
if not selection or selection.startswith("━━━"):
|
||||
return
|
||||
|
||||
# Strip leading whitespace and status icon
|
||||
clean = selection.strip()
|
||||
if clean.startswith("●") or clean.startswith("○") or clean.startswith("⊘"):
|
||||
clean = clean[1:].strip()
|
||||
|
||||
# Get current state
|
||||
active_wg = get_active_wg()
|
||||
wg_configs = get_wg_configs()
|
||||
ts_accounts = get_ts_accounts()
|
||||
ts_exit_nodes = get_ts_exit_nodes()
|
||||
|
||||
# Check if it's a WireGuard config
|
||||
for name in wg_configs:
|
||||
if clean == name:
|
||||
if name in active_wg:
|
||||
# Turn off
|
||||
conf_path = os.path.join(VPN_DIR, f"{name}.conf")
|
||||
pkexec_helper("wg-down", conf_path)
|
||||
notify(f"WireGuard: {name} disconnected")
|
||||
else:
|
||||
# Turn on (disable others first)
|
||||
disconnect_all_wg()
|
||||
if is_tailscale_up():
|
||||
run(["tailscale", "set", "--exit-node="])
|
||||
conf_path = os.path.join(VPN_DIR, f"{name}.conf")
|
||||
pkexec_helper("wg-up", conf_path)
|
||||
notify(f"WireGuard: {name} connected")
|
||||
return
|
||||
|
||||
# Check if it's a Tailscale account
|
||||
for acc in ts_accounts:
|
||||
if acc["tailnet"] in clean and acc["account"] in clean:
|
||||
if acc["active"]:
|
||||
notify("Already on this Tailscale account")
|
||||
else:
|
||||
run(["tailscale", "switch", acc["id"]])
|
||||
notify(f"Tailscale: Switched to {acc['tailnet']}")
|
||||
return
|
||||
|
||||
# Check if it's a Tailscale exit node
|
||||
if clean == "Exit Node Active - Disconnect":
|
||||
run(["tailscale", "set", "--exit-node="])
|
||||
notify("Tailscale exit node disconnected")
|
||||
return
|
||||
|
||||
if clean == "Start Tailscale":
|
||||
run(["tailscale", "up"])
|
||||
notify("Tailscale started")
|
||||
return
|
||||
|
||||
for node in ts_exit_nodes:
|
||||
if node["hostname"] in clean or node["short"] in clean:
|
||||
disconnect_all_wg()
|
||||
run(["tailscale", "set", f"--exit-node={node['ip']}"])
|
||||
notify(f"Tailscale: Connected via {node['short']}")
|
||||
return
|
||||
|
||||
# Actions
|
||||
if clean == "Disconnect All":
|
||||
disconnect_all_wg()
|
||||
if is_tailscale_up():
|
||||
run(["tailscale", "set", "--exit-node="])
|
||||
notify("All VPNs disconnected")
|
||||
|
||||
def main():
|
||||
menu = build_menu()
|
||||
|
||||
# Run wofi
|
||||
result = subprocess.run(
|
||||
WOFI_CMD,
|
||||
input=menu,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
selection = result.stdout.strip()
|
||||
if selection:
|
||||
handle_selection(selection)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
WORKSPACE=$1
|
||||
CORRECT_PIN="1234"
|
||||
LOG_FILE="$HOME/.workspace_access_log"
|
||||
|
||||
# Fuzzel colors - Nord theme
|
||||
BG_COLOR="242a36ee"
|
||||
TEXT_COLOR="eceff4ff"
|
||||
SELECTION_COLOR="88c0d0ff"
|
||||
SELECTION_TEXT_COLOR="2e3440ff"
|
||||
BORDER_COLOR="5e81acff"
|
||||
|
||||
# Display an informational notification before showing PIN dialog
|
||||
notify-send --icon=dialog-password-symbolic \
|
||||
--app-name="Workspace Security" \
|
||||
"Workspace $WORKSPACE" "Protected workspace - PIN required" \
|
||||
--urgency=low
|
||||
|
||||
# Get PIN using fuzzel
|
||||
ENTERED_PIN=$(echo -e "\n" | fuzzel \
|
||||
--dmenu \
|
||||
--password \
|
||||
--prompt="Enter PIN for workspace $WORKSPACE: " \
|
||||
--width=35 \
|
||||
--lines=0 \
|
||||
--horizontal-pad=20 \
|
||||
--vertical-pad=15 \
|
||||
--inner-pad=10 \
|
||||
--border-width=2 \
|
||||
--border-radius=10 \
|
||||
--font="JetBrains Mono:size=14" \
|
||||
--background=${BG_COLOR} \
|
||||
--text-color=${TEXT_COLOR} \
|
||||
--match-color=${SELECTION_COLOR} \
|
||||
--selection-color=${SELECTION_COLOR} \
|
||||
--selection-text-color=${SELECTION_TEXT_COLOR} \
|
||||
--border-color=${BORDER_COLOR} )
|
||||
|
||||
# Trim whitespace
|
||||
ENTERED_PIN=$(echo "$ENTERED_PIN" | xargs)
|
||||
|
||||
# Check if PIN entry was cancelled (empty)
|
||||
if [ -z "$ENTERED_PIN" ]; then
|
||||
notify-send --icon=dialog-information \
|
||||
--app-name="Workspace Security" \
|
||||
"Access Cancelled" "PIN entry was cancelled" \
|
||||
--urgency=low
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Verify PIN
|
||||
if [ "$ENTERED_PIN" == "$CORRECT_PIN" ]; then
|
||||
# Correct PIN - switch to workspace with nice visual feedback
|
||||
notify-send --icon=dialog-password \
|
||||
--app-name="Workspace Security" \
|
||||
"Access Granted" "Switching to workspace $WORKSPACE" \
|
||||
--urgency=low
|
||||
|
||||
# Optional: Log successful attempt
|
||||
echo "$(date): Successful PIN entry for workspace $WORKSPACE" >> "$LOG_FILE"
|
||||
|
||||
# Small delay for notification visibility before switching
|
||||
sleep 0.2
|
||||
hyprctl dispatch workspace "$WORKSPACE"
|
||||
else
|
||||
# Wrong PIN - show notification with error icon
|
||||
notify-send --icon=dialog-error \
|
||||
--app-name="Workspace Security" \
|
||||
"Access Denied" "Incorrect PIN for workspace $WORKSPACE" \
|
||||
--urgency=normal
|
||||
|
||||
# Log failed attempt
|
||||
echo "$(date): Failed PIN attempt for workspace $WORKSPACE" >> "$LOG_FILE"
|
||||
fi
|
||||
15
home/private_dot_config/hypr/workspaces.conf
Normal file
15
home/private_dot_config/hypr/workspaces.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
# Workspace definitions
|
||||
workspace = 1, name:WEB
|
||||
workspace = 2, name:CODE
|
||||
workspace = 3, name:TERM
|
||||
workspace = 4, name:IDE
|
||||
workspace = 5, name:VM
|
||||
workspace = 6, name:GFX
|
||||
workspace = 7, name:DOC
|
||||
workspace = 8, name:GAME
|
||||
workspace = 9, name:MISC
|
||||
workspace = 10, name:TMP
|
||||
|
||||
# Smart gaps rules
|
||||
workspace = w[tv1], gapsout:0, gapsin:0
|
||||
workspace = f[1], gapsout:0, gapsin:0
|
||||
124
home/private_dot_config/waybar/config.jsonc.tmpl
Normal file
124
home/private_dot_config/waybar/config.jsonc.tmpl
Normal file
@@ -0,0 +1,124 @@
|
||||
// -*- mode: jsonc -*-
|
||||
// Device: {{ .deviceProfile }} ({{ .hostname }})
|
||||
{
|
||||
"height": 30,
|
||||
"spacing": 6,
|
||||
{{- if eq .deviceProfile "desktop" }}
|
||||
"output": ["{{ .primaryMonitor }}"],
|
||||
{{- end }}
|
||||
"position": "bottom",
|
||||
"modules-left": ["hyprland/workspaces"],
|
||||
"modules-center": [],
|
||||
"modules-right": [
|
||||
"custom/vpn",
|
||||
"pulseaudio#spk",
|
||||
"pulseaudio#mic",
|
||||
"network",
|
||||
{{- if .hasBattery }}
|
||||
"battery",
|
||||
"backlight",
|
||||
{{- end }}
|
||||
"cpu",
|
||||
"memory",
|
||||
"clock",
|
||||
"tray"
|
||||
],
|
||||
|
||||
"hyprland/workspaces": {
|
||||
"disable-scroll": true,
|
||||
"warp-on-scroll": false,
|
||||
"show-special": true,
|
||||
"sort-by": "id",
|
||||
"format": "{name}",
|
||||
"on-scroll-up": "hyprctl dispatch workspace e+1",
|
||||
"on-scroll-down": "hyprctl dispatch workspace e-1",
|
||||
"ignore-workspaces": ["(special:)?chrome-sharing-indicator"],
|
||||
"format-icons": {
|
||||
"1": "WEB",
|
||||
"2": "CODE",
|
||||
"3": "TERM",
|
||||
"4": "IDE",
|
||||
"5": "VM",
|
||||
"6": "GFX",
|
||||
"7": "DOC",
|
||||
"8": "GAME",
|
||||
"9": "MISC",
|
||||
"10": "TMP",
|
||||
"special:termius": "s:term",
|
||||
"special:org": "s:org",
|
||||
"special:llm": "s:llm",
|
||||
"default": "WS"
|
||||
}
|
||||
},
|
||||
|
||||
"hyprland/window": {
|
||||
"format": "{title}",
|
||||
"max-length": 60
|
||||
},
|
||||
|
||||
"pulseaudio#spk": {
|
||||
"scroll-step": 2,
|
||||
"format": "VOL {volume}%",
|
||||
"format-muted": "VOL muted",
|
||||
"on-click": "pavucontrol -t 3",
|
||||
"on-click-right": "pactl set-sink-mute @DEFAULT_SINK@ toggle",
|
||||
"on-scroll-up": "~/.config/hypr/scripts/scroll-audio-sink.sh up",
|
||||
"on-scroll-down": "~/.config/hypr/scripts/scroll-audio-sink.sh down"
|
||||
},
|
||||
|
||||
"pulseaudio#mic": {
|
||||
"scroll-step": 2,
|
||||
"format": "{format_source}",
|
||||
"format-source": "MIC {volume}%",
|
||||
"format-source-muted": "MIC muted",
|
||||
"on-click": "pavucontrol -t 4",
|
||||
"on-click-right": "pactl set-source-mute @DEFAULT_SOURCE@ toggle",
|
||||
"on-scroll-up": "pactl set-source-volume @DEFAULT_SOURCE@ +2%",
|
||||
"on-scroll-down": "pactl set-source-volume @DEFAULT_SOURCE@ -2%"
|
||||
},
|
||||
|
||||
"network": {
|
||||
"format-wifi": "NET wifi {essid} {signalStrength}%",
|
||||
"format-ethernet": "NET eth {ipaddr}",
|
||||
"format-linked": "NET link",
|
||||
"format-disconnected": "NET down",
|
||||
"tooltip-format": "{ifname} via {gwaddr}"
|
||||
},
|
||||
|
||||
{{- if .hasBattery }}
|
||||
"battery": {
|
||||
"format": "BAT {capacity}%",
|
||||
"format-charging": "BAT+ {capacity}%",
|
||||
"format-plugged": "BAT= {capacity}%",
|
||||
"format-full": "BAT full",
|
||||
"states": {
|
||||
"warning": 30,
|
||||
"critical": 15
|
||||
}
|
||||
},
|
||||
|
||||
"backlight": {
|
||||
"format": "BRT {percent}%",
|
||||
"on-scroll-up": "brightnessctl s 5%+",
|
||||
"on-scroll-down": "brightnessctl s 5%-"
|
||||
},
|
||||
{{- end }}
|
||||
|
||||
"cpu": { "format": "CPU {usage}%", "interval": 5, "tooltip": false },
|
||||
"memory": { "format": "MEM {}%", "interval": 5 },
|
||||
|
||||
"clock": {
|
||||
"format": "TIME {:%Y-%m-%d %H:%M}",
|
||||
"tooltip-format": "<tt>{calendar}</tt>",
|
||||
"interval": 60
|
||||
},
|
||||
|
||||
"tray": { "spacing": 4 },
|
||||
|
||||
"custom/vpn": {
|
||||
"exec": "~/.config/waybar/scripts/vpn-status.sh",
|
||||
"return-type": "json",
|
||||
"interval": 5,
|
||||
"on-click": "~/.config/hypr/scripts/vpn-switcher.sh"
|
||||
}
|
||||
}
|
||||
101
home/private_dot_config/waybar/scripts/executable_vpn-status.sh
Normal file
101
home/private_dot_config/waybar/scripts/executable_vpn-status.sh
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# VPN status for waybar - outputs JSON
|
||||
|
||||
get_status() {
|
||||
local wg_status=""
|
||||
local nb_status=""
|
||||
local ts_status=""
|
||||
local text="VPN off"
|
||||
local tooltip="No VPN active"
|
||||
local class="disconnected"
|
||||
local has_tunnel=false # true if WG, NetBird, or TS with exit node
|
||||
|
||||
# Check NetBird
|
||||
if command -v netbird &>/dev/null; then
|
||||
nb_json=$(netbird status --json 2>/dev/null)
|
||||
if [[ -n "$nb_json" ]]; then
|
||||
nb_mgmt=$(echo "$nb_json" | jq -r '.management.connected // false')
|
||||
if [[ "$nb_mgmt" == "true" ]]; then
|
||||
nb_ip=$(echo "$nb_json" | jq -r '.netbirdIp // empty' | cut -d'/' -f1)
|
||||
nb_fqdn=$(echo "$nb_json" | jq -r '.fqdn // empty')
|
||||
nb_connected=$(echo "$nb_json" | jq -r '.peers.connected // 0')
|
||||
nb_total=$(echo "$nb_json" | jq -r '.peers.total // 0')
|
||||
|
||||
# Extract network name from fqdn (e.g., box-128-45.lumia.overlay -> lumia)
|
||||
nb_net=$(echo "$nb_fqdn" | awk -F'.' '{print $(NF-1)}')
|
||||
|
||||
nb_status="NB:${nb_net}(${nb_connected}/${nb_total})"
|
||||
tooltip="NetBird: ${nb_fqdn}\nIP: ${nb_ip}\nPeers: ${nb_connected}/${nb_total}"
|
||||
# NetBird mesh connection alone doesn't route all traffic like a VPN
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check WireGuard interfaces (excluding NetBird's wt0)
|
||||
wg_ifaces=$(ip link show type wireguard 2>/dev/null | grep -oP '^\d+: \K[^:]+' | grep -v '^wt')
|
||||
if [[ -n "$wg_ifaces" ]]; then
|
||||
wg_name=$(echo "$wg_ifaces" | head -1)
|
||||
wg_status="WG:$wg_name"
|
||||
[[ -z "$tooltip" || "$tooltip" == "No VPN active" ]] && tooltip=""
|
||||
[[ -n "$tooltip" ]] && tooltip+="\n"
|
||||
tooltip+="WireGuard: $wg_name"
|
||||
has_tunnel=true
|
||||
fi
|
||||
|
||||
# Check Tailscale
|
||||
if command -v tailscale &>/dev/null; then
|
||||
ts_json=$(tailscale status --json 2>/dev/null)
|
||||
if [[ -n "$ts_json" ]]; then
|
||||
ts_state=$(echo "$ts_json" | jq -r '.BackendState // empty')
|
||||
|
||||
if [[ "$ts_state" == "Running" ]]; then
|
||||
# Get tailnet name (domain)
|
||||
tailnet=$(echo "$ts_json" | jq -r '.CurrentTailnet.Name // empty')
|
||||
tailnet_short="${tailnet%.ts.net}"
|
||||
tailnet_short="${tailnet_short%.tail*}"
|
||||
|
||||
# Check for exit node
|
||||
exit_node_id=$(echo "$ts_json" | jq -r '.ExitNodeStatus.ID // empty')
|
||||
exit_online=$(echo "$ts_json" | jq -r '.ExitNodeStatus.Online // false')
|
||||
|
||||
if [[ -n "$exit_node_id" && "$exit_online" == "true" ]]; then
|
||||
exit_host=$(echo "$ts_json" | jq -r '
|
||||
.Peer | to_entries[] | select(.value.ExitNode == true) | .value.HostName // "exit"
|
||||
' 2>/dev/null)
|
||||
[[ -z "$exit_host" || "$exit_host" == "null" ]] && exit_host="exit"
|
||||
|
||||
ts_status="TS:${tailnet_short}→${exit_host}"
|
||||
[[ -n "$tooltip" ]] && tooltip+="\n"
|
||||
tooltip+="Tailscale: ${tailnet}\nExit: ${exit_host}"
|
||||
has_tunnel=true
|
||||
else
|
||||
ts_status="TS:${tailnet_short}"
|
||||
[[ -n "$tooltip" ]] && tooltip+="\n"
|
||||
tooltip+="Tailscale: ${tailnet} (no exit)"
|
||||
# No exit node = no tunnel, don't set has_tunnel
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build final output
|
||||
local parts=()
|
||||
[[ -n "$wg_status" ]] && parts+=("$wg_status")
|
||||
[[ -n "$nb_status" ]] && parts+=("$nb_status")
|
||||
[[ -n "$ts_status" ]] && parts+=("$ts_status")
|
||||
|
||||
if [[ ${#parts[@]} -gt 0 ]]; then
|
||||
text=$(IFS=' | '; echo "${parts[*]}")
|
||||
fi
|
||||
|
||||
# Only green if actual tunnel (WG, NetBird, or TS+exit)
|
||||
if [[ "$has_tunnel" == true ]]; then
|
||||
class="connected"
|
||||
elif [[ ${#parts[@]} -gt 0 ]]; then
|
||||
class="connected-no-tunnel"
|
||||
fi
|
||||
|
||||
printf '{"text": "%s", "tooltip": "%s", "class": "%s"}\n' "$text" "$tooltip" "$class"
|
||||
}
|
||||
|
||||
get_status
|
||||
88
home/private_dot_config/waybar/style.css
Normal file
88
home/private_dot_config/waybar/style.css
Normal file
@@ -0,0 +1,88 @@
|
||||
/* ANSI/BBS monospace */
|
||||
* {
|
||||
font-family: "Terminus", "IBM Plex Mono", "JetBrainsMono Nerd Font",
|
||||
monospace;
|
||||
font-size: 12px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
window#waybar {
|
||||
background: #141414;
|
||||
color: #cccccc;
|
||||
border: 0px solid #cccccc;
|
||||
border-radius: 0;
|
||||
margin: 4px 6px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
/* Boxy ASCII-ish modules */
|
||||
#workspaces button,
|
||||
#custom-vpn,
|
||||
#pulseaudio,
|
||||
#network,
|
||||
#battery,
|
||||
#backlight,
|
||||
#cpu,
|
||||
#memory,
|
||||
#clock,
|
||||
#tray {
|
||||
background: transparent;
|
||||
color: #cccccc;
|
||||
border: 1px dashed #cccccc;
|
||||
border-radius: 0;
|
||||
padding: 1px 6px;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
/* Workspaces: focused/urgent with inverse/red */
|
||||
#workspaces button:hover {
|
||||
background: #001900;
|
||||
}
|
||||
#workspaces button.focused,
|
||||
#workspaces button.active {
|
||||
background: #cccccc;
|
||||
color: #000;
|
||||
border-color: #cccccc;
|
||||
}
|
||||
#workspaces button.urgent {
|
||||
background: #ff0000;
|
||||
color: #000;
|
||||
border-color: #ff0000;
|
||||
}
|
||||
|
||||
/* Specials: dotted border to mark them */
|
||||
#workspaces button.special {
|
||||
border-style: dotted;
|
||||
}
|
||||
|
||||
/* VPN status colors */
|
||||
#custom-vpn.connected {
|
||||
color: #00ff00;
|
||||
border-color: #00ff00;
|
||||
}
|
||||
#custom-vpn.connected-no-tunnel {
|
||||
color: #cccccc;
|
||||
}
|
||||
#custom-vpn.disconnected {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* Battery states */
|
||||
#battery.warning {
|
||||
color: #ffaa00;
|
||||
border-color: #ffaa00;
|
||||
}
|
||||
#battery.critical {
|
||||
color: #ff0000;
|
||||
border-color: #ff0000;
|
||||
}
|
||||
#battery.charging {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
/* Retro tooltip */
|
||||
tooltip {
|
||||
background: #000;
|
||||
color: #cccccc;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
180
install.sh
Executable file
180
install.sh
Executable file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Dotfiles Bootstrap & Management Script
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
||||
|
||||
DISTRO="unknown"
|
||||
DOTFILES_DIR="$HOME/dotfiles"
|
||||
|
||||
detect_distro() {
|
||||
if [[ -f /etc/arch-release ]]; then
|
||||
echo "arch"
|
||||
elif command -v rpm-ostree &>/dev/null; then
|
||||
echo "fedora-atomic"
|
||||
elif [[ -f /etc/fedora-release ]]; then
|
||||
echo "fedora"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
install_chezmoi() {
|
||||
if command -v chezmoi &>/dev/null; then
|
||||
log_ok "chezmoi already installed: $(chezmoi --version | head -1)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing chezmoi..."
|
||||
case "$DISTRO" in
|
||||
arch)
|
||||
sudo pacman -S --noconfirm chezmoi
|
||||
;;
|
||||
fedora)
|
||||
sudo dnf install -y chezmoi
|
||||
;;
|
||||
fedora-atomic)
|
||||
if command -v brew &>/dev/null; then
|
||||
brew install chezmoi
|
||||
else
|
||||
mkdir -p ~/.local/bin
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b ~/.local/bin
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
mkdir -p ~/.local/bin
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b ~/.local/bin
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
;;
|
||||
esac
|
||||
log_ok "chezmoi installed"
|
||||
}
|
||||
|
||||
setup_secrets() {
|
||||
local secrets_dir="$HOME/secrets"
|
||||
if [[ ! -d "$secrets_dir" ]]; then
|
||||
log_info "Creating secrets directory..."
|
||||
mkdir -p "$secrets_dir/vpn"
|
||||
chmod 700 "$secrets_dir"
|
||||
log_ok "Created $secrets_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
init_chezmoi() {
|
||||
local repo_url="${1:-}"
|
||||
if [[ -n "$repo_url" ]]; then
|
||||
log_info "Initializing from: $repo_url"
|
||||
chezmoi init "$repo_url"
|
||||
elif [[ -d "$DOTFILES_DIR/home" ]]; then
|
||||
log_info "Initializing from local: $DOTFILES_DIR/home"
|
||||
chezmoi init --source="$DOTFILES_DIR/home" --prompt=false
|
||||
else
|
||||
log_error "No repo URL and ~/dotfiles not found"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
is_initialized() {
|
||||
[[ -f "$HOME/.config/chezmoi/chezmoi.toml" ]] && chezmoi source-path &>/dev/null
|
||||
}
|
||||
|
||||
check_config_outdated() {
|
||||
# Check if config template has changed
|
||||
if chezmoi diff 2>&1 | grep -q "config file template has changed"; then
|
||||
log_warn "Config template changed, regenerating..."
|
||||
chezmoi init --source="$DOTFILES_DIR/home" --prompt=false
|
||||
log_ok "Config regenerated"
|
||||
fi
|
||||
}
|
||||
|
||||
show_menu() {
|
||||
echo ""
|
||||
echo " 1) diff - Show pending changes"
|
||||
echo " 2) apply - Apply dotfiles"
|
||||
echo " 3) reinit - Re-initialize (change profile)"
|
||||
echo " 4) update - Pull latest and apply"
|
||||
echo " 5) edit - Edit a managed file"
|
||||
echo " q) quit"
|
||||
echo ""
|
||||
}
|
||||
|
||||
manage_menu() {
|
||||
while true; do
|
||||
show_menu
|
||||
read -rp "Choice: " choice
|
||||
echo ""
|
||||
case "$choice" in
|
||||
1|diff)
|
||||
chezmoi diff | less -R
|
||||
;;
|
||||
2|apply)
|
||||
chezmoi apply -v
|
||||
log_ok "Applied!"
|
||||
;;
|
||||
3|reinit)
|
||||
chezmoi init --force
|
||||
;;
|
||||
4|update)
|
||||
chezmoi update -v
|
||||
log_ok "Updated!"
|
||||
;;
|
||||
5|edit)
|
||||
read -rp "File to edit (e.g. ~/.config/hypr/hyprland.conf): " file
|
||||
[[ -n "$file" ]] && chezmoi edit "$file"
|
||||
;;
|
||||
q|quit|exit)
|
||||
break
|
||||
;;
|
||||
*)
|
||||
log_warn "Invalid choice"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
DISTRO=$(detect_distro)
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Dotfiles Manager "
|
||||
echo "========================================"
|
||||
echo ""
|
||||
log_info "Distro: $DISTRO | Host: $(hostname)"
|
||||
|
||||
install_chezmoi
|
||||
setup_secrets
|
||||
|
||||
if is_initialized; then
|
||||
log_ok "Chezmoi already initialized"
|
||||
check_config_outdated
|
||||
manage_menu
|
||||
else
|
||||
init_chezmoi "${1:-}"
|
||||
echo ""
|
||||
log_ok "Bootstrap complete!"
|
||||
echo ""
|
||||
read -rp "Apply dotfiles now? [Y/n] " apply
|
||||
if [[ ! "$apply" =~ ^[Nn]$ ]]; then
|
||||
chezmoi apply -v
|
||||
log_ok "Applied!"
|
||||
else
|
||||
echo "Run 'chezmoi diff' to review, 'chezmoi apply' to install"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user