2026-07-24 10:39:10 +08:00
#!/usr/bin/env bash
set -euo pipefail
if [ [ ${ EUID } -ne 0 ] ] ; then
echo "run as root" >& 2
exit 1
fi
script_dir = $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd )
install_root = /opt/guanghu/lake-lamp-authz
2026-08-02 22:04:09 +08:00
state_root = /var/lib/guanghu/architecture-provision
secret_root = /etc/guanghu/secrets/lake-lamp
authorization_env = $secret_root /authorization.env
provider_registry = /etc/guanghu/secrets/hololake-ai-providers.json
knowledge_repo = /var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/hololake-knowledge-base.git
2026-08-02 23:47:34 +08:00
ghdr_authorizer_key = /var/lib/guanghu/lake-lamp-authz/ghdr-authorizer-private.pem
2026-08-02 22:04:09 +08:00
stamp = $( date -u +%Y%m%dT%H%M%SZ)
backup_root = $state_root /manual-backups/lake-lamp-authz-$stamp
required_source_files = (
server.js
workorder-manager.js
map-gate.js
smtp-mailer.js
action-client.js
architecture-provision-broker.js
deployment-event.js
deployment-event-worker.js
deployment-source-policy.js
guanghu-router.js
repo-push-broker.js
hololake-session.js
hololake-capabilities.js
2026-08-02 23:47:34 +08:00
ghdr-authorizer.js
ghdr-controller-broker.js
ghdr-controllers.json
2026-08-02 22:04:09 +08:00
)
for file in " ${ required_source_files [@] } " lake-lamp-authz.service lake-lamp-architecture-provision.service lake-lamp-deployment-event-worker.service; do
[ [ -f " $script_dir / $file " ] ] || {
echo " missing required release file: $file " >& 2
exit 1
}
done
2026-08-02 23:47:34 +08:00
[ [ -f " $script_dir /navigation-maps/GH-CVM-MAIN-PROD-01.json " ] ] || {
echo "missing required GHDR navigation map" >& 2
exit 1
}
2026-08-02 22:04:09 +08:00
install -d -m 0700 " $backup_root "
[ [ ! -e " $install_root " ] ] || cp -a " $install_root " " $backup_root /install-root "
for existing in \
/etc/systemd/system/lake-lamp-authz.service \
/etc/systemd/system/lake-lamp-architecture-provision.service \
/etc/systemd/system/lake-lamp-deployment-event-worker.service \
" $authorization_env " \
2026-08-02 23:47:34 +08:00
" $provider_registry " \
" $ghdr_authorizer_key " ; do
2026-08-02 22:04:09 +08:00
if [ [ -e " $existing " ] ] ; then
destination = $backup_root /existing${ existing }
install -d -m 0700 " $( dirname " $destination " ) "
cp -a " $existing " " $destination "
fi
done
rollback( ) {
set +e
if [ [ -d " $backup_root /install-root " ] ] ; then
rm -rf -- " $install_root "
cp -a " $backup_root /install-root " " $install_root "
fi
for existing in \
/etc/systemd/system/lake-lamp-authz.service \
/etc/systemd/system/lake-lamp-architecture-provision.service \
/etc/systemd/system/lake-lamp-deployment-event-worker.service \
" $authorization_env " \
2026-08-02 23:47:34 +08:00
" $provider_registry " \
" $ghdr_authorizer_key " ; do
2026-08-02 22:04:09 +08:00
saved = $backup_root /existing${ existing }
if [ [ -e " $saved " ] ] ; then
install -d -m 0755 " $( dirname " $existing " ) "
cp -a " $saved " " $existing "
2026-08-02 23:47:34 +08:00
elif [ [ " $existing " = " $provider_registry " || " $existing " = " $ghdr_authorizer_key " ] ] ; then
2026-08-02 22:04:09 +08:00
rm -f -- " $existing "
fi
done
systemctl daemon-reload
systemctl restart lake-lamp-authz.service
systemctl restart lake-lamp-architecture-provision.service
systemctl restart lake-lamp-deployment-event-worker.service
}
trap 'rc=$?; if [[ $rc -ne 0 ]]; then rollback; fi; exit "$rc"' EXIT
2026-07-24 10:39:10 +08:00
install -d -m 0755 " $install_root "
2026-08-02 22:04:09 +08:00
for file in " ${ required_source_files [@] } " ; do
2026-07-24 10:39:10 +08:00
install -m 0644 " $script_dir / $file " " $install_root / $file "
done
2026-08-02 23:41:48 +08:00
install -d -m 0755 " $install_root /navigation-maps "
2026-08-02 23:47:34 +08:00
install -m 0644 \
" $script_dir /navigation-maps/GH-CVM-MAIN-PROD-01.json " \
" $install_root /navigation-maps/GH-CVM-MAIN-PROD-01.json "
2026-08-02 22:04:09 +08:00
install -m 0644 " $script_dir /lake-lamp-authz.service " /etc/systemd/system/lake-lamp-authz.service
2026-07-24 10:39:10 +08:00
install -m 0644 " $script_dir /lake-lamp-architecture-provision.service " /etc/systemd/system/lake-lamp-architecture-provision.service
2026-07-27 15:05:46 +08:00
install -m 0644 " $script_dir /lake-lamp-deployment-event-worker.service " /etc/systemd/system/lake-lamp-deployment-event-worker.service
2026-08-02 22:04:09 +08:00
install -d -m 0700 " $state_root "
2026-07-27 15:05:46 +08:00
install -d -m 0750 /var/lib/guanghu/deployment-events
install -d -m 0700 /var/lib/guanghu/deployment-events/receipts
2026-07-24 10:39:10 +08:00
install -d -m 0755 /opt/guanghu/architecture-releases
2026-07-27 15:05:46 +08:00
install -d -m 0755 /etc/guanghu/lake-lamp
2026-08-02 22:04:09 +08:00
install -d -m 0750 " $secret_root "
2026-07-27 15:05:46 +08:00
if [ [ ! -e /etc/guanghu/lake-lamp/deployment-repositories.json ] ] ; then
install -m 0644 " $script_dir /deployment-repositories.example.json " /etc/guanghu/lake-lamp/deployment-repositories.json
fi
2026-07-24 10:39:10 +08:00
2026-08-02 22:04:09 +08:00
[ [ -f " $authorization_env " ] ] || {
echo "private authorization environment is missing" >& 2
exit 1
}
append_setting( ) {
local key = $1
local value = $2
if ! grep -q " ^ ${ key } = " " $authorization_env " ; then
printf '%s=%s\n' " $key " " $value " >>" $authorization_env "
fi
}
if ! grep -q '^HOLOLAKE_SESSION_PEPPER=' " $authorization_env " ; then
append_setting HOLOLAKE_SESSION_PEPPER " $( /usr/bin/openssl rand -hex 32) "
fi
append_setting HOLOLAKE_SESSION_STATE_FILE /var/lib/guanghu/lake-lamp-authz/hololake-sessions.json
append_setting HOLOLAKE_OTP_TTL 600
append_setting HOLOLAKE_ACCOUNT_SESSION_TTL 86400
append_setting HOLOLAKE_OTP_REQUEST_LIMIT 6
append_setting HOLOLAKE_KNOWLEDGE_REPOSITORY_PATH " $knowledge_repo "
append_setting HOLOLAKE_KNOWLEDGE_MAX_ARCHIVE_BYTES 134217728
append_setting HOLOLAKE_AI_PROVIDERS_FILE " $provider_registry "
chmod 0600 " $authorization_env "
if [ [ ! -f " $provider_registry " ] ] ; then
/usr/bin/python3 - " $provider_registry " <<'PY'
import json
import pathlib
import sys
destination = pathlib.Path( sys.argv[ 1] )
values = { }
roots = (
pathlib.Path( "/etc/guanghu/secrets" ) ,
pathlib.Path( "/etc/guanghu/persona-secrets" ) ,
)
for root in roots:
if not root.exists( ) :
continue
for file in root.rglob( "*.env" ) :
try:
for raw in file.read_text( ) .splitlines( ) :
line = raw.strip( )
if not line or line.startswith( "#" ) or "=" not in line:
continue
key, value = line.split( "=" , 1)
values.setdefault( key.strip( ) , value.strip( ) .strip( "'\"" ) )
except ( OSError, UnicodeError) :
continue
providers = { }
if values.get( "DEEPSEEK_API_KEY" ) :
providers[ "deepseek" ] = {
"name" : "DeepSeek" ,
"base_url" : "https://api.deepseek.com" ,
"api_key" : values[ "DEEPSEEK_API_KEY" ] ,
"models" : [ "deepseek-chat" , "deepseek-reasoner" ] ,
}
elif values.get( "OPENAI_API_KEY" ) :
providers[ "openai" ] = {
"name" : "OpenAI" ,
"base_url" : "https://api.openai.com/v1" ,
"api_key" : values[ "OPENAI_API_KEY" ] ,
"models" : [ "gpt-4.1-mini" ] ,
}
elif values.get( "DASHSCOPE_API_KEY" ) :
providers[ "qwen" ] = {
"name" : "Qwen" ,
"base_url" : "https://dashscope.aliyuncs.com/compatible-mode/v1" ,
"api_key" : values[ "DASHSCOPE_API_KEY" ] ,
"models" : [ "qwen-plus" ] ,
}
if providers:
destination.parent.mkdir( parents = True, exist_ok = True)
temporary = destination.with_suffix( ".tmp" )
temporary.write_text( json.dumps( {
"schema" : "guanghu.hololake-ai-providers/v1" ,
"providers" : providers,
} , ensure_ascii = False, indent = 2) + "\n" )
temporary.chmod( 0o640)
temporary.replace( destination)
PY
fi
if [ [ -f " $provider_registry " ] ] ; then
chown root:guanghu-authz " $provider_registry "
chmod 0640 " $provider_registry "
fi
[ [ -d " $knowledge_repo " ] ] || {
echo "registered HoloLake knowledge repository is missing" >& 2
exit 1
}
2026-08-02 22:30:04 +08:00
command -v setfacl >/dev/null 2>& 1 || {
echo "setfacl is required for private knowledge repository access" >& 2
exit 1
}
setfacl -R -m u:guanghu-authz:rX " $knowledge_repo "
setfacl -R -d -m u:guanghu-authz:rX " $knowledge_repo "
2026-08-02 22:04:09 +08:00
runuser -u guanghu-authz -- git --git-dir= " $knowledge_repo " rev-parse --verify refs/heads/main >/dev/null
2026-07-24 10:39:10 +08:00
systemctl daemon-reload
systemctl enable --now lake-lamp-architecture-provision.service
systemctl restart lake-lamp-authz.service
2026-07-27 15:05:46 +08:00
systemctl enable --now lake-lamp-deployment-event-worker.service
2026-07-24 10:39:10 +08:00
systemctl is-active --quiet lake-lamp-architecture-provision.service
systemctl is-active --quiet lake-lamp-authz.service
2026-07-27 15:05:46 +08:00
systemctl is-active --quiet lake-lamp-deployment-event-worker.service
2026-08-02 22:04:09 +08:00
health = $( /usr/bin/curl -fsS --max-time 10 http://127.0.0.1:3921/health)
/usr/bin/node -e '
const health = JSON.parse( process.argv[ 1] ) ;
if ( !health.ok || health.service != = "lake-lamp-authz" ) process.exit( 1) ;
if ( !health.hololake_mobile || !health.hololake_mobile.email_session || !health.hololake_mobile.knowledge_snapshot) process.exit( 1) ;
' " $health "
if [ [ -f " $provider_registry " ] ] ; then
/usr/bin/node -e '
const health = JSON.parse( process.argv[ 1] ) ;
if ( !health.hololake_mobile || !health.hololake_mobile.ai_gateway) process.exit( 1) ;
' " $health "
fi
2026-08-02 23:47:34 +08:00
ghdr_public = $( /usr/bin/curl -fsS --max-time 10 http://127.0.0.1:3921/api/ghdr/authorizer-public-key)
/usr/bin/node -e '
const response = JSON.parse( process.argv[ 1] ) ;
const binding = response && response.binding;
if ( !response.ok || !binding || binding.algorithm != = "Ed25519" ) process.exit( 1) ;
if ( !/^[ -A-Za-z0-9+/= \r \n ] *PUBLIC KEY[ -A-Za-z0-9+/= \r \n ] *$/.test( binding.public_key_pem) ) process.exit( 1) ;
if ( !/^[ 0-9a-f] { 64} $/.test( binding.public_key_sha256) ) process.exit( 1) ;
if ( /PRIVATE KEY/.test( JSON.stringify( response) ) ) process.exit( 1) ;
' " $ghdr_public "
[ [ -f " $ghdr_authorizer_key " && ! -L " $ghdr_authorizer_key " ] ]
[ [ $( stat -c '%a' " $ghdr_authorizer_key " ) = 600 ] ]
2026-08-02 22:04:09 +08:00
trap - EXIT
2026-08-02 23:47:34 +08:00
printf 'HOLOLAKE_MOBILE_AND_GHDR_CAPABILITIES_INSTALLED backup=%s\n' " $backup_root "