#!/bin/sh
# Initialize the system-wide Wine prefix.
#
# Idempotent by design: the image build runs it once to bake a prefix into the
# image, and first boot runs it again in case the image build could not. The
# stamp file is what makes the second run cheap.
set -eu

SG_LIB="${SG_LIB:-/usr/lib/stained-glass}"
# shellcheck source=lib/sg-common.sh
. "$SG_LIB/sg-common.sh"
# The machine's Windows system never uses a display. Whatever screen this was
# started from -- a build host's, a developer's desktop -- would otherwise be
# recorded in the shared prefix as the machine's monitor, and every user's
# desktop would be laid out for it.
unset DISPLAY WAYLAND_DISPLAY XAUTHORITY
sg_wine_env

STAMP="$SG_PREFIX/.sg-initialized"

# The Wine commands below run on a transient server of their own, before the
# machine's (sg-wineserver) exists. Stop it when done, whatever happens: once
# the prefix has auto-start services -- Microsoft Edge installs three -- they
# keep a transient server alive, and the machine's server then found the
# prefix taken and never started.
# Never the machine's own (an update runs this while it serves everyone):
# stopping it ended every Windows program in every session.
stop_transient_server() {
    sg_machine_wineserver_up && return 0
    timeout 30 wineserver -k 2>/dev/null || true
}
trap stop_transient_server EXIT

# Run the installers for staged payloads. Each is stamp-guarded, so on an
# up-to-date prefix this costs a few file reads; on a newer payload it installs.
run_installers() {
    for _inst in sg-install-d3d sg-install-apps; do
        if command -v "$_inst" >/dev/null 2>&1; then
            "$_inst" || sg_log "WARNING: $_inst failed; continuing without it"
        elif [ -x "${SG_BIN:-/usr/bin}/$_inst" ]; then
            "${SG_BIN:-/usr/bin}/$_inst" || sg_log "WARNING: $_inst failed; continuing without it"
        fi
    done
}

# Is the prefix up to date with the installed Wine? Wine's own test: the
# prefix's .update-timestamp holds wine.inf's modification time ("disable"
# turns updates off). Not knowing where wine.inf is means "update", as before.
prefix_current() {
    if [ -n "${SG_WINE_INF:-}" ]; then _inf=$SG_WINE_INF
    elif [ -n "${SG_WINE_DIR:-}" ]; then _inf="$SG_WINE_DIR/share/wine/wine.inf"   # wine is a function then
    else _inf="$(dirname "$(command -v wine)")/../share/wine/wine.inf"
    fi
    _ts=$(tr -d '\r\n ' < "$SG_PREFIX/.update-timestamp" 2>/dev/null) || return 1   # Wine writes it CRLF
    [ "$_ts" = disable ] && return 0
    [ -f "$_inf" ] && [ "$_ts" = "$(stat -c %Y "$_inf")" ]
}

# Machine-wide Wine settings, in HKLM, written as the SYSTEM account: which
# graphics driver this machine's display path needs (multi-user debt D4). Every
# boot, so a changed display path or an upgraded prefix picks it up.
set_machine_settings() {
    # The print spool: on Windows the spooler service writes a job's spool
    # file; here the user's own process does, in the machine's
    # system32\spool\printers -- which was SYSTEM's alone, so every print
    # job was silently dropped. Writable by the Windows users' group, sticky
    # so no one can touch another user's job. Every boot: existing prefixes too.
    _spool="$SG_PREFIX/drive_c/windows/system32/spool/printers"
    if [ -d "$_spool" ] && getent group "$SG_WINE_GROUP" >/dev/null 2>&1; then
        if ! { chgrp "$SG_WINE_GROUP" "$_spool" && chmod 3770 "$_spool"; } 2>/dev/null; then
            sg_log "WARNING: could not open the print spool to $SG_WINE_GROUP"
        fi
    fi
    if wine reg add 'HKLM\Software\Wine\Drivers' /v Graphics /t REG_SZ \
            /d "$(sg_graphics_driver)" /f >/dev/null 2>&1; then
        sg_log "machine graphics driver: $(sg_graphics_driver)"
    else
        sg_log "WARNING: could not set HKLM\\Software\\Wine\\Drivers\\Graphics"
    fi
    # Elevated programs run as this account on a display of their own, whose
    # window manager is sg-compositor (ADR 0012, B56). It draws no title bars,
    # so Wine draws its own -- the same Stained Glass caption as everywhere.
    wine reg add 'HKCU\Software\Wine\X11 Driver' /v Decorated /t REG_SZ /d N /f >/dev/null 2>&1 \
        || sg_log "WARNING: could not set the SYSTEM account's Decorated=N"
}

