Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
e1c4f3cb4f | |||
5bd769aedb | |||
0f269deebb | |||
3365186b6c | |||
ca277b8d88 | |||
|
73302490c5 | ||
|
f9e76c5981 |
1 changed files with 79 additions and 218 deletions
291
menhera.sh
291
menhera.sh
|
@ -3,69 +3,25 @@ set -Eeuo pipefail
|
||||||
# disable command path hashing as we are going to move fast and break things
|
# disable command path hashing as we are going to move fast and break things
|
||||||
set +h
|
set +h
|
||||||
|
|
||||||
# config
|
|
||||||
WORKDIR="/tmp/menhera"
|
WORKDIR="/tmp/menhera"
|
||||||
MIRROR="https://iso-ultralite.meson.cc/archlinux"
|
MIRROR="https://iso.meson.cc/ultralite/archlinux"
|
||||||
ROOTFS="${MIRROR}/iso/latest/arch/x86_64/airootfs.sfs"
|
ROOTFS="${MIRROR}/iso/latest/arch/x86_64/airootfs.sfs"
|
||||||
|
|
||||||
declare -A ARCH_MAP=(
|
|
||||||
["x86_64"]="amd64"
|
|
||||||
)
|
|
||||||
|
|
||||||
# internal global variables
|
# internal global variables
|
||||||
OLDROOT="/"
|
OLDROOT="/"
|
||||||
NEWROOT=""
|
NEWROOT=""
|
||||||
|
|
||||||
MACHINE_TYPE=$(uname -m)
|
[[ "$(uname -m)" == "x86_64" ]] || { echo "error: wrong arch $(uname -m) (not x86_64)"; exit 1; }
|
||||||
ARCH_ID=${ARCH_MAP[$MACHINE_TYPE]:-$MACHINE_TYPE}
|
|
||||||
|
|
||||||
# fix possible PATH pMYMAP[missing]+_roblems
|
PUBKEY="./key.pub"
|
||||||
export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
[ -f "$PUBKEY" ] || { echo "error: you should put your ssh public key at ${PUBKEY} in order to ssh with root account and this key later"; exit 1; }
|
||||||
|
|
||||||
menhera::reset_sshd_config() {
|
if [[ $EUID -ne 0 ]]; then
|
||||||
cat > /etc/ssh/sshd_config <<EOF
|
echo "This script must be run as root"
|
||||||
PermitRootLogin yes
|
|
||||||
AcceptEnv LANG LC_*
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# environment compatibility
|
|
||||||
menhera::__compat_restart_ssh() {
|
|
||||||
if [ -x "$(command -v systemctl)" ]; then
|
|
||||||
systemctl daemon-reload
|
|
||||||
if ! systemctl restart sshd; then
|
|
||||||
echo "SSH daemon start failed, try resetting config..."
|
|
||||||
menhera::reset_sshd_config
|
|
||||||
if ! systemctl restart sshd; then
|
|
||||||
echo "SSH daemon fail to start, dropping you to a shell..."
|
|
||||||
sh
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
elif false && [ -x "$(command -v service)" ]; then # there is no such thing in archlinux
|
|
||||||
if ! service ssh restart; then
|
|
||||||
echo "SSH daemon start failed, try resetting config..."
|
|
||||||
menhera::reset_sshd_config
|
|
||||||
if ! service ssh restart; then
|
|
||||||
echo "SSH daemon fail to start, dropping you to a shell..."
|
|
||||||
sh
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "ERROR: Cannot restart SSH server, init system not recoginzed" >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
menhera::__compat_reload_init() {
|
systemctl status > /dev/null || { echo "error: no systemd"; exit 1; }
|
||||||
if [ -x "$(command -v systemctl)" ]; then
|
|
||||||
systemctl daemon-reexec
|
|
||||||
elif [ -x "$(command -v telinit)" ]; then
|
|
||||||
telinit u
|
|
||||||
else
|
|
||||||
echo "ERROR: Cannot re-exec init, init system not recoginzed" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# helper functions
|
# helper functions
|
||||||
# https://stackoverflow.com/a/3232082/2646069
|
# https://stackoverflow.com/a/3232082/2646069
|
||||||
|
@ -82,160 +38,6 @@ menhera::confirm() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# jobs
|
|
||||||
menhera::get_rootfs() {
|
|
||||||
if [ -z "${ROOTFS}" ]; then
|
|
||||||
echo "Getting rootfs URL..."
|
|
||||||
|
|
||||||
# forgive me for parsing HTML with these shit
|
|
||||||
# and hope it works
|
|
||||||
ROOTFS_TIME=$(wget -qO- --show-progress "https://images.linuxcontainers.org/images/debian/buster/${ARCH_ID}/default/?C=M;O=D" | grep -oP '(\d{8}_\d{2}:\d{2})' | head -n 1)
|
|
||||||
|
|
||||||
ROOTFS="https://images.linuxcontainers.org/images/debian/buster/${ARCH_ID}/default/${ROOTFS_TIME}/rootfs.squashfs"
|
|
||||||
else
|
|
||||||
echo "\$ROOTFS is set to '$ROOTFS'"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::sync_filesystem() {
|
|
||||||
echo "Syncing..."
|
|
||||||
sync
|
|
||||||
sync
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::prepare_environment() {
|
|
||||||
echo "Loading kernel modules..."
|
|
||||||
modprobe overlay
|
|
||||||
modprobe squashfs
|
|
||||||
|
|
||||||
sysctl kernel.panic=10
|
|
||||||
|
|
||||||
echo "Creating workspace in '${WORKDIR}'..."
|
|
||||||
# workspace
|
|
||||||
mkdir -p "${WORKDIR}"
|
|
||||||
mount -t tmpfs -o size=100% tmpfs "${WORKDIR}"
|
|
||||||
|
|
||||||
# new rootfs
|
|
||||||
mkdir -p "${WORKDIR}/newroot"
|
|
||||||
# readonly part of new rootfs
|
|
||||||
mkdir -p "${WORKDIR}/newrootro"
|
|
||||||
# writeable part of new rootfs
|
|
||||||
mkdir -p "${WORKDIR}/newrootrw"
|
|
||||||
# overlayfs workdir
|
|
||||||
mkdir -p "${WORKDIR}/overlayfs_workdir"
|
|
||||||
|
|
||||||
echo "Downloading temporary rootfs..."
|
|
||||||
wget -q --show-progress -O "${WORKDIR}/rootfs.squashfs" "${ROOTFS}"
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::mount_new_rootfs() {
|
|
||||||
echo "Mounting temporary rootfs..."
|
|
||||||
mount -t squashfs "${WORKDIR}/rootfs.squashfs" "${WORKDIR}/newrootro"
|
|
||||||
mount -t overlay overlay -o rw,lowerdir="${WORKDIR}/newrootro",upperdir="${WORKDIR}/newrootrw",workdir="${WORKDIR}/overlayfs_workdir" "${WORKDIR}/newroot"
|
|
||||||
|
|
||||||
NEWROOT="${WORKDIR}/newroot"
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::install_software() {
|
|
||||||
return
|
|
||||||
echo "Installing OpenSSH Server into new rootfs..."
|
|
||||||
|
|
||||||
# disable APT cache
|
|
||||||
echo -e 'Dir::Cache "";\nDir::Cache::archives "";' > "${NEWROOT}/etc/apt/apt.conf.d/00_disable-cache-directories"
|
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive chroot "${NEWROOT}" apt-get update -y
|
|
||||||
DEBIAN_FRONTEND=noninteractive chroot "${NEWROOT}" apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y openssh-server
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::copy_config() {
|
|
||||||
echo "Copying important config into new rootfs..."
|
|
||||||
! cp -axL "${OLDROOT}/etc/resolv.conf" "${NEWROOT}/etc"
|
|
||||||
! cp -axr "${OLDROOT}/etc/ssh" "${NEWROOT}/etc"
|
|
||||||
! cp -ax "${OLDROOT}/etc/"{passwd,shadow} "${NEWROOT}/etc"
|
|
||||||
! cp -axr "${OLDROOT}/root/.ssh" "${NEWROOT}/root"
|
|
||||||
|
|
||||||
chroot "${NEWROOT}" chsh -s /bin/bash root
|
|
||||||
|
|
||||||
cat > "${NEWROOT}/etc/motd" <<EOF
|
|
||||||
|
|
||||||
Download menhera.sh at https://github.com/Jamesits/menhera.sh
|
|
||||||
|
|
||||||
!!!NOTICE!!!
|
|
||||||
|
|
||||||
This is a minimal RAM system created by menhera.sh. Feel free to format your disk, but don't blame anyone
|
|
||||||
except yourself if you lost important files or your system is broken.
|
|
||||||
|
|
||||||
If you think you've done something wrong, reboot immediately -- there is still hope.
|
|
||||||
|
|
||||||
Your original rootfs is at /mnt/oldroot. Be careful dealing with it.
|
|
||||||
|
|
||||||
Have a lot of fun...
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::swap_root() {
|
|
||||||
echo "Swapping rootfs..."
|
|
||||||
# prepare future mount point for our old rootfs
|
|
||||||
mkdir -p "${WORKDIR}/newroot/mnt/oldroot"
|
|
||||||
mount --make-rprivate /
|
|
||||||
|
|
||||||
# swap root
|
|
||||||
pivot_root "${WORKDIR}/newroot" "${WORKDIR}/newroot/mnt/oldroot"
|
|
||||||
|
|
||||||
OLDROOT="/mnt/oldroot"
|
|
||||||
NEWROOT="/"
|
|
||||||
|
|
||||||
# move mounts
|
|
||||||
for i in dev proc sys run; do
|
|
||||||
if [ -d "${OLDROOT}/$i" ]; then
|
|
||||||
mount --move "${OLDROOT}/$i" "${NEWROOT}/$i"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
mount -t tmpfs -o size=100% tmpfs "${NEWROOT}/tmp"
|
|
||||||
|
|
||||||
mkdir -p "${WORKDIR}"
|
|
||||||
mount --move "${OLDROOT}/${WORKDIR}" "${WORKDIR}"
|
|
||||||
|
|
||||||
echo "Restarting SSH daemon..."
|
|
||||||
menhera::__compat_restart_ssh
|
|
||||||
}
|
|
||||||
|
|
||||||
menhera::clear_processes() {
|
|
||||||
echo "Disabling swap..."
|
|
||||||
swapoff -a
|
|
||||||
|
|
||||||
echo "Restarting init process..."
|
|
||||||
menhera::__compat_reload_init
|
|
||||||
# hope 15s is enough
|
|
||||||
sleep 15
|
|
||||||
|
|
||||||
echo "Killing all programs still using the old root..."
|
|
||||||
fuser -kvm "${OLDROOT}" -15
|
|
||||||
# in most cases the parent process of this script will be killed, so goodbye
|
|
||||||
}
|
|
||||||
|
|
||||||
# main procedure
|
|
||||||
LIBRARY_ONLY=0
|
|
||||||
|
|
||||||
while test $# -gt 0
|
|
||||||
do
|
|
||||||
case "$1" in
|
|
||||||
--lib) LIBRARY_ONLY=1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $LIBRARY_ONLY -eq 1 ]]; then
|
|
||||||
# acting as a library only
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
|
||||||
echo "This script must be run as root"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "We will start a temporary RAM system as your recovery environment."
|
echo -e "We will start a temporary RAM system as your recovery environment."
|
||||||
echo -e "Note that this script will kill programs and umount filesystems without prompting."
|
echo -e "Note that this script will kill programs and umount filesystems without prompting."
|
||||||
echo -e "Please confirm:"
|
echo -e "Please confirm:"
|
||||||
|
@ -243,23 +45,82 @@ echo -e "\tYou have closed all programs you can, and backed up all important dat
|
||||||
echo -e "\tYou can SSH into your system as root user"
|
echo -e "\tYou can SSH into your system as root user"
|
||||||
menhera::confirm || exit -1
|
menhera::confirm || exit -1
|
||||||
|
|
||||||
menhera::get_rootfs
|
# fix possible PATH problems
|
||||||
menhera::sync_filesystem
|
export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
menhera::prepare_environment
|
echo "Loading kernel modules..."
|
||||||
menhera::mount_new_rootfs
|
modprobe overlay
|
||||||
menhera::copy_config
|
modprobe squashfs
|
||||||
menhera::install_software
|
sysctl kernel.panic=10
|
||||||
menhera::swap_root
|
|
||||||
|
echo "Creating workspace in '${WORKDIR}'..."
|
||||||
|
# workspace
|
||||||
|
mkdir -p "${WORKDIR}"
|
||||||
|
mount -t tmpfs -o size=100% tmpfs "${WORKDIR}"
|
||||||
|
|
||||||
|
mkdir -p "${WORKDIR}/newroot" # where iso is stored
|
||||||
|
mkdir -p "${WORKDIR}/newrootro" # lower
|
||||||
|
mkdir -p "${WORKDIR}/newrootrw" # upper
|
||||||
|
mkdir -p "${WORKDIR}/overlayfs_workdir" # work
|
||||||
|
|
||||||
|
echo "Downloading temporary rootfs..."
|
||||||
|
wget -O "${WORKDIR}/rootfs.squashfs" "${ROOTFS}"
|
||||||
|
|
||||||
|
echo "Mounting temporary rootfs..."
|
||||||
|
mount -t squashfs "${WORKDIR}/rootfs.squashfs" "${WORKDIR}/newrootro"
|
||||||
|
mount -t overlay overlay -o rw,lowerdir="${WORKDIR}/newrootro",upperdir="${WORKDIR}/newrootrw",workdir="${WORKDIR}/overlayfs_workdir" "${WORKDIR}/newroot"
|
||||||
|
NEWROOT="${WORKDIR}/newroot"
|
||||||
|
|
||||||
|
rm -f "${NEWROOT}/etc/resolv.conf"
|
||||||
|
cat "${OLDROOT}/etc/resolv.conf" > "${NEWROOT}/etc/resolv.conf"
|
||||||
|
install -DTm0600 <(cat "$PUBKEY") "${NEWROOT}/root/.ssh/authorized_keys"
|
||||||
|
chmod 0700 "${NEWROOT}/root/.ssh"
|
||||||
|
sync && sync # why sync twice? idk
|
||||||
|
|
||||||
|
echo "Swapping rootfs..."
|
||||||
|
# prepare future mount point for our old rootfs
|
||||||
|
mkdir -p "${WORKDIR}/newroot/mnt/oldroot"
|
||||||
|
mount --make-rprivate /
|
||||||
|
|
||||||
|
# swap root
|
||||||
|
pivot_root "${WORKDIR}/newroot" "${WORKDIR}/newroot/mnt/oldroot"
|
||||||
|
|
||||||
|
OLDROOT="/mnt/oldroot"
|
||||||
|
NEWROOT="/"
|
||||||
|
|
||||||
|
# move mounts
|
||||||
|
for i in dev proc sys run; do
|
||||||
|
if [ -d "${OLDROOT}/$i" ]; then
|
||||||
|
mount --move "${OLDROOT}/$i" "${NEWROOT}/$i"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
mount -t tmpfs -o size=100% tmpfs "${NEWROOT}/tmp"
|
||||||
|
|
||||||
|
mkdir -p "${WORKDIR}"
|
||||||
|
mount --move "${OLDROOT}/${WORKDIR}" "${WORKDIR}"
|
||||||
|
|
||||||
|
echo "Restarting SSH daemon..."
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl is-active ssh && systemctl stop ssh || true
|
||||||
|
systemctl restart sshd
|
||||||
|
|
||||||
|
echo "Disabling swap..."
|
||||||
|
swapoff -a
|
||||||
|
|
||||||
|
echo "Restarting init process..."
|
||||||
|
systemctl daemon-reexec
|
||||||
|
|
||||||
|
systemctl --quiet is-system-running || true
|
||||||
|
|
||||||
echo -e "If you are connecting from SSH, please create a second session to this host use root and"
|
echo -e "If you are connecting from SSH, please create a second session to this host use root and"
|
||||||
echo -e "confirm you can get a shell."
|
echo -e "confirm you can get a shell."
|
||||||
echo -e "After your confirmation, we are going to kill the old SSH server."
|
echo -e "After your confirmation, we are going to kill the old SSH server."
|
||||||
|
|
||||||
if menhera::confirm; then
|
if menhera::confirm; then
|
||||||
menhera::clear_processes
|
echo "Killing all programs still using the old root..."
|
||||||
|
fuser -kvm "${OLDROOT}" -15
|
||||||
|
# in most cases the parent process of this script will be killed, so goodbye
|
||||||
else
|
else
|
||||||
echo -e "Please manually issue a reboot to recover your old OS. If you believe there is a bug in menhera.sh, "
|
echo -e "Please manually issue a reboot to recover your old OS. If you believe there is a bug in menhera.sh, "
|
||||||
echo -e "raise a ticket at https://github.com/Jamesits/menhera.sh/issues ."
|
echo -e "raise a ticket at https://github.com/Jamesits/menhera.sh/issues ."
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in a new issue