Configuracion inicial completa del entorno personal
Modulos de restauracion: - bootstrap: instala yq, age y dependencias base (curl, wget, git, nano, gpg) - ssh: descifra e instala claves SSH desde secrets/sshKeys.tar.gz.age - registry: aplica paquetes apt/snap/flatpak, dotfiles, servicios y configs Docker - thunderbird: instala Thunderbird snap y restaura perfil desde ZIP - claudeCode: configura repositorio apt de Anthropic e instala claude-code - easyEffects: restaura configuracion y presets desde ZIP - wireplumber: restaura dispositivo Bluetooth por defecto y perfiles de audio - cups: restaura impresoras y drivers PPD desde ZIP Scripts de captura (correr antes de push): - scripts/encryptSsh.sh: cifra ~/.ssh con age - scripts/thunderbird/capture.sh: captura perfil de Thunderbird snap - scripts/easyEffects/capture.sh: captura config de EasyEffects flatpak - scripts/wireplumber/capture.sh: captura estado de WirePlumber - scripts/cups/capture.sh: captura impresoras CUPS y PPDs (requiere sudo) Registro de aplicaciones (config/registry.yaml): - 9 paquetes apt, 1 snap (dbeaver-ce), 22 flatpaks incluyendo VSCodium, Bitwarden, Inkscape, LibreOffice, OBS Studio, Nextcloud Desktop, entre otros Secretos incluidos: - secrets/sshKeys.tar.gz.age: claves SSH cifradas con age - secrets/thunderbirdProfile.zip: perfil de Thunderbird sin emails ni cache - secrets/easyEffectsConfig.zip: ajustes y presets de salida de audio - secrets/wireplumberState.zip: estado de audio incluyendo auriculares Bluetooth - secrets/cupsConfig.zip: 5 impresoras configuradas con sus drivers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
latestGithubReleaseTag() {
|
||||
local repository="$1"
|
||||
curl -fsSL "https://api.github.com/repos/${repository}/releases/latest" \
|
||||
| grep '"tag_name"' \
|
||||
| cut -d'"' -f4
|
||||
}
|
||||
|
||||
ensureBasePackagesAreInstalled() {
|
||||
local requiredPackages=(curl wget git nano gpg ca-certificates)
|
||||
local missingPackages=()
|
||||
|
||||
for packageName in "${requiredPackages[@]}"; do
|
||||
util::isAptInstalled "$packageName" || missingPackages+=("$packageName")
|
||||
done
|
||||
|
||||
if [[ ${#missingPackages[@]} -gt 0 ]]; then
|
||||
util::aptUpdateOnce
|
||||
sudo apt-get install -y "${missingPackages[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
installYqFromGithub() {
|
||||
local version
|
||||
version="$(latestGithubReleaseTag "mikefarah/yq")"
|
||||
sudo wget -qO /usr/local/bin/yq \
|
||||
"https://github.com/mikefarah/yq/releases/download/${version}/yq_linux_amd64"
|
||||
sudo chmod +x /usr/local/bin/yq
|
||||
log::ok "yq ${version} instalado"
|
||||
}
|
||||
|
||||
ensureYqIsInstalled() {
|
||||
if util::cmdExists yq; then
|
||||
log::info "yq ya disponible: $(yq --version 2>&1 | head -1)"
|
||||
return
|
||||
fi
|
||||
log::info "Instalando yq..."
|
||||
installYqFromGithub
|
||||
}
|
||||
|
||||
ageIsAvailableInApt() {
|
||||
apt-cache show age &>/dev/null 2>&1
|
||||
}
|
||||
|
||||
installAgeFromApt() {
|
||||
sudo apt-get install -y age
|
||||
}
|
||||
|
||||
installAgeFromGithub() {
|
||||
local version
|
||||
version="$(latestGithubReleaseTag "FiloSottile/age")"
|
||||
local temporaryDirectory
|
||||
temporaryDirectory="$(mktemp -d)"
|
||||
trap 'rm -rf "$temporaryDirectory"' RETURN
|
||||
curl -fsSL \
|
||||
"https://github.com/FiloSottile/age/releases/download/${version}/age-${version}-linux-amd64.tar.gz" \
|
||||
| tar -xz -C "$temporaryDirectory"
|
||||
sudo mv "$temporaryDirectory/age/age" "$temporaryDirectory/age/age-keygen" /usr/local/bin/
|
||||
}
|
||||
|
||||
ensureAgeIsInstalled() {
|
||||
if util::cmdExists age; then
|
||||
log::info "age ya disponible: $(age --version 2>&1)"
|
||||
return
|
||||
fi
|
||||
log::info "Instalando age..."
|
||||
if ageIsAvailableInApt; then
|
||||
installAgeFromApt
|
||||
else
|
||||
installAgeFromGithub
|
||||
fi
|
||||
log::ok "age instalado"
|
||||
}
|
||||
|
||||
log::info "Verificando dependencias base"
|
||||
ensureBasePackagesAreInstalled
|
||||
ensureYqIsInstalled
|
||||
ensureAgeIsInstalled
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
readonly claudeCodeKeyring="/etc/apt/keyrings/claude-code.asc"
|
||||
readonly claudeCodeSources="/etc/apt/sources.list.d/claude-code.list"
|
||||
readonly claudeCodeSourcesEntry="deb [signed-by=${claudeCodeKeyring}] https://downloads.claude.ai/claude-code/apt/stable stable main"
|
||||
|
||||
repoIsConfigured() {
|
||||
[[ -f "$claudeCodeSources" ]]
|
||||
}
|
||||
|
||||
addSigningKey() {
|
||||
sudo install -m 644 "$stpRoot/config/keys/claude-code.asc" "$claudeCodeKeyring"
|
||||
log::ok "Clave GPG instalada: $claudeCodeKeyring"
|
||||
}
|
||||
|
||||
addRepository() {
|
||||
echo "$claudeCodeSourcesEntry" | sudo tee "$claudeCodeSources" > /dev/null
|
||||
sudo apt-get update -qq
|
||||
log::ok "Repositorio configurado: $claudeCodeSources"
|
||||
}
|
||||
|
||||
if repoIsConfigured; then
|
||||
log::info "Repositorio claude-code ya configurado"
|
||||
else
|
||||
addSigningKey
|
||||
addRepository
|
||||
fi
|
||||
|
||||
if util::isAptInstalled "claude-code"; then
|
||||
log::info "Ya instalado (apt): claude-code"
|
||||
else
|
||||
sudo apt-get install -y claude-code
|
||||
log::ok "Instalado: claude-code"
|
||||
fi
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
readonly cupsArchive="$stpRoot/secrets/cupsConfig.zip"
|
||||
|
||||
cupsIsInstalled() {
|
||||
util::cmdExists lpstat
|
||||
}
|
||||
|
||||
printersAreConfigured() {
|
||||
sudo test -s /etc/cups/printers.conf
|
||||
}
|
||||
|
||||
restoreConfig() {
|
||||
log::info "Restaurando configuración de CUPS desde secrets/cupsConfig.zip..."
|
||||
unzip -q "$cupsArchive" -d "$cupsTemp"
|
||||
|
||||
sudo cp "$cupsTemp/printers.conf" /etc/cups/printers.conf
|
||||
sudo chown root:lp /etc/cups/printers.conf
|
||||
sudo chmod 600 /etc/cups/printers.conf
|
||||
|
||||
if [[ -d "$cupsTemp/ppd" ]]; then
|
||||
sudo mkdir -p /etc/cups/ppd
|
||||
sudo cp -r "$cupsTemp/ppd/." /etc/cups/ppd/
|
||||
sudo chown -R root:lp /etc/cups/ppd/
|
||||
sudo chmod 644 /etc/cups/ppd/*.ppd 2>/dev/null || true
|
||||
fi
|
||||
|
||||
sudo systemctl restart cups
|
||||
log::ok "Configuración de CUPS restaurada"
|
||||
}
|
||||
|
||||
if ! cupsIsInstalled; then
|
||||
log::info "CUPS no instalado, salteando"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$cupsArchive" ]]; then
|
||||
log::info "Sin respaldo de CUPS (secrets/cupsConfig.zip), salteando"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
util::requireSudo
|
||||
|
||||
if printersAreConfigured; then
|
||||
log::info "CUPS ya tiene impresoras configuradas, salteando restauración"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cupsTemp="$(mktemp -d)"
|
||||
trap 'rm -rf "$cupsTemp"' EXIT
|
||||
|
||||
restoreConfig
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
readonly easyEffectsAppId="com.github.wwmm.easyeffects"
|
||||
readonly easyEffectsApplicationDirectory="$HOME/.var/app/com.github.wwmm.easyeffects"
|
||||
readonly easyEffectsConfig="$easyEffectsApplicationDirectory/config/easyeffects/db/easyeffectsrc"
|
||||
readonly easyEffectsArchive="$stpRoot/secrets/easyEffectsConfig.zip"
|
||||
|
||||
easyEffectsIsInstalled() {
|
||||
util::isFlatpakInstalled "$easyEffectsAppId"
|
||||
}
|
||||
|
||||
easyEffectsConfigExists() {
|
||||
[[ -f "$easyEffectsConfig" ]]
|
||||
}
|
||||
|
||||
restoreConfig() {
|
||||
log::info "Restaurando configuración desde secrets/easyEffectsConfig.zip..."
|
||||
mkdir -p "$easyEffectsApplicationDirectory"
|
||||
unzip -qo "$easyEffectsArchive" -d "$easyEffectsApplicationDirectory"
|
||||
log::ok "Configuración restaurada en: $easyEffectsApplicationDirectory"
|
||||
}
|
||||
|
||||
if ! easyEffectsIsInstalled; then
|
||||
log::info "EasyEffects no instalado aún, salteando restauración de config"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$easyEffectsArchive" ]]; then
|
||||
log::info "Sin respaldo de configuración (secrets/easyEffectsConfig.zip), salteando"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if easyEffectsConfigExists; then
|
||||
log::info "Configuración de EasyEffects ya existe, salteando restauración"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
restoreConfig
|
||||
Executable
+414
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
source "$stpRoot/lib/yaml.sh"
|
||||
|
||||
readonly registryConfig="$stpRoot/config/registry.yaml"
|
||||
|
||||
fieldOf() {
|
||||
local entryId="$1" field="$2" default="${3:-}"
|
||||
local value
|
||||
value="$(yq ".registry[] | select(.id == \"$entryId\") | .${field}" "$registryConfig" 2>/dev/null)"
|
||||
if util::isYamlNull "$value"; then
|
||||
echo "$default"
|
||||
else
|
||||
echo "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
arrayFieldOf() {
|
||||
local entryId="$1" field="$2"
|
||||
yq -r ".registry[] | select(.id == \"$entryId\") | .${field}[]" "$registryConfig" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ---- PPA ----
|
||||
|
||||
ppaOwnerFrom() {
|
||||
local ppaAddress="$1"
|
||||
local owner="${ppaAddress#ppa:}"
|
||||
echo "${owner%%/*}"
|
||||
}
|
||||
|
||||
ppaIsAlreadyAdded() {
|
||||
local ppaAddress="$1"
|
||||
find /etc/apt/sources.list.d/ -name "*$(ppaOwnerFrom "$ppaAddress")*" 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
applyPpa() {
|
||||
local entryId="$1"
|
||||
local ppaAddress
|
||||
ppaAddress="$(fieldOf "$entryId" "address")"
|
||||
|
||||
if ppaIsAlreadyAdded "$ppaAddress"; then
|
||||
log::info "PPA ya existe: $ppaAddress"
|
||||
return
|
||||
fi
|
||||
|
||||
util::cmdExists add-apt-repository \
|
||||
|| sudo apt-get install -y software-properties-common
|
||||
|
||||
sudo add-apt-repository -y "$ppaAddress"
|
||||
sudo apt-get update -qq
|
||||
log::ok "PPA agregado: $ppaAddress"
|
||||
}
|
||||
|
||||
# ---- APT ----
|
||||
|
||||
installedVersionOf() {
|
||||
local packageName="$1"
|
||||
dpkg-query -W -f='${Version}' "$packageName" 2>/dev/null
|
||||
}
|
||||
|
||||
aptVersionIsInRange() {
|
||||
local installedVersion="$1" minVersion="$2" maxVersion="$3"
|
||||
if [[ -n "$minVersion" ]] && dpkg --compare-versions "$installedVersion" lt "$minVersion"; then
|
||||
return 1
|
||||
fi
|
||||
if [[ -n "$maxVersion" ]] && dpkg --compare-versions "$installedVersion" gt "$maxVersion"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
applyAptPackage() {
|
||||
local entryId="$1"
|
||||
local packageName minVersion maxVersion
|
||||
packageName="$(fieldOf "$entryId" "package" "$entryId")"
|
||||
minVersion="$(fieldOf "$entryId" "minVersion")"
|
||||
maxVersion="$(fieldOf "$entryId" "maxVersion")"
|
||||
|
||||
if util::isAptInstalled "$packageName"; then
|
||||
local installedVersion
|
||||
installedVersion="$(installedVersionOf "$packageName")"
|
||||
if aptVersionIsInRange "$installedVersion" "$minVersion" "$maxVersion"; then
|
||||
log::info "Ya instalado (apt): $packageName ($installedVersion)"
|
||||
return
|
||||
fi
|
||||
log::warn "$packageName: versión $installedVersion instalada, fuera del rango requerido"
|
||||
fi
|
||||
|
||||
util::aptUpdateOnce
|
||||
if [[ -n "$maxVersion" ]]; then
|
||||
sudo apt-get install -y --allow-downgrades "${packageName}=${maxVersion}"
|
||||
log::ok "Instalado (apt): ${packageName}=${maxVersion}"
|
||||
else
|
||||
sudo apt-get install -y "$packageName"
|
||||
log::ok "Instalado (apt): $packageName"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- SNAP ----
|
||||
|
||||
applySnapPackage() {
|
||||
local entryId="$1"
|
||||
local packageName classicFlag installFlags
|
||||
packageName="$(fieldOf "$entryId" "package" "$entryId")"
|
||||
classicFlag="$(fieldOf "$entryId" "classic" "false")"
|
||||
installFlags=()
|
||||
[[ "$classicFlag" == "true" ]] && installFlags+=(--classic)
|
||||
|
||||
if util::isSnapInstalled "$packageName"; then
|
||||
log::info "Ya instalado (snap): $packageName"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! util::cmdExists snap; then
|
||||
log::warn "snap no disponible, salteando: $entryId"
|
||||
return
|
||||
fi
|
||||
|
||||
sudo snap install "$packageName" "${installFlags[@]}"
|
||||
log::ok "Instalado (snap): $packageName"
|
||||
}
|
||||
|
||||
# ---- FLATPAK ----
|
||||
|
||||
applyFlatpakApp() {
|
||||
local entryId="$1"
|
||||
local appId remote
|
||||
appId="$(fieldOf "$entryId" "appId" "$entryId")"
|
||||
remote="$(fieldOf "$entryId" "remote" "flathub")"
|
||||
|
||||
if util::isFlatpakInstalled "$appId"; then
|
||||
log::info "Ya instalado (flatpak): $appId"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! util::cmdExists flatpak; then
|
||||
log::warn "flatpak no disponible, salteando: $entryId"
|
||||
return
|
||||
fi
|
||||
|
||||
flatpak install -y "$remote" "$appId"
|
||||
log::ok "Instalado (flatpak): $appId"
|
||||
}
|
||||
|
||||
# ---- DOTFILE ----
|
||||
|
||||
dotfileSymlinkAlreadyExists() {
|
||||
local destination="$1"
|
||||
[[ -L "$destination" ]]
|
||||
}
|
||||
|
||||
backupExistingFile() {
|
||||
local destination="$1"
|
||||
[[ -e "$destination" ]] || return 0
|
||||
mv "$destination" "${destination}.stpbackup"
|
||||
log::info "Copia de seguridad: ~/${destination#${HOME}/}"
|
||||
}
|
||||
|
||||
deployFileFromElement() {
|
||||
local sourceFile="$1" elementSourceDirectory="$2"
|
||||
local relativePath destination
|
||||
relativePath="${sourceFile#${elementSourceDirectory}/}"
|
||||
destination="$HOME/$relativePath"
|
||||
|
||||
if dotfileSymlinkAlreadyExists "$destination"; then
|
||||
log::info "Enlace ya existe: ~/$relativePath"
|
||||
return
|
||||
fi
|
||||
|
||||
backupExistingFile "$destination"
|
||||
mkdir -p "$(dirname "$destination")"
|
||||
ln -sfn "$sourceFile" "$destination"
|
||||
log::ok "Enlazado: ~/$relativePath"
|
||||
}
|
||||
|
||||
applyDotfile() {
|
||||
local entryId="$1"
|
||||
local elementSourceDirectory="$stpRoot/dotfiles/$entryId"
|
||||
|
||||
if [[ ! -d "$elementSourceDirectory" ]]; then
|
||||
log::warn "Carpeta no encontrada: dotfiles/$entryId/"
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r sourceFile; do
|
||||
deployFileFromElement "$sourceFile" "$elementSourceDirectory"
|
||||
done < <(find "$elementSourceDirectory" -not -type d | sort)
|
||||
}
|
||||
|
||||
# ---- SERVICE ----
|
||||
|
||||
applyService() {
|
||||
local entryId="$1"
|
||||
local serviceName scope state
|
||||
serviceName="$(fieldOf "$entryId" "name" "$entryId")"
|
||||
scope="$(fieldOf "$entryId" "scope" "system")"
|
||||
state="$(fieldOf "$entryId" "state" "enable")"
|
||||
|
||||
local scopeFlags=()
|
||||
[[ "$scope" == "user" ]] && scopeFlags+=(--user)
|
||||
|
||||
case "$state" in
|
||||
enable)
|
||||
systemctl "${scopeFlags[@]}" enable --now "$serviceName" 2>/dev/null \
|
||||
|| log::warn "No se pudo habilitar: $serviceName"
|
||||
;;
|
||||
disable)
|
||||
systemctl "${scopeFlags[@]}" disable --now "$serviceName" 2>/dev/null || true
|
||||
;;
|
||||
mask)
|
||||
systemctl "${scopeFlags[@]}" mask "$serviceName" 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
log::ok "Servicio [$scope] $state: $serviceName"
|
||||
}
|
||||
|
||||
# ---- PIPEWIRE ----
|
||||
|
||||
allPipewirePackagesAreInstalled() {
|
||||
local entryId="$1"
|
||||
while IFS= read -r packageName; do
|
||||
util::isYamlNull "$packageName" && continue
|
||||
util::isAptInstalled "$packageName" || return 1
|
||||
done < <(arrayFieldOf "$entryId" "packages")
|
||||
}
|
||||
|
||||
installMissingPipewirePackages() {
|
||||
local entryId="$1"
|
||||
local missingPackages=()
|
||||
while IFS= read -r packageName; do
|
||||
util::isYamlNull "$packageName" && continue
|
||||
util::isAptInstalled "$packageName" || missingPackages+=("$packageName")
|
||||
done < <(arrayFieldOf "$entryId" "packages")
|
||||
|
||||
if [[ ${#missingPackages[@]} -gt 0 ]]; then
|
||||
util::aptUpdateOnce
|
||||
sudo apt-get install -y "${missingPackages[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
disablePulseaudio() {
|
||||
systemctl --user --now disable pulseaudio.service pulseaudio.socket 2>/dev/null || true
|
||||
systemctl --user mask pulseaudio 2>/dev/null || true
|
||||
}
|
||||
|
||||
enablePipewireUserServices() {
|
||||
local entryId="$1"
|
||||
while IFS= read -r serviceName; do
|
||||
util::isYamlNull "$serviceName" && continue
|
||||
systemctl --user --now enable "$serviceName" 2>/dev/null \
|
||||
|| log::warn "No se pudo habilitar servicio de usuario: $serviceName"
|
||||
done < <(arrayFieldOf "$entryId" "userServices")
|
||||
}
|
||||
|
||||
applyPipewire() {
|
||||
local entryId="$1"
|
||||
|
||||
allPipewirePackagesAreInstalled "$entryId" \
|
||||
|| installMissingPipewirePackages "$entryId"
|
||||
|
||||
local shouldReplace
|
||||
shouldReplace="$(fieldOf "$entryId" "replacePulseaudio" "false")"
|
||||
[[ "$shouldReplace" == "true" ]] && disablePulseaudio
|
||||
|
||||
enablePipewireUserServices "$entryId"
|
||||
log::ok "PipeWire configurado"
|
||||
}
|
||||
|
||||
# ---- VIDEO ----
|
||||
|
||||
detectGpuVendor() {
|
||||
if lspci 2>/dev/null | grep -qi nvidia; then echo "nvidia"
|
||||
elif lspci 2>/dev/null | grep -qi "amd\|radeon"; then echo "amd"
|
||||
elif lspci 2>/dev/null | grep -qi intel; then echo "intel"
|
||||
else echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
applyVideoDrivers() {
|
||||
local entryId="$1"
|
||||
local gpuVendor
|
||||
gpuVendor="$(detectGpuVendor)"
|
||||
log::info "GPU detectada: $gpuVendor"
|
||||
|
||||
if [[ "$gpuVendor" == "unknown" ]]; then
|
||||
log::warn "No se pudo identificar la GPU, salteando drivers"
|
||||
return
|
||||
fi
|
||||
|
||||
local missingPackages=()
|
||||
while IFS= read -r packageName; do
|
||||
util::isYamlNull "$packageName" && continue
|
||||
util::isAptInstalled "$packageName" || missingPackages+=("$packageName")
|
||||
done < <(arrayFieldOf "$entryId" "drivers.${gpuVendor}.packages")
|
||||
|
||||
if [[ ${#missingPackages[@]} -eq 0 ]]; then
|
||||
log::info "Drivers $gpuVendor ya instalados"
|
||||
return
|
||||
fi
|
||||
|
||||
util::aptUpdateOnce
|
||||
sudo apt-get install -y "${missingPackages[@]}"
|
||||
log::ok "Drivers $gpuVendor instalados"
|
||||
}
|
||||
|
||||
# ---- DOCKER ----
|
||||
|
||||
dockerIsInstalled() {
|
||||
util::cmdExists docker
|
||||
}
|
||||
|
||||
findDockerComposeFile() {
|
||||
local destinationDirectory="$1"
|
||||
local composeFile
|
||||
for composeFile in "compose.yaml" "compose.yml" "docker-compose.yaml" "docker-compose.yml"; do
|
||||
[[ -f "$destinationDirectory/$composeFile" ]] && echo "$destinationDirectory/$composeFile" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
deployDockerConfigFile() {
|
||||
local sourceFile="$1" sourceDirectory="$2" destinationDirectory="$3"
|
||||
local relativePath destination
|
||||
relativePath="${sourceFile#${sourceDirectory}/}"
|
||||
destination="$destinationDirectory/$relativePath"
|
||||
|
||||
if [[ -L "$destination" ]]; then
|
||||
log::info "Enlace ya existe: $relativePath"
|
||||
return
|
||||
fi
|
||||
|
||||
[[ -e "$destination" ]] && mv "$destination" "${destination}.stpbackup"
|
||||
mkdir -p "$(dirname "$destination")"
|
||||
ln -sfn "$sourceFile" "$destination"
|
||||
log::ok "Enlazado: $relativePath"
|
||||
}
|
||||
|
||||
applyDocker() {
|
||||
local entryId="$1"
|
||||
local rawDestination destination autostart
|
||||
rawDestination="$(fieldOf "$entryId" "destination" "~/docker/$entryId")"
|
||||
destination="${rawDestination/#\~/$HOME}"
|
||||
autostart="$(fieldOf "$entryId" "autostart" "false")"
|
||||
|
||||
local sourceDirectory="$stpRoot/docker/$entryId"
|
||||
|
||||
if [[ ! -d "$sourceDirectory" ]]; then
|
||||
log::warn "Carpeta no encontrada: docker/$entryId/"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! dockerIsInstalled; then
|
||||
log::warn "Docker no instalado, salteando: $entryId"
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$destination"
|
||||
|
||||
while IFS= read -r sourceFile; do
|
||||
deployDockerConfigFile "$sourceFile" "$sourceDirectory" "$destination"
|
||||
done < <(find "$sourceDirectory" -not -type d | sort)
|
||||
|
||||
if [[ "$autostart" == "true" ]]; then
|
||||
local composeFile
|
||||
if composeFile="$(findDockerComposeFile "$destination")"; then
|
||||
docker compose -f "$composeFile" up -d \
|
||||
|| log::warn "No se pudo iniciar el servicio: $entryId"
|
||||
log::ok "Servicio Docker iniciado: $entryId"
|
||||
else
|
||||
log::warn "No se encontró archivo compose en: $destination"
|
||||
fi
|
||||
fi
|
||||
|
||||
log::ok "Docker configurado: $entryId"
|
||||
}
|
||||
|
||||
# ---- Dispatcher ----
|
||||
|
||||
dispatchByType() {
|
||||
local entryId="$1" entryType="$2"
|
||||
case "$entryType" in
|
||||
ppa) applyPpa "$entryId" ;;
|
||||
apt) applyAptPackage "$entryId" ;;
|
||||
snap) applySnapPackage "$entryId" ;;
|
||||
flatpak) applyFlatpakApp "$entryId" ;;
|
||||
dotfile) applyDotfile "$entryId" ;;
|
||||
service) applyService "$entryId" ;;
|
||||
pipewire) applyPipewire "$entryId" ;;
|
||||
video) applyVideoDrivers "$entryId" ;;
|
||||
docker) applyDocker "$entryId" ;;
|
||||
*) log::warn "Tipo no reconocido '$entryType' en entrada: $entryId" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
processRegistry() {
|
||||
local totalProcessed=0
|
||||
while IFS=$'\t' read -r entryId entryType; do
|
||||
util::isYamlNull "$entryId" && continue
|
||||
util::isYamlNull "$entryType" && continue
|
||||
log::info "[$entryType] $entryId"
|
||||
dispatchByType "$entryId" "$entryType"
|
||||
((++totalProcessed))
|
||||
done < <(yq -r '.registry[] | [.id, .type] | @tsv' "$registryConfig")
|
||||
log::ok "$totalProcessed entrada(s) del registro procesadas"
|
||||
}
|
||||
|
||||
if ! yaml::has "$registryConfig" ".registry[0]"; then
|
||||
log::info "Registro vacío, salteando"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
processRegistry
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
readonly sshDestination="$HOME/.ssh"
|
||||
readonly encryptedArchive="$stpRoot/secrets/sshKeys.tar.gz.age"
|
||||
|
||||
sshPermissionsForFile() {
|
||||
local keyFilename="$1"
|
||||
case "$keyFilename" in
|
||||
*.pub|known_hosts|config) echo 644 ;;
|
||||
*) echo 600 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensureSshDirectoryExists() {
|
||||
if [[ ! -d "$sshDestination" ]]; then
|
||||
mkdir -p "$sshDestination"
|
||||
chmod 700 "$sshDestination"
|
||||
fi
|
||||
}
|
||||
|
||||
decryptArchiveInto() {
|
||||
local workingDirectory="$1"
|
||||
log::info "Ingresá la passphrase para descifrar las claves SSH:"
|
||||
if ! age -d -o "$workingDirectory/sshKeys.tar.gz" "$encryptedArchive"; then
|
||||
log::error "Error al descifrar. Verificá la passphrase."
|
||||
return 1
|
||||
fi
|
||||
tar -xzf "$workingDirectory/sshKeys.tar.gz" -C "$workingDirectory"
|
||||
}
|
||||
|
||||
installSshKey() {
|
||||
local sourceFile="$1"
|
||||
local keyFilename
|
||||
keyFilename="$(basename "$sourceFile")"
|
||||
local destination="$sshDestination/$keyFilename"
|
||||
|
||||
if [[ -f "$destination" ]]; then
|
||||
log::warn "Ya existe (salteando): $keyFilename"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$sourceFile" "$destination"
|
||||
chmod "$(sshPermissionsForFile "$keyFilename")" "$destination"
|
||||
log::ok "Instalada: $keyFilename"
|
||||
}
|
||||
|
||||
installAllKeysFrom() {
|
||||
local sourceDirectory="$1"
|
||||
local installedCount=0 skippedCount=0
|
||||
|
||||
for sourceFile in "$sourceDirectory/.ssh/"*; do
|
||||
[[ -f "$sourceFile" ]] || continue
|
||||
if installSshKey "$sourceFile"; then
|
||||
((++installedCount))
|
||||
else
|
||||
((++skippedCount))
|
||||
fi
|
||||
done
|
||||
|
||||
log::ok "$installedCount clave(s) instaladas, $skippedCount salteada(s)"
|
||||
}
|
||||
|
||||
if [[ ! -f "$encryptedArchive" ]]; then
|
||||
log::warn "Archivo de claves no encontrado: secrets/sshKeys.tar.gz.age"
|
||||
log::warn "Para cifrar tus claves actuales: bash scripts/encryptSsh.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! util::cmdExists age; then
|
||||
log::error "age no está instalado. Ejecutá primero el módulo bootstrap"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log::info "Restaurando claves SSH..."
|
||||
|
||||
workingDirectory="$(mktemp -d)"
|
||||
trap 'rm -rf "$workingDirectory"' EXIT
|
||||
|
||||
decryptArchiveInto "$workingDirectory"
|
||||
ensureSshDirectoryExists
|
||||
installAllKeysFrom "$workingDirectory"
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
readonly thunderbirdApplicationDirectory="$HOME/snap/thunderbird/common"
|
||||
readonly thunderbirdProfileDirectory="$thunderbirdApplicationDirectory/.thunderbird"
|
||||
readonly thunderbirdArchive="$stpRoot/secrets/thunderbirdProfile.zip"
|
||||
|
||||
thunderbirdIsInstalled() {
|
||||
util::isSnapInstalled "thunderbird"
|
||||
}
|
||||
|
||||
thunderbirdProfileExists() {
|
||||
[[ -f "$thunderbirdProfileDirectory/profiles.ini" ]]
|
||||
}
|
||||
|
||||
installThunderbird() {
|
||||
sudo snap install thunderbird
|
||||
log::ok "Thunderbird instalado"
|
||||
}
|
||||
|
||||
restoreProfile() {
|
||||
log::info "Restaurando perfil desde secrets/thunderbirdProfile.zip..."
|
||||
mkdir -p "$thunderbirdApplicationDirectory"
|
||||
unzip -qo "$thunderbirdArchive" -d "$thunderbirdApplicationDirectory"
|
||||
log::ok "Perfil restaurado en: $thunderbirdProfileDirectory"
|
||||
}
|
||||
|
||||
if thunderbirdIsInstalled; then
|
||||
log::info "Thunderbird ya instalado"
|
||||
else
|
||||
installThunderbird
|
||||
fi
|
||||
|
||||
if [[ ! -f "$thunderbirdArchive" ]]; then
|
||||
log::info "Sin respaldo de perfil (secrets/thunderbirdProfile.zip), salteando restauración"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if thunderbirdProfileExists; then
|
||||
log::info "Perfil de Thunderbird ya existe, salteando restauración"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
restoreProfile
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$stpRoot/lib/log.sh"
|
||||
source "$stpRoot/lib/utils.sh"
|
||||
|
||||
readonly wireplumberStateDirectory="$HOME/.local/state/wireplumber"
|
||||
readonly wireplumberArchive="$stpRoot/secrets/wireplumberState.zip"
|
||||
|
||||
wireplumberIsInstalled() {
|
||||
util::cmdExists wireplumber
|
||||
}
|
||||
|
||||
wireplumberStateExists() {
|
||||
[[ -f "$wireplumberStateDirectory/default-nodes" ]]
|
||||
}
|
||||
|
||||
restoreState() {
|
||||
log::info "Restaurando estado desde secrets/wireplumberState.zip..."
|
||||
mkdir -p "$HOME/.local/state"
|
||||
unzip -qo "$wireplumberArchive" -d "$HOME/.local/state"
|
||||
log::ok "Estado restaurado en: $wireplumberStateDirectory"
|
||||
log::info "Reiniciá WirePlumber para aplicar los cambios: systemctl --user restart wireplumber"
|
||||
}
|
||||
|
||||
if ! wireplumberIsInstalled; then
|
||||
log::info "WirePlumber no instalado, salteando"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "$wireplumberArchive" ]]; then
|
||||
log::info "Sin respaldo de estado (secrets/wireplumberState.zip), salteando"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if wireplumberStateExists; then
|
||||
log::info "Estado de WirePlumber ya existe, salteando restauración"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
restoreState
|
||||
Reference in New Issue
Block a user