#!/bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2015-2016, 2026 InterGenJLU
#
# intergen 0.1.0 — InterGen AI assistant for InterGenOS
# https://github.com/InterGenJLU/intergenos
#
# Installs: Python modules, systemd service, D-Bus activation,
# default config, CLI wrapper, Forge integration hook.
#
# Python dependencies (numpy, sentence-transformers, huggingface-hub,
# torch-cpu) are NOT installed at build time. The InterGenOS chroot is
# intentionally offline during the build (security-by-design: no untrusted
# network access during build). The design already supports user-side
# setup via `intergen setup` (post_install message line below); numpy
# etc. are installed at first-run, not build-time.
#
# Halt #22 (2026-05-08): the prior pip install in build() failed because
# the chroot has no resolv.conf — by design. Moved deps to user-side.

build() {
    set -e
    # No build-time work: package contents are pure Python source +
    # systemd/dbus units, copied verbatim by do_install(). Python deps
    # are installed by user at first run via `intergen setup`.
    :
}

do_install() {
    set -e
    # InterGen Python package
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/interfaces"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/tools"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/scanner"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/cloud"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/tests"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/console"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/panel"
    install -dm755 "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/web"

    # Source lives at the top-level /mnt/intergenos/intergen/ (virtiofs-shared
    # from host). package.yml has source: [] so no extraction happens —
    # use absolute paths for the cp.
    cp -a /mnt/intergenos/intergen/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/"
    cp -a /mnt/intergenos/intergen/interfaces/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/interfaces/"
    cp -a /mnt/intergenos/intergen/tools/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/tools/"
    # InterGen Sentinel — scanner architecture (Local-Rules, Local-Qwen, cloud
    # routing) + cloud provider adapters. Both are real import packages
    # (intergen.scanner / intergen.cloud) that ToolRegistry + Sentinel init
    # depend on; omitting them left the registry/Sentinel unable to load at
    # runtime. *.py only — __pycache__ is a build artifact, excluded.
    cp -a /mnt/intergenos/intergen/scanner/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/scanner/"
    cp -a /mnt/intergenos/intergen/cloud/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/cloud/"
    cp -a /mnt/intergenos/intergen/tests/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/tests/"
    cp -a /mnt/intergenos/intergen/console/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/console/"
    cp -a /mnt/intergenos/intergen/panel/*.py "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/panel/"
    cp -a /mnt/intergenos/intergen/web/*.html /mnt/intergenos/intergen/web/*.css \
          /mnt/intergenos/intergen/web/*.js /mnt/intergenos/intergen/web/*.json \
          /mnt/intergenos/intergen/web/*.svg \
          "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/web/"

    # GNOME Shell extension
    install -dm755 "${DESTDIR}/usr/share/gnome-shell/extensions/intergen@intergenos.org"
    cp -a /mnt/intergenos/intergen/panel/extension/* \
          "${DESTDIR}/usr/share/gnome-shell/extensions/intergen@intergenos.org/"

    # Normalize ownership to root:root. The cp -a calls above preserve attributes
    # from the virtiofs-shared source tree /mnt/intergenos/intergen/, which is
    # owned by the unprivileged build user (uid 1000) — so the staged .py + web +
    # extension files carry uid 1000 into the package. The squashfs-assembly guard
    # catches and normalizes this, but fix it at the source so the package is
    # correct on its own and the guard stays a backstop rather than a load-bearing
    # step. cp -a is kept for mode/timestamp/symlink fidelity; only ownership is
    # reset here.
    chown -R root:root "${DESTDIR}/usr/lib/python3.14/site-packages/intergen"
    chown -R root:root "${DESTDIR}/usr/share/gnome-shell/extensions/intergen@intergenos.org"

    # Compile the extension's keybinding settings schema. extension.js
    # enable() calls this.getSettings() + Main.wm.addKeybinding(
    # "toggle-intergen", ...) for the Super+I shortcut. On GNOME 49 a
    # Gio.Settings construction against a non-existent schema is a hard
    # g_error that ABORTS the GJS process (uncatchable), so without a
    # compiled gschemas.compiled in the extension's schemas/ dir the
    # extension cannot enable at all — no top-bar icon, no Super+I. The
    # schema source ships via the cp -a above; compile it here. (Contrast
    # pkm-notifier, which declares settings-schema but ships no schema and
    # never calls getSettings(), so it needs no compile step.)
    glib-compile-schemas \
        "${DESTDIR}/usr/share/gnome-shell/extensions/intergen@intergenos.org/schemas"

    # CLI wrapper
    install -Dm755 /dev/stdin "${DESTDIR}/usr/bin/intergen" << 'WRAPPER'
#!/usr/bin/env python3
"""InterGen CLI — command-line interface to the InterGen AI assistant."""
from intergen.cli import main
main()
WRAPPER

    # Panel entry point
    install -Dm755 /dev/stdin "${DESTDIR}/usr/bin/intergen-panel" << 'PANELWRAPPER'
#!/usr/bin/env python3
"""InterGen Panel — GTK4 WebKitGTK desktop window."""
from intergen.panel import main
main()
PANELWRAPPER

    # Default configuration
    install -Dm644 /dev/stdin "${DESTDIR}/etc/intergen/config.yml" << 'CONFIG'
# InterGen AI Assistant — Configuration
# User overrides: ~/.config/intergen/config.yml

llm:
  endpoint: "http://127.0.0.1:8080/v1/chat/completions"
  temperature: 0.6
  top_p: 0.8
  top_k: 20
  max_tokens: 4096
  tool_calling: true
  presence_penalty: 1.5
  context_size: 16384

escalation:
  mode: "ask"

models:
  path: "/var/lib/intergen/models"
  embedding_model: "nomic-ai/nomic-embed-text-v1.5"
  embedding_device: "cpu"

llama_server:
  port: 8080
  gpu_layers: 999
  jinja: true
  parallel: 1
  reasoning: "off"

logging:
  level: "INFO"
  file: "/var/log/intergen/intergen.log"
  event_log: "/var/log/intergen/events.jsonl"
  mcp_audit: "/var/log/intergen/mcp-audit.log"
  max_file_size_mb: 50
  backup_count: 5

memory:
  # Memory is per-user state. Left unset so InterGen resolves a per-user
  # XDG path ($XDG_DATA_HOME/intergen/memory.db, default
  # ~/.local/share/intergen/memory.db). The hardened user service
  # (ProtectSystem=strict) cannot write the old /var default, and a shared
  # /var memory.db would leak one user's stored facts to another. To pin a
  # custom location, set db_path here or in ~/.config/intergen/config.yml.
  db_path: null

security:
  mcp_config: "/etc/intergen/mcp.yml"
  mcp_permissions: "/etc/intergen/mcp.d"
  schema_pins: "/var/lib/intergen/mcp-pins"
CONFIG

    # Systemd user service. Closes audit F-038 (HG) "intergen.service no
    # hardening" per matrix row 211 + line 1309: previously this unit
    # shipped strictly less hardened than nginx + the audit's canonical
    # example of "every InterGenOS-authored service VIOLATES" the ratified
    # 2026-04-29 4-0 unanimous AppArmor + daemon-hardening baseline.
    #
    # The directives below are the systemd canonical hardening set for a
    # user service that needs network access (LLM API + MCP transport) +
    # XDG home-dir writes (config + state caches). Read-only system trees
    # via ProtectSystem=strict; no privilege escalation via
    # NoNewPrivileges; private /tmp + /dev to isolate from peer-process
    # state; kernel-tuning + namespace + realtime + SUID surfaces all
    # restricted; address-family restriction to UNIX + INET + INET6 only
    # (NETLINK + PACKET + others denied — intergen does not enumerate
    # network interfaces or capture raw frames). SystemCallFilter pares
    # to the @system-service umbrella with explicit denials of
    # @privileged (capability-changing) + @resources (mlock-class
    # resource-exhaustion). CapabilityBoundingSet + AmbientCapabilities
    # cleared — a user service needs no Linux capabilities.
    #
    # MemoryDenyWriteExecute= is intentionally NOT set: torch + ggml +
    # sentence-transformers use JIT regions that the directive would
    # break. Re-evaluate when llama.cpp-only CPU inference is the v1.0
    # ship target.
    #
    # ProtectHome= is intentionally NOT set: this is a USER unit; the
    # user's own daemon legitimately needs read/write access to its own
    # ${HOME}/.config/intergen + ${HOME}/.local/share/intergen per the
    # AppArmor profile coverage. ProtectHome=read-only would block the
    # user's daemon from writing to its own home.
    #
    # ConditionUser=!@system scopes the unit to real human user sessions
    # only. Forge globally enables intergen.service on the D-010 YES path
    # (installer/backend/install.py ~line 747), which writes the wants/
    # symlink into every systemd-user manager including transient ones —
    # GDM's DynamicUser greeter sessions (B10 USA-1 closure) get a
    # user@<dynamic-UID>.service slice, root su gets user@0.service, etc.
    # None of those sessions have an opted-in human user for InterGen to
    # serve; firing there wastes resources, generates audit_log_subj_ctx
    # noise (dynamic UIDs are unresolvable by /etc/passwd by design), and
    # tripped the SIGSYS coredump cascade on install #29 (2026-05-27)
    # when state_cache pollers shelled out under the hardened seccomp
    # filter. ConditionUser=!@system is systemd-canonical scope-narrowing
    # for "this unit serves logged-in humans, not system users."
    install -Dm644 /dev/stdin "${DESTDIR}/usr/lib/systemd/user/intergen.service" << 'SERVICE'
[Unit]
Description=InterGen AI Assistant
Documentation=https://github.com/InterGenJLU/intergenos
After=graphical-session.target
ConditionUser=!@system

[Service]
Type=dbus
BusName=com.intergenos.InterGen
ExecStart=/usr/bin/intergen daemon
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1

# Hardening — F-038 closure (T0-4-A Commit 2; matrix row 211 + line 1309).
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
SystemCallFilter=~@resources
CapabilityBoundingSet=
AmbientCapabilities=

[Install]
WantedBy=default.target
SERVICE

    # D-Bus service activation. SystemdService= makes dbus-daemon delegate
    # activation to the systemd user unit (a single, systemd-managed instance)
    # instead of spawning its own Exec= copy. Without it BOTH the systemd unit
    # (Type=dbus, auto-started when enabled on the D-010 YES path) AND bus
    # activation launch a daemon, and the two race to bind the 8089 web port
    # -> EADDRINUSE crash on cold boot. Exec= stays as the fallback for a
    # non-systemd session bus.
    install -Dm644 /dev/stdin "${DESTDIR}/usr/share/dbus-1/services/com.intergenos.InterGen.service" << 'DBUS'
[D-BUS Service]
Name=com.intergenos.InterGen
Exec=/usr/bin/intergen daemon
SystemdService=intergen.service
DBUS

    # T0-4-E integration pkexec gate (RFC v0.1 §6 line 161 D-007 Option A):
    # privileged tool dispatch goes through PolicyKit. Policy declares
    # the org.intergenos.intergen.privileged-tool action; runner is the
    # exec.path target that re-enters Python for argument validation +
    # dispatch via intergen.privileged_dispatch. The provenance gate
    # (intergen/provenance.py — IGOSC's T0-4-E surface) authorizes INTENT
    # before this PolicyKit gate authorizes AUTHENTICATION; both fire for
    # privileged operations per the RFC "the two don't replace each other"
    # invariant.
    install -Dm644 /mnt/intergenos/intergen/data/org.intergenos.intergen.policy \
        "${DESTDIR}/usr/share/polkit-1/actions/org.intergenos.intergen.policy"
    install -Dm755 /mnt/intergenos/intergen/data/intergen-privileged-runner \
        "${DESTDIR}/usr/bin/intergen-privileged-runner"
    # Setup-time model-storage provisioner (org.intergenos.intergen.provision-
    # model-storage). Lets the unprivileged `intergen setup` install a
    # downloaded+pin-verified model into the root-owned store under one auth
    # prompt; the runner re-verifies the sha256 against the shipped manifest
    # before any write. Distinct from the runtime AI tool-dispatch runner above.
    install -Dm755 /mnt/intergenos/intergen/data/intergen-model-setup-runner \
        "${DESTDIR}/usr/bin/intergen-model-setup-runner"

    # D-008 RFC §14.3 audit-log retention — OPERATOR-CONFIRMED 2026-05-19T23:14:33Z.
    # 30-day daily rotation of the per-user tool-dispatch JSONL audit log
    # written by intergen.audit_log. Pairs system-side rotation (this snippet)
    # with user-side wipe via `intergen tool-log --clear` (intergen.cli
    # cmd_tool_log). 644 because logrotate.d snippets are world-readable
    # by design; the actual log files retain their 0o600 perms set by the
    # audit_log writer.
    install -Dm644 /mnt/intergenos/intergen/data/intergen-tool-dispatch.logrotate \
        "${DESTDIR}/etc/logrotate.d/intergen-tool-dispatch"

    # T0-4-D — model SHA256 pinning manifest + operator-PIV-signed
    # armored detached signature. The consumer (intergen.model_manager._load_pins)
    # reads PINS_MANIFEST_PATH = /usr/share/intergen/models-manifest.json
    # at ModelManager construction; empty/missing/malformed manifest
    # triggers fail-closed verify_model + download_model refusal per the
    # T0-4-D contract. The detached .asc file is the operator-PIV-rooted
    # supply-chain anchor (master FP 5597A3E0587B253006D0DD7B8C50826182083050
    # per docs/signing-key.md), produced via `gpg --detach-sign --armor`;
    # v1.0 ships structural signature chain; consumer-side gpg --verify
    # wiring is v1.x scope per the audit doc.
    install -Dm644 /mnt/intergenos/intergen/data/models-manifest.json \
        "${DESTDIR}/usr/share/intergen/models-manifest.json"
    install -Dm644 /mnt/intergenos/intergen/data/models-manifest.json.asc \
        "${DESTDIR}/usr/share/intergen/models-manifest.json.asc"

    # Sentinel decision #5 — destructive-policy never-list manifest + operator
    # OpenPGP detached signature. The consumer (intergen.destructive_policy.
    # load_policy) reads DEFAULT_MANIFEST_PATH = /usr/share/intergen/
    # destructive-policy-manifest.json (+ .asc), verifies the detached signature
    # over the exact bytes against the pinned operator fingerprint
    # (5597A3E0587B253006D0DD7B8C50826182083050), and returns None on ANY doubt —
    # the write_file/run_command chokepoint then fail-closes to its interim
    # AI_IMMUTABLE_PREFIXES floor. Without this install step the signed never-list
    # is never present at runtime and load_policy can ONLY ever fail-close to the
    # floor; shipping it here is what makes the full signed system_ai/boot/identity
    # never-list active. Ships to /usr/share/intergen/ (dm-verity read-only).
    install -Dm644 /mnt/intergenos/intergen/data/destructive-policy-manifest.json \
        "${DESTDIR}/usr/share/intergen/destructive-policy-manifest.json"
    install -Dm644 /mnt/intergenos/intergen/data/destructive-policy-manifest.json.asc \
        "${DESTDIR}/usr/share/intergen/destructive-policy-manifest.json.asc"

    # Perceived-latency filler pools (hop-1 ack + hop-2 progress nudges). Loaded
    # as DATA at runtime so the voice is tunable without a code change; the
    # filler-emit runtime (ring buffer + WS events) consumes this asset. See
    # docs/architecture/intergen-perceived-latency-design.md.
    install -Dm644 /mnt/intergenos/intergen/data/voice/fillers.json \
        "${DESTDIR}/usr/share/intergen/voice/fillers.json"

    # Governance tamper-detection baseline (build-established, read-only).
    # governance.verify_hash() at runtime hashes the INSTALLED governance.py
    # and compares it to this file; a missing OR mismatched baseline fails
    # CLOSED. The baseline is set HERE, by the build, over the exact bytes just
    # installed (cp -a is byte-identical), and shipped read-only into
    # dm-verity-protected /usr/share — so it is never written trust-on-first-use
    # by the running user daemon (which couldn't, /var being root-owned + RO)
    # and a tampered governance.py can never self-bless its own hash.
    install -dm755 "${DESTDIR}/usr/share/intergen"
    sha256sum "${DESTDIR}/usr/lib/python3.14/site-packages/intergen/governance.py" \
        | awk '{print $1}' > "${DESTDIR}/usr/share/intergen/governance.sha256"
    chmod 644 "${DESTDIR}/usr/share/intergen/governance.sha256"

    # Create data directories
    install -dm755 "${DESTDIR}/var/lib/intergen/models/llm"
    install -dm755 "${DESTDIR}/var/lib/intergen/models/embedding"
    install -dm755 "${DESTDIR}/var/lib/intergen/data"
    install -dm755 "${DESTDIR}/var/lib/intergen/mcp-pins"
    install -dm755 "${DESTDIR}/var/log/intergen"
    install -dm755 "${DESTDIR}/etc/intergen/mcp.d"

    # Desktop entry + app icon for the intergen-panel window. The window is a
    # GtkApplication with app-id org.intergenos.InterGenPanel; GNOME's window
    # tracker matches a window to the .desktop whose id equals the app-id, then
    # shows that entry's Icon in the taskbar/overview. Without this the
    # Windows-style DtP taskbar showed a generic gear for the InterGen window
    # (dock arc, .241 2026-06-10). The icon is the bare free-floating InterGen
    # ECG pulse (AI = bare; the OS badge is the framed ArcMenu squircle). The
    # entry is NoDisplay (see the file) — it drives window->icon matching, not
    # an app-grid launcher, since InterGen is opt-in and launches via the
    # readiness-gated panel icon / Super+I.
    install -Dm644 /mnt/intergenos/intergen/data/org.intergenos.InterGenPanel.svg \
        "${DESTDIR}/usr/share/icons/hicolor/scalable/apps/org.intergenos.InterGenPanel.svg"
    install -Dm644 /mnt/intergenos/intergen/data/org.intergenos.InterGenPanel.desktop \
        "${DESTDIR}/usr/share/applications/org.intergenos.InterGenPanel.desktop"

    # Man page
    install -Dm644 /mnt/intergenos/packages/ai/intergen/intergen.1 \
        "${DESTDIR}/usr/share/man/man1/intergen.1"
}

post_install() {
    set -e
    # D-010 (2026-05-19 owner-direct, docs/owner-directives.md): InterGen
    # is opt-in. This package_install path MUST NOT enable the user
    # service. The Forge installer prompts at install time (default NO);
    # the YES path runs `systemctl --global enable intergen.service` in
    # the chroot from installer/backend/install.py PHASE_SERVICES.
    # scripts/check-d010-compliance.sh is a Class A ship-gate that
    # blocks ISO assembly if any package_install or autostart path
    # would re-enable intergen by default.
    echo "InterGen installed. Run 'intergen setup' to configure your AI model."
}
