#!/bin/sh
# Gerbit private distribution. Asset-only OPNsense package; no service reload.
set -eu
umask 077
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
export PATH
BASE='https://temas.gerbit.com.br'
VERSION='1.1.1'
THEME='gerbit-20261028.2'
MODE=${1:---check}
die() { echo "ERRO: $*" >&2; exit 1; }
case "$MODE" in --check|--install|--rollback) ;; *) die 'Use --check, --install ou --rollback.';; esac
[ "$(id -u)" = 0 ] || die 'Execute no shell root do proprio firewall.'
[ "$(uname -s)" = FreeBSD ] || die 'Somente OPNsense/FreeBSD.'
[ "$(uname -m)" = amd64 ] || die 'Arquitetura nao validada.'
[ -x /usr/local/sbin/opnsense-version ] || command -v opnsense-version >/dev/null || die 'OPNsense ausente.'
[ "$(opnsense-version -v)" = '26.7.7' ] || die 'Versao nao validada: esta release aceita somente OPNsense26.7.7.'
php -r 'require_once("config.inc"); exit(($config["interfaces"]["lan"]["ipaddr"]??"")==="172.19.20.2"?0:1);' || die 'Esta release esta limitada ao IP proprio do piloto172.19.20.2. Nao use VIP.'
WORK=$(mktemp -d /tmp/gerbit-theme.XXXXXXXX)
LOCK=/var/run/gerbit-theme-install.lock
HAVE_LOCK=0
cleanup() { rm -rf "$WORK"; if [ "$HAVE_LOCK" = 1 ]; then rmdir "$LOCK"; fi; }
trap cleanup EXIT HUP INT TERM
mkdir "$LOCK" 2>/dev/null || die 'Outra instalacao esta em andamento; confira antes de remover o lock.'
HAVE_LOCK=1
cat > "$WORK/trust.pem" <<'GERBIT_PUBLIC_KEY'
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAtGDyo4GrIHt4oIEXgULw
cZJL6FsgXJvDcaaMPe9PAVZh8Cae/A3KH49xaAmVagO6+cOpa2S5Q7FrWxQH7z36
rGNBnXFf7zltagSd1rgkCCg0qc0KLS0r+7BjlVsmfkhBJaNdQnHHeqqGOpvpF8hp
Dywvy1n/ytp6eh+Z5/FmYsbatrSSHUaf/kf/NdWgVEgWCYYzjaoXa1p3TQK0d2Ab
VBytaeKdRUcP6Fc4hOVollYMkcl3hgOOA+MH5GvkZVqTJ4BZqhGd1R/Gt5lBrMVF
GHC9P7jw2bT80HPFficUyQ9T/gci8xDW15H2O0iaanlpsGsD39bPYfGgFLHTMRO2
/a2B7AA86dPV9X9Jk4bn4O1e3eHGkilB9GQLwgcy1JGj9EpVX+DPkAi5iG2/yX9R
xs+FPbIMaIWMmhVtQtUomnK0qdfVSQ0m1sHMT3Aj+wXkHfhVfNBT/VEyHOdxSnuI
tqUytxYEfp1hpK68rE5MjLYriYYEo4eB98ub5rmeSIN7AgMBAAE=
-----END PUBLIC KEY-----
GERBIT_PUBLIC_KEY
download() { fetch -T 30 -o "$2" "$BASE/$1" || die 'Download HTTPS falhou; nenhuma excecao TLS e permitida.'; }
if [ "$MODE" != --rollback ]; then
 download releases/1.1.1/release.json "$WORK/release.json"
 download releases/1.1.1/release.sig "$WORK/release.sig"
 openssl dgst -sha256 -verify "$WORK/trust.pem" -signature "$WORK/release.sig" "$WORK/release.json" >/dev/null || die 'Assinatura do manifesto invalida.'
 php -r '$m=json_decode(file_get_contents($argv[1]),true,512,JSON_THROW_ON_ERROR);if(($m["package"]??"")!=="os-theme-gerbit"||($m["version"]??"")!=="1.1.1"||($m["opnsense"]??"")!=="26.7.7"||($m["target"]??"")!=="172.19.20.2"||!preg_match("/^[a-f0-9]{64}$/",$m["sha256"]??""))exit(1);echo $m["sha256"];' "$WORK/release.json" > "$WORK/expected" || die 'Manifesto fora do escopo.'
 download releases/1.1.1/os-theme-gerbit-1.1.1.pkg "$WORK/theme.pkg"
 [ "$(sha256 -q "$WORK/theme.pkg")" = "$(cat "$WORK/expected")" ] || die 'Hash do pacote incorreto.'
 tar -xOf "$WORK/theme.pkg" +MANIFEST > "$WORK/pkg.json"
 php -r '$m=json_decode(file_get_contents($argv[1]),true,512,JSON_THROW_ON_ERROR);$r=json_decode(file_get_contents($argv[2]),true,512,JSON_THROW_ON_ERROR);if($m["name"]!=="os-theme-gerbit"||$m["version"]!=="1.1.1"||!empty($m["scripts"])||!empty($m["deps"])||($m["abi"]??$m["arch"])!=="FreeBSD:15:amd64")exit(1);$prefix="/usr/local/opnsense/www/themes/gerbit-20261028.2/";$files=$m["files"]??[];if(count($files)!==count($r["files"]))exit(1);foreach($r["files"] as $p=>$v){if(str_contains($p,"..")||!isset($files[$prefix.$p])||!is_array($files[$prefix.$p])||($files[$prefix.$p]["perm"]??"")!=="0644")exit(1);}' "$WORK/pkg.json" "$WORK/release.json" || die 'Pacote contem arquivos, scripts ou dependencias inesperados.'
 CURRENT=$(pkg query '%v' os-theme-gerbit 2>/dev/null || true)
 if [ -n "$CURRENT" ] && [ "$(pkg version -t "$CURRENT" "$VERSION")" = '>' ]; then die 'Downgrade bloqueado.'; fi
 if [ "$MODE" = --check ]; then echo 'VALIDADO: manifesto assinado, hash, versao, IP proprio e pacote somente visual. Nenhuma alteracao aplicada.'; exit 0; fi
 # Reinstall only when package or installed public assets differ.
 if [ "$CURRENT" != "$VERSION" ] || ! php -r '$r=json_decode(file_get_contents($argv[1]),true);foreach($r["files"] as $p=>$v){$f="/usr/local/opnsense/www/themes/gerbit-20261028.2/".$p;if(!is_file($f)||hash_file("sha256",$f)!==$v["sha256"])exit(1);}' "$WORK/release.json"; then
  pkg -o RUN_SCRIPTS=false add -f "$WORK/theme.pkg"
 fi
 php -r '$r=json_decode(file_get_contents($argv[1]),true);foreach($r["files"] as $p=>$v){$f="/usr/local/opnsense/www/themes/gerbit-20261028.2/".$p;if(!is_file($f)||hash_file("sha256",$f)!==$v["sha256"])exit(1);}' "$WORK/release.json" || die 'Validacao dos arquivos instalados falhou.'
