feat: SugarCRM 6.5.26 CE - Docker + compose + CI/CD
Some checks failed
Docker Build & Push SugarCRM 6.5 CE / build-and-push (push) Has been cancelled
Some checks failed
Docker Build & Push SugarCRM 6.5 CE / build-and-push (push) Has been cancelled
- PHP 5.6 Apache Dockerfile (Debian Jessie, archive repos) - Source from bklein01/sugarcrm GitHub mirror - MySQL 5.7 database with healthcheck - Silent install via init.sh (AdminWizard disabled) - REST v4.1 API test script (test_api.py) - Gitea Actions CI/CD for registry push - Full README with API docs and pitfall notes
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
.env
|
.env
|
||||||
upload/
|
|
||||||
cache/
|
|
||||||
*.log
|
*.log
|
||||||
vendor/
|
.DS_Store
|
||||||
.env.local
|
.git/
|
||||||
.env.*.local
|
.gitignore
|
||||||
|
|||||||
22
.env
22
.env
@@ -1,16 +1,16 @@
|
|||||||
# SuiteCRM Docker Compose Configuration
|
# SugarCRM 6.5.26 CE Docker Umgebung
|
||||||
# Copy to .env and customize
|
# Passwörter ANPASSEN vor erstem Start!
|
||||||
|
|
||||||
# SuiteCRM
|
# Ports
|
||||||
SUITECRM_PORT=8080
|
SUGARCRM_PORT=2080
|
||||||
SUITECRM_SITE_URL=http://localhost:8080
|
MYSQL_PORT=3306
|
||||||
|
|
||||||
# MariaDB
|
# MySQL / MariaDB
|
||||||
MYSQL_PORT=3307
|
|
||||||
MYSQL_ROOT_PASSWORD=change_this_root_password
|
MYSQL_ROOT_PASSWORD=change_this_root_password
|
||||||
MYSQL_DATABASE=suitecrm
|
MYSQL_DATABASE=sugarcrm
|
||||||
MYSQL_USER=suitecrm
|
MYSQL_USER=sugarcrm
|
||||||
MYSQL_PASSWORD=change_this_db_password
|
MYSQL_PASSWORD=change_this_db_password
|
||||||
|
|
||||||
# Redis (only with --profile full or --profile redis)
|
# SugarCRM Admin (wird bei Erst-Installation gesetzt)
|
||||||
REDIS_PORT=6379
|
SUGARCRM_ADMIN_USER=admin
|
||||||
|
SUGARCRM_ADMIN_PASSWORD=admin123
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
name: Docker Build & Push
|
name: Docker Build & Push SugarCRM 6.5 CE
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: docker:27-dind
|
|
||||||
options: --privileged
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Gitea Container Registry
|
- name: Login to Gitea Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -26,13 +21,12 @@ jobs:
|
|||||||
username: ${{ secrets.REGISTRY_USER }}
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Build and Push
|
- name: Build Docker Image
|
||||||
uses: docker/build-push-action@v6
|
run: |
|
||||||
with:
|
docker build -t git.kgessner.de/luiicode/sugar-crm:6.5.26 .
|
||||||
context: .
|
docker tag git.kgessner.de/luiicode/sugar-crm:6.5.26 git.kgessner.de/luiicode/sugar-crm:latest
|
||||||
push: true
|
|
||||||
tags: |
|
- name: Push to Registry
|
||||||
git.kgessner.de/luiicode/sugar-crm:latest
|
run: |
|
||||||
git.kgessner.de/luiicode/sugar-crm:7.15.1
|
docker push git.kgessner.de/luiicode/sugar-crm:6.5.26
|
||||||
cache-from: type=gha
|
docker push git.kgessner.de/luiicode/sugar-crm:latest
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|||||||
124
Dockerfile
124
Dockerfile
@@ -1,96 +1,52 @@
|
|||||||
# SuiteCRM 7.15.1 - PHP 8.1 + Apache
|
FROM php:5.6-apache-jessie
|
||||||
FROM php:8.1-apache
|
|
||||||
|
|
||||||
LABEL maintainer="Kevin Gessner"
|
ENV MAJOR_VERSION=6.5
|
||||||
LABEL description="SuiteCRM 7.15.1 containerized with PHP 8.1 and Apache"
|
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
|
# Jessie is EOL - switch to archive.debian.org
|
||||||
RUN set -eux; \
|
RUN echo "deb http://archive.debian.org/debian/ jessie main contrib non-free" > /etc/apt/sources.list && \
|
||||||
apt-get update && apt-get install -y --no-install-recommends \
|
echo "deb http://archive.debian.org/debian-security/ jessie/updates main contrib non-free" >> /etc/apt/sources.list && \
|
||||||
# Core
|
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99no-check-valid-until && \
|
||||||
libzip-dev \
|
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 \
|
libpng-dev \
|
||||||
libjpeg-dev \
|
unzip \
|
||||||
libfreetype6-dev \
|
cron \
|
||||||
libonig-dev \
|
re2c \
|
||||||
libxml2-dev \
|
python \
|
||||||
libldap2-dev \
|
curl \
|
||||||
libc-client-dev \
|
libc-client-dev \
|
||||||
libkrb5-dev \
|
libkrb5-dev \
|
||||||
libcurl4-openssl-dev \
|
git \
|
||||||
libicu-dev \
|
&& rm -r /var/lib/apt/lists/*
|
||||||
# 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/*
|
|
||||||
|
|
||||||
# Configure PHP for SuiteCRM
|
# Clone SugarCRM 6.5.26 CE from GitHub mirror
|
||||||
RUN { \
|
RUN git clone --depth 1 ${SUGARCRM_REPO} ${WWW_FOLDER} \
|
||||||
echo 'memory_limit = 512M'; \
|
&& cd ${WWW_FOLDER} && git checkout ${SUGARCRM_COMMIT} 2>/dev/null || true \
|
||||||
echo 'upload_max_filesize = 64M'; \
|
&& rm -rf ${WWW_FOLDER}/.git \
|
||||||
echo 'post_max_size = 64M'; \
|
&& chown -R www-data:www-data ${WWW_FOLDER}
|
||||||
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
|
|
||||||
|
|
||||||
# Configure OPcache
|
# PHP upload limits
|
||||||
RUN { \
|
COPY docker-php-ext-filesize.ini /usr/local/etc/php/conf.d/docker-php-ext-filesize.ini
|
||||||
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
|
|
||||||
|
|
||||||
# SuiteCRM version
|
# PHP extensions
|
||||||
ENV SUITECRM_VERSION=7.15.1
|
RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \
|
||||||
ENV SUITECRM_SHA256=468b811addd21dfb29d411ee6e815dbdf7099f912347e88cd3e8d010d829db7a
|
docker-php-ext-install imap mysql zip gd
|
||||||
|
|
||||||
# Download and extract SuiteCRM
|
# Copy entrypoint and templates
|
||||||
RUN set -eux; \
|
COPY config_override.php.pyt /usr/local/src/config_override.php.pyt
|
||||||
wget -q "https://github.com/salesagility/SuiteCRM/releases/download/v${SUITECRM_VERSION}/SuiteCRM-${SUITECRM_VERSION}.zip" \
|
COPY envtemplate.py /usr/local/bin/envtemplate.py
|
||||||
-O /tmp/suitecrm.zip; \
|
COPY init.sh /usr/local/bin/init.sh
|
||||||
echo "${SUITECRM_SHA256} /tmp/suitecrm.zip" | sha256sum -c -; \
|
RUN chmod u+x /usr/local/bin/init.sh
|
||||||
unzip -q /tmp/suitecrm.zip -d /var/www/html/; \
|
|
||||||
rm /tmp/suitecrm.zip; \
|
|
||||||
chown -R www-data:www-data /var/www/html
|
|
||||||
|
|
||||||
# Apache configuration
|
# Cron
|
||||||
RUN a2enmod rewrite expires headers
|
COPY crons.conf /root/crons.conf
|
||||||
|
RUN crontab /root/crons.conf
|
||||||
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"]
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
ENTRYPOINT ["/usr/local/bin/init.sh"]
|
||||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
|
||||||
CMD ["apache2-foreground"]
|
|
||||||
|
|||||||
246
README.md
246
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.
|
**100% containerisierte SugarCRM Community Edition 6.5.26**
|
||||||
Ein Befehl, alles läuft: SuiteCRM + MariaDB + (optional) Redis.
|
Ein `docker compose up -d` — alles läuft: Apache/PHP 5.6 + MySQL 5.7 + REST API v4.1.
|
||||||
|
|
||||||
## Systemanforderungen
|
## 🚀 Quickstart
|
||||||
|
|
||||||
- Docker 20.10+ und Docker Compose v2
|
|
||||||
- 2 GB RAM empfohlen (MariaDB Puffer)
|
|
||||||
|
|
||||||
## Schnellstart
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Repository klonen
|
# 1. Clone
|
||||||
git clone https://git.kgessner.de/LuiiCode/sugar-crm.git
|
git clone https://git.kgessner.de/LuiiCode/sugar-crm.git
|
||||||
cd sugar-crm
|
cd sugar-crm
|
||||||
|
|
||||||
# 2. Umgebungsvariablen anpassen
|
# 2. Konfigurieren (Passwörter ändern!)
|
||||||
cp .env.example .env
|
nano .env
|
||||||
nano .env # Passwörter ändern!
|
|
||||||
|
|
||||||
# 3. Starten
|
# 3. Starten
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 4. SuiteCRM Installation im Browser abschließen:
|
# 4. Web-UI öffnen
|
||||||
# http://localhost:8080
|
# → http://localhost:2080
|
||||||
|
# Login: admin / admin123
|
||||||
|
|
||||||
|
# 5. API testen
|
||||||
|
python3 test_api.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architektur
|
## 🏗️ Architektur
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────────┐
|
┌─────────────────────────────────────────┐
|
||||||
│ Docker Compose │
|
│ Docker Compose │
|
||||||
│ │
|
│ │
|
||||||
│ ┌──────────┐ ┌──────────┐ │
|
│ ┌───────────────┐ ┌───────────────┐ │
|
||||||
│ │ SuiteCRM │ │ MariaDB │ Redis? │
|
│ │ SugarCRM 6.5 │ │ MySQL 5.7 │ │
|
||||||
│ │ :8080 │ │ :3307 │ :6379 │
|
│ │ Apache/PHP5.6 │ │ :3306 │ │
|
||||||
│ │ PHP 8.1 │ │ 10.11 │ (opt.) │
|
│ │ :2080 │◄─│ │ │
|
||||||
│ │ Apache │ │ │ │
|
│ │ │ │ │ │
|
||||||
│ └──────────┘ └──────────┘ │
|
│ └───────────────┘ └───────────────┘ │
|
||||||
│ │ │ │
|
│ │
|
||||||
│ Volumes: Volumes: │
|
│ Data (volumes): │
|
||||||
│ - upload - /var/lib/mysql │
|
│ sugarcrm_custom → custom modules │
|
||||||
│ - custom │
|
│ sugarcrm_upload → uploads │
|
||||||
│ - config │
|
│ mysql_data → DB Daten │
|
||||||
└──────────────────────────────────────┘
|
└─────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## Services
|
## 📦 Services
|
||||||
|
|
||||||
| Service | Image | Port | Profil |
|
| Service | Image | Port | Beschreibung |
|
||||||
|-----------|------------------|-------|---------------|
|
|---------|-------|------|-------------|
|
||||||
| suitecrm | Custom (PHP 8.1) | 8080 | standard |
|
| `sugarcrm` | Custom (PHP 5.6) | `2080` → `80` | SugarCRM 6.5.26 CE Web + API |
|
||||||
| mariadb | mariadb:10.11 | 3307 | standard |
|
| `db` | `mysql:5.7` | `3306` | MySQL Datenbank |
|
||||||
| redis | redis:7-alpine | 6379 | `redis`/`full`|
|
|
||||||
|
|
||||||
## Konfiguration
|
## ⚙️ Konfiguration
|
||||||
|
|
||||||
### Umgebungsvariablen (`.env`)
|
### Umgebungsvariablen (`.env`)
|
||||||
|
|
||||||
| Variable | Default | Beschreibung |
|
| Variable | Default | Beschreibung |
|
||||||
|-----------------------|-----------------------------|-------------------------|
|
|----------|---------|-------------|
|
||||||
| `SUITECRM_PORT` | 8080 | Webinterface-Port |
|
| `SUGARCRM_PORT` | `2080` | Web-UI Port |
|
||||||
| `SUITECRM_SITE_URL` | http://localhost:8080 | Öffentliche URL |
|
| `MYSQL_PORT` | `3306` | MySQL Port |
|
||||||
| `MYSQL_PORT` | 3307 | DB-Port (Host) |
|
| `MYSQL_ROOT_PASSWORD` | `change_this…` | **Sofort ändern!** |
|
||||||
| `MYSQL_ROOT_PASSWORD` | change_this… | Root-Passwort |
|
| `MYSQL_DATABASE` | `sugarcrm` | Datenbank-Name |
|
||||||
| `MYSQL_DATABASE` | suitecrm | Datenbank-Name |
|
| `MYSQL_USER` | `sugarcrm` | DB-Nutzer |
|
||||||
| `MYSQL_USER` | suitecrm | Datenbank-Nutzer |
|
| `MYSQL_PASSWORD` | `change_this…` | **Sofort ändern!** |
|
||||||
| `MYSQL_PASSWORD` | change_this… | Nutzer-Passwort |
|
| `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
|
```bash
|
||||||
# Grundbefehle
|
# Start/Stop
|
||||||
docker compose up -d # Alle Services starten
|
docker compose up -d # Starten
|
||||||
docker compose up -d redis # + Redis-Cache starten
|
docker compose down # Stoppen (Daten bleiben)
|
||||||
docker compose down # Stoppen
|
|
||||||
docker compose down -v # Stoppen + ALLE DATEN LÖSCHEN
|
docker compose down -v # Stoppen + ALLE DATEN LÖSCHEN
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
docker compose logs -f suitecrm # SuiteCRM-Logs verfolgen
|
docker compose logs -f sugarcrm # SugarCRM-Logs
|
||||||
docker compose logs mariadb # DB-Logs
|
docker compose logs db # MySQL-Logs
|
||||||
|
|
||||||
|
# Shell
|
||||||
|
docker compose exec sugarcrm bash # Container-Shell
|
||||||
|
|
||||||
# Backup
|
# Backup
|
||||||
docker compose exec mariadb mysqldump -u suitecrm -p suitecrm > backup.sql
|
docker compose exec db mysqldump -u sugarcrm -p sugarcrm > backup.sql
|
||||||
tar -czf upload-backup.tar.gz -C /var/lib/docker/volumes/sugarcrmreponame_suitecrm_data/_data .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
Secrets erforderlich: `REGISTRY_USER`, `REGISTRY_TOKEN` (bereits gesetzt).
|
||||||
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.
|
|
||||||
|
|
||||||
## Redis aktivieren
|
## ⚠️ Technische Pitfalls
|
||||||
|
|
||||||
```bash
|
| Problem | Fix |
|
||||||
# Mit Redis-Profil starten
|
|---------|-----|
|
||||||
docker compose --profile redis up -d
|
| 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)
|
## 📋 Systemanforderungen
|
||||||
docker compose --profile full up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Redis-Konfiguration in SuiteCRM Admin → System → Redis:
|
- Docker 20.10+
|
||||||
- Host: `redis`
|
- Docker Compose v2
|
||||||
- Port: `6379`
|
- ~500 MB RAM (Apache + MySQL)
|
||||||
|
- Ports `2080` und `3306` verfügbar
|
||||||
## 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`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**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
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<VirtualHost *:80>
|
|
||||||
ServerAdmin webmaster@localhost
|
|
||||||
DocumentRoot /var/www/html
|
|
||||||
|
|
||||||
<Directory /var/www/html>
|
|
||||||
Options Indexes FollowSymLinks
|
|
||||||
AllowOverride All
|
|
||||||
Require all granted
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
# Protect sensitive files
|
|
||||||
<FilesMatch "\.(log|ini|git|sh|yml|yaml|md)$">
|
|
||||||
Require all denied
|
|
||||||
</FilesMatch>
|
|
||||||
|
|
||||||
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"
|
|
||||||
</VirtualHost>
|
|
||||||
14
config_override.php.pyt
Normal file
14
config_override.php.pyt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
if(null !== $installer_defaults) {
|
||||||
|
$installer_defaults['setup_db_host_name'] = '$db_host_name';
|
||||||
|
$installer_defaults['setup_db_database_name'] = '$database_name';
|
||||||
|
$installer_defaults['setup_db_admin_user_name'] = '$db_user_name';
|
||||||
|
$installer_defaults['setup_db_admin_password'] = '$db_password';
|
||||||
|
$installer_defaults['db_type'] = '$db_type';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(null !== $sugar_config) {
|
||||||
|
$sugar_config['dbconfig']['db_port'] = '$db_tcp_port';
|
||||||
|
$sugar_config['dbconfig']['db_manager'] = '$db_manager';
|
||||||
|
}
|
||||||
|
?>
|
||||||
1
crons.conf
Normal file
1
crons.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* * * * * cd /var/www/html; php -f cron.php > /dev/null 2>&1
|
||||||
@@ -1,87 +1,69 @@
|
|||||||
# SuiteCRM Docker Compose Environment
|
# SugarCRM 6.5.26 CE — Docker Compose Environment
|
||||||
# ====================================
|
# Start: docker compose up -d
|
||||||
# Start: docker compose up -d
|
# Stop: docker compose down
|
||||||
# Stop: docker compose down
|
# Clean: docker compose down -v (Achtung: löscht ALLE Daten!)
|
||||||
# Data persists in Docker volumes unless you run: docker compose down -v
|
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# --- SuiteCRM Application ---
|
sugarcrm:
|
||||||
suitecrm:
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: suitecrm:7.15.1
|
image: sugarce:6.5.26
|
||||||
container_name: suitecrm-app
|
container_name: sugarce-app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${SUITECRM_PORT:-8080}:80"
|
- "${SUGARCRM_PORT:-2080}: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
|
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
db:
|
||||||
condition: service_healthy
|
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:
|
networks:
|
||||||
- suitecrm-net
|
- sugarce-net
|
||||||
|
|
||||||
# --- MariaDB Database ---
|
db:
|
||||||
mariadb:
|
image: mysql:5.7
|
||||||
image: mariadb:10.11
|
container_name: sugarce-db
|
||||||
container_name: suitecrm-db
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${MYSQL_PORT:-3307}:3306"
|
- "${MYSQL_PORT:-3306}:3306"
|
||||||
environment:
|
environment:
|
||||||
- MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-root_secret}
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root_secret}
|
||||||
- MARIADB_DATABASE=${MYSQL_DATABASE:-suitecrm}
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-sugarcrm}
|
||||||
- MARIADB_USER=${MYSQL_USER:-suitecrm}
|
MYSQL_USER: ${MYSQL_USER:-sugarcrm}
|
||||||
- MARIADB_PASSWORD=${MYSQL_PASSWORD:-suitecrm_secret}
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-sugarcrm_secret}
|
||||||
volumes:
|
volumes:
|
||||||
- mariadb_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
command:
|
command:
|
||||||
- --character-set-server=utf8mb4
|
- --character-set-server=utf8
|
||||||
- --collation-server=utf8mb4_unicode_ci
|
- --collation-server=utf8_general_ci
|
||||||
- --max-allowed-packet=64M
|
- --max-allowed-packet=64M
|
||||||
- --innodb-buffer-pool-size=256M
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
networks:
|
networks:
|
||||||
- suitecrm-net
|
- sugarce-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
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
suitecrm_data:
|
sugarcrm_custom:
|
||||||
suitecrm_custom:
|
sugarcrm_upload:
|
||||||
suitecrm_config:
|
sugarcrm_config_override:
|
||||||
mariadb_data:
|
mysql_data:
|
||||||
redis_data:
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
suitecrm-net:
|
sugarce-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -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 "$@"
|
|
||||||
2
docker-php-ext-filesize.ini
Normal file
2
docker-php-ext-filesize.ini
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
upload_max_filesize = 32M
|
||||||
|
post_max_size = 32M
|
||||||
41
envtemplate.py
Normal file
41
envtemplate.py
Normal file
@@ -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 <inputfile> -o <outputfile>'
|
||||||
|
sys.exit(2)
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt == '-h':
|
||||||
|
print 'envtemplate.py -i <inputfile> -o <outputfile>'
|
||||||
|
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:])
|
||||||
137
init.sh
Normal file
137
init.sh
Normal file
@@ -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
|
||||||
|
<?php
|
||||||
|
\$sugar_config_si = array(
|
||||||
|
'setup_db_host_name' => '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
|
||||||
140
test_api.py
Normal file
140
test_api.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user