Compare commits

..

No commits in common. "next" and "main" have entirely different histories.
next ... main

47 changed files with 2757 additions and 257 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.data/
target/

View file

@ -1,11 +0,0 @@
super_apply() {
pacman -S --needed sudo reflector
if ! grep -q "Reflector" /etc/pacman.d/mirrorlist; then
cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
reflector -c NL -f 10 --threads 4 --save /etc/pacman.d/mirrorlist
else
echo "err: reflector already executed -- skipping..."
fi
cp files/pacman.conf /etc/pacman.conf
}

View file

@ -1,7 +0,0 @@
#!/bin/sh
describe="Install stuff on the system!"
super_apply() {
cp files/sudoers /etc/sudoers
}

View file

@ -1,31 +0,0 @@
[options]
HoldPkg = pacman glibc yay
Architecture = auto
Color
CheckSpace
ParallelDownloads = 5
SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
#[testing]
#Include = /etc/pacman.d/mirrorlist
[core]
Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
#[community-testing]
#Include = /etc/pacman.d/mirrorlist
[community]
Include = /etc/pacman.d/mirrorlist
#[multilib-testing]
#Include = /etc/pacman.d/mirrorlist
[multilib]
Include = /etc/pacman.d/mirrorlist

View file

@ -1,4 +0,0 @@
root ALL=(ALL:ALL) ALL
%wheel ALL=(ALL:ALL) ALL
%root ALL=(ALL:ALL) NOPASSWD: ALL
@includedir /etc/sudoers.d

View file

@ -1,7 +0,0 @@
super_apply() {
pacman -S --needed zsh cmake
}
super_undo() {
pacman -R zsh
}

View file

