#!/usr/bin/env bash
# Dify 1.19.0: one-time, local learning installation for the current Ubuntu user.
# No Windows settings changes, administrator elevation, accounts, or API keys.
set -Eeuo pipefail
trap 'printf "\nSTOP: step failed at line %s. Keep this window open.\n" "$LINENO"' ERR
umask 077
printf '\n[1/5] Checking Ubuntu and Docker...\n'
test "$(id -un)" != root || { echo 'Run as your normal Ubuntu user.'; exit 1; }
command -v python3 >/dev/null
command -v docker >/dev/null || { echo 'Docker is not available. Check Docker Desktop WSL integration.'; exit 1; }
docker info --format 'Docker server: {{.ServerVersion}}'
docker compose version
python3 - <<'PY'
import re, subprocess
v=subprocess.check_output(['docker','compose','version','--short'],text=True).strip()
m=re.search(r'(\d+)\.(\d+)\.(\d+)',v)
if not m or tuple(map(int,m.groups())) < (2,35,0):
    raise SystemExit('Docker Compose 2.35.0 or newer is required.')
PY
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ARCHIVE="$SCRIPT_DIR/dify-1.19.0.zip"
TARGET="$HOME/dify-training/dify-1.19.0"
export ARCHIVE TARGET
printf '\n[2/5] Verifying the official release and copying it into Ubuntu...\n'
python3 - <<'PY'
import hashlib, os, pathlib, zipfile, stat
archive=pathlib.Path(os.environ['ARCHIVE'])
target=pathlib.Path(os.environ['TARGET'])
if target.exists():
    raise SystemExit('Installation folder already exists; stopped without overwriting it: '+str(target))
expected='a13a974657ced6c146b12459eb0ba9c0deea9b407d5c099fe1108355d53f5e40'
if hashlib.sha256(archive.read_bytes()).hexdigest()!=expected:
    raise SystemExit('Archive verification failed.')
target.parent.mkdir(parents=True,exist_ok=True)
with zipfile.ZipFile(archive) as z:
    for info in z.infolist():
        name=info.filename
        resolved=(target.parent/name).resolve()
        if not name.startswith('dify-1.19.0/') or not resolved.is_relative_to(target.resolve()):
            raise SystemExit('Unexpected archive path. Nothing extracted.')
        if stat.S_ISLNK(info.external_attr >> 16):
            link=z.read(info).decode('utf-8')
            if not (resolved.parent/link).resolve().is_relative_to(target.resolve()):
                raise SystemExit('Unexpected symbolic-link target. Nothing extracted.')
    for info in z.infolist():
        destination=target.parent/info.filename
        if stat.S_ISLNK(info.external_attr >> 16):
            destination.parent.mkdir(parents=True,exist_ok=True)
            destination.symlink_to(z.read(info).decode('utf-8'))
        else:
            z.extract(info,target.parent)
            mode=(info.external_attr >> 16) & 0o777
            if mode:
                destination.chmod(mode)
print('Saved to:',target)
PY
cd -- "$TARGET/docker"
printf '\n[3/5] Creating the local learning configuration...\n'
python3 - <<'PY'
from pathlib import Path
import secrets
p=Path('.env')
if p.exists(): raise SystemExit('Existing .env found; stopped without overwriting it.')
text=Path('.env.example').read_text()
key='DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN='
if text.count(key)!=1: raise SystemExit('Unexpected configuration template.')
text=text.replace(key,key+secrets.token_hex(32))
text+='\n# Local learning HTTP/HTTPS ports\nEXPOSE_NGINX_PORT=8080\nEXPOSE_NGINX_SSL_PORT=8443\n'
p.write_text(text)
p.chmod(0o600)
PY
# Restrict every published service port in this selected stack to this PC.
# Compose's !override tag replaces the original port list completely.
python3 - <<'PY'
from pathlib import Path
import json, subprocess
config=json.loads(subprocess.check_output(['docker','compose','-f','docker-compose.yaml','config','--format','json'],text=True))
lines=['services:']
for name, service in config['services'].items():
    ports=service.get('ports',[])
    if not ports: continue
    lines += ['  '+name+':','    ports: !override']
    for port in ports:
        if not isinstance(port,dict): raise SystemExit('Unexpected port format.')
        port=dict(port)
        port['host_ip']='127.0.0.1'
        lines.append('      - '+json.dumps(port))
Path('docker-compose.override.yaml').write_text('\n'.join(lines)+'\n')
final=json.loads(subprocess.check_output(['docker','compose','config','--format','json'],text=True))
for service in final['services'].values():
    for port in service.get('ports',[]):
        if port.get('host_ip')!='127.0.0.1':
            raise SystemExit('Port binding verification failed. Nothing was started.')
print('All published ports verified as local to this PC.')
PY
docker compose config --quiet
printf '\n[4/5] Downloading and starting Dify components. This can take several minutes...\n'
python3 -u - <<'PY'
import json,os,pathlib,subprocess,tempfile
config=json.loads(subprocess.check_output(['docker','compose','config','--format','json'],text=True))
images=set()
profiles={'weaviate','postgresql','collaboration'}
for service in config['services'].values():
    selected=service.get('profiles',[])
    if selected and not profiles.intersection(selected):
        continue
    for port in service.get('ports',[]):
        if port.get('host_ip')!='127.0.0.1':
            raise SystemExit('STOP: unexpected non-local port binding. Nothing was started.')
    if service.get('image'): images.add(service['image'])
if not images: raise SystemExit('STOP: no images found in the existing configuration.')
print('Configuration verified. Existing Docker credentials will not be edited.',flush=True)
with tempfile.TemporaryDirectory(prefix='dify-public-pull-') as tmp:
    pathlib.Path(tmp,'config.json').write_text(json.dumps({'auths':{}}))
    env=os.environ.copy()
    env.pop('DOCKER_CONTEXT',None)
    for i,image in enumerate(sorted(images),1):
        print(f'\nImage {i}/{len(images)}: {image}',flush=True)
        result=subprocess.run(['docker','--config',tmp,'--host','unix:///var/run/docker.sock','image','pull',image],env=env)
        if result.returncode:
            raise SystemExit('STOP: image download failed. Keep the final error visible. Re-running this script can reuse completed downloads.')
print('\nAll public images downloaded.',flush=True)
PY

docker compose up -d --pull never --no-build
printf '\n[5/5] Checking containers...\n'
docker compose ps --all
printf '\nStartup command completed. The web application may need more time to become ready.\n'
printf 'Open: http://localhost:8080/install\n'
printf 'Folder: %s/docker\n' "$TARGET"
printf 'Keep this terminal open and report the last messages. Do not share passwords or .env.\n'