fi
cat > "$WORK/select.php" <<'GERBIT_SELECT_THEME'
<?php
require_once('config.inc');
require_once('util.inc');
$mode=$argv[1];
$c=\OPNsense\Core\Config::getInstance();$c->lock(true);$root=$c->object();
if((string)$root->interfaces->lan->ipaddr!=='172.19.20.2')throw new RuntimeException('Target changed');
$functional=function($xml){$d=new DOMDocument();$d->preserveWhiteSpace=false;$d->loadXML($xml);foreach(['theme','revision'] as $tag){foreach(iterator_to_array($d->documentElement->childNodes) as $n){if($n->nodeName===$tag)$d->documentElement->removeChild($n);}}return hash('sha256',$d->C14N());};
$before=$functional(file_get_contents('/conf/config.xml'));$old=(string)$root->theme;
$state='/var/db/gerbit-theme';if(!is_dir($state))mkdir($state,0700,true);
if($mode==='--rollback'){
 if(!is_file($state.'/previous-theme.json'))throw new RuntimeException('No previous theme recorded by this installer');
 $selected=json_decode(file_get_contents($state.'/previous-theme.json'),true,512,JSON_THROW_ON_ERROR)['theme'];
}else{$selected='gerbit-20261028.2';}
if(!preg_match('/^[A-Za-z0-9_.-]+$/',$selected)||!is_file('/usr/local/opnsense/www/themes/'.$selected.'/build/css/main.css'))throw new RuntimeException('Theme not available');
if($old===$selected){echo "Tema ja selecionado; configuracao preservada.\n";exit;}
if($mode!=='--rollback'&&!is_file($state.'/previous-theme.json')){file_put_contents($state.'/previous-theme.json',json_encode(['theme'=>$old]));chmod($state.'/previous-theme.json',0600);}
$root->theme=$selected;$c->save('Gerbit: visual theme only; no service reload');
if($before!==$functional(file_get_contents('/conf/config.xml'))){$root->theme=$old;$c->save('Gerbit: restore previous visual selection after validation failure');throw new RuntimeException('Nonvisual configuration changed; stop and inspect');}
echo "Tema selecionado. Rede, autenticacao e servicos preservados.\n";
GERBIT_SELECT_THEME
php "$WORK/select.php" "$MODE"