@ -1,41 +0,0 @@
#!/bin/sh
describe="Install zsh and oh-my-zsh!"
scripts="@distro @self"
super_apply() {
[ -d /tmp/fastfetch ] || git clone https://github.com/LinusDierheimer/fastfetch /tmp/fastfetch
if ! command -v fastfetch &> /dev/null; then
cd /tmp/fastfetch
mkdir -p build
cd build
cmake ..
cmake --build . --target fastfetch
mv fastfetch /usr/local/bin/fastfetch
fi
usermod $USER --shell /bin/zsh
}
super_undo() {
usermod $USER --shell /bin/bash
}
apply() {
if [ ! -d "$HOME/.oh-my-zsh" ]; then
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
PL_DIR=${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
[ -d "$PL_DIR" ] || git clone https://github.com/zsh-users/zsh-autosuggestions $PL_DIR
PL_DIR=${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
[ -d "$PL_DIR" ] || git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $PL_DIR
fi
[ -e "$HOME/.zshrc" ] || ln files/.zshrc $HOME/.zshrc
}
undo() {
unlink $HOME/.zshrc
rm -rf $HOME/.oh-my-zsh
}

View file

@ -1,23 +0,0 @@
export GPG_TTY=$(tty)
# paths
export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"
# ZSH
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="afowler"
plugins=(git docker docker-compose node zsh-autosuggestions zsh-syntax-highlighting)
source $ZSH/oh-my-zsh.sh
# ALIASES
alias open="xdg-open"
alias nobeep="sudo modprobe -r pcspkr"
alias s="title $HOST && cmatrix -rs && clear"
alias q="exit"
# COSMETICS
fastfetch
export PATH="${PATH}:/home/${USER}/.local/bin"
export SSH_AUTH_SOCK="/run/user/1000/keyring/ssh"

View file

@ -1,7 +0,0 @@
super_apply() {
pacman -S --needed vim
}
super_undo() {
echo "we never uninstall vim -_-"
}

View file

@ -1,14 +0,0 @@
#!/bin/sh
describe="Install vim and it's stuff"
scripts="@distro @self"
apply() {
[ -e "$HOME/.vimrc" ] || ln files/.vimrc $HOME/.vimrc
[ -e "$HOME/.ideavimrc" ] || ln files/.ideavimrc $HOME/.ideavimrc
}
undo() {
unlink $HOME/.vimrc
unlink $HOME/.ideavimrc
}

View file

@ -1,2 +0,0 @@
set number
set relativenumber

View file

@ -1,7 +0,0 @@
super_apply() {
pacman -S --needed alacritty
}
super_undo() {
pacman -R alacritty
}

View file

@ -1,12 +0,0 @@
#!/bin/sh
describe="Installs alacritty and configs"
scripts="@distro @self"
apply() {
[ -e "$HOME/.config/alacritty/alacritty.yml" ] || ln files/alacritty.yml $HOME/.config/alacritty/alacritty.yml
}
undo() {
unlink $HOME/.config/alacritty/alacritty.yml
}

View file

@ -1,6 +0,0 @@
cursor:
style:
shape: 'Block'
blinking: 'On'
blink_interval: 500

28
crates/common/crate.toml Normal file
View file

@ -0,0 +1,28 @@
[crate]
name = "common"
description = "A versatile crate for managing dotfiles and system configurations."
[[packages]]
name = "git"
description = "Version control system"
[[packages]]
name = "nvim"
description = "Text editor for creating and editing files"
distro_name_mapping = { pacman = "neovim" }
[[packages]]
name = "zsh"
description = "Shell designed for interactive use"
[[actions.command]]
user = "root"
command = "sh ./scripts/pam_wheel.sh"
description = "Pam wheel setup"
[[actions.command]]
command = "git config --global user.name Strix && git config --global user.email strix@saluco.nl"
description = "git setup"
[metadata]
repository = "https://git.saluco.nl/strix/dotfiles"

View file

@ -0,0 +1,4 @@
if [ -f /usr/lib/security/pam_wheel.so ] && ! grep -q "# pam_wheel.so added" /etc/pam.d/su; then
echo "auth sufficient pam_wheel.so trust use_uid" > /etc/pam.d/su
echo "# pam_wheel.so added" > /etc/pam.d/su
fi

62
crates/i3/crate.toml Normal file
View file

@ -0,0 +1,62 @@
[crate]
name = "i3"
description = "install and setup i3"
[[packages]]
name = "i3"
[[packages]]
name = "i3lock"
[[packages]]
name = "i3status"
[[packages]]
name = "libpulse"
require = ["distro:arch"]
[[packages]]
name = "brightnessctl"
[[packages]]
name = "xss-lock"
[[packages]]
name = "dex"
[[packages]]
name = "maim"
[[packages]]
name = "dmenu"
[[packages]]
name = "gnome-keyring"
[[packages]]
name = "feh"
[[packages]]
name = "picom"
[[actions.link]]
src = "files/i3config"
dest = "~/.config/i3/config"
[[actions.link]]
src = "files/i3status_config"
dest = "~/.config/i3status/config"
[[actions.link]]
src = "files/picom.conf"
dest = "~/.config/picom.conf"
[[actions.command]]
user = "root"
command = "[ -d '/etc/X11/xorg.conf.d' ] || mkdir -p /etc/X11/xorg.conf.d/"
description = "ensure /etc/X11/xorg.conf.d"
[[actions.command]]
user = "root"
command = "cp files/touchpad.conf /etc/X11/xorg.conf.d/40-touchpad.conf"
description = "copy touchpad config to /etc/X11/xorg.conf.d"

206
crates/i3/files/i3config Normal file
View file

@ -0,0 +1,206 @@
#######################
## Strix' i3 config ##
## Mar 22, 2023 ##
## mutation: 1m ##
#######################
## Mostly similar to i3's config yet differs
# Useful variables:
# This section probably gets changed a lot
# ---
set $terminal alacritty
set $screen_lock i3lock -c "#111111" --nofork
set $appmenu i3-dmenu-desktop
set $screenshot maim -s | xclip -selection clipboard -t image/png
set $font pango:monospace 8
# Startup Applications:
# Things that will startup when starting i3.
# ---
exec_always --no-startup-id dex --autostart --environment i3
exec_always --no-startup-id nm-applet
exec_always --no-startup-id xss-lock --transfer-sleep-lock -- $screen_lock
exec_always --no-startup-id gnome-keyring-daemon --start --components=ssh,secrets,pkcs11
exec_always --no-startup-id feh --bg-fill ~/Pictures/wallpaper.jpg
exec_always --no-startup-id picom
# Gaps & Borders:
# ---
gaps inner 10
smart_gaps on
default_border pixel 1
hide_edge_borders smart_no_gaps
# Extra variables:
# Only need changing for very specific installations.
# ---
set $audio_volume_up XF86AudioRaiseVolume
set $audio_volume_down XF86AudioLowerVolume
set $audio_mute XF86AudioMute
set $audio_mute_mic XF86AudioMicMute
set $brightness_up XF86MonBrightnessUp
set $brightness_down XF86MonBrightnessDown
set $ws_1 "1:main"
set $ws_2 "2:term"
set $ws_3 "3:docs"
set $ws_4 "4:mail"
set $ws_5 "5"
set $ws_6 "6"
set $ws_7 "7"
set $ws_8 "8"
set $ws_9 "9:bg"
set $ws_10 "10:misc"
set $meta_refresh_statusbar killall -SIGUSR1 i3status
bar {
strip_workspace_numbers yes
status_command i3status
}
# END OF CONFIG
set $mod Mod4
set $alt Mod1
# Customization
# class border backgr. text indic. child_border
client.focused #81A1C1 #81A1C1 #ffffff #D8DEE9
client.focused_inactive #2E3440 #2E3440 #88C0D0 #454948
client.unfocused #2E3440 #2E3440 #88C0D0 #454948
client.urgent #D08770 #3B4252 #ffffff #268BD2
client.placeholder #000000 #0c0c0c #ffffff #000000
client.background #3B4252
# Generic
floating_modifier $mod
tiling_drag modifier titlebar
font $font
# Open a terminal
bindsym $mod+Return exec $terminal
# Kill current window
bindsym $mod+$alt+q kill
# Open app menu
bindsym $mod+d exec --no-startup-id $appmenu
# Lock screen
bindsym $mod+l exec --no-startup-id $screen_lock
# Screenshots
bindsym Print exec --no-startup-id $screenshot
# Audio
bindsym $audio_volume_up exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $meta_refresh_statusbar
bindsym $audio_volume_down exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $meta_refresh_statusbar
bindsym $audio_mute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $meta_refresh_statusbar
bindsym $audio_mute_mic exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle && $meta_refresh_statusbar
# Brightness
bindsym $brightness_up exec --no-startup-id brightnessctl set +5% && $meta_refresh_statusbar
bindsym $brightness_down exec --no-startup-id brightnessctl set 5%- && $meta_refresh_statusbar
# Change focused window
bindsym $mod+Up focus up
bindsym $mod+Down focus down
bindsym $mod+Left focus left
bindsym $mod+Right focus right
# Move focused window
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Right move right
# Splits
bindsym $mod+Shift+h split h
bindsym $mod+Shift+v split v
# Change container layout
bindsym $mod+Shift+s layout stacking
bindsym $mod+Shift+t layout tabbed
bindsym $mod+Shift+d layout toggle split
# Make current window fullscreen
bindsym $mod+Shift+f fullscreen toggle
# Floating stuff
bindsym $mod+space focus mode_toggle
bindsym $mod+Shift+space floating toggle
# Scratchpad
bindsym $mod+Shift+minus move scratchpad
bindsym $mod+minus scratchpad show
# Switch to workspace
bindsym $mod+1 workspace $ws_1
bindsym $mod+2 workspace $ws_2
bindsym $mod+3 workspace $ws_3
bindsym $mod+4 workspace $ws_4
bindsym $mod+5 workspace $ws_5
bindsym $mod+6 workspace $ws_6
bindsym $mod+7 workspace $ws_7
bindsym $mod+8 workspace $ws_8
bindsym $mod+9 workspace $ws_9
bindsym $mod+0 workspace $ws_10
# Move focused container to workspace
bindsym $mod+Shift+1 move container to workspace $ws_1
bindsym $mod+Shift+2 move container to workspace $ws_2
bindsym $mod+Shift+3 move container to workspace $ws_3
bindsym $mod+Shift+4 move container to workspace $ws_4
bindsym $mod+Shift+5 move container to workspace $ws_5
bindsym $mod+Shift+6 move container to workspace $ws_6
bindsym $mod+Shift+7 move container to workspace $ws_7
bindsym $mod+Shift+8 move container to workspace $ws_8
bindsym $mod+Shift+9 move container to workspace $ws_9
bindsym $mod+Shift+0 move container to workspace $ws_10
# Move through workspaces
bindsym $mod+$alt+Left workspace prev
bindsym $mod+$alt+Right workspace next
# WM stuff
# reload i3 config
bindsym $mod+$alt+c reload
# restart i3
bindsym $mod+$alt+r restart
# exit i3
bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'Exit i3?' -B 'Yes, exit i3' 'i3-msg exit'"
# Modes
bindsym $mod+r mode resize
mode "resize" {
bindsym $nav_left resize shrink width 10 px or 10 ppt
bindsym $nav_down resize grow height 10 px or 10 ppt
bindsym $nav_up resize shrink height 10 px or 10 ppt
bindsym $nav_right resize grow width 10 px or 10 ppt
bindsym Left resize shrink width 10 px or 10 ppt
bindsym Down resize grow height 10 px or 10 ppt
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
bindsym Return mode "default"
bindsym Escape mode "default"
bindsym $mod+r mode "default"
}
bindsym $mod+s mode "session"
mode "session" {
bindsym l exec --no-startup-id $lock, mode "default"
bindsym e exec --no-startup-id i3-msg exit, mode "default"
bindsym Shift+r exec --no-startup-id systemctl reboot, mode "default"
bindsym Shift+s exec --no-startup-id systemctl poweroff -i, mode "default"
bindsym Return mode "default"
bindsym Escape mode "default"
bindsym $mod+s mode "default"
}

View file

@ -0,0 +1,57 @@
general {
output_format = "i3bar"
colors = true
interval = 5
}
order += "ipv6"
order += "wireless wlp58s0"
order += "battery 0"
order += "disk /"
order += "memory"
order += "load"
order += "tztime local"
wireless wlp58s0 {
format_up = "W: (%quality at %essid, %bitrate) %ip"
format_down = "W: down"
}
battery 0 {
format = "%status %percentage %remaining %emptytime"
format_down = "No battery"
status_chr = "⚡ CHR"
status_bat = "🔋 BAT"
status_unk = "? UNK"
status_full = "☻ FULL"
path = "/sys/class/power_supply/BAT%d/uevent"
low_threshold = 10
}
tztime local {
format = "%Y-%m-%d %H:%M:%S"
hide_if_equals_localtime = false
}
tztime berlin {
format = "%Y-%m-%d %H:%M:%S %Z"
timezone = "Europe/Berlin"
}
load {
format = "%5min"
}
memory {
format = "%used"
threshold_degraded = "10%"
format_degraded = "MEMORY: %free"
}
disk "/" {
format = "%free"
}
read_file uptime {
path = "/proc/uptime"
}

View file

@ -0,0 +1,9 @@
inactive-opacity = 0.75;
blur: {
method = "box";
size = 10;
background = false;
background-frame = false;
background-fixed = false;
}

View file

@ -0,0 +1,8 @@
Section "InputClass"
Identifier "libinput touchpad catchall"
MatchIsTouchpad "on"
MatchDevicePath "/dev/input/event*"
Driver "libinput"
Option "NaturalScrolling" "True"
Option "Tapping" "on"
EndSection

22
crates/ssh/config Normal file
View file

@ -0,0 +1,22 @@
## neb servers
# hydrogen red helix
Host H
Hostname hydrogen.red.helix.saluco.nl
# argon red helix
Host A
Hostname argon.red.helix.saluco.nl
# iron red sphere
Host I
Hostname iron.red.sphere.saluco.nl
## utility servers
Host git
Hostname git.saluco.nl
User git
Host github
Hostname github.com
User git

10
crates/ssh/crate.toml Normal file
View file

@ -0,0 +1,10 @@
[crate]
name = "ssh"
description = "fixes the ssh files"
[[actions.command]]
command = "ls -lah"
[[actions.link]]
src = "./config"
dest = "~/.ssh/config"

15
crates/vim/crate.toml Normal file
View file

@ -0,0 +1,15 @@
[crate]
name = "vim"
description = "install & configure vim"
[[packages]]
name = "neovim"
[[actions.link]]
src = "./ideavimrc"
dest = "~/.ideavimrc"
[[actions.link]]
src = "./init.vim"
dest = "~/.config/nvim/init.vim"

27
crates/vim/init.vim Normal file
View file

@ -0,0 +1,27 @@
" vim preferences
set number
set relativenumber
syntax on
" No arrow keys
noremap <Up> <Nop>
noremap <Down> <Nop>
noremap <Left> <Nop>
noremap <Right> <Nop>
" ensure vim-plug
let data_dir = has('nvim') ? stdpath('data') . '/site' : '~/.vim'
if empty(glob(data_dir . '/autoload/plug.vim'))
silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
endif
call plug#begin()
Plug 'ThePrimeagen/vim-be-good'
Plug 'machakann/vim-highlightedyank'
Plug 'tpope/vim-commentary'
Plug 'wellle/targets.vim'
Plug 'vim-scripts/loremipsum'
call plug#end()

14
crates/zsh/crate.toml Normal file
View file

@ -0,0 +1,14 @@
[crate]
name = "zsh"
description = "install & configure zsh"
[[packages]]
name = "zsh"
[[actions.command]]
command = "sh ./setup-omzsh.sh"
description = "oh-my-zsh setup"
[[actions.link]]
src = "./zshrc"
dest = "~/.zshrc"

10
crates/zsh/setup-omzsh.sh Normal file
View file

@ -0,0 +1,10 @@
if [ ! -d "$HOME/.oh-my-zsh" ]; then
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
PL_DIR=${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
[ -d "$PL_DIR" ] || git clone https://github.com/zsh-users/zsh-autosuggestions $PL_DIR
PL_DIR=${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
[ -d "$PL_DIR" ] || git clone https://github.com/zsh-users/zsh-syntax-highlighting.git $PL_DIR
fi
[ -f "$HOME/.zshrc" ] && unlink $HOME/.zshrc

44
crates/zsh/zshrc Normal file
View file

@ -0,0 +1,44 @@
#!/bin/zsh
# Raine's .zshrc
if [ "$PROFILEINC" = "1" ]; then
. ~/.profile
fi
export EDITOR=vim
export GPG_TTY=$(tty)
export PATH="$PATH:$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$HOME/.local/bin"
# oh-my-zsh init
if [ -f "$HOME/.oh-my-zsh/oh-my-zsh.sh" ]; then
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="afowler"
plugins=(git docker docker-compose node zsh-autosuggestions zsh-syntax-highlighting zsh-cargo-completion)
source $ZSH/oh-my-zsh.sh
else
echo "oh-my-zsh not detected :("
PS1="$(whoami)@${HOSTNAME:-$(hostname)} $ "
fi
update_dotfiles() {
cd $HOME/.dotfiles
git pull
cd - &> /dev/null
}
container() {
docker run \
-ti \
--rm \
--name tmp-$(id -u)-$(openssl rand -hex 8) \
--network ${CONTAINER_NETWORK:-internal} \
alpine \
ash
}
[ -f "$HOME/.config/i3/shortcuts-i3.sh" ] && . ~/.config/i3/shortcuts-i3.sh
alias q="exit"
alias vim="nvim"
alias vi="nvim"

85
dot
View file

@ -1,85 +0,0 @@
#!/bin/sh
ask=0
is_function() {
type "$1" 2> /dev/null | sed "s/$1//" | grep -qwi function
}
curr_distro() {
cat /etc/os-release | grep -G "^ID=" | sed 's/ID=//'
}
include() {
[ -f "$1" ] || return 1
. $1
return 0
}
func() {
if is_function $(echo "super_$2"); then
sudo sh -c ". $1 && super_$2"
fi
is_function $2 && $2
}
get_command() {
case $1 in
a*)
printf "apply"
;;
u*)
printf "undo"
;;
*)
echo "err: not supported" >&2
exit 1
;;
esac
}
# only run this *in crate dir*
run_crate() {
include ./crate.sh || exit 1
if [ -n "$describe" ]; then
echo "desc($(basename $PWD)): $describe"
fi
cmd=$(get_command $1)
scripts=${scripts:-"@self @distro"}
for s in $scripts; do
echo "exec($(basename $PWD)): $s/$cmd"
case $s in
@self)
func ./crate.sh $cmd
;;
@distro)
unset -f super_$cmd
unset -f $cmd
include ./crate.$(curr_distro).sh
func ./crate.$(curr_distro).sh $cmd
unset -f super_$cmd
unset -f $cmd
include ./crate.sh
;;
*)
sh $s
;;
esac
done
}
if [ $# -eq 2 ]; then
[ -d crates/*$1 ] || exit 1
cd crates/*$1
run_crate $2
cd ../..
else
[ $# -eq 0 ] && exit 2
for crate in $(find ./crates -mindepth 1 -maxdepth 1 -type d | sort); do
cd $crate
run_crate $1
cd ../..
done
fi

1332
sync-runner/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

23
sync-runner/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "sync-runner"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.29", features = ["derive"] }
colored = "3.0.0"
dbus = "0.9.7"
execute = "0.2.13"
fern = "0.7.1"
git2 = "0.20.0"
glob = "0.3.2"
hex = "0.4.3"
lazy_static = "1.5.0"
log = "0.4.26"
regex = "1.11.1"
resolve-path = "0.1.0"
serde = { version = "1.0.218", features = ["serde_derive"] }
sha2 = "0.10.8"
shellexpand = "3.1.0"
toml = "0.8.20"
whoami = "1.5.2"

10
sync-runner/src/action.rs Normal file
View file

@ -0,0 +1,10 @@
use clap::Parser;
#[derive(Parser, Debug)]
pub enum Action {
/// Sync your device with dotfiles repository
Sync {
#[arg(short, long)]
config_path: Option<String>,
},
}

View file

@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Daemon {
/// interval in minutes
pub interval: u64,
}

View file

@ -0,0 +1,21 @@
#![allow(unused)]
use std::{collections::HashMap, fs::read_to_string};
use serde::{Deserialize, Serialize};
mod daemon;
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub title: String,
pub daemon: daemon::Daemon,
pub source: HashMap<String, crate::source::Source>
}
impl Config {
pub fn parse(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
Ok(toml::from_str::<Config>(&read_to_string(path)?)?)
}
}

View file

@ -0,0 +1,102 @@
use std::{
collections::HashMap,
fs::{self, remove_file},
process::{Command, Stdio},
time::Duration,
};
use dbus::{
arg::Variant,
blocking::{BlockingSender, Connection},
Message,
};
use log::{debug, error, info, trace};
use resolve_path::PathResolveExt;
use serde::{Deserialize, Serialize};
use crate::tags::Tag;
#[derive(Serialize, Deserialize, Debug)]
pub struct Actions {
/// command actions
#[serde(rename = "command")]
pub commands: Option<Vec<CommandAction>>,
/// link actions
#[serde(rename = "link")]
pub links: Option<Vec<LinkAction>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CommandAction {
#[serde(default = "whoami::username")]
user: String,
command: String,
pub description: Option<String>,
pub require: Option<Vec<Tag>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LinkAction {
pub src: String,
pub dest: String,
overwrite: Option<bool>,
}
impl CommandAction {
pub fn new<S: Into<String>>(command: S, user: S) -> Self {
Self {
command: command.into(),
user: user.into(),
description: None,
require: None,
}
}
pub fn run(&self) -> Result<i32, Box<dyn std::error::Error>> {
trace!("running \"{}\" as {}...", &self.command, &self.user);
if self.user != whoami::username() {
Ok(Command::new("sudo")
.arg("-u")
.arg(&self.user)
.arg("--")
.arg("sh")
.arg("-c")
.arg(&self.command)
.status()?
.code()
.unwrap_or(1))
} else {
Ok(Command::new("sh")
.arg("-c")
.arg(&self.command)
.status()?
.code()
.unwrap_or(1))
}
}
}
impl LinkAction {
pub fn link(&self) -> Result<(), Box<dyn std::error::Error>> {
trace!(
"linking from {:?} to {:?}...",
&self.src.resolve(),
&self.dest.resolve()
);
if let Ok(existing) = fs::read_link(&self.dest.resolve()) {
if existing == self.src.resolve() {
debug!("link OK");
return Ok(());
} else {
if self.overwrite.unwrap_or(false) == true {
debug!("removing {}...", self.dest);
remove_file(self.dest.resolve())?;
} else {
return Err("Destination is linked to a different path".into());
}
}
}
std::os::unix::fs::symlink(&self.src.resolve(), &self.dest.resolve())?;
Ok(())
}
}

View file

@ -0,0 +1,27 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use super::{action::Actions, package::Package};
#[derive(Serialize, Deserialize, Debug)]
pub struct CrateManifest {
#[serde(rename = "crate")]
pub crate_info: CrateInfo,
pub packages: Option<Vec<Package>>,
pub actions: Option<Actions>,
pub metadata: Option<Metadata>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CrateInfo {
pub name: String,
pub description: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Metadata {
pub homepage: Option<String>,
pub repository: Option<String>,
pub issues: Option<String>,
}

View file

@ -0,0 +1,62 @@
use log::{error, info, warn};
use manifest::CrateManifest;
use package::PackageManager;
use serde::{Deserialize, Serialize};
pub mod action;
pub mod manifest;
pub mod package;
pub struct Crate {
pub manifest: CrateManifest,
}
impl Crate {
pub fn from_toml_str(string: &str) -> Result<Self, toml::de::Error> {
Ok(Crate {
manifest: toml::from_str::<manifest::CrateManifest>(string)?,
})
}
pub fn install_packages(&self) -> bool {
if let Some(packages) = &self.manifest.packages {
info!("Installing packages...");
let pkgs: Vec<String> = packages
.iter()
.map(|p| p.get_correct_package_name())
.collect();
info!(target: "item", "pkgs: {}", pkgs.join(", "));
if let Some(pm) = PackageManager::get_available() {
pm.install(pkgs).is_ok()
} else {
false
}
} else {
false
}
}
pub fn run_actions(&self) -> Result<(), Box<dyn std::error::Error>> {
if let Some(actions) = &self.manifest.actions {
if let Some(commands) = &actions.commands {
for command in commands {
info!(
"Running {}...",
&command.description.clone().unwrap_or("action".to_string())
);
command.run()?;
}
}
if let Some(links) = &actions.links {
for link in links {
info!("Link {} -> {}...", link.src, link.dest);
if let Err(e) = link.link() {
error!("could not link: {e}");
continue;
}
}
}
}
Ok(())
}
}

View file

@ -0,0 +1,78 @@
use std::{collections::HashMap, io};
use log::{error, info};
use serde::{Deserialize, Serialize};
use super::action::CommandAction;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub enum PackageManager {
#[serde(rename = "pacman")]
Pacman,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Package {
pub name: String,
pub distro_name_mapping: Option<HashMap<PackageManager, String>>,
}
impl PackageManager {
pub fn install(&self, packages: Vec<String>) -> Result<bool, Box<dyn std::error::Error>> {
match &self {
PackageManager::Pacman => {
CommandAction::new(
format!("pacman -S --noconfirm {}", packages.join(" ")),
"root".to_string(),
)
.run()?;
}
}
Ok(true)
}
pub fn get_available() -> Option<Self> {
Self::from_str(match whoami::distro().as_str() {
"Manjaro Linux" => "pacman",
_ => "unknown"
})
}
pub fn from_str(distro: &str) -> Option<Self> {
match distro {
"pacman" => Some(PackageManager::Pacman),
_ => None,
}
}
}
impl Package {
pub fn get_correct_package_name(&self) -> String {
if let Some(pm) = PackageManager::get_available() {
if let Some(mappings) = &self.distro_name_mapping {
if let Some(name) = mappings.get(&pm) {
name.to_string()
} else {
self.name.clone()
}
} else {
self.name.clone()
}
} else {
self.name.clone()
}
}
pub fn install(&self) -> Result<bool, Box<dyn std::error::Error>> {
if let Some(pm) = PackageManager::get_available() {
pm.install(vec![self.get_correct_package_name()])?;
} else {
error!("no package manager found...");
return Err(Box::new(io::Error::new(
io::ErrorKind::NotFound,
"package manager not found",
)));
}
Ok(false)
}
}

View file

@ -0,0 +1,64 @@
use std::env;
use colored::Colorize;
use fern::Dispatch;
use log::{Record, SetLoggerError};
fn format_regular<S: Into<String>>(log: S, record: &Record) -> String {
let log = log.into();
let line_prefix = |line: String, extend: bool| {
let prefix = if extend {
match record.level() {
log::Level::Trace => " ]".bright_blue(),
log::Level::Debug => " ?".green(),
log::Level::Info => " >".blue(),
log::Level::Warn => " #".yellow(),
log::Level::Error => " !".red(),
}.to_string()
} else {
match record.level() {
log::Level::Trace => "[TRACE]".bright_blue().italic(),
log::Level::Debug => "??".green(),
log::Level::Info => "=>".blue(),
log::Level::Warn => "##".yellow(),
log::Level::Error => "!!".red().bold()
}.to_string()
};
return format!("{} {}", prefix, line);
};
let mut lines = log.lines().peekable();
let mut output = match lines.peek() {
Some(_line) => line_prefix(lines.next().unwrap().to_string(), false),
None => return "".to_string(),
};
for line in lines {
output.push_str(&*format!("\n{}", line_prefix(line.to_string(), true)));
}
output
}
pub fn setup_logger() -> Result<(), SetLoggerError> {
Dispatch::new()
.format(|out, message, record| {
match record.metadata().target() {
// command output logging
"command:stdout" => out.finish(format_args!("{} {}", ">>".cyan(), message.to_string())),
"command:stderr" => out.finish(format_args!("{} {}", ">>".red(), message.to_string())),
// this target means, it's an item and not a log.
"item" => out.finish(format_args!("{} {}", "*".blue(), message.to_string())),
// default logging
_ => out.finish(format_args!("{}", format_regular(message.to_string(), record))),
}
})
.level(
env::var("SYNCR_LOG_LEVEL")
.unwrap_or_else(|_| "info".to_string())
.parse()
.unwrap_or(log::LevelFilter::Info),
)
.chain(std::io::stdout())
.apply()
}

87
sync-runner/src/main.rs Normal file
View file

@ -0,0 +1,87 @@
use std::{
env::set_current_dir,
fs::{exists, read_to_string, File},
path::{absolute, Path},
process::{exit, Stdio},
};
use action::Action;
use cfg::Config;
use clap::Parser;
use colored::Colorize;
use execute::{command_args, Execute};
use log::{debug, error, info, trace, warn};
use prelude::abspath;
mod action;
mod cfg;
mod crates;
mod logging;
mod prelude;
mod source;
mod tags;
fn main() -> Result<(), Box<dyn std::error::Error>> {
logging::setup_logger()?;
info!(target: "item", "user: {}", whoami::username());
info!(target: "item", "distro: {}", whoami::distro());
let action = Action::parse();
match action {
Action::Sync { config_path } => {
trace!("fetching config dir... {config_path:?}");
if let Some(config_path) = abspath(&config_path.unwrap_or("~/.syncr".into())) {
trace!("setting config dir as cwd... {config_path}");
set_current_dir(config_path)?;
}
let config = Config::parse(&abspath("./syncr.toml").unwrap())?;
info!("syncing \"{}\"...", config.title.bold());
info!("updating sources...");
let mut available_sources = vec![];
for (name, source) in &config.source {
debug!("checking {name}...");
if !source.available() {
warn!("source \"{name}\" unavailable.");
} else {
info!("source \"{name}\" available!");
available_sources.push(source);
}
}
if available_sources.len() == 0 {
error!("{}", "sync impossible; no sources.".bold());
exit(1);
}
let oldpwd = absolute(".")?;
for source in available_sources {
// cd to source dir
source.go_to_dir()?;
for (mut path, c) in source.get_crates()? {
path.pop();
set_current_dir(absolute(path)?)?;
info!("Syncing crate: {}...", c.manifest.crate_info.name);
c.install_packages();
if let Err(e) = c.run_actions() {
error!("action failed: {e}");
}
set_current_dir(&oldpwd)?; // i hate this but im lazy okay
source.go_to_dir()?;
}
}
set_current_dir(oldpwd)?;
info!("Completed sync.");
}
_ => {
println!("{action:#?}");
}
}
Ok(())
}

View file

@ -0,0 +1,9 @@
use std::path::{Path, PathBuf};
use std::env;
pub fn abspath(p: &str) -> Option<String> {
let exp_path = shellexpand::full(p).ok()?;
let can_path = std::fs::canonicalize(exp_path.as_ref()).ok()?;
can_path.into_os_string().into_string().ok()
}

View file

@ -0,0 +1,245 @@
use std::{
env::current_dir,
fs::{self, create_dir_all, exists},
io::Write,
path::{Path, PathBuf},
};
use git2::{build::RepoBuilder, Repository};
use log::{debug, info, trace, warn};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256, Sha512};
use crate::prelude::abspath;
#[derive(Serialize, Deserialize, Debug)]
pub struct Git {
pub url: String,
#[serde(default = "default_branch")]
pub branch: String,
}
fn default_branch() -> String {
"main".to_string()
}
impl Git {
fn url_hash(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(&self.url);
let hash = hasher.finalize();
hex::encode(hash)
}
fn branch_hash(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(&self.branch);
let hash = hasher.finalize();
hex::encode(hash)
}
pub fn repository_path_str(&self) -> String {
format!(".data/git/{}.{}", self.url_hash(), self.branch_hash())
}
pub fn repository_path(&self) -> Result<PathBuf, std::io::Error> {
fs::canonicalize(self.repository_path_str())
}
pub fn exists_on_fs(&self) -> bool {
self.repository_path().is_ok()
}
pub fn clone_repository(&self) -> Result<Repository, git2::Error> {
RepoBuilder::new()
.branch(&self.branch)
.clone(&self.url, Path::new(&self.repository_path_str()))
}
pub fn repository(&self) -> Result<Repository, git2::Error> {
if !self.exists_on_fs() {
create_dir_all(self.repository_path_str()).unwrap();
}
match Repository::open(self.repository_path().unwrap()) {
Ok(r) => Ok(r),
Err(_) => self.clone_repository(),
}
}
fn up_to_date(&self) -> Result<bool, Box<dyn std::error::Error>> {
debug!("checking repo up to date...");
let repo = self.repository()?;
let mut remote = repo.find_remote("origin")?;
// Fetch latest references from remote
remote.fetch(&[self.branch.clone()], None, None)?;
let fetch_head = repo.refname_to_id(&format!("refs/remotes/origin/{}", self.branch))?;
let local_head = repo.refname_to_id(&format!("refs/heads/{}", self.branch))?;
Ok(fetch_head == local_head)
}
pub fn update(&self) -> Result<bool, Box<dyn std::error::Error>> {
if self.up_to_date()? {
return Ok(true);
}
debug!("updating repository...");
let repository = self.repository()?;
let mut remote = repository.find_remote("origin")?;
let mut cb = git2::RemoteCallbacks::new();
cb.transfer_progress(|stats| {
if stats.received_objects() == stats.total_objects() {
print!(
"resolving deltas {}/{}\r",
stats.indexed_deltas(),
stats.total_deltas()
);
} else if stats.total_objects() > 0 {
print!(
"received {}/{} objects ({}) in {} bytes\r",
stats.received_objects(),
stats.total_objects(),
stats.indexed_objects(),
stats.received_bytes()
);
}
std::io::stdout().flush().unwrap();
true
});
let mut fo = git2::FetchOptions::new();
fo.remote_callbacks(cb);
// Always fetch all tags.
// Perform a download and also update tips
fo.download_tags(git2::AutotagOption::All);
info!("fetching {}...", remote.name().unwrap());
remote.fetch(&[self.branch.clone()], Some(&mut fo), None)?;
let fetch_head = repository.find_reference("FETCH_HEAD")?;
do_merge(
&repository,
&self.branch,
repository.reference_to_annotated_commit(&fetch_head)?,
)?;
Ok(true)
}
pub fn ensure(&self) -> Result<&Self, Box<dyn std::error::Error>> {
if self.exists_on_fs() {
self.update();
} else {
self.clone_repository()?;
}
Ok(self)
}
}
fn do_merge<'a>(
repo: &'a Repository,
remote_branch: &str,
fetch_commit: git2::AnnotatedCommit<'a>,
) -> Result<(), git2::Error> {
// 1. do a merge analysis
let analysis = repo.merge_analysis(&[&fetch_commit])?;
// 2. Do the appopriate merge
if analysis.0.is_fast_forward() {
info!("doing a fast forward...");
// do a fast forward
let refname = format!("refs/heads/{}", remote_branch);
match repo.find_reference(&refname) {
Ok(mut r) => {
fast_forward(repo, &mut r, &fetch_commit)?;
}
Err(_) => {
// The branch doesn't exist so just set the reference to the
// commit directly. Usually this is because you are pulling
// into an empty repository.
repo.reference(
&refname,
fetch_commit.id(),
true,
&format!("Setting {} to {}", remote_branch, fetch_commit.id()),
)?;
repo.set_head(&refname)?;
repo.checkout_head(Some(
git2::build::CheckoutBuilder::default()
.allow_conflicts(true)
.conflict_style_merge(true)
.force(),
))?;
}
};
} else if analysis.0.is_normal() {
// do a normal merge
let head_commit = repo.reference_to_annotated_commit(&repo.head()?)?;
normal_merge(&repo, &head_commit, &fetch_commit)?;
} else {
info!("nothing to do...");
}
Ok(())
}
fn fast_forward(
repo: &Repository,
lb: &mut git2::Reference,
rc: &git2::AnnotatedCommit,
) -> Result<(), git2::Error> {
let name = match lb.name() {
Some(s) => s.to_string(),
None => String::from_utf8_lossy(lb.name_bytes()).to_string(),
};
let msg = format!("fast-forward: setting {} to id: {}", name, rc.id());
info!("{}", msg);
lb.set_target(rc.id(), &msg)?;
repo.set_head(&name)?;
repo.checkout_head(Some(
git2::build::CheckoutBuilder::default()
// For some reason the force is required to make the working directory actually get updated
// I suspect we should be adding some logic to handle dirty working directory states
// but this is just an example so maybe not.
.force(),
))?;
Ok(())
}
fn normal_merge(
repo: &Repository,
local: &git2::AnnotatedCommit,
remote: &git2::AnnotatedCommit,
) -> Result<(), git2::Error> {
let local_tree = repo.find_commit(local.id())?.tree()?;
let remote_tree = repo.find_commit(remote.id())?.tree()?;
let ancestor = repo
.find_commit(repo.merge_base(local.id(), remote.id())?)?
.tree()?;
let mut idx = repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
if idx.has_conflicts() {
warn!("merge conficts detected...");
repo.checkout_index(Some(&mut idx), None)?;
return Ok(());
}
let result_tree = repo.find_tree(idx.write_tree_to(repo)?)?;
// now create the merge commit
let msg = format!("Merge: {} into {}", remote.id(), local.id());
let sig = repo.signature()?;
let local_commit = repo.find_commit(local.id())?;
let remote_commit = repo.find_commit(remote.id())?;
// Do our merge commit and set current branch head to that commit.
let _merge_commit = repo.commit(
Some("HEAD"),
&sig,
&sig,
&msg,
&result_tree,
&[&local_commit, &remote_commit],
)?;
// Set working tree to match head.
repo.checkout_head(None)?;
Ok(())
}

View file

@ -0,0 +1,78 @@
use std::{
env::{current_dir, set_current_dir},
fs::{create_dir_all, exists, read_to_string},
path::PathBuf,
};
use log::{debug, info, trace};
use serde::{Deserialize, Serialize};
use crate::{crates::Crate, prelude::abspath};
pub mod git;
#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct Source {
interval: u64,
git: Option<git::Git>,
dir: Option<String>,
}
impl Default for Source {
fn default() -> Self {
Source {
interval: 60,
git: None,
dir: None,
}
}
}
impl Source {
pub fn available(&self) -> bool {
if let Some(git) = &self.git {
trace!("checking git...");
return git.ensure().is_ok();
}
if let Some(dir) = &self.dir {
return exists(dir).is_ok();
}
false
}
pub fn go_to_dir(&self) -> Result<(), Box<dyn std::error::Error>> {
if let Some(git) = &self.git {
if PathBuf::from(git.repository_path_str()) == current_dir()? {
return Ok(());
}
let dir = git.ensure()?.repository_path()?;
trace!(
"setting git dir as cwd... ({}@{}, {})",
git.url,
git.branch,
dir.display()
);
set_current_dir(dir)?;
}
if let Some(path) = &self.dir {
set_current_dir(path)?;
}
Ok(())
}
pub fn get_crates(&self) -> Result<Vec<(PathBuf, Crate)>, Box<dyn std::error::Error>> {
let mut crates = vec![];
// get crates (read dir, crates/*/crate.toml)
for crate_file in glob::glob("crates/*/crate.toml").expect("err") {
match crate_file {
Ok(cd) => {
debug!("found {}", cd.display());
crates.push((cd.clone(), Crate::from_toml_str(&read_to_string(cd)?)?))
}
_ => continue,
}
}
Ok(crates)
}
}

