numerous updates
This commit is contained in:
@@ -93,6 +93,48 @@ The printing service (cups) is turned on. This may need more work to "just work"
|
|||||||
|
|
||||||
mDNS is installed and enabled so the your can find and SSH to your machine on your LAN using {machinename}.local.
|
mDNS is installed and enabled so the your can find and SSH to your machine on your LAN using {machinename}.local.
|
||||||
|
|
||||||
|
### Firewall
|
||||||
|
|
||||||
|
The ufw firewall is on. It blocks incoming connections except SSH (22), mDNS (5353/udp), LocalSend (53317) and anything arriving over Tailscale. ufw-docker is installed too, because ports published by Docker containers otherwise get around ufw. To let a container's port through, use `sudo ufw-docker allow {container} {port}`. To open a port for something else, e.g. a dev server you want to reach from another machine, use `sudo ufw allow {port}/tcp`. `sudo ufw status` shows the current rules.
|
||||||
|
|
||||||
|
### Shell
|
||||||
|
|
||||||
|
Your ~/.bashrc sources ~/.local/share/coredesktop/shell/bashrc, which sets up the starship prompt, zoxide (`z {part of a path}` jumps to a directory you've used before), fzf key bindings (Ctrl-R searches history, Ctrl-T inserts a file path), eza for `ls`/`ll`/`la`/`lt`, and the aliases and functions in [shell/](shell/). If you already have ~/.config/starship.toml, your prompt config is used. Anything you add to ~/.bashrc after the CoreDesktop line overrides it.
|
||||||
|
|
||||||
|
### Snapshots
|
||||||
|
|
||||||
|
If / is btrfs (the archinstall default), Snapper takes a snapshot of / before every `update` and keeps the last 5. /home is a separate subvolume and isn't included, so rolling back never touches your files. `sudo snapper -c root list` shows the snapshots.
|
||||||
|
|
||||||
|
These snapshots can't be picked from the boot menu. To roll back after a bad update, boot the Arch ISO and replace the @ subvolume with a snapshot:
|
||||||
|
|
||||||
|
```
|
||||||
|
mount -o subvolid=5 /dev/{root partition} /mnt
|
||||||
|
mv /mnt/@ /mnt/@.broken
|
||||||
|
btrfs subvolume snapshot /mnt/@.snapshots/{number}/snapshot /mnt/@
|
||||||
|
reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
If the disk is encrypted, open it with `cryptsetup open` first and mount the opened device. Once the system works again, free the space the broken root used:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo mount -o subvolid=5 /dev/{root partition} /mnt
|
||||||
|
sudo btrfs subvolume delete /mnt/@.broken
|
||||||
|
sudo umount /mnt
|
||||||
|
```
|
||||||
|
|
||||||
|
### GPU drivers
|
||||||
|
|
||||||
|
The installer adds the Vulkan driver for Intel and AMD GPUs. If there's an NVIDIA GPU and archinstall's proprietary driver wasn't chosen, it installs NVIDIA's driver: the open kernel modules for GTX 16xx / RTX 20xx and newer, or the 580xx legacy driver for older cards back to the GTX 900 series. Reboot afterwards.
|
||||||
|
|
||||||
|
### System defaults
|
||||||
|
|
||||||
|
- SSH connections notice a dropped link within a minute instead of hanging (override per host in ~/.ssh/config).
|
||||||
|
- Logins allow 10 wrong passwords before locking the account for 2 minutes (Arch's default is 3 tries, then 10 minutes).
|
||||||
|
- Shutdown waits at most 10 seconds for a service that won't stop, instead of 90.
|
||||||
|
- If memory runs out, systemd-oomd closes the app causing it instead of the whole desktop freezing.
|
||||||
|
- Boot no longer waits for the network.
|
||||||
|
- Commands run over SSH (`ssh {machine} {command}`) can find tools in ~/.local/bin and mise.
|
||||||
|
|
||||||
## Keep your System Up-to-date
|
## Keep your System Up-to-date
|
||||||
|
|
||||||
From time-to-time, you'll want to update your system. Hopefully you'll do this more than once-a-month.
|
From time-to-time, you'll want to update your system. Hopefully you'll do this more than once-a-month.
|
||||||
@@ -103,7 +145,16 @@ To update the system type into the terminal:
|
|||||||
update
|
update
|
||||||
```
|
```
|
||||||
|
|
||||||
The script does three things. It runs 'flatpak update' to update your Flatpak apps, it runs 'gem update' to update your Rails environment, and it runs 'sudo pacman -Syu' to update your Arch and Gnome installation.
|
The script pulls the latest CoreDesktop, then runs each step in ~/.local/share/coredesktop/updates.d/ in order. Among other things it:
|
||||||
|
|
||||||
|
- stops if there's less than 10 GiB free on / (run `UPDATE_FORCE=1 update` to skip the check)
|
||||||
|
- snapshots / first, if Snapper is set up (see Snapshots above)
|
||||||
|
- updates the Arch keyring, then Arch and GNOME, then AUR packages, Flatpaks, Ruby, GNOME extensions and firmware
|
||||||
|
- warns you not to reboot if the initramfs failed to rebuild
|
||||||
|
- runs any one-time migrations, the way existing machines pick up new CoreDesktop setup
|
||||||
|
- lists packages that are no longer needed, and the command to remove them
|
||||||
|
|
||||||
|
If a step fails, the other steps still run, and the failed ones are listed at the end.
|
||||||
|
|
||||||
It is a good idea to reboot after an update especially if you see you have a new Linux kernel installed (and that happens frequently enough for Arch).
|
It is a good idea to reboot after an update especially if you see you have a new Linux kernel installed (and that happens frequently enough for Arch).
|
||||||
|
|
||||||
@@ -141,6 +192,8 @@ If you want to use mDNS networking on your LAN (the ability to log in to other l
|
|||||||
|
|
||||||
This whole "project" is licensed under the MIT License. Want to help make CoreDesktop better? Awesome! We welcome any and all help. Just contribute a PR and we're off to a better CoreDesktop.
|
This whole "project" is licensed under the MIT License. Want to help make CoreDesktop better? Awesome! We welcome any and all help. Just contribute a PR and we're off to a better CoreDesktop.
|
||||||
|
|
||||||
|
Changes to install.sh only reach new installs. If existing machines need the change too, also add a migration: a script named `migrations/$(date +%s).sh` that `update` runs once on each machine. It may also run on fresh installs, so make it safe to repeat.
|
||||||
|
|
||||||
## Bonus
|
## Bonus
|
||||||
|
|
||||||
Bonus: here's a [nice song to listen to while you're installing](https://music.youtube.com/watch?v=y-5o2f9wzmw&si=_z9kcKHRwj-KTs7w).
|
Bonus: here's a [nice song to listen to while you're installing](https://music.youtube.com/watch?v=y-5o2f9wzmw&si=_z9kcKHRwj-KTs7w).
|
||||||
|
|||||||
+27
-1
@@ -70,7 +70,8 @@ echo "Starting full system setup..."
|
|||||||
|
|
||||||
# Bring the system fully up to date before installing anything new
|
# Bring the system fully up to date before installing anything new
|
||||||
echo "Updating system..."
|
echo "Updating system..."
|
||||||
sudo pacman -Syu --noconfirm
|
sudo pacman -Sy --needed --noconfirm archlinux-keyring
|
||||||
|
sudo pacman -Su --noconfirm
|
||||||
|
|
||||||
cd ~/
|
cd ~/
|
||||||
|
|
||||||
@@ -168,6 +169,26 @@ if ! systemctl is-active tailscaled &> /dev/null; then
|
|||||||
sudo systemctl start tailscaled
|
sudo systemctl start tailscaled
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# SSH keepalives, login lockout, shutdown timeout, OOM protection, boot, SSH PATH
|
||||||
|
echo "Applying system defaults..."
|
||||||
|
bash ~/.local/share/coredesktop/system/defaults.sh
|
||||||
|
|
||||||
|
# Block incoming connections except SSH, mDNS, LocalSend and Tailscale
|
||||||
|
echo "Configuring the firewall..."
|
||||||
|
bash ~/.local/share/coredesktop/system/firewall.sh
|
||||||
|
|
||||||
|
# Snapshot / before each update (btrfs only)
|
||||||
|
echo "Configuring Snapper..."
|
||||||
|
bash ~/.local/share/coredesktop/system/snapper.sh
|
||||||
|
|
||||||
|
echo "Installing GPU drivers..."
|
||||||
|
bash ~/.local/share/coredesktop/system/vulkan.sh
|
||||||
|
bash ~/.local/share/coredesktop/system/nvidia.sh
|
||||||
|
|
||||||
|
# Prompt, zoxide, fzf key bindings, aliases and functions
|
||||||
|
echo "Configuring the shell..."
|
||||||
|
bash ~/.local/share/coredesktop/shell/setup.sh
|
||||||
|
|
||||||
# Apply GNOME dconf settings only if the settings file has changed since last run
|
# Apply GNOME dconf settings only if the settings file has changed since last run
|
||||||
bash ~/.local/share/coredesktop/gnome/apply-gnome-settings.sh
|
bash ~/.local/share/coredesktop/gnome/apply-gnome-settings.sh
|
||||||
|
|
||||||
@@ -177,6 +198,11 @@ if systemctl is-enabled systemd-resolved.service &> /dev/null; then
|
|||||||
bash ~/.local/share/coredesktop/fixmdns.sh
|
bash ~/.local/share/coredesktop/fixmdns.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Run one-time migrations not yet recorded on this machine. On a fresh install
|
||||||
|
# they repeat setup done above, which is safe because each one is idempotent.
|
||||||
|
echo "Running migrations..."
|
||||||
|
run_migrations
|
||||||
|
|
||||||
# Check whether the running kernel matches the installed modules — if not, a
|
# Check whether the running kernel matches the installed modules — if not, a
|
||||||
# newer kernel was installed during this run and a reboot is required
|
# newer kernel was installed during this run and a reboot is required
|
||||||
if ! ls /usr/lib/modules/$(uname -r) > /dev/null 2>&1; then
|
if ! ls /usr/lib/modules/$(uname -r) > /dev/null 2>&1; then
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Rename the coreupdate and corerefresh commands to update and refresh
|
||||||
|
|
||||||
COREDESKTOP="$HOME/.local/share/coredesktop"
|
COREDESKTOP="$HOME/.local/share/coredesktop"
|
||||||
|
|
||||||
source "$COREDESKTOP/utils.sh"
|
source "$COREDESKTOP/utils.sh"
|
||||||
@@ -15,3 +17,5 @@ if [ -f ~/.local/bin/corerefresh ]; then
|
|||||||
install_script "$COREDESKTOP/refresh" ~/.local/bin/refresh
|
install_script "$COREDESKTOP/refresh" ~/.local/bin/refresh
|
||||||
sudo rm -f ~/.local/bin/corerefresh
|
sudo rm -f ~/.local/bin/corerefresh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Hook CoreDesktop's shell setup into ~/.bashrc
|
||||||
|
bash "$HOME/.local/share/coredesktop/shell/setup.sh"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# SSH keepalives, login lockout, shutdown timeout, OOM protection, boot and SSH PATH
|
||||||
|
bash "$HOME/.local/share/coredesktop/system/defaults.sh"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Turn on the ufw firewall
|
||||||
|
bash "$HOME/.local/share/coredesktop/system/firewall.sh"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Set up Snapper for pre-update snapshots on btrfs
|
||||||
|
bash "$HOME/.local/share/coredesktop/system/snapper.sh"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Install Vulkan drivers for Intel and AMD GPUs
|
||||||
|
bash "$HOME/.local/share/coredesktop/system/vulkan.sh"
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# System utilities
|
# System utilities
|
||||||
SYSTEM_UTILS=(
|
SYSTEM_UTILS=(
|
||||||
age
|
age
|
||||||
|
bash-completion
|
||||||
bluez
|
bluez
|
||||||
bluez-utils
|
bluez-utils
|
||||||
borg
|
borg
|
||||||
@@ -48,6 +49,8 @@ SYSTEM_UTILS=(
|
|||||||
tailscale
|
tailscale
|
||||||
traceroute
|
traceroute
|
||||||
tree
|
tree
|
||||||
|
ufw
|
||||||
|
ufw-docker
|
||||||
unzip
|
unzip
|
||||||
usbutils
|
usbutils
|
||||||
vi
|
vi
|
||||||
@@ -128,6 +131,7 @@ MEDIA=(
|
|||||||
FONTS=(
|
FONTS=(
|
||||||
noto-fonts
|
noto-fonts
|
||||||
noto-fonts-emoji
|
noto-fonts-emoji
|
||||||
|
ttf-nerd-fonts-symbols
|
||||||
)
|
)
|
||||||
|
|
||||||
# Services to enable
|
# Services to enable
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# CoreDesktop aliases, sourced by shell/bashrc
|
||||||
|
|
||||||
|
# Files
|
||||||
|
if command -v eza &> /dev/null; then
|
||||||
|
alias ls='eza --group-directories-first --icons=auto'
|
||||||
|
alias ll='eza -lh --group-directories-first --icons=auto'
|
||||||
|
alias la='eza -lha --group-directories-first --icons=auto'
|
||||||
|
alias lt='eza --tree --level=2 --long --icons=auto --git'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fuzzy-find a file with a preview, or open the one you pick in your editor
|
||||||
|
alias ff="fzf --preview 'bat --style=numbers --color=always {}'"
|
||||||
|
alias eff='${EDITOR:-nvim} "$(ff)"'
|
||||||
|
|
||||||
|
alias decompress='tar -xzf'
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
alias ..='cd ..'
|
||||||
|
alias ...='cd ../..'
|
||||||
|
alias ....='cd ../../..'
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
alias g='git'
|
||||||
|
alias lg='lazygit'
|
||||||
|
alias d='docker'
|
||||||
|
alias ld='lazydocker'
|
||||||
|
|
||||||
|
# Git
|
||||||
|
alias gcm='git commit -m'
|
||||||
|
alias gcam='git commit -a -m'
|
||||||
|
alias gcad='git commit -a --amend'
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# CoreDesktop shell setup, sourced from ~/.bashrc (added by shell/setup.sh).
|
||||||
|
# Anything in ~/.bashrc after the line that sources this file overrides it.
|
||||||
|
|
||||||
|
# Only for interactive shells
|
||||||
|
[[ $- != *i* ]] && return
|
||||||
|
|
||||||
|
COREDESKTOP_SHELL="$HOME/.local/share/coredesktop/shell"
|
||||||
|
|
||||||
|
# History: keep a lot of it, skip duplicates, and don't let shells overwrite
|
||||||
|
# each other's history on exit
|
||||||
|
shopt -s histappend
|
||||||
|
HISTCONTROL=ignoreboth
|
||||||
|
HISTSIZE=32768
|
||||||
|
HISTFILESIZE="$HISTSIZE"
|
||||||
|
|
||||||
|
if [[ ! -v BASH_COMPLETION_VERSINFO && -f /usr/share/bash-completion/bash_completion ]]; then
|
||||||
|
source /usr/share/bash-completion/bash_completion
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$COREDESKTOP_SHELL/aliases"
|
||||||
|
source "$COREDESKTOP_SHELL/functions"
|
||||||
|
|
||||||
|
# Prompt. Uses CoreDesktop's starship config unless you have your own.
|
||||||
|
if [[ $TERM != "dumb" ]] && command -v starship &> /dev/null; then
|
||||||
|
[[ -f "$HOME/.config/starship.toml" ]] || export STARSHIP_CONFIG="$COREDESKTOP_SHELL/starship.toml"
|
||||||
|
eval "$(starship init bash)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# z <part of a path> jumps to a directory you've visited before
|
||||||
|
if command -v zoxide &> /dev/null; then
|
||||||
|
eval "$(zoxide init bash)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ctrl-R fuzzy history search, Ctrl-T to insert a file path, Alt-C to cd
|
||||||
|
if [[ -f /usr/share/fzf/key-bindings.bash ]]; then
|
||||||
|
source /usr/share/fzf/key-bindings.bash
|
||||||
|
fi
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# CoreDesktop shell functions, sourced by shell/bashrc
|
||||||
|
|
||||||
|
# n opens Neovim in the current directory, or on the files given
|
||||||
|
n() { if [ "$#" -eq 0 ]; then command nvim . ; else command nvim "$@"; fi; }
|
||||||
|
|
||||||
|
# open a file or URL with its default app, detached from the terminal
|
||||||
|
open() (
|
||||||
|
xdg-open "$@" > /dev/null 2>&1 &
|
||||||
|
)
|
||||||
|
|
||||||
|
# compress <dir> makes <dir>.tar.gz (decompress is an alias for tar -xzf)
|
||||||
|
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }
|
||||||
|
|
||||||
|
# SSH port forwarding
|
||||||
|
# fip <host> <port>... forward each localhost port to the same port on host
|
||||||
|
# dip <port>... stop forwarding those ports
|
||||||
|
# lip list active forwards
|
||||||
|
fip() {
|
||||||
|
(( $# < 2 )) && echo "Usage: fip <host> <port1> [port2] ..." && return 1
|
||||||
|
local host="$1"
|
||||||
|
shift
|
||||||
|
for port in "$@"; do
|
||||||
|
ssh -f -N -L "${port}:localhost:${port}" "$host" && echo "Forwarding localhost:$port -> $host:$port"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
dip() {
|
||||||
|
(( $# == 0 )) && echo "Usage: dip <port1> [port2] ..." && return 1
|
||||||
|
for port in "$@"; do
|
||||||
|
pkill -f "ssh.*-L ${port}:localhost:${port}" && echo "Stopped forwarding port $port" || echo "No forwarding on port $port"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
lip() {
|
||||||
|
pgrep -af "ssh.*-L [0-9]+:localhost:[0-9]+" || echo "No active forwards"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Git worktrees
|
||||||
|
# ga <branch> create ../<repo>--<branch> on a new branch and cd into it
|
||||||
|
# gd from inside that worktree, remove it and its branch
|
||||||
|
ga() {
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
echo "Usage: ga [branch name]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local branch="$1"
|
||||||
|
local base="$(basename "$PWD")"
|
||||||
|
local wt_path="../${base}--${branch}"
|
||||||
|
|
||||||
|
git worktree add -b "$branch" "$wt_path" || return 1
|
||||||
|
command -v mise &> /dev/null && mise trust "$wt_path"
|
||||||
|
cd "$wt_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
gd() {
|
||||||
|
local cwd base branch root worktree answer
|
||||||
|
|
||||||
|
cwd="$(pwd)"
|
||||||
|
worktree="$(basename "$cwd")"
|
||||||
|
|
||||||
|
# split on first `--`
|
||||||
|
root="${worktree%%--*}"
|
||||||
|
branch="${worktree#*--}"
|
||||||
|
|
||||||
|
# Protect against accidentally nuking a non-worktree directory
|
||||||
|
if [[ "$root" == "$worktree" ]]; then
|
||||||
|
echo "Not in a worktree made by ga (expected a <repo>--<branch> directory)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -rp "Remove worktree $worktree and branch $branch? [y/N] " answer
|
||||||
|
[[ $answer == [yY]* ]] || return 0
|
||||||
|
|
||||||
|
cd "../$root"
|
||||||
|
git worktree remove "$cwd" --force || return 1
|
||||||
|
git branch -D "$branch"
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : Hook CoreDesktop's shell setup (prompt, zoxide, fzf key bindings,
|
||||||
|
# aliases and functions in shell/) into ~/.bashrc
|
||||||
|
|
||||||
|
LINE='[[ -f "$HOME/.local/share/coredesktop/shell/bashrc" ]] && source "$HOME/.local/share/coredesktop/shell/bashrc"'
|
||||||
|
|
||||||
|
if grep -qF "$LINE" ~/.bashrc 2>/dev/null; then
|
||||||
|
echo "CoreDesktop shell setup already in .bashrc"
|
||||||
|
else
|
||||||
|
echo "$LINE" >> ~/.bashrc
|
||||||
|
echo "CoreDesktop shell setup added to .bashrc"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
add_newline = true
|
||||||
|
command_timeout = 200
|
||||||
|
format = "[$directory$git_branch$git_status]($style)$character"
|
||||||
|
|
||||||
|
[character]
|
||||||
|
error_symbol = "[✗](bold cyan)"
|
||||||
|
success_symbol = "[❯](bold cyan)"
|
||||||
|
|
||||||
|
[directory]
|
||||||
|
truncation_length = 2
|
||||||
|
truncation_symbol = "…/"
|
||||||
|
repo_root_style = "bold cyan"
|
||||||
|
repo_root_format = "[$repo_root]($repo_root_style)[$path]($style)[$read_only]($read_only_style) "
|
||||||
|
|
||||||
|
[git_branch]
|
||||||
|
format = "[$branch]($style) "
|
||||||
|
style = "italic cyan"
|
||||||
|
|
||||||
|
[git_status]
|
||||||
|
format = '[$all_status]($style)'
|
||||||
|
style = "cyan"
|
||||||
|
ahead = "⇡${count} "
|
||||||
|
diverged = "⇕⇡${ahead_count}⇣${behind_count} "
|
||||||
|
behind = "⇣${count} "
|
||||||
|
conflicted = " "
|
||||||
|
up_to_date = " "
|
||||||
|
untracked = "? "
|
||||||
|
modified = " "
|
||||||
|
stashed = ""
|
||||||
|
staged = ""
|
||||||
|
renamed = ""
|
||||||
|
deleted = ""
|
||||||
@@ -60,8 +60,9 @@ Existing coredesktop hooks use a numeric prefix to control execution order:
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|---------------------------|-----------------------------------------|
|
|---------------------------|-----------------------------------------|
|
||||||
| `00-rename-migration.sh` | Legacy command renames |
|
| `09-snapshot.sh` | Snapper snapshot of / (btrfs only) |
|
||||||
| `10-pacman.sh` | Arch system update |
|
| `10-pacman.sh` | Arch system update |
|
||||||
|
| `12-migrations.sh` | One-time migrations from `migrations/` |
|
||||||
| `15-services.sh` | Enable new systemd services |
|
| `15-services.sh` | Enable new systemd services |
|
||||||
| `20-aur.sh` | AUR packages |
|
| `20-aur.sh` | AUR packages |
|
||||||
| `30-flatpak.sh` | Flatpak apps |
|
| `30-flatpak.sh` | Flatpak apps |
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : System defaults adapted from Omarchy: SSH keepalives, a gentler
|
||||||
|
# login lockout, faster shutdown, out-of-memory protection, faster
|
||||||
|
# boot, and a PATH for commands run over SSH
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# SSH client: notice a dropped connection within a minute instead of hanging
|
||||||
|
# until TCP gives up. Settings in ~/.ssh/config still win per host.
|
||||||
|
echo "Configuring SSH keepalives..."
|
||||||
|
sudo install -d -m 755 /etc/ssh/ssh_config.d
|
||||||
|
sudo tee /etc/ssh/ssh_config.d/20-coredesktop-keepalive.conf > /dev/null << 'EOF'
|
||||||
|
# CoreDesktop: notice dropped connections quickly instead of hanging until TCP
|
||||||
|
# times out. Settings in ~/.ssh/config take precedence over these defaults.
|
||||||
|
Host *
|
||||||
|
ServerAliveInterval 15
|
||||||
|
ServerAliveCountMax 3
|
||||||
|
ConnectTimeout 10
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Login lockout: Arch locks an account for 10 minutes after 3 wrong passwords.
|
||||||
|
# Allow 10 tries and unlock after 2 minutes.
|
||||||
|
echo "Relaxing the failed-login lockout..."
|
||||||
|
sudo sed -i -E \
|
||||||
|
-e 's/^#?[[:space:]]*deny[[:space:]]*=.*/deny = 10/' \
|
||||||
|
-e 's/^#?[[:space:]]*unlock_time[[:space:]]*=.*/unlock_time = 120/' \
|
||||||
|
/etc/security/faillock.conf
|
||||||
|
|
||||||
|
# Shutdown: don't wait 90 seconds on a service or app that won't stop
|
||||||
|
echo "Shortening the shutdown timeout..."
|
||||||
|
sudo install -d -m 755 /etc/systemd/system.conf.d /etc/systemd/user.conf.d
|
||||||
|
printf '[Manager]\nDefaultTimeoutStopSec=10s\n' \
|
||||||
|
| sudo tee /etc/systemd/system.conf.d/10-coredesktop-timeout.conf > /dev/null
|
||||||
|
printf '[Manager]\nDefaultTimeoutStopSec=10s\n' \
|
||||||
|
| sudo tee /etc/systemd/user.conf.d/10-coredesktop-timeout.conf > /dev/null
|
||||||
|
|
||||||
|
# Out of memory: let systemd-oomd kill the app causing memory pressure instead
|
||||||
|
# of the whole session freezing. GNOME launches apps into app.slice, so GNOME
|
||||||
|
# Shell itself is never a candidate.
|
||||||
|
echo "Enabling out-of-memory protection..."
|
||||||
|
sudo install -d -m 755 /etc/systemd/user/app.slice.d
|
||||||
|
sudo tee /etc/systemd/user/app.slice.d/10-coredesktop-oomd.conf > /dev/null << 'EOF'
|
||||||
|
[Slice]
|
||||||
|
ManagedOOMMemoryPressure=kill
|
||||||
|
ManagedOOMSwap=kill
|
||||||
|
EOF
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now systemd-oomd.service
|
||||||
|
|
||||||
|
# Boot: nothing on the desktop needs to wait for the network to come up
|
||||||
|
echo "Masking NetworkManager-wait-online..."
|
||||||
|
sudo systemctl mask NetworkManager-wait-online.service
|
||||||
|
|
||||||
|
# `ssh host command` runs without a login shell, so ~/.bashrc never sets PATH
|
||||||
|
# and tools in ~/.local/bin or installed with mise aren't found. PAM sets the
|
||||||
|
# environment for every login, including those. @{HOME} expands per user.
|
||||||
|
if ! grep -qE '^PATH[[:space:]]' /etc/security/pam_env.conf; then
|
||||||
|
echo "Adding user tool paths for SSH commands..."
|
||||||
|
sudo tee -a /etc/security/pam_env.conf > /dev/null << 'EOF'
|
||||||
|
|
||||||
|
# CoreDesktop: give SSH commands and other non-shell logins the user-level tool paths
|
||||||
|
PATH DEFAULT=/usr/local/sbin:/usr/local/bin:/usr/bin:@{HOME}/.local/share/mise/shims:@{HOME}/.local/bin
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : Turn on the ufw firewall. Incoming connections are blocked except
|
||||||
|
# for the services CoreDesktop runs, and ufw-docker stops ports
|
||||||
|
# published by Docker containers from getting around the firewall.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source "$HOME/.local/share/coredesktop/utils.sh"
|
||||||
|
|
||||||
|
install_packages ufw ufw-docker
|
||||||
|
|
||||||
|
sudo ufw default deny incoming
|
||||||
|
sudo ufw default allow outgoing
|
||||||
|
|
||||||
|
sudo ufw allow 22/tcp comment 'SSH'
|
||||||
|
sudo ufw allow 5353/udp comment 'mDNS'
|
||||||
|
sudo ufw allow 53317 comment 'LocalSend'
|
||||||
|
sudo ufw allow in on tailscale0 comment 'Tailscale'
|
||||||
|
|
||||||
|
sudo ufw --force enable
|
||||||
|
sudo systemctl enable ufw.service
|
||||||
|
|
||||||
|
# ufw-docker only installs its rules while ufw is active, and they take effect
|
||||||
|
# once ufw restarts
|
||||||
|
if ! sudo grep -q 'BEGIN UFW AND DOCKER' /etc/ufw/after.rules; then
|
||||||
|
sudo ufw-docker install
|
||||||
|
sudo systemctl restart ufw.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo ufw status verbose
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : Install NVIDIA's driver when the machine has an NVIDIA GPU and no
|
||||||
|
# NVIDIA driver yet (i.e. archinstall's proprietary option wasn't
|
||||||
|
# picked). Turing (GTX 16xx / RTX 20xx) and newer get the open kernel
|
||||||
|
# modules; Maxwell, Pascal and Volta get the 580xx legacy driver from
|
||||||
|
# the AUR. Older cards stay on nouveau.
|
||||||
|
#
|
||||||
|
# Run from install.sh only, never from an update: silently moving a
|
||||||
|
# working machine off nouveau is too risky to do mid-update.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source "$HOME/.local/share/coredesktop/utils.sh"
|
||||||
|
|
||||||
|
# Read device IDs from sysfs: lspci needs pciutils and wakes suspended GPUs.
|
||||||
|
# Turing is the first generation with GSP firmware (open modules) and the first
|
||||||
|
# with device IDs at 0x1e00 or above; Maxwell starts at 0x1340.
|
||||||
|
GPU=none
|
||||||
|
for device in /sys/bus/pci/devices/*; do
|
||||||
|
[[ $(< "$device/vendor") == 0x10de && $(< "$device/class") == 0x03* ]] || continue
|
||||||
|
id=$(( $(< "$device/device") ))
|
||||||
|
if (( id >= 0x1e00 )); then
|
||||||
|
GPU=open
|
||||||
|
elif (( id >= 0x1340 )) && [ "$GPU" = none ]; then
|
||||||
|
GPU=legacy
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$GPU" = none ]; then
|
||||||
|
echo "No NVIDIA GPU supported by the current drivers found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_installed nvidia-utils || is_installed nvidia-580xx-utils; then
|
||||||
|
echo "NVIDIA driver already installed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# DKMS builds the module for every installed kernel, so each needs its headers
|
||||||
|
PACKAGES=()
|
||||||
|
for kernel in linux linux-lts linux-zen linux-hardened; do
|
||||||
|
if is_installed "$kernel"; then
|
||||||
|
PACKAGES+=("$kernel-headers")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$GPU" = open ]; then
|
||||||
|
PACKAGES+=(nvidia-open-dkms nvidia-utils libva-nvidia-driver)
|
||||||
|
else
|
||||||
|
PACKAGES+=(nvidia-580xx-dkms nvidia-580xx-utils)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing NVIDIA driver..."
|
||||||
|
install_packages "${PACKAGES[@]}"
|
||||||
|
|
||||||
|
# Load the driver from the initramfs so the GPU is ready when GDM starts
|
||||||
|
sudo install -d -m 755 /etc/mkinitcpio.conf.d
|
||||||
|
echo 'MODULES+=(nvidia nvidia_modeset nvidia_uvm nvidia_drm)' \
|
||||||
|
| sudo tee /etc/mkinitcpio.conf.d/nvidia.conf > /dev/null
|
||||||
|
sudo mkinitcpio -P
|
||||||
|
|
||||||
|
# GDM only offers Wayland on NVIDIA when these are enabled, since without them
|
||||||
|
# the GPU loses its memory contents across suspend
|
||||||
|
sudo systemctl enable nvidia-suspend.service nvidia-hibernate.service nvidia-resume.service
|
||||||
|
|
||||||
|
echo "NVIDIA driver installed. Reboot to start using it."
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : Configure Snapper so update can snapshot / before each upgrade
|
||||||
|
# (updates.d/09-snapshot.sh). Only applies when / is btrfs, which is
|
||||||
|
# archinstall's default. /home is a separate subvolume and is not
|
||||||
|
# included in the snapshots.
|
||||||
|
|
||||||
|
if [ "$(findmnt -no FSTYPE /)" != "btrfs" ]; then
|
||||||
|
echo "/ is not btrfs; skipping Snapper setup."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source "$HOME/.local/share/coredesktop/utils.sh"
|
||||||
|
|
||||||
|
install_packages snapper
|
||||||
|
|
||||||
|
if [ ! -f /etc/snapper/configs/root ]; then
|
||||||
|
echo "Creating Snapper config for /..."
|
||||||
|
|
||||||
|
# create-config makes its own /.snapshots subvolume and fails if the path
|
||||||
|
# already exists. archinstall mounts a separate @.snapshots subvolume there,
|
||||||
|
# so follow the Arch wiki: unmount it, let snapper create its config, then
|
||||||
|
# swap snapper's nested subvolume back out for the @.snapshots mount.
|
||||||
|
if mountpoint -q /.snapshots; then
|
||||||
|
sudo umount /.snapshots
|
||||||
|
sudo rmdir /.snapshots
|
||||||
|
sudo snapper -c root create-config /
|
||||||
|
sudo btrfs subvolume delete /.snapshots
|
||||||
|
sudo mkdir /.snapshots
|
||||||
|
sudo mount /.snapshots
|
||||||
|
else
|
||||||
|
if [ -e /.snapshots ] && ! sudo rmdir /.snapshots 2>/dev/null; then
|
||||||
|
echo "/.snapshots exists and isn't an empty directory. Move it aside and run this again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sudo snapper -c root create-config /
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Keep only the last 5 pre-update snapshots, and no hourly timeline. Only set
|
||||||
|
# on a config we created, so a config you made yourself keeps its settings.
|
||||||
|
sudo snapper -c root set-config \
|
||||||
|
TIMELINE_CREATE=no \
|
||||||
|
NUMBER_CLEANUP=yes \
|
||||||
|
NUMBER_MIN_AGE=0 \
|
||||||
|
NUMBER_LIMIT=5 \
|
||||||
|
NUMBER_LIMIT_IMPORTANT=5
|
||||||
|
sudo systemctl disable --now snapper-timeline.timer 2> /dev/null || true
|
||||||
|
else
|
||||||
|
echo "Snapper config for / already exists."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo systemctl enable --now snapper-cleanup.timer
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : Install the Vulkan driver for each Intel or AMD GPU in the machine.
|
||||||
|
# NVIDIA's Vulkan driver comes with nvidia-utils (see nvidia.sh).
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source "$HOME/.local/share/coredesktop/utils.sh"
|
||||||
|
|
||||||
|
# Read vendor IDs from sysfs: lspci needs pciutils and wakes suspended GPUs
|
||||||
|
PACKAGES=()
|
||||||
|
for device in /sys/bus/pci/devices/*; do
|
||||||
|
[[ $(< "$device/class") == 0x03* ]] || continue
|
||||||
|
case "$(< "$device/vendor")" in
|
||||||
|
0x8086) PACKAGES+=(vulkan-intel) ;;
|
||||||
|
0x1002) PACKAGES+=(vulkan-radeon) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#PACKAGES[@]} > 0 )); then
|
||||||
|
mapfile -t PACKAGES < <(printf '%s\n' "${PACKAGES[@]}" | sort -u)
|
||||||
|
install_packages "${PACKAGES[@]}"
|
||||||
|
else
|
||||||
|
echo "No Intel or AMD GPU found."
|
||||||
|
fi
|
||||||
@@ -48,6 +48,16 @@ if [[ -z "$UPDATE_REEXECED" ]]; then
|
|||||||
exec ~/.local/bin/update
|
exec ~/.local/bin/update
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# An upgrade that runs out of disk space part way through can leave the system
|
||||||
|
# half-upgraded. Set UPDATE_FORCE=1 to skip this check.
|
||||||
|
MIN_FREE_GIB=10
|
||||||
|
FREE_KIB="$(df -k --output=avail / | tail -n 1 | tr -d ' ')"
|
||||||
|
if [[ -z "$UPDATE_FORCE" && "$FREE_KIB" =~ ^[0-9]+$ ]] && (( FREE_KIB < MIN_FREE_GIB * 1024 * 1024 )); then
|
||||||
|
echo "Only $(( FREE_KIB / 1024 / 1024 )) GiB free on /; updating needs at least $MIN_FREE_GIB GiB."
|
||||||
|
echo "Free up space (sudo paccache -rk1 clears old cached packages), or run: UPDATE_FORCE=1 update"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Ask for the password once, then keep sudo's cached credentials fresh until
|
# Ask for the password once, then keep sudo's cached credentials fresh until
|
||||||
# update exits, so long-running hooks don't stop for a password again.
|
# update exits, so long-running hooks don't stop for a password again.
|
||||||
sudo -v || exit 1
|
sudo -v || exit 1
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Snapshot / before the system update so a bad upgrade can be rolled back.
|
||||||
|
# system/snapper.sh creates the "root" config on btrfs machines and keeps the
|
||||||
|
# last 5 snapshots.
|
||||||
|
|
||||||
|
is_installed() {
|
||||||
|
command -v snapper &>/dev/null && [ -f /etc/snapper/configs/root ]
|
||||||
|
}
|
||||||
|
|
||||||
|
run_update() {
|
||||||
|
echo "Snapshotting / before updating..."
|
||||||
|
sudo snapper -c root create -c number -d "Before CoreDesktop update" \
|
||||||
|
&& sudo snapper -c root cleanup number
|
||||||
|
}
|
||||||
+19
-1
@@ -6,12 +6,28 @@ source "$COREDESKTOP/utils.sh"
|
|||||||
source "$COREDESKTOP/packages.conf"
|
source "$COREDESKTOP/packages.conf"
|
||||||
source "$COREDESKTOP/remove-packages.conf"
|
source "$COREDESKTOP/remove-packages.conf"
|
||||||
|
|
||||||
|
STATUS=0
|
||||||
|
|
||||||
echo "Removing unwanted packages..."
|
echo "Removing unwanted packages..."
|
||||||
remove_packages "${REMOVE_PACMAN[@]}"
|
remove_packages "${REMOVE_PACMAN[@]}"
|
||||||
remove_flatpaks "${REMOVE_FLATPAKS[@]}"
|
remove_flatpaks "${REMOVE_FLATPAKS[@]}"
|
||||||
|
|
||||||
|
# A machine that hasn't updated in a while can have a keyring too old to verify
|
||||||
|
# the new packages. Update it first; the -Su below completes this -Sy.
|
||||||
|
echo "Updating Arch keyring..."
|
||||||
|
sudo pacman -Sy --needed --noconfirm archlinux-keyring || STATUS=1
|
||||||
|
|
||||||
|
# script(1) keeps pacman's progress bars while logging its output for the
|
||||||
|
# initramfs check
|
||||||
echo "Updating Arch..."
|
echo "Updating Arch..."
|
||||||
sudo pacman -Syu --noconfirm
|
mkdir -p "$(dirname "$PACMAN_UPDATE_LOG")"
|
||||||
|
: > "$PACMAN_UPDATE_LOG"
|
||||||
|
sudo script -qefc "pacman -Su --noconfirm" "$PACMAN_UPDATE_LOG" || STATUS=1
|
||||||
|
|
||||||
|
if initramfs_build_failed; then
|
||||||
|
echo -e "\e[31mRebuilding the initramfs failed. Don't reboot until it's fixed - see the output above or $PACMAN_UPDATE_LOG.\e[0m"
|
||||||
|
STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Checking for new packages..."
|
echo "Checking for new packages..."
|
||||||
install_packages "${SYSTEM_UTILS[@]}"
|
install_packages "${SYSTEM_UTILS[@]}"
|
||||||
@@ -20,3 +36,5 @@ install_packages "${MAINTENANCE[@]}"
|
|||||||
install_packages "${DESKTOP[@]}"
|
install_packages "${DESKTOP[@]}"
|
||||||
install_packages "${MEDIA[@]}"
|
install_packages "${MEDIA[@]}"
|
||||||
install_packages "${FONTS[@]}"
|
install_packages "${FONTS[@]}"
|
||||||
|
|
||||||
|
exit $STATUS
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run one-time changes from migrations/ that haven't run on this machine yet.
|
||||||
|
# This is how machines installed before a change to install.sh catch up, so a
|
||||||
|
# change that should reach existing machines needs a migration as well. To add
|
||||||
|
# one, create migrations/$(date +%s).sh; it runs with bash, once, on success.
|
||||||
|
#
|
||||||
|
# Runs after 10-pacman.sh so migrations install packages against fresh
|
||||||
|
# package databases.
|
||||||
|
|
||||||
|
COREDESKTOP="$HOME/.local/share/coredesktop"
|
||||||
|
|
||||||
|
source "$COREDESKTOP/utils.sh"
|
||||||
|
|
||||||
|
run_migrations
|
||||||
@@ -8,3 +8,12 @@ if command -v paccache &>/dev/null; then
|
|||||||
sudo paccache -rk2
|
sudo paccache -rk2
|
||||||
sudo paccache -ruk0
|
sudo paccache -ruk0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Report packages that were installed as dependencies and are no longer needed.
|
||||||
|
# Not removed automatically: some may be tools you use directly.
|
||||||
|
ORPHANS="$(pacman -Qtdq 2>/dev/null)"
|
||||||
|
if [ -n "$ORPHANS" ]; then
|
||||||
|
echo "Packages no longer needed by anything:"
|
||||||
|
echo "$ORPHANS" | sed 's/^/ /'
|
||||||
|
echo "To remove them: sudo pacman -Rns \$(pacman -Qtdq)"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "$HOME/.local/share/coredesktop/utils.sh"
|
||||||
|
|
||||||
|
if initramfs_build_failed; then
|
||||||
|
echo -e "\e[31mDon't reboot yet: the initramfs failed to rebuild (see $PACMAN_UPDATE_LOG).\e[0m"
|
||||||
# If the running kernel's modules are gone, a newer kernel was installed
|
# If the running kernel's modules are gone, a newer kernel was installed
|
||||||
if [ ! -d "/usr/lib/modules/$(uname -r)" ]; then
|
elif [ ! -d "/usr/lib/modules/$(uname -r)" ]; then
|
||||||
echo "A newer kernel has been installed. Please reboot when convenient."
|
echo "A newer kernel has been installed. Please reboot when convenient."
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -73,6 +73,40 @@ enable_services() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Run each script in migrations/ that hasn't run on this machine yet, oldest
|
||||||
|
# first. A migration is recorded as done only when it succeeds, and the first
|
||||||
|
# failure stops the run so a later migration never runs ahead of an earlier one.
|
||||||
|
run_migrations() {
|
||||||
|
local dir="${COREDESKTOP:-$HOME/.local/share/coredesktop}/migrations"
|
||||||
|
local state="$HOME/.local/state/coredesktop/migrations"
|
||||||
|
local file name
|
||||||
|
|
||||||
|
mkdir -p "$state"
|
||||||
|
for file in "$dir"/*.sh; do
|
||||||
|
[ -f "$file" ] || continue
|
||||||
|
name="$(basename "$file")"
|
||||||
|
[ -f "$state/$name" ] && continue
|
||||||
|
|
||||||
|
echo "Running migration $name..."
|
||||||
|
if ! bash "$file"; then
|
||||||
|
echo "Migration $name failed; it will run again on the next update."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
touch "$state/$name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output of the last update's pacman -Su, written by updates.d/10-pacman.sh
|
||||||
|
PACMAN_UPDATE_LOG="$HOME/.local/state/coredesktop/pacman-update.log"
|
||||||
|
|
||||||
|
# True if the last update rebuilt the initramfs and mkinitcpio reported a
|
||||||
|
# failure. Rebooting then can leave the machine unbootable.
|
||||||
|
initramfs_build_failed() {
|
||||||
|
[ -f "$PACMAN_UPDATE_LOG" ] || return 1
|
||||||
|
grep -q "Updating linux initcpios" "$PACMAN_UPDATE_LOG" || return 1
|
||||||
|
grep -q "==> ERROR" "$PACMAN_UPDATE_LOG" || ! grep -q "Image generation successful" "$PACMAN_UPDATE_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
# Install a script atomically (copy to a temp file, then rename over the target)
|
# Install a script atomically (copy to a temp file, then rename over the target)
|
||||||
# so a copy of it that is running right now keeps reading the old file
|
# so a copy of it that is running right now keeps reading the old file
|
||||||
install_script() {
|
install_script() {
|
||||||
|
|||||||
Reference in New Issue
Block a user