From 53f0ccf49e49db91f6f8eeedb9dfcce5abdea255 Mon Sep 17 00:00:00 2001 From: vegard Date: Fri, 13 Mar 2026 04:53:38 +0100 Subject: [PATCH] Oppdater CLAUDE.md med serverinfo og produksjonsstatus Co-Authored-By: Claude Opus 4.6 --- .claude/settings.local.json | 65 +++++++++++++++++++++++++++++++++++++ CLAUDE.md | 8 +++++ 2 files changed, 73 insertions(+) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..adf4a92 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,65 @@ +{ + "permissions": { + "allow": [ + "Bash(ssh-keygen -t ed25519 -C \"vegard@sidelinja\" -f ~/.ssh/id_ed25519 -N \"\")", + "Bash(ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new root@157.180.81.26 \"hostname && cat /etc/os-release | head -4\")", + "Bash(ssh -o ConnectTimeout=10 root@157.180.81.26 \"hostname && cat /etc/os-release | head -4\")", + "Bash(ssh -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new root@157.180.81.26 \"hostname && cat /etc/os-release | head -4\")", + "Bash(ssh-keygen -f '/home/vegard/.ssh/known_hosts' -R '157.180.81.26' && ssh -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new root@157.180.81.26 \"hostname && cat /etc/os-release | head -4\")", + "Bash(ssh -o ConnectTimeout=15 -vvv root@157.180.81.26 2>&1 | grep -E \"\\(Offering|Server accepts|Trying\\)\")", + "Bash(ssh-keygen -f '/home/vegard/.ssh/known_hosts' -R '157.180.81.26' 2>/dev/null; ssh -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new root@157.180.81.26 \"hostname\")", + "Bash(ssh-keygen -f '/home/vegard/.ssh/known_hosts' -R '157.180.81.26' 2>/dev/null; ssh -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new root@157.180.81.26 \"hostname && cat /etc/os-release | head -4\")", + "Bash(ssh root@157.180.81.26 \"apt update && apt upgrade -y\" 2>&1 | tail -5)", + "Bash(ssh root@157.180.81.26 bash << 'REMOTE'\n# Opprett tjenestebruker\nadduser --disabled-password --gecos \"Sidelinja\" sidelinja\nusermod -aG sudo sidelinja\n\n# Gi sudo uten passord \\(brukeren har ingen passord, kun SSH-nøkkel\\)\necho \"sidelinja ALL=\\(ALL\\) NOPASSWD:ALL\" > /etc/sudoers.d/sidelinja\n\n# Kopier SSH-nøkkel til ny bruker\nmkdir -p /home/sidelinja/.ssh\ncp /root/.ssh/authorized_keys /home/sidelinja/.ssh/\nchown -R sidelinja:sidelinja /home/sidelinja/.ssh\nchmod 700 /home/sidelinja/.ssh\nchmod 600 /home/sidelinja/.ssh/authorized_keys\n\n# Deaktiver passordautentisering og root-login\nsed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config\nsed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config\nsystemctl restart sshd\n\necho \"DONE: Bruker opprettet, SSH sikret\"\nREMOTE)", + "Bash(ssh root@157.180.81.26 \"systemctl restart ssh && echo 'SSH restarted'\")", + "Bash(ssh root@157.180.81.26 bash << 'REMOTE'\nufw allow OpenSSH\nufw allow 80/tcp\nufw allow 443/tcp\necho \"y\" | ufw enable\nufw status\nREMOTE)", + "Bash(ssh root@157.180.81.26 \"ufw delete allow 'Nginx Full' && ufw status\")", + "Bash(ssh -o StrictHostKeyChecking=accept-new sidelinja@157.180.81.26 \"whoami && sudo whoami\")", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\nsudo apt install -y ca-certificates curl gnupg\nsudo install -m 0755 -d /etc/apt/keyrings\ncurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg\necho \"deb [arch=$\\(dpkg --print-architecture\\) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $\\(lsb_release -cs\\) stable\" | sudo tee /etc/apt/sources.list.d/docker.list\nsudo apt update\nsudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin\nsudo usermod -aG docker sidelinja\necho \"Docker installed: $\\(docker --version\\)\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\nsudo apt remove -y docker-compose-v2\nsudo apt install -y docker-compose-plugin\necho \"Docker Compose: $\\(docker compose version\\)\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Mappestruktur\nsudo mkdir -p /srv/sidelinja/{config,data,media,logs}\nsudo mkdir -p /srv/sidelinja/config/{caddy,authentik}\nsudo mkdir -p /srv/sidelinja/data/{postgres,spacetimedb,forgejo,authentik}\nsudo mkdir -p /srv/sidelinja/media/podcast\nsudo mkdir -p /srv/sidelinja/logs/caddy\nsudo chown -R sidelinja:sidelinja /srv/sidelinja\n\n# Verifiser\nfind /srv/sidelinja -type d | sort\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\nPG_PASS=$\\(openssl rand -hex 32\\)\nAUTHENTIK_SECRET=$\\(openssl rand -hex 64\\)\nAUTHENTIK_PG_PASS=$\\(openssl rand -hex 32\\)\nFORGEJO_PG_PASS=$\\(openssl rand -hex 32\\)\nLIVEKIT_KEY=$\\(openssl rand -hex 16\\)\nLIVEKIT_SECRET=$\\(openssl rand -hex 32\\)\n\ncat > /srv/sidelinja/.env << EOF\n# === Domener ===\nDOMAIN_SIDELINJA=sidelinja.org\nDOMAIN_VEGARD=vegard.info\nDOMAIN_AUTH=auth.sidelinja.org\nCOMPOSE_PROJECT_NAME=sidelinja\n\n# === PostgreSQL ===\nPOSTGRES_USER=sidelinja\nPOSTGRES_PASSWORD=${PG_PASS}\nPOSTGRES_DB=sidelinja\n\n# === Authentik ===\nAUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET}\nAUTHENTIK_POSTGRESQL_PASSWORD=${AUTHENTIK_PG_PASS}\nAUTHENTIK_POSTGRESQL_HOST=postgres\nAUTHENTIK_POSTGRESQL_USER=authentik\nAUTHENTIK_POSTGRESQL_NAME=authentik\n\n# === Forgejo ===\nFORGEJO_DB_PASSWD=${FORGEJO_PG_PASS}\n\n# === LiveKit ===\nLIVEKIT_API_KEY=${LIVEKIT_KEY}\nLIVEKIT_API_SECRET=${LIVEKIT_SECRET}\n\n# === OpenRouter ===\nOPENROUTER_API_KEY=PLACEHOLDER\n\n# === Intern ===\n# Ingen porter eksponeres utenom 80/443\nEOF\n\nchmod 600 /srv/sidelinja/.env\necho \"DONE: .env opprettet\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\ncat > /srv/sidelinja/docker-compose.yml << 'EOF'\nnetworks:\n sidelinja-net:\n driver: bridge\n\nservices:\n # === Lag A: Fundament ===\n\n postgres:\n image: postgres:16\n restart: unless-stopped\n environment:\n POSTGRES_USER: ${POSTGRES_USER}\n POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}\n POSTGRES_DB: ${POSTGRES_DB}\n volumes:\n - /srv/sidelinja/data/postgres:/var/lib/postgresql/data\n - /srv/sidelinja/config/postgres/init:/docker-entrypoint-initdb.d\n networks:\n - sidelinja-net\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U ${POSTGRES_USER}\"]\n interval: 10s\n timeout: 5s\n retries: 5\n\n caddy:\n image: caddy:2\n restart: unless-stopped\n ports:\n - \"80:80\"\n - \"443:443\"\n volumes:\n - /srv/sidelinja/config/caddy/Caddyfile:/etc/caddy/Caddyfile\n - /srv/sidelinja/data/caddy:/data\n - /srv/sidelinja/logs/caddy:/var/log/caddy\n - /srv/sidelinja/media:/srv/media:ro\n networks:\n - sidelinja-net\n\n authentik-server:\n image: ghcr.io/goauthentik/server:latest\n restart: unless-stopped\n command: server\n environment:\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_POSTGRESQL__HOST: postgres\n AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRESQL_USER}\n AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRESQL_NAME}\n AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRESQL_PASSWORD}\n AUTHENTIK_REDIS__HOST: redis\n volumes:\n - /srv/sidelinja/data/authentik/media:/media\n - /srv/sidelinja/data/authentik/templates:/templates\n networks:\n - sidelinja-net\n depends_on:\n postgres:\n condition: service_healthy\n redis:\n condition: service_healthy\n\n authentik-worker:\n image: ghcr.io/goauthentik/server:latest\n restart: unless-stopped\n command: worker\n environment:\n AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}\n AUTHENTIK_POSTGRESQL__HOST: postgres\n AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRESQL_USER}\n AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRESQL_NAME}\n AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRESQL_PASSWORD}\n AUTHENTIK_REDIS__HOST: redis\n volumes:\n - /srv/sidelinja/data/authentik/media:/media\n - /srv/sidelinja/data/authentik/templates:/templates\n networks:\n - sidelinja-net\n depends_on:\n postgres:\n condition: service_healthy\n redis:\n condition: service_healthy\n\n redis:\n image: redis:7-alpine\n restart: unless-stopped\n command: --save 60 1 --loglevel warning\n volumes:\n - /srv/sidelinja/data/redis:/data\n networks:\n - sidelinja-net\n healthcheck:\n test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG\"]\n interval: 10s\n timeout: 5s\n retries: 5\n\n forgejo:\n image: codeberg.org/forgejo/forgejo:10\n restart: unless-stopped\n environment:\n FORGEJO__database__DB_TYPE: postgres\n FORGEJO__database__HOST: postgres:5432\n FORGEJO__database__NAME: forgejo\n FORGEJO__database__USER: forgejo\n FORGEJO__database__PASSWD: ${FORGEJO_DB_PASSWD}\n FORGEJO__server__ROOT_URL: https://git.sidelinja.org\n FORGEJO__server__SSH_DOMAIN: git.sidelinja.org\n FORGEJO__server__SSH_PORT: 222\n volumes:\n - /srv/sidelinja/data/forgejo:/data\n ports:\n - \"222:22\"\n networks:\n - sidelinja-net\n depends_on:\n postgres:\n condition: service_healthy\nEOF\n\necho \"DONE: docker-compose.yml opprettet\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\nsudo ufw allow 222/tcp\nmkdir -p /srv/sidelinja/data/{caddy,redis}\necho \"DONE\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose up -d\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"sudo systemctl start docker && sudo systemctl enable docker && echo 'Docker started'\")", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose ps -a\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose logs caddy\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose up -d caddy && docker compose logs caddy\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Sjekk om systemd-resolved blokkerer\ncat /etc/resolv.conf | head -5\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\nsudo mkdir -p /etc/docker\necho '{\"dns\": [\"1.1.1.1\", \"8.8.8.8\"]}' | sudo tee /etc/docker/daemon.json\nsudo systemctl restart docker\necho \"DONE: Docker DNS konfigurert\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose up -d && sleep 10 && docker compose ps\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"sudo lsof -i :80 -P -n | head -10\")", + "Bash(ssh sidelinja@157.180.81.26 \"sudo systemctl stop nginx && sudo systemctl disable nginx && sudo apt remove -y nginx && echo 'Nginx fjernet'\")", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose up -d caddy && sleep 5 && docker compose ps\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose logs authentik-worker --tail 20\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Authentik kjører som UID 1000 inne i containeren\nsudo chown -R 1000:1000 /srv/sidelinja/data/authentik\ncd /srv/sidelinja && docker compose restart authentik-worker\nsleep 10\ndocker compose ps authentik-worker\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && sleep 15 && docker compose ps\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Test om containere kan nå internett\ndocker run --rm alpine ping -c 2 8.8.8.8 2>&1\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\ndocker run --rm --network sidelinja_sidelinja-net alpine ping -c 2 8.8.8.8 2>&1\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose restart caddy && sleep 15 && docker compose logs caddy 2>&1 | grep -iE '\\(obtained|certificate|error\\)' | tail -10\")", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Test DNS direkte fra Caddy-containeren\ndocker compose -f /srv/sidelinja/docker-compose.yml exec caddy sh -c \"nslookup acme-v02.api.letsencrypt.org 8.8.8.8 2>&1 || wget -q -O- http://ifconfig.me 2>&1 || echo 'No connectivity'\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Sjekk nettverksdetaljer for Caddy\ndocker inspect sidelinja-caddy-1 --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}: Gateway={{$v.Gateway}} IP={{$v.IPAddress}}{{\"\\\\n\"}}{{end}}'\n# Sjekk iptables\nsudo iptables -t nat -L POSTROUTING -n | head -10\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"docker inspect sidelinja-caddy-1 -f '{{json .NetworkSettings.Networks}}' | python3 -m json.tool\")", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose down && docker compose up -d && sleep 15 && docker compose ps\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose logs caddy 2>&1 | grep -iE '\\(obtained|successfully|error\\)' | tail -10\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\" https://sidelinja.org && echo \" sidelinja.org\" && curl -s -o /dev/null -w \"%{http_code}\" https://auth.sidelinja.org && echo \" auth.sidelinja.org\" && curl -s -o /dev/null -w \"%{http_code}\" https://git.sidelinja.org && echo \" git.sidelinja.org\" && curl -s -o /dev/null -w \"%{http_code}\" https://vegard.info && echo \" vegard.info\")", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose exec authentik-server ak list_fake_admins 2>&1 || docker compose exec authentik-server ak create_admin 2>&1 || echo 'Trying user list...' && docker compose exec authentik-server ak user_list 2>&1 | head -20\")", + "Bash(ssh sidelinja@157.180.81.26 \"cd /srv/sidelinja && docker compose exec authentik-server ak create_recovery_key 10 akadmin 2>&1\")", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Legg til Authentik OAuth2-konfigurasjon i Forgejo\ndocker compose -f /srv/sidelinja/docker-compose.yml exec -e FORGEJO__service__DISABLE_REGISTRATION=false forgejo forgejo admin auth add-oauth \\\\\n --name \"authentik\" \\\\\n --provider \"openidConnect\" \\\\\n --key \"hGAG9UJWT5tBhL0FdtRMkSbvyHQI7GT5nwoEVQ9e\" \\\\\n --secret \"8FDxqb3yeDVI5fbfQjl3RSrEaPdMJsUBYAMocxcTmVt5IQBVcnrfJLQZ1QDTdhyDMopnloeFFwG0icnYqmgaodqcO1EU4h7vYlDrHeNouanrgfATqgWm6He018nXWL5k\" \\\\\n --auto-discover-url \"https://auth.sidelinja.org/application/o/forgejo/.well-known/openid-configuration\" \\\\\n 2>&1\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\ndocker compose -f /srv/sidelinja/docker-compose.yml exec --user git forgejo forgejo admin auth add-oauth \\\\\n --name \"authentik\" \\\\\n --provider \"openidConnect\" \\\\\n --key \"hGAG9UJWT5tBhL0FdtRMkSbvyHQI7GT5nwoEVQ9e\" \\\\\n --secret \"8FDxqb3yeDVI5fbfQjl3RSrEaPdMJsUBYAMocxcTmVt5IQBVcnrfJLQZ1QDTdhyDMopnloeFFwG0icnYqmgaodqcO1EU4h7vYlDrHeNouanrgfATqgWm6He018nXWL5k\" \\\\\n --auto-discover-url \"https://auth.sidelinja.org/application/o/forgejo/.well-known/openid-configuration\" \\\\\n 2>&1\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"docker compose -f /srv/sidelinja/docker-compose.yml exec --user git forgejo ls /data/gitea/conf/\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\ndocker compose -f /srv/sidelinja/docker-compose.yml exec --user git forgejo forgejo admin auth add-oauth \\\\\n --config /data/gitea/conf/app.ini \\\\\n --name \"authentik\" \\\\\n --provider \"openidConnect\" \\\\\n --key \"hGAG9UJWT5tBhL0FdtRMkSbvyHQI7GT5nwoEVQ9e\" \\\\\n --secret \"8FDxqb3yeDVI5fbfQjl3RSrEaPdMJsUBYAMocxcTmVt5IQBVcnrfJLQZ1QDTdhyDMopnloeFFwG0icnYqmgaodqcO1EU4h7vYlDrHeNouanrgfATqgWm6He018nXWL5k\" \\\\\n --auto-discover-url \"https://auth.sidelinja.org/application/o/forgejo/.well-known/openid-configuration\" \\\\\n 2>&1\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"docker compose -f /srv/sidelinja/docker-compose.yml exec --user git forgejo cat /data/gitea/conf/app.ini\" 2>&1 | head -30)", + "Bash(ssh sidelinja@157.180.81.26 \"docker compose -f /srv/sidelinja/docker-compose.yml exec --user git forgejo grep -A5 'security' /data/gitea/conf/app.ini\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 \"docker compose -f /srv/sidelinja/docker-compose.yml exec --user git forgejo forgejo admin auth list --config /data/gitea/conf/app.ini\" 2>&1)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\n# Opprett SSH-nøkkel for Forgejo Git-operasjoner på serveren\nif [ ! -f /home/sidelinja/.ssh/id_ed25519 ]; then\n ssh-keygen -t ed25519 -C \"sidelinja@server\" -f /home/sidelinja/.ssh/id_ed25519 -N \"\"\nfi\n\n# Legg til git.sidelinja.org som kjent host for SSH på port 222\nssh-keyscan -p 222 git.sidelinja.org >> /home/sidelinja/.ssh/known_hosts 2>/dev/null\necho \"DONE\"\nREMOTE)", + "Bash(ssh sidelinja@157.180.81.26 \"cat /home/sidelinja/.ssh/id_ed25519.pub\")", + "Bash(git init:*)", + "Bash(git config:*)", + "Bash(git remote:*)", + "Bash(ssh sidelinja@157.180.81.26 bash << 'REMOTE'\ncd /tmp\ngit clone ssh://git@git.sidelinja.org:222/sidelinja/sidelinja.git test-clone 2>&1\nls test-clone/\nrm -rf test-clone\necho \"DONE\"\nREMOTE)", + "Bash(git add:*)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 91da9e2..474fa95 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,14 @@ Self-hosted på Hetzner VPS med full datakontroll. - **AI:** faster-whisper (transkripsjon), OpenRouter (Claude-modeller) - **Infra:** Docker Compose, Caddy, Authentik (SSO), Forgejo (Git) +## Produksjonsserver +- **IP:** 157.180.81.26 +- **SSH:** `ssh sidelinja@157.180.81.26` (nøkkelbasert, sudo uten passord) +- **Filer:** `/srv/sidelinja/` (docker-compose.yml, .env, config/, data/, media/, logs/) +- **Git remote:** `forgejo` → `ssh://git@git.sidelinja.org:222/sidelinja/sidelinja.git` +- **Domener:** sidelinja.org, auth.sidelinja.org (Authentik), git.sidelinja.org (Forgejo), vegard.info +- **Status:** Lag A komplett (PostgreSQL, Caddy, Authentik, Forgejo, Redis). Lag B-C gjenstår. + ## Viktige regler - Aldri eksponere databaseporter mot internett (kun port 80/443 via Caddy) - Bruk `tea` CLI, ikke `gh` (vi bruker Forgejo, ikke GitHub)