71
sync-runner/src/tags.rs Normal file
View file

@ -0,0 +1,71 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug)]
pub struct Tag {
pub category: String,
pub value: String
}
impl<'de> Deserialize<'de> for Tag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// First, deserialize the string
let s: String = Deserialize::deserialize(deserializer)?;
// Split the string into category and value by ":"
let parts: Vec<&str> = s.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&s),
&"a string in the format 'category:value'",
));
}
// Return a Tag with the split parts
Ok(Tag {
category: parts[0].to_string(),
value: parts[1].to_string(),
})
}
}
impl Serialize for Tag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{}:{}", self.category, self.value))
}
}
impl Into<String> for Tag {
fn into(self) -> String {
self.category + " : " + &self.value
}
}
impl TryFrom<String> for Tag {
type Error = std::io::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
let parts: Vec<&str> = value.split(':').collect();
if parts.len() != 2 {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "could not match string to tag"))
} else {
Ok(Self {
category: parts[0].into(),
value: parts[1].into()
})
}
}
}
impl Tag {
pub fn distro() -> Tag {
Tag {
category: "distro".into(),
value: whoami::distro()
}
}
}

6
sync.sh Normal file
View file

@ -0,0 +1,6 @@
if ! [ -f /tmp/sync-runner ]; then
curl -O /tmp/sync-runner https://git.saluco.nl/repos/strix/releases/download/latest/sync-runner
chmod +x /tmp/sync-runner
fi
/tmp/sync-runner

17
syncr.toml Normal file
View file

@ -0,0 +1,17 @@
title = "strix's syncr config"
[daemon]
# interval
# discription: how often to check for new updates, this is the default for syncs
# you can define a custom interval for specific sources
# unit = minutes
interval = 60
[source.personal]
dir = "."
# [source.personal.git] # default is the uid
# url = "https://git.saluco.nl/strix/dotfiles.git"
# branch = "syncr"
# crate_dir = "./crates" # default
# cfg_toml = "./syncr.toml" # default