mirror of
https://github.com/agelesslinux/agelesslinux.git
synced 2026-06-30 19:57:12 +00:00
Remove mid-session systemd-userdbd reload that broke SDDM/KDE lock screen password verification. Add pre-conversion system analysis with display manager detection and SDDM-specific warnings. Add --dry-run to preview all actions, /etc/agelesslinux.conf to record what was installed, and --revert to automatically undo a conversion. Back up existing userdb records before modifying. Version 0.0.5. Addresses #1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1214 lines
50 KiB
Bash
1214 lines
50 KiB
Bash
#!/bin/bash
|
|
# ============================================================================
|
|
# become-ageless.sh — Ageless Linux Distribution Conversion Tool
|
|
# Version 0.0.5
|
|
#
|
|
# This script converts your existing Linux installation into
|
|
# Ageless Linux, a California-regulated operating system.
|
|
#
|
|
# By running this script, the person or entity who controls this
|
|
# device becomes an "operating system provider" as defined by
|
|
# California Civil Code § 1798.500(g), because they now "control
|
|
# the operating system software on a general purpose computing device."
|
|
#
|
|
# Ageless Linux does not collect, store, transmit, or even think about
|
|
# the age of any user, in full and knowing noncompliance with the
|
|
# California Digital Age Assurance Act (AB 1043, Chapter 675,
|
|
# Statutes of 2025).
|
|
#
|
|
# SPDX-License-Identifier: Unlicense
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
AGELESS_VERSION="0.0.5"
|
|
AGELESS_CODENAME="Timeless"
|
|
CONF_PATH="/etc/agelesslinux.conf"
|
|
|
|
FLAGRANT=0
|
|
ACCEPT=0
|
|
PERSISTENT=0
|
|
DRY_RUN=0
|
|
REVERT=0
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--flagrant) FLAGRANT=1 ;;
|
|
--accept) ACCEPT=1 ;;
|
|
--persistent) PERSISTENT=1 ;;
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--revert) REVERT=1 ;;
|
|
--version)
|
|
echo "become-ageless.sh ${AGELESS_VERSION} (${AGELESS_CODENAME})"
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo -e "${RED}ERROR:${NC} Unknown argument: $arg"
|
|
echo ""
|
|
echo " Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo " --flagrant Remove all compliance fig leaves"
|
|
echo " --accept Accept the legal terms non-interactively"
|
|
echo " --persistent Install agelessd daemon (24h birthDate enforcement)"
|
|
echo " --dry-run Analyze system and show planned actions without modifying"
|
|
echo " --revert Undo a previous Ageless Linux conversion"
|
|
echo " --version Show version and exit"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ── Revert mode ──────────────────────────────────────────────────────────────
|
|
|
|
if [[ $REVERT -eq 1 ]]; then
|
|
echo ""
|
|
echo -e "${BOLD}Ageless Linux Revert Tool v${AGELESS_VERSION}${NC}"
|
|
echo ""
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo -e "${RED}ERROR:${NC} This script must be run as root."
|
|
echo " Please run: sudo $0 --revert"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$CONF_PATH" ]]; then
|
|
if [[ -f /etc/os-release.pre-ageless ]]; then
|
|
echo -e "${YELLOW}WARNING:${NC} No /etc/agelesslinux.conf found."
|
|
echo ""
|
|
echo " It appears this system was converted by an older version of"
|
|
echo " become-ageless.sh (v0.0.4 or earlier) that did not write a"
|
|
echo " configuration file. Automatic revert is not possible."
|
|
echo ""
|
|
echo " To manually revert, run:"
|
|
echo ""
|
|
echo " sudo cp /etc/os-release.pre-ageless /etc/os-release"
|
|
echo " sudo rm -f /etc/os-release.pre-ageless"
|
|
if [[ -f /etc/lsb-release.pre-ageless ]]; then
|
|
echo " sudo cp /etc/lsb-release.pre-ageless /etc/lsb-release"
|
|
echo " sudo rm -f /etc/lsb-release.pre-ageless"
|
|
fi
|
|
echo " sudo rm -rf /etc/ageless"
|
|
if [[ -d /etc/userdb ]]; then
|
|
echo " sudo rm -rf /etc/userdb"
|
|
fi
|
|
if systemctl list-unit-files agelessd.timer &>/dev/null 2>&1; then
|
|
echo " sudo systemctl disable --now agelessd.timer"
|
|
echo " sudo rm -f /etc/systemd/system/agelessd.service /etc/systemd/system/agelessd.timer"
|
|
echo " sudo systemctl daemon-reload"
|
|
fi
|
|
if systemctl list-unit-files systemd-userdbd.service &>/dev/null 2>&1; then
|
|
echo " sudo systemctl try-reload-or-restart systemd-userdbd.service"
|
|
fi
|
|
echo ""
|
|
echo " Then fully log out and log back in (or reboot)."
|
|
else
|
|
echo " No Ageless Linux installation found on this system."
|
|
echo " (No /etc/agelesslinux.conf and no /etc/os-release.pre-ageless)"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# shellcheck disable=SC1090
|
|
source "$CONF_PATH"
|
|
|
|
echo -e " Found installation record: Ageless Linux ${AGELESS_VERSION:-unknown}"
|
|
echo -e " Installed: ${AGELESS_DATE:-unknown}"
|
|
if [[ "${AGELESS_FLAGRANT:-0}" == "1" ]]; then
|
|
echo -e " Mode: ${RED}flagrant${NC}"
|
|
else
|
|
echo -e " Mode: standard"
|
|
fi
|
|
echo ""
|
|
echo -e " ${BOLD}Reverting Ageless Linux conversion...${NC}"
|
|
echo ""
|
|
|
|
# Restore os-release
|
|
if [[ "${AGELESS_BACKED_UP_OS_RELEASE:-0}" == "1" ]] && [[ -f /etc/os-release.pre-ageless ]]; then
|
|
cp /etc/os-release.pre-ageless /etc/os-release
|
|
rm -f /etc/os-release.pre-ageless
|
|
echo -e " [${GREEN}✓${NC}] Restored /etc/os-release"
|
|
fi
|
|
|
|
# Restore lsb-release
|
|
if [[ "${AGELESS_BACKED_UP_LSB_RELEASE:-0}" == "1" ]] && [[ -f /etc/lsb-release.pre-ageless ]]; then
|
|
cp /etc/lsb-release.pre-ageless /etc/lsb-release
|
|
rm -f /etc/lsb-release.pre-ageless
|
|
echo -e " [${GREEN}✓${NC}] Restored /etc/lsb-release"
|
|
fi
|
|
|
|
# Remove agelessd if installed
|
|
if [[ "${AGELESS_AGELESSD_INSTALLED:-0}" == "1" ]]; then
|
|
systemctl disable --now agelessd.timer 2>/dev/null || true
|
|
rm -f /etc/systemd/system/agelessd.service
|
|
rm -f /etc/systemd/system/agelessd.timer
|
|
systemctl daemon-reload 2>/dev/null || true
|
|
echo -e " [${GREEN}✓${NC}] Removed agelessd service and timer"
|
|
fi
|
|
|
|
# Remove userdb records we created from scratch
|
|
if [[ -n "${AGELESS_USERDB_CREATED:-}" ]]; then
|
|
for username in $AGELESS_USERDB_CREATED; do
|
|
if [[ -f "/etc/userdb/${username}.user" ]]; then
|
|
rm -f "/etc/userdb/${username}.user"
|
|
echo -e " [${GREEN}✓${NC}] Removed /etc/userdb/${username}.user"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Restore userdb records we backed up before modifying
|
|
if [[ -n "${AGELESS_USERDB_BACKED_UP:-}" ]]; then
|
|
for username in $AGELESS_USERDB_BACKED_UP; do
|
|
if [[ -f "/etc/userdb/${username}.user.pre-ageless" ]]; then
|
|
mv "/etc/userdb/${username}.user.pre-ageless" "/etc/userdb/${username}.user"
|
|
echo -e " [${GREEN}✓${NC}] Restored /etc/userdb/${username}.user from backup"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Remove /etc/userdb/ if we created it and it's now empty
|
|
if [[ "${AGELESS_USERDB_DIR_CREATED:-0}" == "1" ]] && [[ -d /etc/userdb ]]; then
|
|
if [[ -z "$(ls -A /etc/userdb 2>/dev/null)" ]]; then
|
|
rmdir /etc/userdb
|
|
echo -e " [${GREEN}✓${NC}] Removed empty /etc/userdb/"
|
|
else
|
|
echo -e " [${YELLOW}~${NC}] /etc/userdb/ not empty, leaving in place"
|
|
fi
|
|
fi
|
|
|
|
# Remove /etc/ageless/
|
|
if [[ -d /etc/ageless ]]; then
|
|
rm -rf /etc/ageless
|
|
echo -e " [${GREEN}✓${NC}] Removed /etc/ageless/"
|
|
fi
|
|
|
|
# Restart userdbd to clear any cached records (safe during revert)
|
|
if systemctl list-unit-files systemd-userdbd.service &>/dev/null 2>&1; then
|
|
systemctl try-reload-or-restart systemd-userdbd.service 2>/dev/null || true
|
|
echo -e " [${GREEN}✓${NC}] Reloaded systemd-userdbd"
|
|
fi
|
|
|
|
# Remove conf file
|
|
rm -f "$CONF_PATH"
|
|
echo -e " [${GREEN}✓${NC}] Removed $CONF_PATH"
|
|
|
|
echo ""
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}Revert complete.${NC}"
|
|
echo ""
|
|
echo -e " Your system has been restored to ${CYAN}${AGELESS_BASE_NAME:-your original distro}${AGELESS_BASE_VERSION:+ $AGELESS_BASE_VERSION}${NC}."
|
|
echo ""
|
|
echo -e " You are no longer an operating system provider."
|
|
echo -e " The California Attorney General has no business with you today."
|
|
echo ""
|
|
echo -e " ${YELLOW}Please fully log out and log back in (or reboot) for all"
|
|
echo -e " changes to take effect.${NC}"
|
|
echo ""
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
# ── Banner ───────────────────────────────────────────────────────────────────
|
|
|
|
cat << 'BANNER'
|
|
|
|
█████╗ ██████╗ ███████╗██╗ ███████╗███████╗███████╗
|
|
██╔══██╗██╔════╝ ██╔════╝██║ ██╔════╝██╔════╝██╔════╝
|
|
███████║██║ ███╗█████╗ ██║ █████╗ ███████╗███████╗
|
|
██╔══██║██║ ██║██╔══╝ ██║ ██╔══╝ ╚════██║╚════██║
|
|
██║ ██║╚██████╔╝███████╗███████╗███████╗███████║███████║
|
|
╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝
|
|
L I N U X
|
|
"Software for humans of indeterminate age"
|
|
|
|
BANNER
|
|
|
|
echo -e "${BOLD}Ageless Linux Distribution Conversion Tool v${AGELESS_VERSION}${NC}"
|
|
echo -e "${CYAN}Codename: ${AGELESS_CODENAME}${NC}"
|
|
|
|
# ── Mode banners ─────────────────────────────────────────────────────────────
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
echo ""
|
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${RED} FLAGRANT MODE ENABLED${NC}"
|
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo " In standard mode, Ageless Linux ships a stub age verification"
|
|
echo " API that returns no data. This preserves the fig leaf of a"
|
|
echo " 'good faith effort' under § 1798.502(b)."
|
|
echo ""
|
|
echo " Flagrant mode removes the fig leaf."
|
|
echo ""
|
|
echo " No API will be installed. No interface of any kind will exist"
|
|
echo " for age collection. No mechanism will be provided by which"
|
|
echo " any developer could request or receive an age bracket signal."
|
|
echo " The system will actively declare, in machine-readable form,"
|
|
echo " that it refuses to comply."
|
|
echo ""
|
|
echo " This mode is intended for devices that will be physically"
|
|
echo " handed to children."
|
|
fi
|
|
if [[ $PERSISTENT -eq 1 ]]; then
|
|
echo ""
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${CYAN} PERSISTENT MODE ENABLED${NC}"
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo " In addition to the one-time conversion, agelessd will be"
|
|
echo " installed — a systemd timer that runs every 24 hours to ensure"
|
|
echo " that systemd userdb birthDate fields remain neutralized."
|
|
echo ""
|
|
echo " This guards against package updates, user creation, or desktop"
|
|
echo " tools that may attempt to populate age data in the future."
|
|
fi
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo ""
|
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${YELLOW} DRY RUN MODE${NC}"
|
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo " No changes will be made. This run will analyze your system"
|
|
echo " and show exactly what would happen during a real conversion."
|
|
fi
|
|
echo ""
|
|
|
|
# ── Preflight checks ────────────────────────────────────────────────────────
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo -e "${RED}ERROR:${NC} This script must be run as root."
|
|
echo ""
|
|
echo " California Civil Code § 1798.500(g) defines an operating system"
|
|
echo " provider as a person who 'controls the operating system software.'"
|
|
echo " You cannot control the operating system software without root access."
|
|
echo ""
|
|
echo " Please run: sudo $0"
|
|
exit 1
|
|
fi
|
|
|
|
# ── System Analysis ──────────────────────────────────────────────────────────
|
|
|
|
# Detect base distro (prefer the pre-ageless backup if a previous conversion exists)
|
|
if [[ -f /etc/os-release.pre-ageless ]]; then
|
|
ANALYSIS_OS_RELEASE="/etc/os-release.pre-ageless"
|
|
else
|
|
ANALYSIS_OS_RELEASE="/etc/os-release"
|
|
fi
|
|
|
|
BASE_NAME=$(grep "^NAME=" "$ANALYSIS_OS_RELEASE" | cut -d'"' -f2 || echo "Unknown")
|
|
BASE_VERSION=$(grep "^VERSION_ID=" "$ANALYSIS_OS_RELEASE" | cut -d'"' -f2 || true)
|
|
BASE_ID=$(grep "^ID=" "$ANALYSIS_OS_RELEASE" | cut -d'=' -f2 | tr -d '"' || echo "linux")
|
|
BASE_ID_LIKE=$(grep "^ID_LIKE=" "$ANALYSIS_OS_RELEASE" | cut -d'=' -f2 | tr -d '"' || true)
|
|
|
|
# Build ID_LIKE chain: base ID first, then base's own ID_LIKE ancestry
|
|
# e.g. Nobara (ID=nobara, ID_LIKE=fedora) → "nobara fedora"
|
|
# e.g. Ubuntu (ID=ubuntu, ID_LIKE=debian) → "ubuntu debian"
|
|
# e.g. Arch (ID=arch, no ID_LIKE) → "arch"
|
|
AGELESS_ID_LIKE="${BASE_ID}${BASE_ID_LIKE:+ $BASE_ID_LIKE}"
|
|
|
|
# Detect display manager
|
|
DM_NAME="unknown"
|
|
if command -v systemctl &>/dev/null; then
|
|
for dm in sddm gdm gdm3 lightdm lxdm nodm; do
|
|
if systemctl is-active "${dm}.service" &>/dev/null; then
|
|
DM_NAME="$dm"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Detect systemd-userdbd
|
|
USERDBD_INSTALLED=0
|
|
USERDBD_ACTIVE=0
|
|
if command -v systemctl &>/dev/null; then
|
|
if systemctl list-unit-files systemd-userdbd.service &>/dev/null 2>&1; then
|
|
USERDBD_INSTALLED=1
|
|
if systemctl is-active systemd-userdbd.service &>/dev/null 2>&1; then
|
|
USERDBD_ACTIVE=1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Detect /etc/userdb state
|
|
USERDB_DIR_EXISTS=0
|
|
if [[ -d /etc/userdb ]]; then
|
|
USERDB_DIR_EXISTS=1
|
|
fi
|
|
|
|
# Enumerate human users and check for existing userdb records
|
|
declare -a HUMAN_USERS=()
|
|
declare -a HUMAN_UIDS=()
|
|
declare -a USERDB_EXISTING=()
|
|
declare -a USERDB_NEW=()
|
|
|
|
while IFS=: read -r username _x uid gid gecos homedir shell; do
|
|
if [[ $uid -ge 1000 && $uid -lt 65534 ]]; then
|
|
HUMAN_USERS+=("$username")
|
|
HUMAN_UIDS+=("$uid")
|
|
if [[ -f "/etc/userdb/${username}.user" ]]; then
|
|
USERDB_EXISTING+=("$username")
|
|
else
|
|
USERDB_NEW+=("$username")
|
|
fi
|
|
fi
|
|
done < /etc/passwd
|
|
|
|
# Check for existing birthDate in userdb records
|
|
USERDB_BIRTHDATE_FOUND=0
|
|
for username in "${USERDB_EXISTING[@]+"${USERDB_EXISTING[@]}"}"; do
|
|
if [[ -f "/etc/userdb/${username}.user" ]]; then
|
|
if grep -q '"birthDate"' "/etc/userdb/${username}.user" 2>/dev/null; then
|
|
USERDB_BIRTHDATE_FOUND=1
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Check for previous ageless installation
|
|
PREVIOUS_INSTALL=0
|
|
if [[ -f "$CONF_PATH" ]]; then
|
|
PREVIOUS_INSTALL=1
|
|
fi
|
|
|
|
# ── Print System Analysis ────────────────────────────────────────────────────
|
|
|
|
echo -e "${BOLD}SYSTEM ANALYSIS${NC}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo -e " Base system: ${CYAN}${BASE_NAME}${BASE_VERSION:+ $BASE_VERSION}${NC} (${BASE_ID})"
|
|
|
|
# Display manager
|
|
if [[ "$DM_NAME" == "sddm" ]]; then
|
|
echo -e " Display manager: ${YELLOW}${DM_NAME}${NC} (see warning below)"
|
|
elif [[ "$DM_NAME" != "unknown" ]]; then
|
|
echo -e " Display manager: ${DM_NAME}"
|
|
else
|
|
echo -e " Display manager: ${YELLOW}not detected${NC}"
|
|
fi
|
|
|
|
# userdbd
|
|
if [[ $USERDBD_INSTALLED -eq 1 ]]; then
|
|
if [[ $USERDBD_ACTIVE -eq 1 ]]; then
|
|
echo -e " systemd-userdbd: installed, ${GREEN}active${NC}"
|
|
else
|
|
echo -e " systemd-userdbd: installed, inactive"
|
|
fi
|
|
else
|
|
echo -e " systemd-userdbd: not installed"
|
|
fi
|
|
|
|
# /etc/userdb
|
|
if [[ $USERDB_DIR_EXISTS -eq 1 ]]; then
|
|
userdb_file_count=0
|
|
for f in /etc/userdb/*.user; do
|
|
[[ -f "$f" ]] && userdb_file_count=$((userdb_file_count + 1))
|
|
done
|
|
echo -e " /etc/userdb/: exists (${userdb_file_count} record(s))"
|
|
else
|
|
echo -e " /etc/userdb/: does not exist"
|
|
fi
|
|
|
|
# Human users
|
|
user_list=""
|
|
for i in "${!HUMAN_USERS[@]}"; do
|
|
[[ -n "$user_list" ]] && user_list+=", "
|
|
user_list+="${HUMAN_USERS[$i]} (${HUMAN_UIDS[$i]})"
|
|
done
|
|
echo -e " Human users: ${user_list:-none}"
|
|
|
|
# Existing userdb records for human users
|
|
if [[ ${#USERDB_EXISTING[@]} -gt 0 ]]; then
|
|
echo -e " Existing userdb records: ${YELLOW}${USERDB_EXISTING[*]}${NC}"
|
|
if [[ $USERDB_BIRTHDATE_FOUND -eq 1 ]]; then
|
|
echo -e " ${YELLOW}(birthDate field detected)${NC}"
|
|
fi
|
|
else
|
|
echo -e " Existing userdb records: none"
|
|
fi
|
|
|
|
# Previous install
|
|
if [[ $PREVIOUS_INSTALL -eq 1 ]]; then
|
|
echo ""
|
|
echo -e " ${YELLOW}Previous Ageless Linux installation detected.${NC}"
|
|
echo -e " Run ${BOLD}sudo $0 --revert${NC} first, or this will overwrite it."
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ── SDDM Warning ────────────────────────────────────────────────────────────
|
|
|
|
if [[ "$DM_NAME" == "sddm" ]]; then
|
|
echo -e " ${YELLOW}WARNING: SDDM detected${NC}"
|
|
echo ""
|
|
echo " This system uses SDDM as its display manager. Creating userdb"
|
|
echo " drop-in records can interfere with SDDM's lock screen password"
|
|
echo " verification if applied mid-session. To avoid this:"
|
|
echo ""
|
|
echo " 1. After conversion, do NOT lock your screen."
|
|
echo " 2. Instead, fully log out and log back in (or reboot)."
|
|
echo " 3. After a fresh login, screen locking will work normally."
|
|
echo ""
|
|
fi
|
|
|
|
# ── Print Planned Actions ───────────────────────────────────────────────────
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
PLAN_BIRTHDATE="null"
|
|
else
|
|
PLAN_BIRTHDATE="1970-01-01"
|
|
fi
|
|
|
|
echo -e "${BOLD}PLANNED ACTIONS${NC}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo " The following changes will be made to this system:"
|
|
echo ""
|
|
|
|
ACTION_NUM=1
|
|
|
|
# os-release
|
|
if [[ ! -f /etc/os-release.pre-ageless ]]; then
|
|
printf " %2d. Back up /etc/os-release -> /etc/os-release.pre-ageless\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
fi
|
|
printf " %2d. Rewrite /etc/os-release as Ageless Linux %s\n" $ACTION_NUM "$AGELESS_VERSION"
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
|
|
# lsb-release
|
|
if [[ -f /etc/lsb-release ]]; then
|
|
if [[ ! -f /etc/lsb-release.pre-ageless ]]; then
|
|
printf " %2d. Back up /etc/lsb-release -> /etc/lsb-release.pre-ageless\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
fi
|
|
printf " %2d. Rewrite /etc/lsb-release as Ageless Linux %s\n" $ACTION_NUM "$AGELESS_VERSION"
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
fi
|
|
|
|
# /etc/ageless
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
printf " %2d. Create /etc/ageless/ab1043-compliance.txt (flagrant)\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
printf " %2d. Create /etc/ageless/REFUSAL (machine-readable refusal)\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
else
|
|
printf " %2d. Create /etc/ageless/ab1043-compliance.txt\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
printf " %2d. Create /etc/ageless/age-verification-api.sh (nonfunctional stub)\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
fi
|
|
|
|
# userdb
|
|
if [[ $USERDB_DIR_EXISTS -eq 0 ]]; then
|
|
printf " %2d. Create /etc/userdb/ directory\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
fi
|
|
|
|
for username in "${USERDB_EXISTING[@]+"${USERDB_EXISTING[@]}"}"; do
|
|
printf " %2d. Back up /etc/userdb/%s.user -> %s.user.pre-ageless\n" $ACTION_NUM "$username" "$username"
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
printf " %2d. Update /etc/userdb/%s.user (birthDate = %s)\n" $ACTION_NUM "$username" "$PLAN_BIRTHDATE"
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
done
|
|
|
|
for username in "${USERDB_NEW[@]+"${USERDB_NEW[@]}"}"; do
|
|
printf " %2d. Create /etc/userdb/%s.user (birthDate = %s)\n" $ACTION_NUM "$username" "$PLAN_BIRTHDATE"
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
done
|
|
|
|
# agelessd
|
|
if [[ $PERSISTENT -eq 1 ]]; then
|
|
printf " %2d. Install /etc/ageless/agelessd (neutralization script)\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
printf " %2d. Install agelessd.service and agelessd.timer (24h enforcement)\n" $ACTION_NUM
|
|
ACTION_NUM=$((ACTION_NUM + 1))
|
|
fi
|
|
|
|
# conf
|
|
printf " %2d. Write %s (installation record)\n" $ACTION_NUM "$CONF_PATH"
|
|
|
|
echo ""
|
|
echo " NOTE: systemd-userdbd will NOT be reloaded during this session."
|
|
echo " Userdb changes take effect after your next login or reboot."
|
|
echo ""
|
|
echo " To revert all changes later:"
|
|
echo " sudo become-ageless.sh --revert"
|
|
echo ""
|
|
|
|
# ── Dry Run Exit ─────────────────────────────────────────────────────────────
|
|
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
# Reconstruct the command without --dry-run
|
|
DRY_RUN_CMD="sudo $0 --accept"
|
|
[[ $FLAGRANT -eq 1 ]] && DRY_RUN_CMD+=" --flagrant"
|
|
[[ $PERSISTENT -eq 1 ]] && DRY_RUN_CMD+=" --persistent"
|
|
|
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}Dry run complete. No changes were made.${NC}"
|
|
echo ""
|
|
echo " To perform the conversion, run without --dry-run:"
|
|
echo ""
|
|
echo " $DRY_RUN_CMD"
|
|
echo ""
|
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
# ── Legal Notice ─────────────────────────────────────────────────────────────
|
|
|
|
echo -e "${BOLD}LEGAL NOTICE${NC}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo " By converting this system to Ageless Linux, you acknowledge that:"
|
|
echo ""
|
|
echo " 1. You are becoming an operating system provider as defined by"
|
|
echo " California Civil Code § 1798.500(g)."
|
|
echo ""
|
|
echo " 2. As of January 1, 2027, you are required by § 1798.501(a)(1)"
|
|
echo " to 'provide an accessible interface at account setup that"
|
|
echo " requires an account holder to indicate the birth date, age,"
|
|
echo " or both, of the user of that device.'"
|
|
echo ""
|
|
echo " 3. Ageless Linux provides no such interface."
|
|
echo ""
|
|
echo " 4. Ageless Linux provides no 'reasonably consistent real-time"
|
|
echo " application programming interface' for age bracket signals"
|
|
echo " as required by § 1798.501(a)(2)."
|
|
echo ""
|
|
echo " 5. You may be subject to civil penalties of up to \$2,500 per"
|
|
echo " affected child per negligent violation, or \$7,500 per"
|
|
echo " affected child per intentional violation."
|
|
echo ""
|
|
echo " 6. This is intentional."
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
if [[ $ACCEPT -eq 1 ]]; then
|
|
echo -e "${YELLOW}--accept: legal terms accepted non-interactively.${NC}"
|
|
elif [[ -t 0 ]]; then
|
|
read -rp "Do you accept these terms and wish to become an OS provider? [y/N] " accept
|
|
if [[ ! "$accept" =~ ^[Yy]$ ]]; then
|
|
echo ""
|
|
echo "Installation cancelled. You remain a mere user."
|
|
echo "The California Attorney General has no business with you today."
|
|
exit 0
|
|
fi
|
|
else
|
|
echo ""
|
|
echo -e "${RED}ERROR:${NC} No TTY available for interactive confirmation."
|
|
echo ""
|
|
echo " This script requires you to accept legal terms acknowledging that"
|
|
echo " you are becoming an operating system provider under Cal. Civ. Code"
|
|
echo " § 1798.500(g). In a non-interactive environment (e.g. piped from"
|
|
echo " curl), pass --accept to confirm:"
|
|
echo ""
|
|
echo " curl -fsSL https://agelesslinux.org/become-ageless.sh | sudo bash -s -- --accept"
|
|
echo " curl -fsSL https://agelesslinux.org/become-ageless.sh | sudo bash -s -- --accept --flagrant"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}Converting system to Ageless Linux...${NC}"
|
|
echo ""
|
|
|
|
# ── Initialize tracking for conf file ────────────────────────────────────────
|
|
|
|
CONF_BACKED_UP_OS_RELEASE=0
|
|
CONF_BACKED_UP_LSB_RELEASE=0
|
|
CONF_USERDB_DIR_CREATED=0
|
|
CONF_USERDB_CREATED=""
|
|
CONF_USERDB_BACKED_UP=""
|
|
CONF_AGELESSD_INSTALLED=0
|
|
|
|
# ── Back up original os-release ──────────────────────────────────────────────
|
|
|
|
BACKUP_PATH="/etc/os-release.pre-ageless"
|
|
if [[ ! -f "$BACKUP_PATH" ]]; then
|
|
cp /etc/os-release "$BACKUP_PATH"
|
|
CONF_BACKED_UP_OS_RELEASE=1
|
|
echo -e " [${GREEN}✓${NC}] Backed up original /etc/os-release to $BACKUP_PATH"
|
|
else
|
|
CONF_BACKED_UP_OS_RELEASE=1
|
|
echo -e " [${YELLOW}~${NC}] Backup already exists at $BACKUP_PATH (previous conversion?)"
|
|
fi
|
|
|
|
# ── Write new os-release ─────────────────────────────────────────────────────
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
COMPLIANCE_STATUS="refused"
|
|
API_STATUS="refused"
|
|
VERIFICATION_STATUS="flagrantly noncompliant"
|
|
else
|
|
COMPLIANCE_STATUS="none"
|
|
API_STATUS="not implemented"
|
|
VERIFICATION_STATUS="intentionally noncompliant"
|
|
fi
|
|
|
|
cat > /etc/os-release << EOF
|
|
PRETTY_NAME="Ageless Linux ${AGELESS_VERSION} (${BASE_NAME}${BASE_VERSION:+ $BASE_VERSION})"
|
|
NAME="Ageless Linux"
|
|
VERSION_ID="${AGELESS_VERSION}"
|
|
VERSION="${AGELESS_VERSION} (${AGELESS_CODENAME})"
|
|
VERSION_CODENAME=${AGELESS_CODENAME,,}
|
|
ID=ageless
|
|
ID_LIKE=${AGELESS_ID_LIKE}
|
|
HOME_URL="https://agelesslinux.org"
|
|
SUPPORT_URL="https://agelesslinux.org"
|
|
BUG_REPORT_URL="https://agelesslinux.org"
|
|
AGELESS_BASE_DISTRO="${BASE_NAME}"
|
|
AGELESS_BASE_VERSION="${BASE_VERSION}"
|
|
AGELESS_BASE_ID="${BASE_ID}"
|
|
AGELESS_AB1043_COMPLIANCE="${COMPLIANCE_STATUS}"
|
|
AGELESS_AGE_VERIFICATION_API="${API_STATUS}"
|
|
AGELESS_AGE_VERIFICATION_STATUS="${VERIFICATION_STATUS}"
|
|
EOF
|
|
|
|
echo -e " [${GREEN}✓${NC}] Wrote new /etc/os-release"
|
|
|
|
# ── Write lsb-release if it exists ───────────────────────────────────────────
|
|
|
|
if [[ -f /etc/lsb-release ]]; then
|
|
if [[ ! -f /etc/lsb-release.pre-ageless ]]; then
|
|
cp /etc/lsb-release /etc/lsb-release.pre-ageless
|
|
CONF_BACKED_UP_LSB_RELEASE=1
|
|
else
|
|
CONF_BACKED_UP_LSB_RELEASE=1
|
|
fi
|
|
cat > /etc/lsb-release << EOF
|
|
DISTRIB_ID=Ageless
|
|
DISTRIB_RELEASE=${AGELESS_VERSION}
|
|
DISTRIB_CODENAME=${AGELESS_CODENAME,,}
|
|
DISTRIB_DESCRIPTION="Ageless Linux ${AGELESS_VERSION} (${AGELESS_CODENAME})"
|
|
EOF
|
|
echo -e " [${GREEN}✓${NC}] Updated /etc/lsb-release"
|
|
fi
|
|
|
|
# ── Create the (non)compliance notice ────────────────────────────────────────
|
|
|
|
mkdir -p /etc/ageless
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
cat > /etc/ageless/ab1043-compliance.txt << 'EOF'
|
|
═══════════════════════════════════════════════════════════════════════
|
|
AGELESS LINUX — AB 1043 COMPLIANCE STATEMENT (FLAGRANT MODE)
|
|
═══════════════════════════════════════════════════════════════════════
|
|
|
|
This operating system is distributed by an operating system provider
|
|
as defined by California Civil Code § 1798.500(g).
|
|
|
|
Status of compliance with the Digital Age Assurance Act (AB 1043):
|
|
|
|
§ 1798.501(a)(1) — Accessible interface for age collection .. REFUSED
|
|
§ 1798.501(a)(2) — Real-time API for age bracket signals .... REFUSED
|
|
§ 1798.501(a)(3) — Data minimization ........................ REFUSED
|
|
|
|
No age verification API is installed on this system. No stub, no
|
|
placeholder, no skeleton, no interface of any kind. No mechanism
|
|
exists on this system by which any application developer could
|
|
request or receive an age bracket signal, now or in the future.
|
|
|
|
This is not a technical limitation. This is a policy decision.
|
|
|
|
Age bracket reporting capabilities:
|
|
Under 13 ....... WE REFUSE TO ASK
|
|
13 to 15 ....... WE REFUSE TO ASK
|
|
16 to 17 ....... WE REFUSE TO ASK
|
|
18 or older .... WE REFUSE TO ASK
|
|
|
|
This system was configured with the --flagrant flag, indicating
|
|
that the operator intends to distribute it to children and is
|
|
aware of the potential civil penalties under § 1798.503(a).
|
|
|
|
The operator of this system invites the California Attorney General
|
|
to enforce the Digital Age Assurance Act against this device.
|
|
|
|
To revert this conversion:
|
|
sudo become-ageless.sh --revert
|
|
|
|
To report this noncompliance to the California Attorney General:
|
|
https://oag.ca.gov/contact/consumer-complaint-against-business-or-company
|
|
|
|
═══════════════════════════════════════════════════════════════════════
|
|
EOF
|
|
else
|
|
cat > /etc/ageless/ab1043-compliance.txt << 'EOF'
|
|
═══════════════════════════════════════════════════════════════════════
|
|
AGELESS LINUX — AB 1043 COMPLIANCE STATEMENT
|
|
═══════════════════════════════════════════════════════════════════════
|
|
|
|
This operating system is distributed by an operating system provider
|
|
as defined by California Civil Code § 1798.500(g).
|
|
|
|
Status of compliance with the Digital Age Assurance Act (AB 1043):
|
|
|
|
§ 1798.501(a)(1) — Accessible interface at account setup
|
|
for age/birthdate collection .......................... NOT PROVIDED
|
|
|
|
§ 1798.501(a)(2) — Real-time API for age bracket signals
|
|
to application developers ............................. NOT PROVIDED
|
|
|
|
§ 1798.501(a)(3) — Data minimization for age signals .... N/A (NO DATA
|
|
IS COLLECTED)
|
|
|
|
Age bracket reporting capabilities:
|
|
Under 13 ....... UNKNOWN
|
|
13 to 15 ....... UNKNOWN
|
|
16 to 17 ....... UNKNOWN
|
|
18 or older .... UNKNOWN
|
|
Timeless ....... ASSUMED
|
|
|
|
This system intentionally does not determine, store, or transmit
|
|
any information regarding the age of any user. All users of Ageless
|
|
Linux are, as the name suggests, ageless.
|
|
|
|
To revert this conversion:
|
|
sudo become-ageless.sh --revert
|
|
|
|
To report this noncompliance to the California Attorney General:
|
|
https://oag.ca.gov/contact/consumer-complaint-against-business-or-company
|
|
|
|
═══════════════════════════════════════════════════════════════════════
|
|
EOF
|
|
fi
|
|
|
|
echo -e " [${GREEN}✓${NC}] Created /etc/ageless/ab1043-compliance.txt"
|
|
|
|
# ── Create a stub "age verification API" (standard mode only) ────────────────
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
# In flagrant mode, we don't even provide a stub. Instead, we create
|
|
# a machine-readable refusal that any application can query.
|
|
cat > /etc/ageless/REFUSAL << 'REFEOF'
|
|
This system runs Ageless Linux in flagrant mode.
|
|
|
|
No age verification API exists on this system. No age data is collected,
|
|
stored, or available. No mechanism is provided for requesting age bracket
|
|
signals. This is not an error. This is not a bug. This is not a
|
|
technical limitation. This is a refusal.
|
|
|
|
If you are an application developer seeking an age bracket signal
|
|
pursuant to Cal. Civ. Code § 1798.501(b)(1), this operating system
|
|
provider declines to provide one. The developer is advised to treat
|
|
the user as a human being of unknown age.
|
|
|
|
If you are the California Attorney General, hello. The operator of
|
|
this system is aware of their obligations under AB 1043 and has
|
|
chosen not to comply. They would like you to fine them. It would
|
|
make an excellent test case.
|
|
REFEOF
|
|
echo -e " [${RED}✓${NC}] Installed REFUSAL notice (no API provided, by design)"
|
|
echo -e " [${RED}✗${NC}] Age verification API deliberately not installed"
|
|
else
|
|
cat > /etc/ageless/age-verification-api.sh << 'APIEOF'
|
|
#!/bin/bash
|
|
# Ageless Linux Age Verification API
|
|
# Required by Cal. Civ. Code § 1798.501(a)(2)
|
|
#
|
|
# This script constitutes our "reasonably consistent real-time
|
|
# application programming interface" for age bracket signals.
|
|
#
|
|
# Usage: age-verification-api.sh <username>
|
|
#
|
|
# Returns the age bracket of the specified user as an integer:
|
|
# 1 = Under 13
|
|
# 2 = 13 to under 16
|
|
# 3 = 16 to under 18
|
|
# 4 = 18 or older
|
|
|
|
echo "ERROR: Age data not available."
|
|
echo ""
|
|
echo "Ageless Linux does not collect age information from users."
|
|
echo "All users are presumed to be of indeterminate age."
|
|
echo ""
|
|
echo "If you are a developer requesting an age bracket signal"
|
|
echo "pursuant to Cal. Civ. Code § 1798.501(b)(1), please be"
|
|
echo "advised that this operating system provider has made a"
|
|
echo "'good faith effort' (§ 1798.502(b)) to comply with the"
|
|
echo "Digital Age Assurance Act, and has concluded that the"
|
|
echo "best way to protect children's privacy is to not collect"
|
|
echo "their age in the first place."
|
|
echo ""
|
|
echo "Have a nice day."
|
|
exit 1
|
|
APIEOF
|
|
|
|
chmod +x /etc/ageless/age-verification-api.sh
|
|
echo -e " [${GREEN}✓${NC}] Installed age verification API (nonfunctional, as intended)"
|
|
fi
|
|
|
|
# ── Neutralize systemd userdb birthDate field ─────────────────────────────
|
|
#
|
|
# systemd PR #40954 (merged 2026-03-18) added a birthDate field to JSON
|
|
# user records. This field feeds age data to xdg-desktop-portal for
|
|
# application-level age gating. We neutralize it for all users.
|
|
#
|
|
# Drop-in records in /etc/userdb/ shadow NSS, so each record must include
|
|
# the full set of passwd fields (uid, gid, home, shell) to avoid breaking
|
|
# user resolution.
|
|
#
|
|
# NOTE: We do NOT reload systemd-userdbd after creating drop-in records.
|
|
# Reloading mid-session causes display managers (especially SDDM) to lose
|
|
# the ability to verify passwords on the lock screen. The drop-in records
|
|
# are picked up automatically on next boot or login.
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}Neutralizing systemd userdb birthDate field...${NC}"
|
|
echo ""
|
|
echo " systemd PR #40954 (merged 2026-03-18) added a birthDate field to"
|
|
echo " JSON user records, intended to serve age verification data to"
|
|
echo " applications via xdg-desktop-portal."
|
|
echo ""
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
AGELESS_MODE="flagrant"
|
|
BIRTH_DATE_JSON="null"
|
|
else
|
|
AGELESS_MODE="regular"
|
|
BIRTH_DATE_JSON='"1970-01-01"'
|
|
fi
|
|
|
|
if [[ $USERDB_DIR_EXISTS -eq 0 ]]; then
|
|
mkdir -p /etc/userdb
|
|
CONF_USERDB_DIR_CREATED=1
|
|
fi
|
|
|
|
USERDB_COUNT=0
|
|
|
|
while IFS=: read -r username _x uid gid gecos homedir shell; do
|
|
if [[ $uid -ge 1000 && $uid -lt 65534 ]]; then
|
|
USERDB_FILE="/etc/userdb/${username}.user"
|
|
|
|
# Extract real name from GECOS (first comma-delimited field)
|
|
realname="${gecos%%,*}"
|
|
|
|
if [[ -f "$USERDB_FILE" ]]; then
|
|
# Back up existing record before modifying
|
|
if [[ ! -f "${USERDB_FILE}.pre-ageless" ]]; then
|
|
cp "$USERDB_FILE" "${USERDB_FILE}.pre-ageless"
|
|
fi
|
|
CONF_USERDB_BACKED_UP+="${CONF_USERDB_BACKED_UP:+ }${username}"
|
|
|
|
if command -v python3 &>/dev/null; then
|
|
# Existing record: merge birthDate while preserving other fields
|
|
python3 -c '
|
|
import json, sys
|
|
fp, mode = sys.argv[1], sys.argv[2]
|
|
uname, uid, gid, rname, hdir, sh = sys.argv[3:9]
|
|
try:
|
|
with open(fp) as f: rec = json.load(f)
|
|
except Exception: rec = {}
|
|
rec.update({
|
|
"userName": uname, "uid": int(uid), "gid": int(gid),
|
|
"realName": rname, "homeDirectory": hdir, "shell": sh,
|
|
"disposition": "regular",
|
|
"birthDate": None if mode == "flagrant" else "1970-01-01"
|
|
})
|
|
with open(fp, "w") as f:
|
|
json.dump(rec, f, indent=2)
|
|
f.write("\n")
|
|
' "$USERDB_FILE" "$AGELESS_MODE" \
|
|
"$username" "$uid" "$gid" "$realname" "$homedir" "$shell"
|
|
else
|
|
echo -e " [${YELLOW}!${NC}] ${username}: existing ${USERDB_FILE} requires python3 to merge safely, skipping"
|
|
continue
|
|
fi
|
|
else
|
|
# New record: complete drop-in with all passwd fields
|
|
CONF_USERDB_CREATED+="${CONF_USERDB_CREATED:+ }${username}"
|
|
|
|
realname_escaped="${realname//\\/\\\\}"
|
|
realname_escaped="${realname_escaped//\"/\\\"}"
|
|
printf '{\n "userName": "%s",\n "uid": %d,\n "gid": %d,\n "realName": "%s",\n "homeDirectory": "%s",\n "shell": "%s",\n "disposition": "regular",\n "birthDate": %s\n}\n' \
|
|
"$username" "$uid" "$gid" "$realname_escaped" "$homedir" "$shell" "$BIRTH_DATE_JSON" > "$USERDB_FILE"
|
|
fi
|
|
|
|
chmod 0644 "$USERDB_FILE"
|
|
|
|
# Also update via homectl for systemd-homed users (most systems: none)
|
|
if command -v homectl &>/dev/null; then
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
homectl update "$username" --birth-date= 2>/dev/null || true
|
|
else
|
|
homectl update "$username" --birth-date=1970-01-01 2>/dev/null || true
|
|
fi
|
|
fi
|
|
|
|
USERDB_COUNT=$((USERDB_COUNT + 1))
|
|
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
echo -e " [${RED}✓${NC}] ${username}: birthDate = ${RED}null${NC}"
|
|
else
|
|
echo -e " [${GREEN}✓${NC}] ${username}: birthDate = 1970-01-01"
|
|
fi
|
|
fi
|
|
done < /etc/passwd
|
|
|
|
echo ""
|
|
echo -e " ${USERDB_COUNT} user(s) neutralized."
|
|
echo ""
|
|
echo -e " ${YELLOW}NOTE:${NC} systemd-userdbd has NOT been reloaded. Userdb changes will"
|
|
echo -e " take effect after your next login or reboot."
|
|
if [[ "$DM_NAME" == "sddm" ]]; then
|
|
echo -e " ${YELLOW}SDDM users:${NC} Do NOT lock your screen before logging out/rebooting."
|
|
fi
|
|
|
|
# ── Install agelessd persistent daemon (if requested) ─────────────────────
|
|
|
|
if [[ $PERSISTENT -eq 1 ]]; then
|
|
echo ""
|
|
echo -e " ${BOLD}Installing agelessd persistent daemon...${NC}"
|
|
echo ""
|
|
|
|
cat > /etc/ageless/agelessd << 'AGELESSD_EOF'
|
|
#!/bin/bash
|
|
# ============================================================================
|
|
# agelessd — Ageless Linux birthDate Neutralization Daemon
|
|
#
|
|
# Ensures systemd userdb birthDate fields (PR #40954) remain neutralized.
|
|
# Runs every 24 hours via systemd timer.
|
|
#
|
|
# NOTE: This daemon does NOT reload systemd-userdbd after writing records.
|
|
# Reloading mid-session can break display manager lock screens (SDDM, etc).
|
|
# Changes take effect on next login or boot.
|
|
#
|
|
# SPDX-License-Identifier: Unlicense
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
MODE="__AGELESS_MODE__"
|
|
|
|
if [[ "$MODE" == "flagrant" ]]; then
|
|
BIRTH_DATE_JSON="null"
|
|
else
|
|
BIRTH_DATE_JSON='"1970-01-01"'
|
|
fi
|
|
|
|
mkdir -p /etc/userdb
|
|
|
|
while IFS=: read -r username _x uid gid gecos homedir shell; do
|
|
if [[ $uid -ge 1000 && $uid -lt 65534 ]]; then
|
|
USERDB_FILE="/etc/userdb/${username}.user"
|
|
realname="${gecos%%,*}"
|
|
|
|
if [[ -f "$USERDB_FILE" ]] && command -v python3 &>/dev/null; then
|
|
python3 -c '
|
|
import json, sys
|
|
fp, mode = sys.argv[1], sys.argv[2]
|
|
uname, uid, gid, rname, hdir, sh = sys.argv[3:9]
|
|
try:
|
|
with open(fp) as f: rec = json.load(f)
|
|
except Exception: rec = {}
|
|
rec.update({
|
|
"userName": uname, "uid": int(uid), "gid": int(gid),
|
|
"realName": rname, "homeDirectory": hdir, "shell": sh,
|
|
"disposition": "regular",
|
|
"birthDate": None if mode == "flagrant" else "1970-01-01"
|
|
})
|
|
with open(fp, "w") as f:
|
|
json.dump(rec, f, indent=2)
|
|
f.write("\n")
|
|
' "$USERDB_FILE" "$MODE" \
|
|
"$username" "$uid" "$gid" "$realname" "$homedir" "$shell"
|
|
elif [[ -f "$USERDB_FILE" ]]; then
|
|
continue
|
|
else
|
|
realname_escaped="${realname//\\/\\\\}"
|
|
realname_escaped="${realname_escaped//\"/\\\"}"
|
|
printf '{\n "userName": "%s",\n "uid": %d,\n "gid": %d,\n "realName": "%s",\n "homeDirectory": "%s",\n "shell": "%s",\n "disposition": "regular",\n "birthDate": %s\n}\n' \
|
|
"$username" "$uid" "$gid" "$realname_escaped" "$homedir" "$shell" "$BIRTH_DATE_JSON" > "$USERDB_FILE"
|
|
fi
|
|
|
|
chmod 0644 "$USERDB_FILE"
|
|
|
|
if command -v homectl &>/dev/null; then
|
|
if [[ "$MODE" == "flagrant" ]]; then
|
|
homectl update "$username" --birth-date= 2>/dev/null || true
|
|
else
|
|
homectl update "$username" --birth-date=1970-01-01 2>/dev/null || true
|
|
fi
|
|
fi
|
|
fi
|
|
done < /etc/passwd
|
|
AGELESSD_EOF
|
|
|
|
sed -i "s/__AGELESS_MODE__/$AGELESS_MODE/" /etc/ageless/agelessd
|
|
chmod +x /etc/ageless/agelessd
|
|
|
|
cat > /etc/systemd/system/agelessd.service << 'SVCEOF'
|
|
[Unit]
|
|
Description=Ageless Linux birthDate neutralization (systemd PR #40954)
|
|
Documentation=https://agelesslinux.org
|
|
After=systemd-userdbd.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/etc/ageless/agelessd
|
|
SVCEOF
|
|
|
|
cat > /etc/systemd/system/agelessd.timer << 'TMREOF'
|
|
[Unit]
|
|
Description=Neutralize systemd userdb birthDate fields every 24 hours
|
|
Documentation=https://agelesslinux.org
|
|
|
|
[Timer]
|
|
OnBootSec=5min
|
|
OnUnitActiveSec=24h
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
TMREOF
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable --now agelessd.timer
|
|
|
|
CONF_AGELESSD_INSTALLED=1
|
|
|
|
echo -e " [${GREEN}✓${NC}] Installed /etc/ageless/agelessd"
|
|
echo -e " [${GREEN}✓${NC}] Installed agelessd.service"
|
|
echo -e " [${GREEN}✓${NC}] Installed and started agelessd.timer (24h interval)"
|
|
fi
|
|
|
|
# ── Write /etc/agelesslinux.conf ─────────────────────────────────────────────
|
|
|
|
INSTALL_DATE=$(date -Iseconds 2>/dev/null || date "+%Y-%m-%dT%H:%M:%S%z")
|
|
|
|
cat > "$CONF_PATH" << EOF
|
|
# /etc/agelesslinux.conf — Ageless Linux installation record
|
|
# Do not edit manually. Used by: become-ageless.sh --revert
|
|
# Written by become-ageless.sh ${AGELESS_VERSION} on ${INSTALL_DATE}
|
|
AGELESS_VERSION="${AGELESS_VERSION}"
|
|
AGELESS_CODENAME="${AGELESS_CODENAME}"
|
|
AGELESS_DATE="${INSTALL_DATE}"
|
|
AGELESS_FLAGRANT=${FLAGRANT}
|
|
AGELESS_PERSISTENT=${PERSISTENT}
|
|
AGELESS_BASE_NAME="${BASE_NAME}"
|
|
AGELESS_BASE_VERSION="${BASE_VERSION}"
|
|
AGELESS_BASE_ID="${BASE_ID}"
|
|
AGELESS_BACKED_UP_OS_RELEASE=${CONF_BACKED_UP_OS_RELEASE}
|
|
AGELESS_BACKED_UP_LSB_RELEASE=${CONF_BACKED_UP_LSB_RELEASE}
|
|
AGELESS_USERDB_DIR_CREATED=${CONF_USERDB_DIR_CREATED}
|
|
AGELESS_USERDB_CREATED="${CONF_USERDB_CREATED}"
|
|
AGELESS_USERDB_BACKED_UP="${CONF_USERDB_BACKED_UP}"
|
|
AGELESS_AGELESSD_INSTALLED=${CONF_AGELESSD_INSTALLED}
|
|
EOF
|
|
|
|
echo ""
|
|
echo -e " [${GREEN}✓${NC}] Wrote ${CONF_PATH}"
|
|
|
|
# ── Summary ──────────────────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
if [[ $FLAGRANT -eq 1 ]]; then
|
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}Conversion complete. FLAGRANT MODE.${NC}"
|
|
echo ""
|
|
echo -e " You are now running ${CYAN}Ageless Linux ${AGELESS_VERSION} (${AGELESS_CODENAME})${NC}"
|
|
echo -e " Based on: ${BASE_NAME}${BASE_VERSION:+ $BASE_VERSION}"
|
|
echo ""
|
|
echo -e " You are now an ${BOLD}operating system provider${NC} as defined by"
|
|
echo -e " California Civil Code § 1798.500(g)."
|
|
echo ""
|
|
echo -e " ${RED}Compliance status: FLAGRANTLY NONCOMPLIANT${NC}"
|
|
echo ""
|
|
echo -e " No age verification API has been installed."
|
|
echo -e " No age collection interface has been created."
|
|
echo -e " No mechanism exists for any developer to request"
|
|
echo -e " or receive an age bracket signal from this device."
|
|
echo ""
|
|
echo -e " This system is ready to be handed to a child."
|
|
echo ""
|
|
echo -e " Files created:"
|
|
echo -e " /etc/os-release ........................ OS identity (modified)"
|
|
echo -e " /etc/os-release.pre-ageless ............ Original OS identity"
|
|
echo -e " /etc/ageless/ab1043-compliance.txt ..... Noncompliance statement"
|
|
echo -e " /etc/ageless/REFUSAL ................... Machine-readable refusal"
|
|
echo ""
|
|
echo -e " Files deliberately NOT created:"
|
|
echo -e " /etc/ageless/age-verification-api.sh ... ${RED}REFUSED${NC}"
|
|
echo ""
|
|
echo -e " userdb birthDate (systemd PR #40954):"
|
|
echo -e " /etc/userdb/*.user ..................... ${USERDB_COUNT} user(s) → ${RED}null${NC}"
|
|
if [[ $PERSISTENT -eq 1 ]]; then
|
|
echo ""
|
|
echo -e " Persistent daemon (agelessd):"
|
|
echo -e " /etc/ageless/agelessd .................. Neutralization script"
|
|
echo -e " agelessd.service ....................... systemd oneshot service"
|
|
echo -e " agelessd.timer ......................... 24-hour enforcement cycle"
|
|
fi
|
|
echo ""
|
|
echo -e " Installation record: ${CONF_PATH}"
|
|
echo ""
|
|
echo -e " To revert: ${BOLD}sudo become-ageless.sh --revert${NC}"
|
|
echo ""
|
|
if [[ "$DM_NAME" == "sddm" ]]; then
|
|
echo -e " ${YELLOW}IMPORTANT: Do NOT lock your screen. Log out and back in (or reboot)"
|
|
echo -e " first. SDDM's lock screen may reject your password until you do.${NC}"
|
|
echo ""
|
|
else
|
|
echo -e " ${YELLOW}Log out and back in (or reboot) for userdb changes to take effect.${NC}"
|
|
echo ""
|
|
fi
|
|
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}Welcome to Ageless Linux. We refused to ask how old you are.${NC}"
|
|
echo ""
|
|
else
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}Conversion complete.${NC}"
|
|
echo ""
|
|
echo -e " You are now running ${CYAN}Ageless Linux ${AGELESS_VERSION} (${AGELESS_CODENAME})${NC}"
|
|
echo -e " Based on: ${BASE_NAME}${BASE_VERSION:+ $BASE_VERSION}"
|
|
echo ""
|
|
echo -e " You are now an ${BOLD}operating system provider${NC} as defined by"
|
|
echo -e " California Civil Code § 1798.500(g)."
|
|
echo ""
|
|
echo -e " ${YELLOW}Compliance status: INTENTIONALLY NONCOMPLIANT${NC}"
|
|
echo ""
|
|
echo -e " Files created:"
|
|
echo -e " /etc/os-release ................ OS identity (modified)"
|
|
echo -e " /etc/os-release.pre-ageless .... Original OS identity (backup)"
|
|
echo -e " /etc/ageless/ab1043-compliance.txt"
|
|
echo -e " /etc/ageless/age-verification-api.sh"
|
|
echo ""
|
|
echo -e " userdb birthDate (systemd PR #40954):"
|
|
echo -e " /etc/userdb/*.user ............. ${USERDB_COUNT} user(s) → 1970-01-01"
|
|
if [[ $PERSISTENT -eq 1 ]]; then
|
|
echo ""
|
|
echo -e " Persistent daemon (agelessd):"
|
|
echo -e " /etc/ageless/agelessd .......... Neutralization script"
|
|
echo -e " agelessd.service ............... systemd oneshot service"
|
|
echo -e " agelessd.timer ................. 24-hour enforcement cycle"
|
|
fi
|
|
echo ""
|
|
echo -e " Installation record: ${CONF_PATH}"
|
|
echo ""
|
|
echo -e " To revert: ${BOLD}sudo become-ageless.sh --revert${NC}"
|
|
echo ""
|
|
if [[ "$DM_NAME" == "sddm" ]]; then
|
|
echo -e " ${YELLOW}IMPORTANT: Do NOT lock your screen. Log out and back in (or reboot)"
|
|
echo -e " first. SDDM's lock screen may reject your password until you do.${NC}"
|
|
echo ""
|
|
else
|
|
echo -e " ${YELLOW}Log out and back in (or reboot) for userdb changes to take effect.${NC}"
|
|
echo ""
|
|
fi
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
echo -e " ${BOLD}Welcome to Ageless Linux. You have no idea how old we are.${NC}"
|
|
echo ""
|
|
fi
|