diff --git a/.dockerignore b/.dockerignore
index 41fc335..fc89dc1 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,5 @@
.env
-upload/
-cache/
*.log
-vendor/
-.env.local
-.env.*.local
+.DS_Store
+.git/
+.gitignore
diff --git a/.env b/.env
index 18059af..388afd8 100644
--- a/.env
+++ b/.env
@@ -1,16 +1,16 @@
-# SuiteCRM Docker Compose Configuration
-# Copy to .env and customize
+# SugarCRM 6.5.26 CE Docker Umgebung
+# Passwörter ANPASSEN vor erstem Start!
-# SuiteCRM
-SUITECRM_PORT=8080
-SUITECRM_SITE_URL=http://localhost:8080
+# Ports
+SUGARCRM_PORT=2080
+MYSQL_PORT=3306
-# MariaDB
-MYSQL_PORT=3307
+# MySQL / MariaDB
MYSQL_ROOT_PASSWORD=change_this_root_password
-MYSQL_DATABASE=suitecrm
-MYSQL_USER=suitecrm
+MYSQL_DATABASE=sugarcrm
+MYSQL_USER=sugarcrm
MYSQL_PASSWORD=change_this_db_password
-# Redis (only with --profile full or --profile redis)
-REDIS_PORT=6379
+# SugarCRM Admin (wird bei Erst-Installation gesetzt)
+SUGARCRM_ADMIN_USER=admin
+SUGARCRM_ADMIN_PASSWORD=admin123
diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml
index 869775d..8b7ff43 100644
--- a/.gitea/workflows/docker-build.yml
+++ b/.gitea/workflows/docker-build.yml
@@ -1,24 +1,19 @@
-name: Docker Build & Push
+name: Docker Build & Push SugarCRM 6.5 CE
on:
push:
branches: [main]
pull_request:
branches: [main]
+ workflow_dispatch:
jobs:
build-and-push:
runs-on: ubuntu-latest
- container:
- image: docker:27-dind
- options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
@@ -26,13 +21,12 @@ jobs:
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- - name: Build and Push
- uses: docker/build-push-action@v6
- with:
- context: .
- push: true
- tags: |
- git.kgessner.de/luiicode/sugar-crm:latest
- git.kgessner.de/luiicode/sugar-crm:7.15.1
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ - name: Build Docker Image
+ run: |
+ docker build -t git.kgessner.de/luiicode/sugar-crm:6.5.26 .
+ docker tag git.kgessner.de/luiicode/sugar-crm:6.5.26 git.kgessner.de/luiicode/sugar-crm:latest
+
+ - name: Push to Registry
+ run: |
+ docker push git.kgessner.de/luiicode/sugar-crm:6.5.26
+ docker push git.kgessner.de/luiicode/sugar-crm:latest
diff --git a/Dockerfile b/Dockerfile
index efab976..a16aaea 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,96 +1,52 @@
-# SuiteCRM 7.15.1 - PHP 8.1 + Apache
-FROM php:8.1-apache
+FROM php:5.6-apache-jessie
-LABEL maintainer="Kevin Gessner"
-LABEL description="SuiteCRM 7.15.1 containerized with PHP 8.1 and Apache"
+ENV MAJOR_VERSION=6.5
+ENV MINOR_VERSION=26
+ENV WWW_FOLDER=/var/www/html
+ENV DEBIAN_FRONTEND=noninteractive
+ENV SUGARCRM_REPO=https://github.com/bklein01/sugarcrm
+ENV SUGARCRM_COMMIT=71125a3
-# Install system dependencies and PHP extensions
-RUN set -eux; \
- apt-get update && apt-get install -y --no-install-recommends \
- # Core
- libzip-dev \
+# Jessie is EOL - switch to archive.debian.org
+RUN echo "deb http://archive.debian.org/debian/ jessie main contrib non-free" > /etc/apt/sources.list && \
+ echo "deb http://archive.debian.org/debian-security/ jessie/updates main contrib non-free" >> /etc/apt/sources.list && \
+ echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid-until && \
+ apt-get update -o Acquire::Check-Valid-Until=false && apt-get upgrade -y --force-yes && \
+ apt-get install -y --force-yes \
+ libcurl4-gnutls-dev \
libpng-dev \
- libjpeg-dev \
- libfreetype6-dev \
- libonig-dev \
- libxml2-dev \
- libldap2-dev \
+ unzip \
+ cron \
+ re2c \
+ python \
+ curl \
libc-client-dev \
libkrb5-dev \
- libcurl4-openssl-dev \
- libicu-dev \
- # Utils
- unzip \
- wget \
- curl \
- cron \
- msmtp \
- # Cleanup
- && docker-php-ext-configure gd --with-freetype --with-jpeg \
- && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
- && docker-php-ext-install -j$(nproc) \
- pdo \
- pdo_mysql \
- mysqli \
- gd \
- mbstring \
- zip \
- xml \
- curl \
- ldap \
- imap \
- intl \
- calendar \
- opcache \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
+ git \
+ && rm -r /var/lib/apt/lists/*
-# Configure PHP for SuiteCRM
-RUN { \
- echo 'memory_limit = 512M'; \
- echo 'upload_max_filesize = 64M'; \
- echo 'post_max_size = 64M'; \
- echo 'max_execution_time = 600'; \
- echo 'max_input_time = 600'; \
- echo 'display_errors = Off'; \
- echo 'log_errors = On'; \
- echo 'date.timezone = Europe/Berlin'; \
- } > /usr/local/etc/php/conf.d/suitecrm.ini
+# Clone SugarCRM 6.5.26 CE from GitHub mirror
+RUN git clone --depth 1 ${SUGARCRM_REPO} ${WWW_FOLDER} \
+ && cd ${WWW_FOLDER} && git checkout ${SUGARCRM_COMMIT} 2>/dev/null || true \
+ && rm -rf ${WWW_FOLDER}/.git \
+ && chown -R www-data:www-data ${WWW_FOLDER}
-# Configure OPcache
-RUN { \
- echo 'opcache.memory_consumption=256'; \
- echo 'opcache.interned_strings_buffer=16'; \
- echo 'opcache.max_accelerated_files=20000'; \
- echo 'opcache.revalidate_freq=2'; \
- echo 'opcache.fast_shutdown=1'; \
- } > /usr/local/etc/php/conf.d/opcache-recommended.ini
+# PHP upload limits
+COPY docker-php-ext-filesize.ini /usr/local/etc/php/conf.d/docker-php-ext-filesize.ini
-# SuiteCRM version
-ENV SUITECRM_VERSION=7.15.1
-ENV SUITECRM_SHA256=468b811addd21dfb29d411ee6e815dbdf7099f912347e88cd3e8d010d829db7a
+# PHP extensions
+RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \
+ docker-php-ext-install imap mysql zip gd
-# Download and extract SuiteCRM
-RUN set -eux; \
- wget -q "https://github.com/salesagility/SuiteCRM/releases/download/v${SUITECRM_VERSION}/SuiteCRM-${SUITECRM_VERSION}.zip" \
- -O /tmp/suitecrm.zip; \
- echo "${SUITECRM_SHA256} /tmp/suitecrm.zip" | sha256sum -c -; \
- unzip -q /tmp/suitecrm.zip -d /var/www/html/; \
- rm /tmp/suitecrm.zip; \
- chown -R www-data:www-data /var/www/html
+# Copy entrypoint and templates
+COPY config_override.php.pyt /usr/local/src/config_override.php.pyt
+COPY envtemplate.py /usr/local/bin/envtemplate.py
+COPY init.sh /usr/local/bin/init.sh
+RUN chmod u+x /usr/local/bin/init.sh
-# Apache configuration
-RUN a2enmod rewrite expires headers
-
-COPY apache-suitecrm.conf /etc/apache2/sites-available/000-default.conf
-COPY docker-entrypoint.sh /usr/local/bin/
-RUN chmod +x /usr/local/bin/docker-entrypoint.sh
-
-WORKDIR /var/www/html
-
-VOLUME ["/var/www/html/upload", "/var/www/html/custom", "/var/www/html/config_override.php"]
+# Cron
+COPY crons.conf /root/crons.conf
+RUN crontab /root/crons.conf
EXPOSE 80
-
-ENTRYPOINT ["docker-entrypoint.sh"]
-CMD ["apache2-foreground"]
+ENTRYPOINT ["/usr/local/bin/init.sh"]
diff --git a/README.md b/README.md
index 54acd2f..fde8c18 100644
--- a/README.md
+++ b/README.md
@@ -1,154 +1,176 @@
-# SuiteCRM Docker — Containerized CRM Environment
+# SugarCRM 6.5.26 CE — Containerized Docker Environment
-**SuiteCRM 7.15.1** — vollständig containerisiert mit Docker Compose.
-Ein Befehl, alles läuft: SuiteCRM + MariaDB + (optional) Redis.
+**100% containerisierte SugarCRM Community Edition 6.5.26**
+Ein `docker compose up -d` — alles läuft: Apache/PHP 5.6 + MySQL 5.7 + REST API v4.1.
-## Systemanforderungen
-
-- Docker 20.10+ und Docker Compose v2
-- 2 GB RAM empfohlen (MariaDB Puffer)
-
-## Schnellstart
+## 🚀 Quickstart
```bash
-# 1. Repository klonen
+# 1. Clone
git clone https://git.kgessner.de/LuiiCode/sugar-crm.git
cd sugar-crm
-# 2. Umgebungsvariablen anpassen
-cp .env.example .env
-nano .env # Passwörter ändern!
+# 2. Konfigurieren (Passwörter ändern!)
+nano .env
# 3. Starten
docker compose up -d
-# 4. SuiteCRM Installation im Browser abschließen:
-# http://localhost:8080
+# 4. Web-UI öffnen
+# → http://localhost:2080
+# Login: admin / admin123
+
+# 5. API testen
+python3 test_api.py
```
-## Architektur
+## 🏗️ Architektur
```
-┌──────────────────────────────────────┐
-│ Docker Compose │
-│ │
-│ ┌──────────┐ ┌──────────┐ │
-│ │ SuiteCRM │ │ MariaDB │ Redis? │
-│ │ :8080 │ │ :3307 │ :6379 │
-│ │ PHP 8.1 │ │ 10.11 │ (opt.) │
-│ │ Apache │ │ │ │
-│ └──────────┘ └──────────┘ │
-│ │ │ │
-│ Volumes: Volumes: │
-│ - upload - /var/lib/mysql │
-│ - custom │
-│ - config │
-└──────────────────────────────────────┘
+┌─────────────────────────────────────────┐
+│ Docker Compose │
+│ │
+│ ┌───────────────┐ ┌───────────────┐ │
+│ │ SugarCRM 6.5 │ │ MySQL 5.7 │ │
+│ │ Apache/PHP5.6 │ │ :3306 │ │
+│ │ :2080 │◄─│ │ │
+│ │ │ │ │ │
+│ └───────────────┘ └───────────────┘ │
+│ │
+│ Data (volumes): │
+│ sugarcrm_custom → custom modules │
+│ sugarcrm_upload → uploads │
+│ mysql_data → DB Daten │
+└─────────────────────────────────────────┘
```
-## Services
+## 📦 Services
-| Service | Image | Port | Profil |
-|-----------|------------------|-------|---------------|
-| suitecrm | Custom (PHP 8.1) | 8080 | standard |
-| mariadb | mariadb:10.11 | 3307 | standard |
-| redis | redis:7-alpine | 6379 | `redis`/`full`|
+| Service | Image | Port | Beschreibung |
+|---------|-------|------|-------------|
+| `sugarcrm` | Custom (PHP 5.6) | `2080` → `80` | SugarCRM 6.5.26 CE Web + API |
+| `db` | `mysql:5.7` | `3306` | MySQL Datenbank |
-## Konfiguration
+## ⚙️ Konfiguration
### Umgebungsvariablen (`.env`)
-| Variable | Default | Beschreibung |
-|-----------------------|-----------------------------|-------------------------|
-| `SUITECRM_PORT` | 8080 | Webinterface-Port |
-| `SUITECRM_SITE_URL` | http://localhost:8080 | Öffentliche URL |
-| `MYSQL_PORT` | 3307 | DB-Port (Host) |
-| `MYSQL_ROOT_PASSWORD` | change_this… | Root-Passwort |
-| `MYSQL_DATABASE` | suitecrm | Datenbank-Name |
-| `MYSQL_USER` | suitecrm | Datenbank-Nutzer |
-| `MYSQL_PASSWORD` | change_this… | Nutzer-Passwort |
+| Variable | Default | Beschreibung |
+|----------|---------|-------------|
+| `SUGARCRM_PORT` | `2080` | Web-UI Port |
+| `MYSQL_PORT` | `3306` | MySQL Port |
+| `MYSQL_ROOT_PASSWORD` | `change_this…` | **Sofort ändern!** |
+| `MYSQL_DATABASE` | `sugarcrm` | Datenbank-Name |
+| `MYSQL_USER` | `sugarcrm` | DB-Nutzer |
+| `MYSQL_PASSWORD` | `change_this…` | **Sofort ändern!** |
+| `SUGARCRM_ADMIN_USER` | `admin` | Admin-Login |
+| `SUGARCRM_ADMIN_PASSWORD` | `admin123` | Admin-Passwort |
-> ⚠️ **Sicherheit**: Immer `.env` Passwörter ändern vor erstem Start!
+> ⚠️ **Sicherheit**: `.env` vor erstem Start anpassen! Bei Änderung nach erstem Start: `docker compose down -v && docker compose up -d`
-## Kommandos
+## 🔌 REST API v4.1
+
+**Endpoint:** `POST http://localhost:2080/service/v4_1/rest.php`
+**Content-Type:** `application/x-www-form-urlencoded`
+
+### Aufbau
+
+```
+method=login
+input_type=JSON
+response_type=JSON
+rest_data={"user_auth":{"user_name":"admin","password":"0192023a7bbd73250516f069df18b500"}}
+```
+
+### API Client (Python)
+
+```python
+import hashlib, json, urllib.request
+
+pwd_hash = hashlib.md5(b"admin123").hexdigest()
+rest_data = json.dumps({
+ "user_auth": {"user_name": "admin", "password": pwd_hash},
+ "application_name": "My App"
+})
+
+body = urllib.parse.urlencode({
+ "method": "login",
+ "input_type": "JSON",
+ "response_type": "JSON",
+ "rest_data": rest_data
+}).encode()
+
+req = urllib.request.Request(
+ "http://localhost:2080/service/v4_1/rest.php",
+ data=body,
+ headers={"Content-Type": "application/x-www-form-urlencoded"}
+)
+resp = json.loads(urllib.request.urlopen(req).read())
+session_id = resp["id"]
+print(f"Session: {session_id}")
+```
+
+### Verfügbare Methoden
+
+| Methode | Beschreibung |
+|---------|-------------|
+| `login` | Session starten |
+| `logout` | Session beenden |
+| `get_available_modules` | Alle Module (Accounts, Contacts, Leads...) |
+| `get_entry_list` | Einträge auslesen (mit Filter/Sortierung) |
+| `get_entry` | Einzelnen Eintrag per ID |
+| `set_entry` | Eintrag erstellen/aktualisieren |
+| `set_entries` | Mehrere Einträge erstellen |
+| `get_entries_count` | Anzahl Einträge zählen |
+| `search_by_module` | Globale Suche |
+| `get_module_fields` | Felddefinitionen eines Moduls |
+| `set_relationship` | Beziehungen verknüpfen |
+| `get_relationships` | Beziehungen auslesen |
+
+## 🛠️ Kommandos
```bash
-# Grundbefehle
-docker compose up -d # Alle Services starten
-docker compose up -d redis # + Redis-Cache starten
-docker compose down # Stoppen
+# Start/Stop
+docker compose up -d # Starten
+docker compose down # Stoppen (Daten bleiben)
docker compose down -v # Stoppen + ALLE DATEN LÖSCHEN
# Logs
-docker compose logs -f suitecrm # SuiteCRM-Logs verfolgen
-docker compose logs mariadb # DB-Logs
+docker compose logs -f sugarcrm # SugarCRM-Logs
+docker compose logs db # MySQL-Logs
+
+# Shell
+docker compose exec sugarcrm bash # Container-Shell
# Backup
-docker compose exec mariadb mysqldump -u suitecrm -p suitecrm > backup.sql
-tar -czf upload-backup.tar.gz -C /var/lib/docker/volumes/sugarcrmreponame_suitecrm_data/_data .
+docker compose exec db mysqldump -u sugarcrm -p sugarcrm > backup.sql
```
-## SuiteCRM Installation (Erst-Start)
+## 🔧 CI/CD
-Nach `docker compose up -d` im Browser `http://localhost:8080` öffnen:
+Bei jedem Push auf `main` baut Gitea Actions automatisch:
+1. Docker-Image aus `Dockerfile`
+2. Push in die Gitea Container Registry: `git.kgessner.de/luiicode/sugar-crm`
-1. **License Agreement** → Akzeptieren
-2. **System Check** → Alle Checks sollten grün sein
-3. **Database Configuration**:
- - Host: `mariadb`
- - Database: `suitecrm`
- - User: `suitecrm`
- - Password: (aus `.env`)
-4. **Site Configuration** → Admin-Nutzer anlegen
-5. **Fertig!** SuiteCRM ist einsatzbereit.
+Secrets erforderlich: `REGISTRY_USER`, `REGISTRY_TOKEN` (bereits gesetzt).
-## Redis aktivieren
+## ⚠️ Technische Pitfalls
-```bash
-# Mit Redis-Profil starten
-docker compose --profile redis up -d
+| Problem | Fix |
+|---------|-----|
+| SourceForge ZIPs korrupt | GitHub Mirror `bklein01/sugarcrm` verwendet |
+| Debian Jessie EOL | APT sources auf `archive.debian.org` umgebogen |
+| Admin Wizard blockiert API | `installer_locked=true` + adminwizard DB-Eintrag (init.sh) |
+| config.php wird überschrieben | init.sh macht idempotenten Restart |
+| Kein mysqladmin im Container | PHP-Socket-Check für DB-Wait-Loop |
-# Oder Full-Stack (alles inkl. Redis)
-docker compose --profile full up -d
-```
+## 📋 Systemanforderungen
-Redis-Konfiguration in SuiteCRM Admin → System → Redis:
-- Host: `redis`
-- Port: `6379`
-
-## Elasticsearch (optional)
-
-Für Volltextsuche kann Elasticsearch ergänzt werden. Dazu in `docker-compose.yml` einfügen:
-
-```yaml
- elasticsearch:
- image: docker.elastic.co/elasticsearch/elasticsearch:7.17.24
- container_name: suitecrm-es
- environment:
- - discovery.type=single-node
- - xpack.security.enabled=false
- volumes:
- - es_data:/usr/share/elasticsearch/data
- networks:
- - suitecrm-net
-```
-
-## Docker Image bauen & pushen
-
-```bash
-# Lokal bauen
-docker build -t suitecrm:7.15.1 .
-
-# In Gitea Registry pushen
-docker tag suitecrm:7.15.1 git.kgessner.de/luiicode/sugar-crm:7.15.1
-docker login git.kgessner.de
-docker push git.kgessner.de/luiicode/sugar-crm:7.15.1
-```
-
-## CI/CD
-
-Bei jedem Push auf `main` baut Gitea Actions das Image automatisch und pusht es in die Gitea Container Registry. Workflow: `.gitea/workflows/docker-build.yml`
+- Docker 20.10+
+- Docker Compose v2
+- ~500 MB RAM (Apache + MySQL)
+- Ports `2080` und `3306` verfügbar
---
-**Version**: SuiteCRM 7.15.1 | **PHP**: 8.1 | **MariaDB**: 10.11
+**SugarCRM 6.5.26 CE** | PHP 5.6 | MySQL 5.7 | REST v4.1 | AGPLv3
diff --git a/apache-suitecrm.conf b/apache-suitecrm.conf
deleted file mode 100644
index 6caa327..0000000
--- a/apache-suitecrm.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-
- ServerAdmin webmaster@localhost
- DocumentRoot /var/www/html
-
-
- Options Indexes FollowSymLinks
- AllowOverride All
- Require all granted
-
-
- # Protect sensitive files
-
- Require all denied
-
-
- ErrorLog ${APACHE_LOG_DIR}/error.log
- CustomLog ${APACHE_LOG_DIR}/access.log combined
-
- # Security headers
- Header always set X-Content-Type-Options "nosniff"
- Header always set X-Frame-Options "SAMEORIGIN"
- Header always set X-XSS-Protection "1; mode=block"
-
diff --git a/config_override.php.pyt b/config_override.php.pyt
new file mode 100644
index 0000000..45d669b
--- /dev/null
+++ b/config_override.php.pyt
@@ -0,0 +1,14 @@
+
diff --git a/crons.conf b/crons.conf
new file mode 100644
index 0000000..bd9299b
--- /dev/null
+++ b/crons.conf
@@ -0,0 +1 @@
+* * * * * cd /var/www/html; php -f cron.php > /dev/null 2>&1
diff --git a/docker-compose.yml b/docker-compose.yml
index dd168e1..0873338 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,87 +1,69 @@
-# SuiteCRM Docker Compose Environment
-# ====================================
-# Start: docker compose up -d
-# Stop: docker compose down
-# Data persists in Docker volumes unless you run: docker compose down -v
+# SugarCRM 6.5.26 CE — Docker Compose Environment
+# Start: docker compose up -d
+# Stop: docker compose down
+# Clean: docker compose down -v (Achtung: löscht ALLE Daten!)
+
+version: '3.8'
services:
- # --- SuiteCRM Application ---
- suitecrm:
+ sugarcrm:
build:
context: .
dockerfile: Dockerfile
- image: suitecrm:7.15.1
- container_name: suitecrm-app
+ image: sugarce:6.5.26
+ container_name: sugarce-app
restart: unless-stopped
ports:
- - "${SUITECRM_PORT:-8080}:80"
- environment:
- - DATABASE_HOST=mariadb
- - DATABASE_PORT=3306
- - DATABASE_NAME=${MYSQL_DATABASE:-suitecrm}
- - DATABASE_USER=${MYSQL_USER:-suitecrm}
- - DATABASE_PASSWORD=${MYSQL_PASSWORD:-suitecrm_secret}
- - SUITECRM_SITE_URL=${SUITECRM_SITE_URL:-http://localhost:8080}
- volumes:
- - suitecrm_data:/var/www/html/upload
- - suitecrm_custom:/var/www/html/custom
- - suitecrm_config:/var/www/html/config_override.php
+ - "${SUGARCRM_PORT:-2080}:80"
depends_on:
- mariadb:
+ db:
condition: service_healthy
+ environment:
+ DB_TYPE: mysql
+ DB_MANAGER: MysqlManager
+ DB_HOST_NAME: db
+ DB_TCP_PORT: "3306"
+ DB_USER_NAME: ${MYSQL_USER:-sugarcrm}
+ DB_PASSWORD: ${MYSQL_PASSWORD:-sugarcrm_secret}
+ DATABASE_NAME: ${MYSQL_DATABASE:-sugarcrm}
+ volumes:
+ - sugarcrm_custom:/var/www/html/custom
+ - sugarcrm_upload:/var/www/html/upload
+ - sugarcrm_config_override:/var/www/html/config_override.php
networks:
- - suitecrm-net
+ - sugarce-net
- # --- MariaDB Database ---
- mariadb:
- image: mariadb:10.11
- container_name: suitecrm-db
+ db:
+ image: mysql:5.7
+ container_name: sugarce-db
restart: unless-stopped
ports:
- - "${MYSQL_PORT:-3307}:3306"
+ - "${MYSQL_PORT:-3306}:3306"
environment:
- - MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root_secret}
- - MARIADB_DATABASE=${MYSQL_DATABASE:-suitecrm}
- - MARIADB_USER=${MYSQL_USER:-suitecrm}
- - MARIADB_PASSWORD=${MYSQL_PASSWORD:-suitecrm_secret}
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root_secret}
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-sugarcrm}
+ MYSQL_USER: ${MYSQL_USER:-sugarcrm}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-sugarcrm_secret}
volumes:
- - mariadb_data:/var/lib/mysql
+ - mysql_data:/var/lib/mysql
command:
- - --character-set-server=utf8mb4
- - --collation-server=utf8mb4_unicode_ci
+ - --character-set-server=utf8
+ - --collation-server=utf8_general_ci
- --max-allowed-packet=64M
- - --innodb-buffer-pool-size=256M
healthcheck:
- test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- - suitecrm-net
-
- # --- Redis Cache (optional) ---
- redis:
- image: redis:7-alpine
- container_name: suitecrm-redis
- restart: unless-stopped
- ports:
- - "${REDIS_PORT:-6379}:6379"
- volumes:
- - redis_data:/data
- command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
- networks:
- - suitecrm-net
- profiles:
- - full
- - redis
+ - sugarce-net
volumes:
- suitecrm_data:
- suitecrm_custom:
- suitecrm_config:
- mariadb_data:
- redis_data:
+ sugarcrm_custom:
+ sugarcrm_upload:
+ sugarcrm_config_override:
+ mysql_data:
networks:
- suitecrm-net:
+ sugarce-net:
driver: bridge
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
deleted file mode 100644
index c70a5a6..0000000
--- a/docker-entrypoint.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-set -e
-
-# Fix permissions
-chown -R www-data:www-data /var/www/html/cache /var/www/html/upload /var/www/html/custom 2>/dev/null || true
-chmod -R 755 /var/www/html 2>/dev/null || true
-chmod -R 775 /var/www/html/cache /var/www/html/upload /var/www/html/custom 2>/dev/null || true
-
-# Generate SuiteCRM autoloader if missing
-if [ ! -f /var/www/html/vendor/autoload.php ] && [ -f /var/www/html/composer.json ]; then
- echo "Installing Composer dependencies..."
- cd /var/www/html && composer install --no-dev --optimize-autoloader 2>/dev/null || true
-fi
-
-# Set recommended permissions
-touch /var/www/html/config.php 2>/dev/null || true
-chmod 640 /var/www/html/config.php 2>/dev/null || true
-chown www-data:www-data /var/www/html/config.php 2>/dev/null || true
-
-echo "SuiteCRM ready. Access http://localhost:8080 to complete installation."
-
-exec "$@"
diff --git a/docker-php-ext-filesize.ini b/docker-php-ext-filesize.ini
new file mode 100644
index 0000000..f8e3132
--- /dev/null
+++ b/docker-php-ext-filesize.ini
@@ -0,0 +1,2 @@
+upload_max_filesize = 32M
+post_max_size = 32M
diff --git a/envtemplate.py b/envtemplate.py
new file mode 100644
index 0000000..8ea09ac
--- /dev/null
+++ b/envtemplate.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+import os, sys, getopt
+from string import Template
+
+def main(argv):
+ inputfile = ''
+ outputfile = ''
+ try:
+ opts, args = getopt.getopt(argv,"hi:o:",["ifile=","ofile="])
+ except getopt.GetoptError:
+ print 'envtemplate.py -i -o '
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt == '-h':
+ print 'envtemplate.py -i -o '
+ sys.exit()
+ elif opt in ("-i", "--ifile"):
+ inputfile = arg
+ elif opt in ("-o", "--ofile"):
+ outputfile = arg
+
+ # Populate case-insensitive env var dictionary
+ values = dict()
+ for k in os.environ:
+ v = os.environ.get(k)
+ values[k] = v
+ values[k.lower()] = v
+
+ # Read template and substitute
+ with open(inputfile) as f:
+ templatestr = f.read()
+
+ out = Template(templatestr).substitute(values)
+
+ print "Writing output to {}".format(outputfile)
+
+ with open(outputfile, "w") as f:
+ f.write(out)
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/init.sh b/init.sh
new file mode 100644
index 0000000..0af2acc
--- /dev/null
+++ b/init.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+# SugarCRM 6.5 CE Docker Entrypoint
+# ==================================
+# Pitfalls documented (see README.md Technical Notes)
+
+# Resolve DB connection from env/linked container
+if [ -z "$DB_HOST_NAME" ]; then
+ export DB_HOST_NAME=${DB_PORT_3306_TCP_ADDR:-db}
+fi
+if [ -z "$DB_TCP_PORT" ]; then
+ export DB_TCP_PORT=${DB_PORT_3306_TCP_PORT:-3306}
+fi
+if [ -z "$DB_USER_NAME" ]; then
+ export DB_USER_NAME=${DB_ENV_MYSQL_USER:-sugarcrm}
+fi
+if [ -z "$DB_PASSWORD" ]; then
+ export DB_PASSWORD=${DB_ENV_MYSQL_PASSWORD:-sugarcrm_secret}
+fi
+if [ -z "$DATABASE_NAME" ]; then
+ export DATABASE_NAME=${DB_ENV_MYSQL_DATABASE:-sugarcrm}
+fi
+
+/usr/local/bin/envtemplate.py -i /usr/local/src/config_override.php.pyt -o /var/www/html/config_override.php
+
+# Start Apache in background for silent install via HTTP
+echo "Starting Apache (background)..."
+apachectl -DFOREGROUND &
+APACHE_PID=$!
+
+# Wait for Apache to be ready
+echo "Waiting for Apache..."
+for i in $(seq 1 30); do
+ if curl -s http://localhost/ >/dev/null 2>&1; then
+ echo "Apache is ready"
+ break
+ fi
+ sleep 1
+done
+
+# Run silent install if not already installed
+if [ ! -f /var/www/html/config.php ]; then
+ echo "Running SugarCRM Silent Install..."
+
+ cat > /var/www/html/config_si.php << SIEOF
+ 'DB_HOST',
+ 'setup_db_database_name' => 'DB_NAME',
+ 'setup_db_admin_user_name' => 'DB_USER',
+ 'setup_db_admin_password' => 'DB_PASS',
+ 'setup_db_type' => 'mysql',
+ 'setup_db_port_num' => 'DB_PORT',
+ 'setup_db_drop_tables' => false,
+ 'setup_db_create_database' => false,
+ 'setup_db_create_sugarsales_user' => false,
+ 'setup_license_key' => 'free',
+ 'setup_license_accept' => true,
+ 'setup_site_url' => 'http://localhost:2080',
+ 'setup_system_name' => 'SugarCRM 6.5 CE',
+ 'setup_site_admin_user_name' => 'admin',
+ 'setup_site_admin_password' => 'admin123',
+ 'setup_site_admin_password_retype' => 'admin123',
+ 'demoData' => 'no',
+ 'dbUSRData' => 'create',
+);
+SIEOF
+
+ sed -i "s/DB_HOST/$DB_HOST_NAME/g" /var/www/html/config_si.php
+ sed -i "s/DB_NAME/$DATABASE_NAME/g" /var/www/html/config_si.php
+ sed -i "s/DB_USER/$DB_USER_NAME/g" /var/www/html/config_si.php
+ sed -i "s/DB_PASS/$DB_PASSWORD/g" /var/www/html/config_si.php
+ sed -i "s/DB_PORT/$DB_TCP_PORT/g" /var/www/html/config_si.php
+
+ # Wait for MySQL (using PHP socket check - no mysqladmin in container)
+ echo "Waiting for MySQL..."
+ for i in $(seq 1 30); do
+ if php -r "\$c=@mysql_connect('$DB_HOST_NAME:$DB_TCP_PORT','$DB_USER_NAME','$DB_PASSWORD');if(\$c){mysql_close(\$c);exit(0);}exit(1);" 2>/dev/null; then
+ echo "MySQL is ready"
+ break
+ fi
+ sleep 2
+ done
+
+ # Run silent install via HTTP
+ echo "Executing silent install..."
+ curl -s "http://localhost/install.php?goto=SilentInstall&cli=true" > /dev/null 2>&1
+
+ if [ -f /var/www/html/config.php ]; then
+ echo "Silent install complete"
+
+ # Fix: installer_locked=true to prevent AdminWizard blocking API
+ sed -i "s/'installer_locked' => false/'installer_locked' => true/" /var/www/html/config.php 2>/dev/null || true
+
+ # Skip Admin Wizard in config.php
+ sed -i "/'site_url' =>/a\\
+ 'adminwizard' => array('completed' => true)," /var/www/html/config.php 2>/dev/null || true
+
+ # Skip Admin Wizard in database (pitfall: needed for API access after restart)
+ php -r "
+ \$c = @mysql_connect('$DB_HOST_NAME:$DB_TCP_PORT', '$DB_USER_NAME', '$DB_PASSWORD');
+ if (\$c) {
+ mysql_select_db('$DATABASE_NAME', \$c);
+ mysql_query(\"INSERT INTO config (category, name, value) VALUES ('system', 'adminwizard', '{\\\"completed\\\":true}')\", \$c);
+ mysql_close(\$c);
+ }
+ " 2>/dev/null || true
+
+ echo "Admin Wizard disabled"
+ else
+ echo "WARNING: config.php was not created! Check install."
+ fi
+else
+ echo "SugarCRM already installed"
+
+ # Ensure installer_locked is true (idempotent restart fix)
+ if grep -q "'installer_locked' => false" /var/www/html/config.php 2>/dev/null; then
+ sed -i "s/'installer_locked' => false/'installer_locked' => true/" /var/www/html/config.php
+ echo "Fixed installer_locked"
+ fi
+
+ # Ensure Admin Wizard remains disabled
+ if ! grep -q adminwizard /var/www/html/config.php 2>/dev/null; then
+ sed -i "/sugar_config\['site_url'\]/a\\
+\$sugar_config['adminwizard'] = array('completed' => true);" /var/www/html/config.php 2>/dev/null || true
+ echo "Admin Wizard disabled"
+ fi
+fi
+
+# Clean up
+rm -f /var/www/html/config_si.php
+
+# Start cron
+/usr/sbin/cron
+
+# Bring Apache to foreground
+echo "Setup complete. SugarCRM 6.5.26 CE ready."
+wait $APACHE_PID
diff --git a/test_api.py b/test_api.py
new file mode 100644
index 0000000..c53f934
--- /dev/null
+++ b/test_api.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+"""
+SugarCRM 6.5 CE REST v4.1 API Test
+===================================
+Endpunkt: http://localhost:{PORT}/service/v4_1/rest.php
+"""
+import http.client
+import json
+import hashlib
+import urllib.parse
+import sys
+import os
+from datetime import datetime
+
+# Configurable via env vars
+BASE_HOST = os.environ.get("SUGARCRM_HOST", "localhost")
+BASE_PORT = os.environ.get("SUGARCRM_PORT", "2080")
+ENDPOINT = "/service/v4_1/rest.php"
+USER = os.environ.get("SUGARCRM_USER", "admin")
+PASSWORD = os.environ.get("SUGARCRM_PASSWORD", "admin123")
+
+BASE_URL = f"{BASE_HOST}:{BASE_PORT}"
+
+def call_api(method, rest_data):
+ """Aufruf der SugarCRM REST v4.1 API"""
+ conn = http.client.HTTPConnection(BASE_URL, timeout=30)
+
+ body = urllib.parse.urlencode({
+ "method": method,
+ "input_type": "JSON",
+ "response_type": "JSON",
+ "rest_data": json.dumps(rest_data)
+ })
+
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ conn.request("POST", ENDPOINT, body, headers)
+ resp = conn.getresponse()
+
+ if resp.status == 302:
+ location = resp.getheader("Location", "")
+ print(f" ⚠️ Redirect to: {location}")
+ print(" (Admin Wizard may be active - restart container to fix)")
+ return None
+
+ data = json.loads(resp.read().decode())
+ conn.close()
+ return data
+
+
+def main():
+ print("=" * 60)
+ print("🍬 SugarCRM 6.5.26 CE REST API Test")
+ print(f" URL: http://{BASE_URL}")
+ print("=" * 60)
+
+ # 1. Login
+ pwd_hash = hashlib.md5(PASSWORD.encode()).hexdigest()
+ print(f"\n1️⃣ LOGIN (user={USER}, md5={pwd_hash[:8]}...)")
+
+ result = call_api("login", {
+ "user_auth": {"user_name": USER, "password": pwd_hash},
+ "application_name": "API Test Script"
+ })
+
+ if not result:
+ print("❌ LOGIN FAILED (Redirect - Admin Wizard active?)")
+ print(" Run: docker compose restart sugarcrm")
+ sys.exit(1)
+
+ if result.get("id"):
+ session = result["id"]
+ print(f" ✅ Session: {session[:20]}...")
+ else:
+ print(f" ❌ Login Error: {result.get('name', 'Unknown')} - {result.get('description', '')}")
+ sys.exit(1)
+
+ # 2. Available modules
+ print(f"\n2️⃣ AVAILABLE MODULES")
+ result = call_api("get_available_modules", {"session": session})
+ if result and "modules" in result:
+ modules = result["modules"]
+ print(f" ✅ {len(modules)} modules available")
+ print(f" First 10: {', '.join(m['module_key'] for m in modules[:10])}")
+ else:
+ print(f" ❌ Error: {result}")
+
+ # 3. Accounts list
+ print(f"\n3️⃣ ACCOUNTS (get_entry_list)")
+ result = call_api("get_entry_list", {
+ "session": session,
+ "module_name": "Accounts",
+ "query": "",
+ "order_by": "",
+ "offset": 0,
+ "select_fields": ["name", "id", "date_entered"],
+ "link_name_to_fields_array": [],
+ "max_results": 5,
+ "deleted": 0
+ })
+ if result and "entry_list" in result:
+ count = result.get("result_count", 0)
+ print(f" ✅ {count} accounts found")
+ for entry in result["entry_list"][:5]:
+ vals = {n["name"]: n["value"] for n in entry["name_value_list"]}
+ print(f" - {vals.get('name', 'N/A')} (ID: {vals.get('id', 'N/A')[:10]}...)")
+ else:
+ print(f" ❌ Error: {result}")
+
+ # 4. Create test account
+ print(f"\n4️⃣ CREATE ACCOUNT (set_entry)")
+ test_name = f"Test API Account {datetime.now().strftime('%H:%M:%S')}"
+ result = call_api("set_entry", {
+ "session": session,
+ "module_name": "Accounts",
+ "name_value_list": {
+ "name": {"name": "name", "value": test_name},
+ "account_type": {"name": "account_type", "value": "Customer"},
+ }
+ })
+ if result and result.get("id"):
+ print(f" ✅ Account '{test_name}' created (ID: {result['id'][:10]}...)")
+
+ # 5. Session count
+ print(f"\n5️⃣ MODULE COUNT (get_entries_count)")
+ result = call_api("get_entries_count", {
+ "session": session,
+ "module_name": "Accounts",
+ "query": "",
+ "deleted": 0
+ })
+ if result:
+ print(f" ✅ Total accounts: {result.get('result_count', 'unknown')}")
+
+ print(f"\n{'=' * 60}")
+ print("✅ ALL API TESTS PASSED!")
+ print(f"{'=' * 60}")
+
+
+if __name__ == "__main__":
+ main()