# Registry defaults other packages ship in $SG_DEFAULTS_DIR (App Paths, file
# associations, shell settings). Each file is imported when it is new or has
# changed since it was last imported -- so an upgraded package's new
# registrations reach a prefix made before it, not only new prefixes. The
# stamp is the file's SHA-256, kept in $SG_STATE/defaults/.
# (HKCU values in them land in the account running this, SYSTEM, and in the
# Default User profile only when the prefix is first made: existing users keep
# their own settings.)
import_defaults() {
    [ -d "$SG_DEFAULTS_DIR" ] || return 0
    _stamps="$SG_STATE/defaults"
    mkdir -p "$_stamps"
    _any=0
    for _reg in "$SG_DEFAULTS_DIR"/*.reg; do
        [ -f "$_reg" ] || continue
        _name=$(basename "$_reg")
        _sum=$(sha256sum < "$_reg" | cut -d' ' -f1)
        [ "$(cat "$_stamps/$_name.sha256" 2>/dev/null)" != "$_sum" ] || continue
        if wine reg import "$(winepath -w "$_reg")" >/dev/null 2>&1; then
            printf '%s\n' "$_sum" > "$_stamps/$_name.sha256"
            sg_log "imported defaults: $_name"
            _any=1
        else
            sg_log "WARNING: could not import defaults from $_reg"
        fi
    done
    [ "$_any" = 0 ] || sg_wineserver_wait 120
}

# Windows' own programs and libraries cut to nothing. A first boot cut short
# (power off, a VM quit) right after the seed was unpacked left 581 of them
# empty -- explorer.exe, notepad.exe, most of syswow64 -- the unpack's data
# never reached the disk, only the names did, and nothing on that machine
# worked again. An empty .exe or .dll is never right, and Wine's prefix update
# will not replace one (it leaves files it does not recognise as its own), so
# look for them on every boot -- a find over C:\windows, a few milliseconds --
# and put them back: from the seed when it has them, otherwise by deleting
# them and letting Wine's prefix update make them again.
empty_system_files() {
    [ -d "$SG_PREFIX/drive_c/windows" ] || return 0
    find "$SG_PREFIX/drive_c/windows" -xdev -type f -size 0 \( -iname '*.exe' -o -iname '*.dll' \
        -o -iname '*.sys' -o -iname '*.drv' -o -iname '*.ocx' -o -iname '*.cpl' -o -iname '*.acm' \
        -o -iname '*.ax' -o -iname '*.tlb' -o -iname '*.com' \) 2>/dev/null
}
repair_empty_system_files() {
    [ "${SG_MUTANT_NO_EMPTY_REPAIR:-}" = 1 ] && return 0
    _empty=$(empty_system_files)
    [ -n "$_empty" ] || return 0
    sg_log "WARNING: $(printf '%s\n' "$_empty" | wc -l) empty system files in C:\\windows (a cut-short first boot?); repairing"
    _list=$(mktemp) || return 0
    printf '%s\n' "$_empty" | while IFS= read -r _f; do
        printf './%s\n' "${_f#"$SG_ROOT"/}"
    done > "$_list"
    if [ -f "$SEED" ]; then
        printf '%s\n' "$_empty" | while IFS= read -r _f; do rm -f "$_f"; done
        # Names the seed lacks make tar complain; what it has is restored.
        zstd -dc "$SEED" | tar -x -C "$SG_ROOT" --no-same-owner --preserve-permissions -T "$_list" 2>/dev/null || true
        sync -f "$SG_PREFIX" 2>/dev/null || true
    fi
    _still=$(empty_system_files)
    _missing=0
    while IFS= read -r _m; do
        [ -e "$SG_ROOT/${_m#./}" ] || _missing=$((_missing + 1))
    done < "$_list"
    rm -f "$_list"
    if [ -n "$_still" ] || [ "$_missing" -gt 0 ]; then
        printf '%s\n' "$_still" | while IFS= read -r _f; do if [ -n "$_f" ]; then rm -f "$_f"; fi; done
        sg_log "the seed did not have them all; Wine's prefix update will make the rest"
        rm -f "$SG_PREFIX/.update-timestamp"   # prefix_current fails: the update below runs
    else
        sg_log "empty system files restored from the seed"
    fi
}

# A prefix built when the image was (sg-image's mkosi.postinst.chroot runs this
# script there, as the SYSTEM account, and packs the result): extracting it
# takes seconds, where building one takes a minute or more -- on every boot of
# a live system, whose prefix is in memory, and on an installed machine's
# first. Only into an empty place; if Wine has since been upgraded, the prefix
# update below happens to the unpacked prefix as it would to any other.
SEED="${SG_PREFIX_SEED:-/usr/lib/stained-glass/prefix-seed.tar.zst}"
if [ ! -e "$STAMP" ] && [ -f "$SEED" ] && [ -z "$(ls -A "$SG_PREFIX" 2>/dev/null)" ]; then
    sg_log "unpacking the prefix built with the image ($SEED)"
    mkdir -p "$SG_ROOT"
    if zstd -dc "$SEED" | tar -x -C "$SG_ROOT" --no-same-owner --preserve-permissions; then
        # On the disk before anything else happens: a boot cut short soon
        # after left the names with no data (empty explorer.exe and the rest).
        sync -f "$SG_PREFIX" 2>/dev/null || sync
        sg_log "prefix unpacked"
    else
        sg_log "WARNING: could not unpack $SEED; building the prefix instead"
        rm -rf "${SG_PREFIX:?}" "$SG_STATE"
    fi
fi

if [ -e "$STAMP" ]; then
    sg_log "prefix already initialized at $SG_PREFIX"
    # first: nothing below may trust what a person put among these files
    sg_shared_state_hardened "$SG_ROOT" "$SG_PREFIX" "$SG_STATE"
    repair_empty_system_files
    # If Wine was upgraded, the prefix is stale and Wine updates it inside
    # whatever process starts it first. Start one here -- at boot, as the
    # machine account, with the Mono/Gecko prompts suppressed -- rather than
    # leave that update to a user's desktop or a Windows service.
    #
    # Only then: Wine calls a prefix stale when its .update-timestamp is not
    # wine.inf's modification time. Starting Wine and waiting for its server
    # to go cost every boot 25 s or more before the login screen could come.
    if prefix_current; then
        sg_log "prefix is current with this Wine"
    else
        sg_log "Wine was updated: updating the prefix"
        rm -f "$SG_STATE/program-files-protected-1"   # what the update made, protected again
        sg_wine_unattended wine cmd /c exit >/dev/null 2>&1 || true
        # Bounded: auto-start services never exit, so an unbounded wait would
        # hang boot on a machine that has any.
        sg_wineserver_wait 120
    fi
    import_defaults
    set_machine_settings
    run_installers
    sg_programdata_shared "$SG_PREFIX/drive_c/ProgramData" "$SG_WINE_GROUP" "$SG_STATE"
    sg_system_access "$SG_PREFIX/drive_c" "$SG_SYSTEM_USER" "$SG_STATE"
    sg_program_files_protected "$SG_PREFIX/drive_c" "$SG_WINE_GROUP" "$SG_STATE"
    sg_public_desktop_ini "$SG_PREFIX/drive_c"
    sg_shared_shortcuts_protected "$SG_PREFIX/drive_c" "$SG_WINE_GROUP"
    sg_windows_protected "$SG_PREFIX/drive_c" "$SG_WINE_GROUP" "$SG_STATE"
    sg_c_root_users "$SG_PREFIX/drive_c" "$SG_WINE_GROUP" "$SG_STATE"
    sg_dotnet_support "$SG_PREFIX/drive_c"
    sg_mono_support_refresh "$SG_STATE"
    sg_systemroot_temp "$SG_PREFIX/drive_c" "$SG_SYSTEM_USER"
    exit 0
fi

sg_log "initializing system prefix at $SG_PREFIX (arch=$WINEARCH)"
mkdir -p "$SG_PREFIX" "$SG_STATE" "$SG_LOG_DIR"

# The group must be able to write the prefix, or no second user can use it --
# wine-sg requires R_OK|W_OK|X_OK on it. mkdir applies the umask, which under
# systemd leaves it group-read-only, so set the mode explicitly rather than
# leaving it to a later conditional.
if [ "${SG_SYSTEM_PREFIX:-1}" = "1" ]; then
    chmod 3770 "$SG_PREFIX" "$SG_STATE" 2>/dev/null || true
fi

# Mark the prefix as shared between Unix users before wineboot runs, so the
# server comes up in that mode from the start rather than being converted
# afterwards. On a Wine without wine-sg's patches the marker is inert.
if [ "${SG_SYSTEM_PREFIX:-1}" = "1" ]; then
    : > "$SG_PREFIX/.sg-system-prefix"
    sg_log "marked as a system prefix (shared between users)"
fi

# wineboot --init builds the prefix. It forks a wineserver that lingers for a
# few seconds after; wineserver -w waits for it so the build step cannot race
# against a half-written registry.
sg_wine_unattended wineboot --init
wineserver -w

# --- registry defaults contributed by other packages ----------------------
# Imported here, before the Default User profile is taken below, so the HKCU
# values in them become every user's defaults rather than only this account's.
# reg.exe is given a Windows path: handed a Unix one, it resolves it as a DOS
# path on C: and fails.
import_defaults

# --- make userdef.reg a real Default User profile -------------------------
# wineboot applies wine.inf's HKCU sections to whoever ran it, then stamps
# .update-timestamp in the *prefix*. The prefix is shared and the hives are
# not, so every later user's wineboot sees an up-to-date prefix and skips the
# per-user install entirely -- they are not seeded badly, they are skipped by
# design.
#
# Windows solves this with a Default User profile that the installer populates
# once. Wine has the same slot, \Registry\User\.Default, stored as
# userdef.reg, but nothing ever fills it beyond a stub. wine-sg patch 0006
# seeds each new user's hive from it, so making it a real template fixes every
# future user at the source instead of repairing each copy.
#
# The hive files are the same format -- all of them are server saves of a user
# branch -- so the template is simply this user's freshly initialised hive,
# taken now, before anyone has personalised anything. Volatile keys are never
# written to disk, so nothing user-specific comes along.
if [ "${SG_SYSTEM_PREFIX:-1}" = "1" ]; then
    _uid=$(id -u)
    _hive="$SG_PREFIX/user-$_uid.reg"
    [ -f "$_hive" ] || _hive="$SG_PREFIX/user.reg"
    if [ -f "$_hive" ] && [ -s "$_hive" ]; then
        # Wine writes this account's own profile path into the hive as
        # absolute strings; copied as is, every new user would get TEMP, TMP
        # and the cached Shell Folders of SYSTEM's profile -- which, now that
        # users' file access runs with their own rights (ADR 0013), they
        # cannot even write (multi-user debt D15). So the template names the
        # profile as %USERPROFILE%, which Wine expands from the user's own
        # name, drops the Shell Folders cache (shell32 rebuilds it from the
        # %USERPROFILE%-relative User Shell Folders) and the per-session
        # Volatile Environment (wineboot writes it at each session's start).
        _me=$(id -un)
        sed -e '/^\[Volatile Environment\]/,/^$/d' \
            -e '/^\[Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders\]/,/^$/d' \
            -e "s/=\"C:\\\\\\\\users\\\\\\\\$_me\([\\\\\"]\)/=str(2):\"%USERPROFILE%\\1/g" \
            -e "s/:\"C:\\\\\\\\users\\\\\\\\$_me\([\\\\\"]\)/:\"%USERPROFILE%\\1/g" \
            "$_hive" > "$SG_PREFIX/userdef.reg.new"
        mv -f "$SG_PREFIX/userdef.reg.new" "$SG_PREFIX/userdef.reg"
        _left=$(grep -ci "users\\\\\\\\$_me\\b" "$SG_PREFIX/userdef.reg" || true)
        sg_log "default user profile seeded from $(basename "$_hive") ($(wc -c < "$SG_PREFIX/userdef.reg") bytes, $_left paths into $_me's profile left)"
    else
        sg_log "WARNING: no hive to seed the default user profile from"
    fi
fi

# The machine registry branches (policy + display config) are protected on the
# *machine* wineserver at boot by sg-services-start, not here: descriptors live
# in the running server and are not persisted, so protecting them in this
# transient prefix-build server would not survive (multi-user debt / S2).

# Direct3D layers and bundled applications, if the image staged them.
# Deliberately before the ownership fixup below, so what they copy in is chowned
# with the rest of the prefix rather than staying root-owned inside a prefix
# nobody else owns.
set_machine_settings
run_installers
sg_programdata_shared "$SG_PREFIX/drive_c/ProgramData" "$SG_WINE_GROUP" "$SG_STATE"
sg_system_access "$SG_PREFIX/drive_c" "$SG_SYSTEM_USER" "$SG_STATE"
sg_program_files_protected "$SG_PREFIX/drive_c" "$SG_WINE_GROUP" "$SG_STATE"
sg_public_desktop_ini "$SG_PREFIX/drive_c"
sg_shared_shortcuts_protected "$SG_PREFIX/drive_c" "$SG_WINE_GROUP"
sg_windows_protected "$SG_PREFIX/drive_c" "$SG_WINE_GROUP" "$SG_STATE"
sg_c_root_users "$SG_PREFIX/drive_c" "$SG_WINE_GROUP" "$SG_STATE"
sg_dotnet_support "$SG_PREFIX/drive_c"
sg_mono_support_refresh "$SG_STATE"
sg_systemroot_temp "$SG_PREFIX/drive_c" "$SG_SYSTEM_USER"

# The prefix is machine state, not user state.
# MULTIUSER-DEBT: D1 — reduced but not retired. With wine-sg's shared prefix the
# hives are separated per user and HKLM is administrator-writable, but the
# prefix is still owned by one Unix user who is implicitly its administrator.
if getent passwd "$SG_USER" >/dev/null 2>&1 || getent passwd "$SG_SYSTEM_USER" >/dev/null 2>&1; then
    # Group-owned so other users can be admitted by adding them to
    # SG_WINE_GROUP; setgid so anything created inside keeps that group.
    # Owned by the SYSTEM account rather than the session user: wine-sg reads
    # the prefix's owner as the machine's administrator, and the interactive
    # user should not be one.
    _owner="$SG_USER"
    getent passwd "$SG_SYSTEM_USER" >/dev/null 2>&1 && _owner="$SG_SYSTEM_USER"
    if getent group "$SG_WINE_GROUP" >/dev/null 2>&1; then
        chown -R "$_owner:$SG_WINE_GROUP" "$SG_ROOT" "$SG_LOG_DIR" 2>/dev/null || true
        chmod 3770 "$SG_ROOT" "$SG_PREFIX"
    else
        chown -R "$_owner" "$SG_ROOT" "$SG_LOG_DIR" 2>/dev/null || true
    fi
fi

sg_shared_state_hardened "$SG_ROOT" "$SG_PREFIX" "$SG_STATE"
: > "$STAMP"
sg_log "prefix initialized"
