diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..4cbc649cf1 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,80 @@ +FROM php:8.4-fpm + +# Set working directory +WORKDIR /var/www/html + +# Install system dependencies and enable PHP extensions +RUN apt-get update && apt-get install -y \ + git \ + unzip \ + libpng-dev \ + libjpeg-dev \ + libfreetype6-dev \ + libzip-dev \ + libicu-dev \ + libonig-dev \ + libxml2-dev \ + libcurl4-openssl-dev \ + libssl-dev \ + wget \ + supervisor \ + default-mysql-client \ + sudo \ + nginx \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j$(nproc) \ + gd \ + mysqli \ + pdo_mysql \ + zip \ + intl \ + mbstring \ + xml \ + curl \ + opcache \ + sockets + +# Install composer +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# Configure PHP +COPY .devcontainer/php.ini /usr/local/etc/php/conf.d/php.ini + +# Configure Nginx +COPY .devcontainer/nginx.conf /etc/nginx/sites-available/default + +# Set proper permissions +RUN chown -R www-data:www-data /var/www/html + +# Install xdebug for debugging +RUN pecl install xdebug \ + && docker-php-ext-enable xdebug + +# Development tools +RUN apt-get install -y \ + vim \ + nano \ + iputils-ping \ + procps \ + less \ + htop \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Create developer user - simplified approach +RUN useradd -m -s /bin/bash developer && \ + echo "developer:developer" | chpasswd && \ + adduser developer sudo && \ + echo "developer ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \ + usermod -aG www-data developer && \ + chown -R developer:developer /var/www/html + +# Expose port 80 +EXPOSE 80 + +# Create start script +COPY .devcontainer/start.sh /usr/local/bin/start.sh +RUN chmod +x /usr/local/bin/start.sh + +# Start Nginx and PHP-FPM +CMD ["/usr/local/bin/start.sh"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..dfa1f69e2b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +{ + "name": "osTicket Development", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/var/www/html", + "forwardPorts": [8080, 3306], + "remoteUser": "developer", + "customizations": { + "vscode": { + "extensions": [ + "xdebug.php-debug", + "bmewburn.vscode-intelephense-client", + "mrmlnc.vscode-apache", + "mikestead.dotenv", + "redhat.vscode-yaml", + "editorconfig.editorconfig", + "formulahendry.auto-close-tag", + "mechatroner.rainbow-csv", + "ms-azuretools.vscode-docker", + "neilbrayfield.php-docblocker", + "mehedidracula.php-namespace-resolver", + "recca0120.vscode-phpunit", + "junstyle.php-cs-fixer", + "felixfbecker.php-intellisense", + "swordev.phpstan", + "mtxr.sqltools", + "mtxr.sqltools-driver-mysql" + ], + "settings": { + "php.validate.executablePath": "/usr/local/bin/php", + "editor.formatOnSave": true, + "editor.tabSize": 4, + "files.associations": { + "*.inc.php": "php", + "*.phtml": "php" + }, + "php.suggest.basic": true + } + } + }, + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000..fe4ff52587 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,54 @@ +version: "3.8" + +services: + app: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/var/www/html + - ./setup-script.sh:/usr/local/bin/setup-script.sh + - ./start.sh:/usr/local/bin/start.sh + - ./nginx.conf:/etc/nginx/sites-available/default + ports: + - 8080:80 + environment: + - PHP_MAX_EXECUTION_TIME=300 + - PHP_MEMORY_LIMIT=256M + - PHP_UPLOAD_MAX_FILESIZE=64M + - PHP_POST_MAX_SIZE=64M + - MYSQL_HOST=db + - MYSQL_PORT=3306 + - MYSQL_DATABASE=osticket + - MYSQL_USER=osticket + - MYSQL_PASSWORD=osticket + depends_on: + - db + restart: unless-stopped + networks: + - osticket-network + command: > + bash -c "/usr/local/bin/setup-script.sh && /usr/local/bin/start.sh" + + db: + image: mysql:8.0 + restart: unless-stopped + volumes: + - osticket-mysql-data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: osticket + MYSQL_USER: osticket + MYSQL_PASSWORD: osticket + ports: + - 3306:3306 + networks: + - osticket-network + command: --default-authentication-plugin=mysql_native_password + +networks: + osticket-network: + driver: bridge + +volumes: + osticket-mysql-data: diff --git a/.devcontainer/nginx.conf b/.devcontainer/nginx.conf new file mode 100644 index 0000000000..a703337bf7 --- /dev/null +++ b/.devcontainer/nginx.conf @@ -0,0 +1,76 @@ +server { + listen 80; + server_name localhost; + root /var/www/html; + index index.php index.html; + + client_max_body_size 64M; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + location ~ /include { + deny all; + return 403; + } + + if ($request_uri ~ "^/api(/[^\?]+)") { + set $path_info $1; + } + + location /api { + try_files $uri $uri/ /api/http.php?$query_string; + } + + if ($request_uri ~ "^/scp/.*\.php(/[^\?]+)") { + set $path_info $1; + } + + location ~ ^/scp/ajax.php/.*$ { + try_files $uri $uri/ /scp/ajax.php?$query_string; + } + + if ($request_uri ~ "^/ajax.php(/[^\?]+)") { + set $path_info $1; + } + + location ~ ^/ajax.php/.*$ { + try_files $uri $uri/ /ajax.php?$query_string; + } + + location / { + try_files $uri $uri/ index.php; + } + + location = /favicon.ico { log_not_found off; access_log off; } + location = /robots.txt { access_log off; log_not_found off; } + + # Deny .htaccess file access + location ~ /\.ht { + deny all; + } + + location ~ \.php$ { + try_files $uri = 404; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_index index.php; + fastcgi_param LARA_ENV local; # Environment variable for Laravel + fastcgi_param PATH_INFO $path_info; + fastcgi_pass 127.0.0.1:9000; + } + + + # location ~ \.php$ { + # fastcgi_split_path_info ^(.+\.php)(/.+)$; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # include fastcgi_params; + # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + # fastcgi_intercept_errors off; + # fastcgi_buffer_size 16k; + # fastcgi_buffers 4 16k; + # fastcgi_param HTTPS off; + # } + +} \ No newline at end of file diff --git a/.devcontainer/php.ini b/.devcontainer/php.ini new file mode 100644 index 0000000000..17a2afa5a7 --- /dev/null +++ b/.devcontainer/php.ini @@ -0,0 +1,17 @@ +; PHP Development Configuration +memory_limit = 256M +upload_max_filesize = 64M +post_max_size = 64M +max_execution_time = 300 +max_input_time = 300 +date.timezone = UTC +display_errors = On +error_reporting = E_ALL +log_errors = On + +; XDebug configuration +xdebug.mode = debug +xdebug.start_with_request = yes +xdebug.client_host = host.docker.internal +xdebug.client_port = 9003 +xdebug.log = /tmp/xdebug.log \ No newline at end of file diff --git a/.devcontainer/setup-script.sh b/.devcontainer/setup-script.sh new file mode 100755 index 0000000000..48f6a05e6f --- /dev/null +++ b/.devcontainer/setup-script.sh @@ -0,0 +1,110 @@ +#!/bin/bash +set -e + +export MYSQL_HOST=${MYSQL_HOST} +export MYSQL_PORT=${MYSQL_PORT} +export MYSQL_DATABASE=${MYSQL_DATABASE} +export MYSQL_USER=${MYSQL_USER} +export MYSQL_PASSWORD=${MYSQL_PASSWORD} + +# Wait for MySQL to be ready +echo "Waiting for MySQL [${MYSQL_HOST}] to be ready..." +until mysql -h ${MYSQL_HOST} -u ${MYSQL_USER} -p${MYSQL_PASSWORD} -e "SELECT 1" >/dev/null 2>&1; do + echo "MySQL [${MYSQL_HOST}] is unavailable - sleeping" + sleep 2 +done +echo "MySQL is up - continuing" + +# Check if osTicket is already installed +DB_INITIALIZED=$(mysql -h ${MYSQL_HOST} -u ${MYSQL_USER} -p${MYSQL_PASSWORD} -e "SHOW TABLES FROM ${MYSQL_DATABASE}" 2>/dev/null | wc -l) +CONFIG_EXISTS=$([ -f "/var/www/html/include/ost-config.php" ] && echo "yes" || echo "no") + +# Prepare setup directory if it doesn't exist +if [ ! -d "/var/www/html/setup_hidden" ] && [ -d "/var/www/html/setup" ]; then + echo "Creating setup_hidden directory" + cp -r /var/www/html/setup /var/www/html/setup_hidden + # chmod -R 755 /var/www/html/setup_hidden +fi + +# Use install.php script to initialize osTicket if needed +if [ "$DB_INITIALIZED" -le "1" ] || [ "$CONFIG_EXISTS" = "no" ]; then + echo "Initializing osTicket using install.php script..." + + # Create a modified version of the sample config file in a temporary location + if [ ! -d "/var/www/html/include" ]; then + mkdir -p /var/www/html/include + fi + + # Make sure sample config exists + if [ ! -f "/var/www/html/include/ost-sampleconfig.php" ]; then + echo "Error: Sample config file not found!" + exit 1 + fi + + # Copy sample config for the installer to use + cp /var/www/html/include/ost-sampleconfig.php /tmp/ost-config-temp.php + chmod 0666 /tmp/ost-config-temp.php + + # Add SESSION_TTL constant with shorter expiration time (e.g., 1800 seconds = 30 minutes) + # This needs to match your OAuth2 provider's session timeout + # echo "# Adding custom SESSION_TTL setting to config" >> /tmp/ost-config-temp.php + # echo "define('SESSION_TTL', 1800);" >> /tmp/ost-config-temp.php + + # Set up environment variables for the installer + export INSTALL_NAME="osTicket Support" + export INSTALL_EMAIL="helpdesk@example.com" + export INSTALL_URL="http://localhost:8080/" + export ADMIN_FIRSTNAME="Admin" + export ADMIN_LASTNAME="User" + export ADMIN_EMAIL="admin@example.com" + export ADMIN_USERNAME="ostadmin" + export ADMIN_PASSWORD="Admin1" + export MYSQL_PREFIX="ost_" + export MYSQL_HOST=${MYSQL_HOST} + export MYSQL_PORT=${MYSQL_PORT} + export MYSQL_DATABASE=${MYSQL_DATABASE} + export MYSQL_USER=${MYSQL_USER} + export MYSQL_PASSWORD=${MYSQL_PASSWORD} + export INSTALL_CONFIG="/tmp/ost-config-temp.php" + + # Check if the install.php file exists in the correct location + if [ -f "/var/www/html/docker/files/data/bin/install.php" ]; then + echo "Found install.php at /var/www/html/docker/files/data/bin/install.php" + php /var/www/html/docker/files/data/bin/install.php + else + echo "Warning: Could not find install.php in the expected location." + echo "Falling back to setup methods..." + + # Use setup/install.php instead since we're building from source + cd /var/www/html/setup + php -f install.php + fi + + # Set proper permissions for config file + if [ -f "/var/www/html/include/ost-config.php" ]; then + chmod 0644 /var/www/html/include/ost-config.php + echo "Installation completed successfully!" + else + echo "Installation might have failed. Config file not found." + fi + + # Clean up + rm -f /tmp/ost-config-temp.php +else + echo "osTicket is already installed." +fi + +# Set proper permissions for development +# chmod -R 775 /var/www/html/include + +# Set proper permission for attachments directory +if [ ! -d "/var/www/html/uploads/tickets" ]; then + mkdir -p /var/www/html/uploads/tickets +fi + +chmod -R 775 /var/www/html/uploads + +echo "osTicket development environment is ready!" +echo "Admin login: ${ADMIN_USERNAME} / ${ADMIN_PASSWORD}" +echo "Access the helpdesk at: http://localhost:8080" +echo "Access the admin panel at: http://localhost:8080/scp" \ No newline at end of file diff --git a/.devcontainer/start.sh b/.devcontainer/start.sh new file mode 100755 index 0000000000..a426596b51 --- /dev/null +++ b/.devcontainer/start.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +# Start PHP-FPM +php-fpm -D + +# Start Nginx +nginx -g "daemon off;" \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..523fde0c36 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Git-related +.git +.gitignore +.github + +# Docker-related +docker-compose.yml +Dockerfile +.dockerignore + +# Node modules and package files if any exist +node_modules +package-lock.json +yarn.lock + +# Editor and IDE files +.vscode +.idea +*.swp +*.swo + +# Temporary files and logs +*.log +logs +tmp +temp + +# Build artifacts +*.tar.gz +*.zip + +# Ignore uploads directory contents but keep the directory +uploads/* +!uploads/.gitkeep + +# OS-specific files +.DS_Store +Thumbs.db + +kubernetes/* \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000000..3eee155b65 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,96 @@ +name: OSTicket CI/CD Pipeline + +on: + push: + branches: + - develop + - release/* + - feature/* + pull_request: + branches: [ develop ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + name: Build and Push + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,format=long + type=ref,event=branch + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: 'arm64,amd64' + + - name: Build and push PHP-FPM Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + target: production + platforms: linux/amd64,linux/arm64 + cache-from: type=gha,scope=build-php + cache-to: type=gha,scope=build-php,mode=max + build-args: | + VERSION=${{ github.sha }} + + - name: Build and push Nginx Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }}-nginx + labels: ${{ steps.meta.outputs.labels }} + target: nginx + platforms: linux/amd64,linux/arm64 + cache-from: type=gha,scope=build-nginx + cache-to: type=gha,scope=build-nginx,mode=max + build-args: | + VERSION=${{ github.sha }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@v1 + # with: + # image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + # format: 'sarif' + # output: 'trivy-results.sarif' + + # - name: Upload Trivy scan results + # uses: github/codeql-action/upload-sarif@v3 + # if: always() + # with: + # sarif_file: 'trivy-results.sarif' + diff --git a/.gitignore b/.gitignore index 7769425590..6328804d65 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ stage include/mpdf/ttfontdata include/mpdf/tmp nbproject/ +secret.txt +setup_hidden/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..9dc6b4df69 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 0, + "runtimeArgs": [ + "-dxdebug.start_with_request=yes" + ], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + }, + { + "name": "Launch Built-in web server", + "type": "php", + "request": "launch", + "runtimeArgs": [ + "-dxdebug.mode=debug", + "-dxdebug.start_with_request=yes", + "-S", + "localhost:0" + ], + "program": "", + "cwd": "${workspaceRoot}", + "port": 9003, + "serverReadyAction": { + "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..2a49603883 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "sqltools.connections": [ + { + "mysqlOptions": { + "authProtocol": "default", + "enableSsl": "Disabled" + }, + "previewLimit": 50, + "server": "db", + "port": 3306, + "driver": "MySQL", + "name": "db", + "database": "osticket", + "username": "osticket" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..8c49f58665 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +API_KEY:= 184F66085DA76CDF5300FE2C6E229238 +PORT:= 8080 + +ticket: + curl -v -H "X-API-Key: $(API_KEY)" -X POST \ + -d '{"name": "Angry Customer", "email": "angry.customer@example.com", "subject": "Test complaint", "message": "It doesnt work"}' \ + "http://localhost:${PORT}/api/tickets.json" + +list-tickets: + curl -v -H "X-API-Key: $(API_KEY)" -X GET \ + "http://localhost:${PORT}/api/tickets.json" \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..61749252d7 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,146 @@ + +# Stage 1: Deploy osTicket files +FROM php:8.4-cli AS deployer +WORKDIR /data + +RUN apt update && apt install -y git + +# Copy local osTicket files to the deployer container +COPY . /tmp/osticket/ + +RUN set -x \ + # Deploy osTicket from local files + && cd /tmp/osticket \ + && php manage.php deploy -sv /var/www/html \ + # www-data is uid:gid 82:82 in php-fpm-alpine + && chown -R 82:82 /var/www/html \ + # Hide setup + && mv /var/www/html/setup /var/www/html/setup_hidden \ + && chown -R root:root /var/www/html/setup_hidden \ + && chmod -R go= /var/www/html/setup_hidden \ + # Clean up + && rm -rf /tmp/osticket + +# Stage 2: Create production PHP-FPM container (without Nginx) +FROM php:8.4-fpm-alpine AS production +LABEL maintainer="Rob Coward " + +# environment for osticket +ENV PHP_MAX_EXECUTION_TIME=30 \ + PHP_MEMORY_LIMIT=128M \ + PHP_UPLOAD_MAX_FILESIZE=32M \ + PHP_POST_MAX_SIZE=32M + +# setup workdir +WORKDIR /var/www/html + +# Copy osTicket files from deployer +COPY --from=deployer /var/www/html /var/www/html + +# Removed duplicate php-fpm-healthcheck block + +# Install production dependencies and PHP extensions +RUN set -x && \ + # Install runtime dependencies + apk add --no-cache \ + wget \ + msmtp \ + ca-certificates \ + libpng \ + c-client \ + libintl \ + libxml2 \ + icu \ + openssl && \ + # Install build dependencies + apk add --no-cache --virtual .build-deps \ + linux-headers \ + imap-dev \ + libpng-dev \ + oniguruma-dev \ + curl-dev \ + openldap-dev \ + gettext-dev \ + libxml2-dev \ + icu-dev \ + autoconf \ + g++ \ + make \ + pcre-dev && \ + # Install and configure PHP extensions + docker-php-ext-configure gd && \ + docker-php-ext-install -j$(nproc) \ + gd \ + curl \ + mysqli \ + sockets \ + gettext \ + mbstring \ + xml \ + intl \ + opcache && \ + # Cleanup + apk del .build-deps && \ + rm -rf /var/cache/apk/* /tmp/* /var/tmp/* + +# Download language packs and plugins +RUN wget -nv -O /var/www/html/include/i18n/en_GB.phar https://s3.amazonaws.com/downloads.osticket.com/lang/1.18.x/en_GB.phar && \ + wget -nv -O /var/www/html/include/plugins/auth-oauth2.phar https://s3.amazonaws.com/downloads.osticket.com/plugin/auth-oauth2.phar + + +# Configure PHP +# COPY docker/files/usr/local/etc/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf +# COPY docker/files/usr/local/etc/php/conf.d/php-osticket.ini /usr/local/etc/php/conf.d/php-osticket.ini +COPY docker/files/data/bin /usr/local/bin + +# # Process environment variables in the PHP config file +# RUN sed -i "s|\${PHP_MEMORY_LIMIT}|${PHP_MEMORY_LIMIT}|g" /usr/local/etc/php/conf.d/php-osticket.ini && \ +# sed -i "s|\${PHP_UPLOAD_MAX_FILESIZE}|${PHP_UPLOAD_MAX_FILESIZE}|g" /usr/local/etc/php/conf.d/php-osticket.ini && \ +# sed -i "s|\${PHP_POST_MAX_SIZE}|${PHP_POST_MAX_SIZE}|g" /usr/local/etc/php/conf.d/php-osticket.ini && \ +# sed -i "s|\${PHP_MAX_EXECUTION_TIME}|${PHP_MAX_EXECUTION_TIME}|g" /usr/local/etc/php/conf.d/php-osticket.ini + # Optimize for production +RUN echo "opcache.memory_consumption=128" >> /usr/local/etc/php/conf.d/opcache-recommended.ini && \ + echo "opcache.interned_strings_buffer=8" >> /usr/local/etc/php/conf.d/opcache-recommended.ini && \ + echo "opcache.max_accelerated_files=10000" >> /usr/local/etc/php/conf.d/opcache-recommended.ini && \ + echo "opcache.revalidate_freq=60" >> /usr/local/etc/php/conf.d/opcache-recommended.ini && \ + echo "opcache.fast_shutdown=1" >> /usr/local/etc/php/conf.d/opcache-recommended.ini && \ + echo "opcache.enable_cli=0" >> /usr/local/etc/php/conf.d/opcache-recommended.ini && \ + # Set proper permissions + chown -R www-data:www-data /var/www/html + +# Run as non-root user +# USER www-data + +# Add PHP-FPM healthcheck +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1 + +VOLUME ["/var/www/html/include/plugins","/var/www/html/include/i18n"] +EXPOSE 9000 + +CMD ["/usr/local/bin/start.sh"] + +# Stage 3: Create Nginx container (for local development) +FROM nginx:alpine AS nginx +LABEL maintainer="Rob Coward " + +# Copy nginx config +# COPY docker/files/etc/nginx/nginx.conf /etc/nginx/nginx.conf +COPY docker/files/etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf + +# Create volume for logs +VOLUME ["/var/log/nginx"] + +# Run as non-root +RUN touch /var/run/nginx.pid && \ + chown -R nginx:nginx /var/cache/nginx /var/run/nginx.pid /var/log/nginx + +USER nginx + +# Add nginx healthcheck +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD wget -q --no-cache --spider http://localhost/api/http.php/status || exit 1 + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..48cd28e7ec --- /dev/null +++ b/docker/README.md @@ -0,0 +1,92 @@ +# Docker Configuration for osTicket + +This directory contains Docker configuration files that align with the Kubernetes deployment strategy. + +## Structure + +- `Dockerfile`: Multi-stage build file for osTicket + - `deployer` stage: Prepares osTicket files from source + - `production` stage: PHP-FPM container for production use + - `nginx` stage: Nginx container for serving static content + +- `docker-compose.yml`: Local development configuration + - Mirrors the Kubernetes deployment structure + - Separates PHP-FPM and Nginx into different containers + - Uses shared volumes for coordination + - Sets resource limits similar to Kubernetes + +## Container Structure + +1. **PHP-FPM Container** + - PHP 8.3 with FPM + - Alpine-based for smaller footprint + - Includes all required PHP extensions + - Runs as non-root (www-data) user + - Includes healthcheck for Kubernetes readiness probes + +2. **Nginx Container** + - Alpine-based + - Runs as non-root (nginx) user + - Configured as reverse proxy to PHP-FPM + - Mounts the PHP application volume read-only + +3. **MySQL Container** + - Standard MySQL 8.0 + - Persistent data storage + +## Usage + +### Local Development + +```bash +cd docker +docker-compose up -d +``` + +Access osTicket at http://localhost:8080 + +### Building for Kubernetes + +#### Multi-Architecture Builds (AMD64 & ARM64) + +For production Kubernetes deployments, build multi-architecture images to support both x86_64 (AMD64) and ARM64 platforms: + +```bash +# Build and push multi-arch images +./build-for-k8s.sh --repo your-registry/osticket --tag v1.0.0 --platforms "linux/amd64,linux/arm64" --push +``` + +#### Testing Multi-Architecture Images + +To test the multi-arch images before deploying to Kubernetes: + +```bash +# Build multi-arch images without pushing +./build-for-k8s.sh --repo your-registry/osticket --tag test --platforms "linux/amd64,linux/arm64" + +# Test the images +./test-docker.sh --build-multi-arch --check-arch +``` + +#### Single Architecture Build (Legacy) + +For backward compatibility, you can still build single-architecture images: + +```bash +docker build -t your-registry/osticket:latest -f docker/Dockerfile --target production . +docker build -t your-registry/osticket-nginx:latest -f docker/Dockerfile --target nginx . +docker push your-registry/osticket:latest +docker push your-registry/osticket-nginx:latest +``` + +Then deploy to Kubernetes using the manifests in the `kubernetes` directory. + +## Resource Limits + +The docker-compose file includes resource limits that mirror the Kubernetes deployment: + +- PHP-FPM: 0.5 CPU, 256MB RAM +- Nginx: 0.2 CPU, 128MB RAM +- MySQL: 0.5 CPU, 512MB RAM + +These can be adjusted as needed for your development environment. diff --git a/docker/build-for-k8s.sh b/docker/build-for-k8s.sh new file mode 100755 index 0000000000..0ca76e5303 --- /dev/null +++ b/docker/build-for-k8s.sh @@ -0,0 +1,178 @@ +#!/bin/bash +# Build and push multi-arch Docker images for Kubernetes deployment + +set -e + +# Check for required commands +function check_command() { + if ! command -v $1 &> /dev/null; then + echo "Error: $1 is required but not installed." + return 1 + fi + return 0 +} + +# Check for QEMU if building for multiple architectures +function setup_qemu() { + if [[ "$PLATFORMS" == *"arm64"* ]] || [[ "$PLATFORMS" == *"arm/v7"* ]]; then + echo "Setting up QEMU for cross-platform builds..." + if ! docker run --privileged --rm tonistiigi/binfmt:latest --install all; then + echo "Failed to set up QEMU. Multi-architecture builds may fail." + fi + fi +} + +# Default values +IMAGE_REPO="your-registry/osticket" +IMAGE_TAG="latest" +PUSH=false +PLATFORMS="linux/amd64,linux/arm64" +BUILDX=true + +# Print usage information +function print_usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -r, --repo Docker image repository (default: your-registry/osticket)" + echo " -t, --tag Docker image tag (default: latest)" + echo " -p, --push Push images after building" + echo " --platforms Comma-separated list of platforms (default: linux/amd64,linux/arm64)" + echo " --no-buildx Don't use buildx for multi-arch images" + echo " -h, --help Show this help message" + echo "" +} + +# Parse command-line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -r|--repo) + IMAGE_REPO="$2" + shift 2 + ;; + -t|--tag) + IMAGE_TAG="$2" + shift 2 + ;; + -p|--push) + PUSH=true + shift + ;; + --platforms) + PLATFORMS="$2" + shift 2 + ;; + --no-buildx) + BUILDX=false + shift + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "Unknown option: $1" + print_usage + exit 1 + ;; + esac +done + +# Move to project root directory +cd "$(dirname "$0")/.." + +# Check for required commands +check_command docker || exit 1 + +# Only check for buildx if we're using it +if [ "$BUILDX" = true ]; then + if ! docker buildx version > /dev/null 2>&1; then + echo "Error: Docker Buildx is required for multi-arch builds. Please install Docker Buildx or use --no-buildx flag." + exit 1 + fi + + # Set up QEMU for cross-platform builds if needed + setup_qemu +fi + +echo "Building osTicket Docker images for platforms: $PLATFORMS" +echo "PHP-FPM Image: $IMAGE_REPO:$IMAGE_TAG" +echo "Nginx Image: $IMAGE_REPO-nginx:$IMAGE_TAG" + +# Set up Docker Buildx if needed +if [ "$BUILDX" = true ]; then + # Check if docker buildx is available + if ! docker buildx version > /dev/null 2>&1; then + echo "Error: Docker Buildx not available. Please install Docker Buildx or use --no-buildx flag." + exit 1 + fi + + # Ensure we have a builder instance + BUILDER_NAME="osticket-multiarch-builder" + + # Check if the builder already exists + if ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then + echo "Creating new buildx builder: $BUILDER_NAME" + docker buildx create --name "$BUILDER_NAME" --driver docker-container --bootstrap + fi + + # Use the builder + docker buildx use "$BUILDER_NAME" + + # Build the PHP-FPM image + echo "Building multi-architecture PHP-FPM image..." + DOCKER_BUILDX_CMD="docker buildx build --platform $PLATFORMS --output='type=image'" + + if [ "$PUSH" = true ]; then + DOCKER_BUILDX_CMD="$DOCKER_BUILDX_CMD --push" + else + DOCKER_BUILDX_CMD="$DOCKER_BUILDX_CMD --load" + fi + + $DOCKER_BUILDX_CMD \ + -t "$IMAGE_REPO:$IMAGE_TAG" \ + -f docker/Dockerfile \ + --target production . + + # Build the Nginx image + echo "Building multi-architecture Nginx image..." + $DOCKER_BUILDX_CMD \ + -t "$IMAGE_REPO-nginx:$IMAGE_TAG" \ + -f docker/Dockerfile \ + --target nginx . +else + # Use regular docker build (single architecture) + echo "Using standard docker build (not multi-arch)" + + # Build the PHP-FPM image + docker build -t "$IMAGE_REPO:$IMAGE_TAG" \ + -f docker/Dockerfile \ + --target production . + + echo "Building osTicket Nginx image..." + docker build -t "$IMAGE_REPO-nginx:$IMAGE_TAG" \ + -f docker/Dockerfile \ + --target nginx . +fi + +# Push images if requested for non-buildx builds (buildx pushes directly with --push) +if [ "$PUSH" = true ] && [ "$BUILDX" = false ]; then + echo "Pushing images to repository..." + docker push "$IMAGE_REPO:$IMAGE_TAG" + docker push "$IMAGE_REPO-nginx:$IMAGE_TAG" +fi + +if [ "$PUSH" = true ]; then + echo "Images pushed successfully:" + echo " $IMAGE_REPO:$IMAGE_TAG" + echo " $IMAGE_REPO-nginx:$IMAGE_TAG" + echo " Platforms: $PLATFORMS" + + # Update Kubernetes deployment files + echo "To update Kubernetes deployment files:" + echo "cd kubernetes" + echo "sed -i 's|IMAGE_REPOSITORY|$IMAGE_REPO|g' kustomization.yaml" + echo "sed -i 's|IMAGE_TAG|$IMAGE_TAG|g' kustomization.yaml" +fi + +echo "Done!" diff --git a/docker/docker-compose.multi-arch.yml b/docker/docker-compose.multi-arch.yml new file mode 100644 index 0000000000..fc7f719dad --- /dev/null +++ b/docker/docker-compose.multi-arch.yml @@ -0,0 +1,102 @@ +# filepath: /var/www/html/docker/docker-compose.multi-arch.yml +# This docker-compose file is for testing multi-arch images +version: "3.8" + +services: + mysql: + container_name: osticket-mysql + image: mysql:8.0 + volumes: + - mysql_data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: osticket + MYSQL_USER: osticket + MYSQL_PASSWORD: osticket + ports: + - "3306:3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + restart: unless-stopped + networks: + - osticket-network + deploy: + resources: + limits: + cpus: '0.5' + memory: 512M + + php-fpm: + container_name: osticket-php-fpm + # Use the environment variable if set, otherwise use the default image + image: ${OSTICKET_IMAGE:-your-registry/osticket:latest} + volumes: + - plugins_data:/var/www/html/include/plugins + - i18n_data:/var/www/html/include/i18n + - shared_html:/var/www/html + environment: + PHP_MAX_EXECUTION_TIME: 30 + PHP_MEMORY_LIMIT: 128M + PHP_UPLOAD_MAX_FILESIZE: 32M + PHP_POST_MAX_SIZE: 32M + MYSQL_HOST: mysql + MYSQL_DATABASE: osticket + MYSQL_USER: osticket + MYSQL_PASSWORD: osticket + depends_on: + - mysql + healthcheck: + test: ["CMD", "php-fpm-healthcheck"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + restart: unless-stopped + networks: + - osticket-network + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + nginx: + container_name: osticket-nginx + # Use the environment variable if set, otherwise use the default image + image: ${OSTICKET_NGINX_IMAGE:-your-registry/osticket-nginx:latest} + ports: + - "8080:80" + volumes: + - nginx_logs:/var/log/nginx + - shared_html:/var/www/html:ro + depends_on: + - php-fpm + healthcheck: + test: ["CMD", "wget", "-q", "--no-cache", "--spider", "http://localhost/api/http.php/status"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + restart: unless-stopped + networks: + - osticket-network + deploy: + resources: + limits: + cpus: '0.2' + memory: 128M + +volumes: + mysql_data: + plugins_data: + i18n_data: + nginx_logs: + shared_html: # shared volume between PHP-FPM and Nginx + +networks: + osticket-network: + driver: bridge diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000000..a201053dbd --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,117 @@ +# filepath: /var/www/html/docker/docker-compose.yml + +# This docker-compose file mirrors the Kubernetes deployment structure +# for local development and testing + +services: + mysql: + container_name: osticket-mysql + image: mysql:8.0 + volumes: + - mysql_data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: osticket + MYSQL_USER: osticket + MYSQL_PASSWORD: osticket + ports: + - "3306:3306" + healthcheck: + test: + ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + restart: unless-stopped + networks: + - osticket-network + deploy: + resources: + limits: + cpus: "0.5" + memory: 512M + + php-fpm: + container_name: osticket-php-fpm + build: + context: .. + dockerfile: docker/Dockerfile + target: production + ports: + - "9000:9000" + volumes: + - plugins_data:/var/www/html/include/plugins + - i18n_data:/var/www/html/include/i18n + - shared_html:/var/www/html + environment: + PHP_MAX_EXECUTION_TIME: 30 + PHP_MEMORY_LIMIT: 128M + PHP_UPLOAD_MAX_FILESIZE: 32M + PHP_POST_MAX_SIZE: 32M + MYSQL_HOST: mysql + MYSQL_DATABASE: osticket + MYSQL_USER: osticket + MYSQL_PASSWORD: osticket + depends_on: + - mysql + healthcheck: + test: ["CMD", "php-fpm-healthcheck"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + restart: unless-stopped + networks: + - osticket-network + deploy: + resources: + limits: + cpus: "0.5" + memory: 256M + + nginx: + container_name: osticket-nginx + build: + context: .. + dockerfile: docker/Dockerfile + target: nginx + ports: + - "8080:80" + volumes: + - nginx_logs:/var/log/nginx + - shared_html:/var/www/html:ro + depends_on: + - php-fpm + healthcheck: + test: + [ + "CMD", + "wget", + "-q", + "--spider", + "http://localhost/api/http.php/status", + ] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + restart: unless-stopped + networks: + - osticket-network + deploy: + resources: + limits: + cpus: "0.2" + memory: 128M + +volumes: + mysql_data: + plugins_data: + i18n_data: + nginx_logs: + shared_html: # shared volume between PHP-FPM and Nginx + +networks: + osticket-network: + driver: bridge diff --git a/docker/files/data/bin/install.php b/docker/files/data/bin/install.php new file mode 100644 index 0000000000..cd4851f836 --- /dev/null +++ b/docker/files/data/bin/install.php @@ -0,0 +1,218 @@ + getenv("INSTALL_NAME") ?: 'My Helpdesk', + 'email' => getenv("INSTALL_EMAIL") ?: 'helpdesk@example.com', + 'url' => getenv("INSTALL_URL") ?: 'http://localhost:8080/', + + 'fname' => getenv("ADMIN_FIRSTNAME") ?: 'Admin', + 'lname' => getenv("ADMIN_LASTNAME") ?: 'User', + 'admin_email' => getenv("ADMIN_EMAIL") ?: 'admin@example.com', + 'username' => getenv("ADMIN_USERNAME") ?: 'ostadmin', + 'passwd' => getenv("ADMIN_PASSWORD") ?: 'Admin1', + 'passwd2' => getenv("ADMIN_PASSWORD") ?: 'Admin1', + + 'prefix' => getenv("MYSQL_PREFIX") ?: 'ost_', + 'dbhost' => getenv("MYSQL_HOST") ?: 'mysql', + 'dbport' => getenv("MYSQL_PORT") ?: 3306, + 'dbname' => getenv("MYSQL_DATABASE") ?: 'osticket', + 'dbuser' => getenv("MYSQL_USER") ?: 'osticket', + 'dbpass' => getenv("MYSQL_PASSWORD") ?: getenv("MYSQL_ENV_MYSQL_PASSWORD"), + + 'smtp_host' => getenv("SMTP_HOST") ?: 'localhost', + 'smtp_port' => getenv("SMTP_PORT") ?: 25, + 'smtp_from' => getenv("SMTP_FROM"), + 'smtp_tls' => getenv("SMTP_TLS"), + 'smtp_tls_certs' => getenv("SMTP_TLS_CERTS") ?: '/etc/ssl/certs/ca-certificates.crt', + 'smtp_user' => getenv("SMTP_USER"), + 'smtp_pass' => getenv("SMTP_PASSWORD"), + + 'cron_interval' => getenv("CRON_INTERVAL") ?: 5, + + 'siri' => getenv("INSTALL_SECRET"), + 'config' => getenv("INSTALL_CONFIG") ?: '/var/www/html/include/ost-sampleconfig.php' +); + +//Script settings +define('CONNECTION_TIMEOUT_SEC', 180); + +function err( $msg) { + fwrite(STDERR, "$msg\n"); + exit(1); +} + +function boolToOnOff($v) { + return ((boolean) $v) ? 'on' : 'off'; +} + +function convertStrToBool($varName, $default) { + global $vars; + if ($vars[$varName] != '') { + return $vars[$varName] == '1'; + } + return $default; +} + +// Override Helpdesk URL. Only applied during database installation. +define("URL",$vars['url']); + +//Require files (must be done before any output to avoid session start warnings) +chdir("/var/www/html/setup_hidden"); +require "/var/www/html/setup_hidden/setup.inc.php"; +require_once INC_DIR.'class.installer.php'; + + +/************************* Mail Configuration *******************************************/ +define('MAIL_CONFIG_FILE','/etc/msmtp'); + +echo "Configuring mail settings\n"; +if (!$mailConfig = file_get_contents('/var/www/html/docker/files/data/msmtp.conf')) { + err("Failed to load mail configuration file"); +}; +$mailConfig = str_replace('%SMTP_HOSTNAME%', $vars['smtp_host'], $mailConfig); +$mailConfig = str_replace('%SMTP_PORT%', $vars['smtp_port'], $mailConfig); +$v = !empty($vars['smtp_from']) ? $vars['smtp_from'] : $vars['smtp_user']; +$mailConfig = str_replace('%SMTP_FROM%', $v, $mailConfig); +$mailConfig = str_replace('%SMTP_USER%', $vars['smtp_user'], $mailConfig); +$mailConfig = str_replace('%SMTP_PASS%', $vars['smtp_pass'], $mailConfig); +$mailConfig = str_replace('%SMTP_TLS_CERTS%', $vars['smtp_tls_certs'], $mailConfig); + +$mailConfig = str_replace('%SMTP_TLS%', boolToOnOff(convertStrToBool('smtp_tls',true)), $mailConfig); +$mailConfig = str_replace('%SMTP_AUTH%', boolToOnOff($vars['smtp_user'] != ''), $mailConfig); + +if (!file_put_contents(MAIL_CONFIG_FILE, $mailConfig) || !chown(MAIL_CONFIG_FILE,'www-data') + || !chgrp(MAIL_CONFIG_FILE,'www-data') || !chmod(MAIL_CONFIG_FILE,0600)) { + err("Failed to write mail configuration file"); +} + +//Cron interval - enable or disable +define('CRON_JOB_FILE','/var/spool/cron/crontabs/www-data'); + +$interval = (int)$vars['cron_interval']; +if ($interval > 0) { + echo "OSTicket cron job is set to run every {$interval} minutes\n"; + $cron = "*/{$interval} * * * * /usr/local/bin/php -c /usr/local/etc/php/php.ini /var/www/html/api/cron.php\n"; + file_put_contents(CRON_JOB_FILE, $cron); +} else { + echo "OSTicket cron job is disabled\n"; + unlink(CRON_JOB_FILE); +} + +/************************* OSTicket Installation *******************************************/ + +//Create installer class +define('OSTICKET_CONFIGFILE','/var/www/html/include/ost-config.php'); +$installer = new Installer(OSTICKET_CONFIGFILE); //Installer instance. + +//Determine if using linked container +$linked = (boolean)getenv("MYSQL_ENV_MYSQL_PASSWORD"); + +if (!$linked) { + //Check mandatory connection settings provided + if (!getenv("MYSQL_HOST")) { + err('Missing required environmental variable MYSQL_HOST'); + } + if (!getenv("MYSQL_PASSWORD")) { + err('Missing required environmental variable: MYSQL_PASSWORD'); + } + + // Always set mysqli.default_port for osTicket db_connect + ini_set('mysqli.default_port', $vars['dbport']); + + echo "Connecting to external MySQL server on {$vars['dbhost']}:{$vars['dbport']}\n"; +} else { + echo "Using linked MySQL container\n"; + + # MYSQL_PORT is a TCP uri injected by container linking. Use port specified in MYSQL_PORT_3306_TCP_PORT. + $vars['dbport'] = getenv("MYSQL_PORT_3306_TCP_PORT"); +} + +//Wait for database connection +echo "Waiting for database TCP connection to become available...\n"; +$s = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); +$t = 0; +while (!@socket_connect($s,$vars['dbhost'],$vars['dbport']) && $t < CONNECTION_TIMEOUT_SEC) { + $t++; + if (($t % 15) == 0) { + echo "Waited for $t seconds...\n"; + } + sleep(1); +} +if ($t >= CONNECTION_TIMEOUT_SEC) { + err("Timed out waiting for database TCP connection"); +} + +//Check database installation status +$db_installed = false; +echo "Connecting to database mysql://{$vars['dbuser']}@{$vars['dbhost']}/{$vars['dbname']}\n"; +if (!db_connect($vars['dbhost'],$vars['dbuser'],$vars['dbpass'])) + err(sprintf(__('Unable to connect to MySQL server: %s'), db_connect_error())); +elseif(explode('.', db_version()) < explode('.', $installer->getMySQLVersion())) + err(sprintf(__('osTicket requires MySQL %s or later!'),$installer->getMySQLVersion())); +elseif(!db_select_database($vars['dbname']) && !db_create_database($vars['dbname'])) { + err("Database doesn't exist"); +} elseif(!db_select_database($vars['dbname'])) { + err('Unable to select the database'); +} else { + $sql = 'SELECT * FROM `'.$vars['prefix'].'config` LIMIT 1'; + if(db_query($sql, false)) { + $db_installed = true; + echo "Database already installed\n"; + } +} + +//Create secret if not set by env var and not previously stored +DEFINE('SECRET_FILE','/var/www/html/secret.txt'); +if (!$vars['siri']) { + if (file_exists(SECRET_FILE)) { + echo "Loading installation secret\n"; + $vars['siri'] = file_get_contents(SECRET_FILE); + } else { + echo "Generating new installation secret and saving\n"; + //Note that this randomly generated value is not intended to secure production sites! + $vars['siri'] = substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_="), 0, 32); + file_put_contents(SECRET_FILE, $vars['siri']); + } +} else { + echo "Using installation secret from INSTALL_SECRET environmental variable\n"; +} + +//Always rewrite config file in case MySQL details changed (e.g. ip address) +echo "Updating configuration file\n"; +if (!$configFile = file_get_contents($vars['config'])) { + err("Failed to load configuration file: {$vars['config']}"); +}; +$configFile= str_replace("define('OSTINSTALLED',FALSE);","define('OSTINSTALLED',TRUE);",$configFile); +$configFile= str_replace('%ADMIN-EMAIL%',$vars['admin_email'],$configFile); +$configFile= str_replace('%CONFIG-DBHOST%',$vars['dbhost'] . ':' . $vars['dbport'],$configFile); +$configFile= str_replace('%CONFIG-DBNAME%',$vars['dbname'],$configFile); +$configFile= str_replace('%CONFIG-DBUSER%',$vars['dbuser'],$configFile); +$configFile= str_replace('%CONFIG-DBPASS%',$vars['dbpass'],$configFile); +$configFile= str_replace('%CONFIG-PREFIX%',$vars['prefix'],$configFile); +$configFile= str_replace('%CONFIG-SIRI%',$vars['siri'],$configFile); + +if (!file_put_contents($installer->getConfigFile(), $configFile)) { + err("Failed to write configuration file"); +} + +//Perform database installation if required +if (!$db_installed) { + echo "Installing database. Please wait...\n"; + if (!$installer->install($vars)) { + $errors=$installer->getErrors(); + echo "Database installation failed. Errors:\n"; + foreach($errors as $e) { + echo " $e\n"; + } + exit(1); + } else { + echo "Database installation successful\n"; + } +} + +?> diff --git a/docker/files/data/bin/start.sh b/docker/files/data/bin/start.sh new file mode 100755 index 0000000000..2adb4ba097 --- /dev/null +++ b/docker/files/data/bin/start.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# (C) Campbell Software Solutions 2015 +set -e + +# Populate "/var/www/html/include/i18n" volume with language packs +if [ ! "$(ls -A /var/www/html/include/i18n)" ]; then + cp -r /var/www/html/include/i18n.dist/* /var/www/html/include/i18n + chown -R www-data:www-data /var/www/html/include/i18n +fi + +# Automate installation +php /usr/local/bin/install.php +echo Applying configuration file security +chmod 644 /var/www/html/include/ost-config.php + +# mkdir -p /run/nginx +# chown -R www-data:www-data /run/nginx +# chown -R www-data:www-data /var/lib/nginx +mkdir -p /var/log/php +chown -R www-data:www-data /var/log/php + +# exec php-fpm -c $PHP_INI_DIR -y /usr/local/etc/php-fpm.conf +php-fpm \ No newline at end of file diff --git a/docker/files/data/msmtp.conf b/docker/files/data/msmtp.conf new file mode 100644 index 0000000000..3966ad4e14 --- /dev/null +++ b/docker/files/data/msmtp.conf @@ -0,0 +1,23 @@ +# msmtp configuration template +# +# This is populated and saved as /etc/msmtp when image starts +# + +# Default settings +defaults + logfile /var/log/msmtp.log + +# OSTicket account +account osticket + protocol smtp + host %SMTP_HOSTNAME% + tls %SMTP_TLS% + tls_trust_file %SMTP_TLS_CERTS% + port %SMTP_PORT% + auth %SMTP_AUTH% + user %SMTP_USER% + password %SMTP_PASS% + from %SMTP_FROM% + +# If you don't use the '-a' parameter in your command line, the default account will be used. +account default: osticket \ No newline at end of file diff --git a/docker/files/data/supervisord.conf b/docker/files/data/supervisord.conf new file mode 100644 index 0000000000..6cc8bb3f53 --- /dev/null +++ b/docker/files/data/supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true + +[program:php-fpm] +command=php-fpm -c $PHP_INI_DIR -y /usr/local/etc/php-fpm.conf + +[program:nginx] +command=nginx + +[program:cron] +command = /usr/sbin/crond -f +stdout_logfile = /var/log/cron.log +stderr_logfile = /var/log/cron.log +autorestart=true \ No newline at end of file diff --git a/docker/files/data/upload/include/class.api.php b/docker/files/data/upload/include/class.api.php new file mode 100644 index 0000000000..c80488a6a4 --- /dev/null +++ b/docker/files/data/upload/include/class.api.php @@ -0,0 +1,516 @@ + + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +include_once INCLUDE_DIR.'class.controller.php'; + +class API { + + var $id; + + var $ht; + + function __construct($id) { + $this->id = 0; + $this->load($id); + } + + function load($id=0) { + + if(!$id && !($id=$this->getId())) + return false; + + $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE id='.db_input($id); + if(!($res=db_query($sql)) || !db_num_rows($res)) + return false; + + $this->ht = db_fetch_array($res); + $this->id = $this->ht['id']; + + return true; + } + + function reload() { + return $this->load(); + } + + function getId() { + return $this->id; + } + + function getKey() { + return $this->ht['apikey']; + } + + function getIPAddr() { + return $this->ht['ipaddr']; + } + + function getNotes() { + return $this->ht['notes']; + } + + function getHashtable() { + return $this->ht; + } + + function isActive() { + return ($this->ht['isactive']); + } + + function canCreateTickets() { + return ($this->ht['can_create_tickets']); + } + + function canExecuteCron() { + return ($this->ht['can_exec_cron']); + } + + function update($vars, &$errors) { + + if(!API::save($this->getId(), $vars, $errors)) + return false; + + $this->reload(); + + return true; + } + + function delete() { + $sql='DELETE FROM '.API_KEY_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1'; + return (db_query($sql) && ($num=db_affected_rows())); + } + + /** Static functions **/ + static function add($vars, &$errors) { + return API::save(0, $vars, $errors); + } + + static function validate($key, $ip) { + return ($key && $ip && self::getIdByKey($key, $ip)); + } + + static function getIdByKey($key, $ip='') { + + $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key); + if($ip) + $sql.=' AND ipaddr='.db_input($ip); + + if(($res=db_query($sql)) && db_num_rows($res)) + list($id) = db_fetch_row($res); + + return $id; + } + + static function lookupByKey($key, $ip='') { + return self::lookup(self::getIdByKey($key, $ip)); + } + + static function lookup($id) { + return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null; + } + + static function save($id, $vars, &$errors) { + + if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr']))) + $errors['ipaddr'] = __('Valid IP is required'); + + if($errors) return false; + + $sql=' updated=NOW() ' + .',isactive='.db_input($vars['isactive']) + .',can_create_tickets='.db_input($vars['can_create_tickets']) + .',can_exec_cron='.db_input($vars['can_exec_cron']) + .',notes='.db_input(Format::sanitize($vars['notes'])); + + if($id) { + $sql='UPDATE '.API_KEY_TABLE.' SET '.$sql.' WHERE id='.db_input($id); + if(db_query($sql)) + return true; + + $errors['err']=sprintf(__('Unable to update %s.'), __('this API key')) + .' '.__('Internal error occurred'); + + } else { + $sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql + .',created=NOW() ' + .',ipaddr='.db_input($vars['ipaddr']) + .',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randCode(16))))); + + if(db_query($sql) && ($id=db_insert_id())) + return $id; + + $errors['err']=sprintf('%s %s', + sprintf(__('Unable to add %s.'), __('this API key')), + __('Correct any errors below and try again.')); + } + + return false; + } +} + +/** + * Controller for API methods. Provides methods to check to make sure the + * API key was sent and that the Client-IP and API-Key have been registered + * in the database, and methods for parsing and validating data sent in the + * API request. + */ + +class ApiController extends Controller { + + private $sapi; + private $key; + + public function __construct($sapi = null) { + // Set API Interface the request is coming from + $this->sapi = $sapi ?: (osTicket::is_cli() ? 'cli' : 'http'); + } + + public function isCli() { + return (strcasecmp($this->sapi, 'cli') === 0); + } + + private function getInputStream() { + return $this->isCli() ? 'php://stdin' : 'php://input'; + } + + protected function getRemoteAddr() { + return $_SERVER['REMOTE_ADDR']; + } + + protected function getApiKey() { + return $_SERVER['HTTP_X_API_KEY']; + } + + function requireApiKey() { + // Require a valid API key sent as X-API-Key HTTP header + // see getApiKey method. + if (!($key=$this->getKey())) + return $this->exerr(401, __('Valid API key required')); + // elseif (!$key->isActive() || $key->getIPAddr() != $this->getRemoteAddr()) + elseif (!$key->isActive()) + return $this->exerr(401, __('API key not found/active or source IP not authorized')); + + return $key; + } + + function getKey() { + // Lookup record using sent API Key && IP Addr + if (!$this->key + && ($key=$this->getApiKey()) + && ($ip=$this->getRemoteAddr())) + // Disable key lookup by IP address for now + // $this->key = API::lookupByKey($key, $ip); + $this->key = API::lookupByKey($key); + + return $this->key; + } + + /** + * Retrieves the body of the API request and converts it to a common + * hashtable. For JSON formats, this is mostly a noop, the conversion + * work will be done for XML requests + */ + function getRequest($format, $validate=true) { + $input = $this->getInputStream(); + if (!($stream = @fopen($input, 'r'))) + $this->exerr(400, sprintf('%s (%s)', + __("Unable to read request body"), $input)); + + return $this->parseRequest($stream, $format, $validate); + } + + function getEmailRequest() { + if (!($data=$this->getRequest('email', false))) + $this->exerr(400, __("Unable to read email request")); + + return $data; + } + + function parseRequest($stream, $format, $validate=true) { + $parser = null; + switch(strtolower($format)) { + case 'xml': + if (!function_exists('xml_parser_create')) + return $this->exerr(501, __('XML extension not supported')); + $parser = new ApiXmlDataParser(); + break; + case 'json': + $parser = new ApiJsonDataParser(); + break; + case 'email': + $parser = new ApiEmailDataParser(); + break; + default: + return $this->exerr(415, __('Unsupported data format')); + } + + if (!($data = $parser->parse($stream)) || !is_array($data)) { + $this->exerr(400, $parser->lastError()); + } + + //Validate structure of the request. + if ($validate && $data) + $this->validate($data, $format, false); + + return $data; + } + + function parseEmail($content) { + return $this->parseRequest($content, 'email', false); + } + + /** + * Structure to validate the request against -- must be overridden to be + * useful + */ + function getRequestStructure($format, $data=null) { return array(); } + /** + * Simple validation that makes sure the keys of a parsed request are + * expected. It is assumed that the functions actually implementing the + * API will further validate the contents of the request + */ + function validateRequestStructure($data, $structure, $prefix="", $strict=true) { + global $ost; + + foreach ($data as $key=>$info) { + if (is_array($structure) && (is_array($info) || $info instanceof ArrayAccess)) { + $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*"; + if (isset($structure[$search])) { + $this->validateRequestStructure($info, $structure[$search], "$prefix$key/", $strict); + continue; + } + } elseif (in_array($key, $structure)) { + continue; + } + + $error = sprintf(__("%s: Unexpected data received in API request"), + "$prefix$key"); + $this->onError(400, $error, __('Unexpected Data'), !$strict); + } + return true; + } + + /** + * Validate request. + * + */ + function validate(&$data, $format, $strict=true) { + return $this->validateRequestStructure( + $data, + $this->getRequestStructure($format, $data), + "", + $strict); + } + + protected function debug($subj, $msg) { + return $this->log($subj, $msg, LOG_DEBUG); + } + + protected function logError($subj, $msg, $fatal=false) { + // If error is not fatal then log it as a warning + return $this->log($subj, $msg, $fatal ? LOG_ERR : LOG_WARN); + } + + protected function log($title, $msg, $level = LOG_WARN) { + global $ost; + switch ($level) { + case LOG_WARN: + case LOG_ERR: + // We are disabling email alerts on API errors / warnings + // due to potential abuse or loops that might cause email DOS + $ost->log($level, $title, $msg, false); + break; + case LOG_DEBUG: + default: + $ost->logDebug($title, $msg); + } + return true; + } + + /** + * API error & logging and response! + * + * final - don not override downstream. + */ + final public function onError($code, $error, $title = null, + $logOnly = false) { + + // Unpack the errors to string if error is an array + if ($error && is_array($error)) + $error = Format::array_implode(": ", "\n", $error); + + // Log the error + $msg = $error; + // TODO: Only include API Key when in debug mode to avoid + // potentialialy leaking a valid key in system logs + if (($key=$this->getApiKey())) + $msg .= "\n[$key]\n"; + + $title = sprintf('%s (%s)', $title ?: __('API Error'), $code); + $this->logError($title, $msg, $logOnly); + + // If the error is not deemed fatal then simply return + if ($logOnly) + return; + + // If the API Interface is CLI then throw a TicketApiError exception + // so the caller can handle the error gracefully. + // Note that we set the error code as well + if ($this->isCli()) + throw new TicketApiError($error, $code); + + // Respond and exit since HTTP endpoint requests + // are considered stateless + $this->response($code, $error); + } +} + +include_once "class.xml.php"; +class ApiXmlDataParser extends XmlDataParser { + + function parse($stream) { + return $this->fixup(parent::parse($stream)); + } + /** + * Perform simple operations to make data consistent between JSON and + * XML data types + */ + function fixup($current) { + global $cfg; + + if (isset($current['ticket'])) + $current = $current['ticket']; + + if (!is_array($current)) + return $current; + foreach ($current as $key=>&$value) { + if ($key == "phone" && is_array($value)) { + $value = $value[":text"]; + } else if ($key == "alert") { + $value = (bool) (strtolower($value) === 'false' ? false : $value); + } else if ($key == "autorespond") { + $value = (bool) (strtolower($value) === 'false' ? false : $value); + } else if ($key == "message") { + if (!is_array($value)) { + $value = array( + "body" => $value, + "type" => "text/plain", + # Use encoding from root node + ); + } else { + $value["body"] = $value[":text"]; + unset($value[":text"]); + } + if (isset($value['encoding'])) + $value['body'] = Charset::utf8($value['body'], $value['encoding']); + + if (!strcasecmp($value['type'], 'text/html')) + $value = new HtmlThreadEntryBody($value['body']); + else + $value = new TextThreadEntryBody($value['body']); + + } else if ($key == "attachments") { + if(isset($value['file']) && !isset($value['file'][':text'])) + $value = $value['file']; + + if($value && is_array($value)) { + foreach ($value as &$info) { + $info["data"] = $info[":text"]; + unset($info[":text"]); + } + unset($info); + } + } else if(is_array($value)) { + $value = $this->fixup($value); + } + } + unset($value); + + return $current; + } +} + +include_once "class.json.php"; +class ApiJsonDataParser extends JsonDataParser { + static function parse($stream, $tidy=false) { + return self::fixup(parent::parse($stream)); + } + static function fixup($current) { + if (!is_array($current)) + return $current; + foreach ($current as $key=>&$value) { + if ($key == "phone") { + $value = strtoupper($value); + } else if ($key == "alert") { + $value = (bool)$value; + } else if ($key == "autorespond") { + $value = (bool)$value; + } elseif ($key == "message") { + // Allow message specified in RFC 2397 format + $data = Format::strip_emoticons(Format::parseRfc2397($value, 'utf-8')); + + if (isset($data['type']) && $data['type'] == 'text/html') + $value = new HtmlThreadEntryBody($data['data']); + else + $value = new TextThreadEntryBody($data['data']); + + } else if ($key == "attachments") { + foreach ($value as &$info) { + $data = reset($info); + # PHP5: fopen("data://$data[5:]"); + $contents = Format::parseRfc2397($data, 'utf-8', false); + $info = array( + "data" => $contents['data'], + "type" => $contents['type'], + "name" => key($info), + ); + } + unset($info); + } + } + unset($value); + + return $current; + } +} + +/* Email parsing */ +include_once "class.mailparse.php"; +class ApiEmailDataParser extends EmailDataParser { + + function parse($stream) { + return $this->fixup(parent::parse($stream)); + } + + function fixup($data) { + global $cfg; + + if (!$data) return $data; + + $data['source'] = 'Email'; + + if (!$data['subject']) + $data['subject'] = '[No Subject]'; + + if (!$data['emailId']) + $data['emailId'] = $cfg->getDefaultEmailId(); + + if( !$cfg->useEmailPriority()) + unset($data['priorityId']); + + return $data; + } +} +?> \ No newline at end of file diff --git a/docker/files/etc/nginx/conf.d/default.conf b/docker/files/etc/nginx/conf.d/default.conf new file mode 100644 index 0000000000..4fd24b8897 --- /dev/null +++ b/docker/files/etc/nginx/conf.d/default.conf @@ -0,0 +1,61 @@ +server { + listen 80; + server_name _; + root /var/www/html; + index index.php; + + client_max_body_size 32M; + + # Security headers + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Frame-Options "SAMEORIGIN" always; + + # Logging + # access_log /var/log/nginx/osticket-access.log; + # error_log /var/log/nginx/osticket-error.log error; + + # Block access to sensitive files + location ~ /\.ht { + deny all; + } + + location ~ /include { + deny all; + return 403; + } + + # Set path info for API + set $path_info ""; + + if ($request_uri ~ "^/api(/[^\?]+)") { + set $path_info $1; + } + + location /api { + try_files $uri $uri/ /api/http.php?$query_string; + } + + # Set path info for SCP + if ($request_uri ~ "^/scp/.*\.php(/[^\?]+)") { + set $path_info $1; + } + + location ~ ^/scp/ajax.php/.*$ { + try_files $uri $uri/ /scp/ajax.php?$query_string; + } + + # Handle PHP files + location ~ \.php$ { + fastcgi_pass php-fpm:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_param PATH_INFO $path_info; + } + + # Try static files first, then index.php + location / { + try_files $uri $uri/ /index.php?$query_string; + } +} diff --git a/docker/files/etc/nginx/nginx.conf b/docker/files/etc/nginx/nginx.conf new file mode 100644 index 0000000000..b53bd975dc --- /dev/null +++ b/docker/files/etc/nginx/nginx.conf @@ -0,0 +1,73 @@ +# filepath: /var/www/html/docker/files/etc/nginx/nginx.conf +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + client_max_body_size 32m; + + # Enable gzip compression + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + # Include virtual host configs + include /etc/nginx/conf.d/*.conf; + + + if ($request_uri ~ "^/ajax.php(/[^\?]+)") { + set $path_info $1; + } + + location ~ ^/ajax.php/.*$ { + try_files $uri $uri/ /ajax.php?$query_string; + } + + location / { + try_files $uri $uri/ index.php; + } + + location = /favicon.ico { log_not_found off; access_log off; } + location = /robots.txt { access_log off; log_not_found off; } + + # Deny .htaccess file access + location ~ /\.ht { + deny all; + } + + location ~ \.php$ { + try_files $uri = 404; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_index index.php; + fastcgi_param LARA_ENV local; # Environment variable for Laravel + fastcgi_param PATH_INFO $path_info; + fastcgi_pass 127.0.0.1:9000; + } + + +} + +daemon off; \ No newline at end of file diff --git a/docker/files/usr/local/etc/php-fpm.d/www.conf b/docker/files/usr/local/etc/php-fpm.d/www.conf new file mode 100644 index 0000000000..7a6b0acfa0 --- /dev/null +++ b/docker/files/usr/local/etc/php-fpm.d/www.conf @@ -0,0 +1,15 @@ +[www] +user = www-data +group = www-data +listen = 9000 +pm = dynamic +pm.max_children = 10 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.max_requests = 500 +php_admin_value[upload_max_filesize] = 32M +php_admin_value[post_max_size] = 32M +php_admin_value[memory_limit] = 128M +php_admin_value[max_execution_time] = 30 +php_admin_value[max_input_time] = 60 \ No newline at end of file diff --git a/docker/files/usr/local/etc/php/conf.d/php-osticket.ini b/docker/files/usr/local/etc/php/conf.d/php-osticket.ini new file mode 100644 index 0000000000..707d3c4984 --- /dev/null +++ b/docker/files/usr/local/etc/php/conf.d/php-osticket.ini @@ -0,0 +1,19 @@ +; PHP Configuration for osTicket +memory_limit = ${PHP_MEMORY_LIMIT} +upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE} +post_max_size = ${PHP_POST_MAX_SIZE} +max_execution_time = ${PHP_MAX_EXECUTION_TIME} +max_input_time = 60 +date.timezone = UTC +display_errors = Off +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +log_errors = On +expose_php = Off + +cgi.fix_pathinfo=0 +sendmail_path="/usr/bin/msmtp -C /etc/msmtp -t " +apc.enabled=1 +apc.enable_cli=1 +apc.ttl=7200 + + diff --git a/docker/files/usr/local/etc/php/php.ini b/docker/files/usr/local/etc/php/php.ini new file mode 100644 index 0000000000..2e23f32530 --- /dev/null +++ b/docker/files/usr/local/etc/php/php.ini @@ -0,0 +1,1917 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. (As of PHP 5.2.0) +; 3. A number of predefined registry keys on Windows (As of PHP 5.2.0) +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (C:\windows or C:\winnt) +; See the PHP docs for more specific information. +; http://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; http://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security conscience applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it is +; much more verbose when it comes to errors. We recommend using the +; development version only in development environments, as errors shown to +; application users can inadvertently leak otherwise secure information. + +; This is php.ini-production INI file. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED +; Development Value: E_ALL +; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT + +; html_errors +; Default Value: On +; Development Value: On +; Production value: On + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.sid_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; track_errors +; Default Value: Off +; Development Value: On +; Production Value: Off + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; http://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; tags as PHP source which should be processed as such. It is +; generally recommended that should be used and that this feature +; should be disabled, as enabling it may result in issues when generating XML +; documents, however this remains supported for backward compatibility reasons. +; Note that this directive does not control the would work. +; http://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; http://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; http://php.net/realpath-cache-size +;realpath_cache_size = 4096k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; http://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +; Enables or disables the circular reference collector. +; http://php.net/zend.enable-gc +zend.enable_gc = On + +; If enabled, scripts may be written in encodings that are incompatible with +; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such +; encodings. To use this feature, mbstring extension must be enabled. +; Default: Off +;zend.multibyte = Off + +; Allows to set the default encoding for the scripts. This value will be used +; unless "declare(encoding=...)" directive appears at the top of the script. +; Only affects if zend.multibyte is set. +; Default: "" +;zend.script_encoding = + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; http://php.net/expose-php +expose_php = On + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; http://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time = 30 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; http://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; http://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; How many GET/POST/COOKIE input variables may be accepted +; max_input_vars = 1000 + +; Maximum amount of memory a script may consume (128MB) +; http://php.net/memory-limit +memory_limit = 128M + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings (includes E_STRICT as of PHP 5.4.0) +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it is automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL (Show all errors, warnings and notices including coding standards.) +; E_ALL & ~E_NOTICE (Show all errors, except for notices) +; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED +; Development Value: E_ALL +; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT +; http://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; For production environments, we recommend logging errors rather than +; sending them to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; http://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. PHP's default behavior is to suppress those +; errors from clients. Turning the display of startup errors on can be useful in +; debugging configuration problems. We strongly recommend you +; set this to 'off' for production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; http://php.net/log-errors +log_errors = On + +; Set maximum length of log_errors. In error_log information about the source is +; added. The default is 1024 and 0 allows to not apply any maximum length at all. +; http://php.net/log-errors-max-len +log_errors_max_len = 1024 + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; http://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; http://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This has only effect in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; http://php.net/report-memleaks +report_memleaks = On + +; This setting is on by default. +;report_zend_debug = 0 + +; Store the last error/warning message in $php_errormsg (boolean). Setting this value +; to On can assist in debugging and is appropriate for development servers. It should +; however be disabled on production servers. +; Default Value: Off +; Development Value: On +; Production Value: Off +; http://php.net/track-errors +track_errors = Off + +; Turn off normal error reporting and emit XML-RPC error XML +; http://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of formatting the +; error message as HTML for easier reading. This directive controls whether +; the error message is formatted as HTML or not. +; Note: This directive is hardcoded to Off for the CLI SAPI +; Default Value: On +; Development Value: On +; Production value: On +; http://php.net/html-errors +html_errors = On + +; If html_errors is set to On *and* docref_root is not empty, then PHP +; produces clickable error messages that direct to a page describing the error +; or function causing the error in detail. +; You can download a copy of the PHP manual from http://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty, in which +; case no links to documentation are generated. +; Note: Never use this feature for production boxes. +; http://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; http://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-prepend-string +; Example: +;error_prepend_string = "" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; http://php.net/error-append-string +; Example: +;error_append_string = "" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; http://php.net/error-log +; Example: +; error_log = /var/log/php/error.log +error_log = /dev/stderr + +;windows.show_crt_warning +; Default value: 0 +; Development value: 0 +; Production value: 0 + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; http://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; http://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. G,P,C,E & S are abbreviations for the following respective super +; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty +; paid for the registration of these arrays and because ENV is not as commonly +; used as the others, ENV is not recommended on productions servers. You +; can still get access to the environment variables through getenv() should you +; need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; http://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P & C) should be +; registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive +; are specified in the same manner as the variables_order directive, +; EXCEPT one. Leaving this value empty will cause PHP to use the value set +; in the variables_order directive. It does not mean it will leave the super +; globals array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; http://php.net/request-order +request_order = "GP" + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; http://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the ENV, REQUEST and SERVER variables are created when they're +; first used (Just In Time) instead of when the script starts. If these +; variables are not used within a script, having this directive on will result +; in a performance gain. The PHP directive register_argc_argv must be disabled +; for this directive to have any affect. +; http://php.net/auto-globals-jit +auto_globals_jit = On + +; Whether PHP will read the POST data. +; This option is enabled by default. +; Most likely, you won't want to disable this option globally. It causes $_POST +; and $_FILES to always be empty; the only way you will be able to read the +; POST data will be through the php://input stream wrapper. This can be useful +; to proxy requests or to process the POST data in a memory efficient fashion. +; http://php.net/enable-post-data-reading +;enable_post_data_reading = Off + +; Maximum size of POST data that PHP will accept. +; Its value may be 0 to disable the limit. It is ignored if POST data reading +; is disabled through enable_post_data_reading. +; http://php.net/post-max-size +post_max_size = 8M + +; Automatically add files before PHP document. +; http://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; http://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a media type using the Content-Type header. To +; disable this, simply set it to be empty. +; +; PHP's built-in default media type is set to text/html. +; http://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to UTF-8. +; http://php.net/default-charset +default_charset = "UTF-8" + +; PHP internal character encoding is set to empty. +; If empty, default_charset is used. +; http://php.net/internal-encoding +;internal_encoding = + +; PHP input character encoding is set to empty. +; If empty, default_charset is used. +; http://php.net/input-encoding +;input_encoding = + +; PHP output character encoding is set to empty. +; If empty, default_charset is used. +; See also output_buffer. +; http://php.net/output-encoding +;output_encoding = + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/php/includes" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; http://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; http://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; http://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; http://php.net/extension-dir +; extension_dir = "./" +; On windows: +; extension_dir = "ext" + +; Directory where the temporary files should be placed. +; Defaults to the system default (see sys_get_temp_dir) +; sys_temp_dir = "/tmp" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; http://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; http://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; http://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; http://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside +; of the web tree and people will not be able to circumvent .htaccess security. +; http://php.net/cgi.dicard-path +;cgi.discard_path=1 + +; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; http://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1 + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If set to 0, PHP sends Status: header that +; is supported by Apache. When this option is set to 1, PHP will send +; RFC2616 compliant header. +; Default is zero. +; http://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #! +; (shebang) at the top of the running script. This line might be needed if the +; script support running both as stand-alone script and via PHP CGI<. PHP in CGI +; mode skips this line and ignores its content if this directive is turned on. +; http://php.net/cgi.check-shebang-line +;cgi.check_shebang_line=1 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; http://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; http://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; http://php.net/upload-max-filesize +upload_max_filesize = 2M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like http:// or ftp://) as files. +; http://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; http://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; http://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; http://php.net/default-socket-timeout +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; http://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename.extension +; +; For example, on Windows: +; +; extension=mysqli.dll +; +; ... or under UNIX: +; +; extension=mysqli.so +; +; ... or with a path: +; +; extension=/path/to/extension/mysqli.so +; +; If you only provide the name of the extension, PHP will look for it in its +; default extension directory. +; +; Windows Extensions +; Note that ODBC support is built in, so no dll is needed for it. +; Note that many DLL files are located in the extensions/ (PHP 4) ext/ (PHP 5+) +; extension folders as well as the separate PECL DLL download (PHP 5+). +; Be sure to appropriately set the extension_dir directive. +; +;extension=php_bz2.dll +;extension=php_curl.dll +;extension=php_fileinfo.dll +;extension=php_ftp.dll +;extension=php_gd2.dll +;extension=php_gettext.dll +;extension=php_gmp.dll +;extension=php_intl.dll +;extension=php_imap.dll +;extension=php_interbase.dll +;extension=php_ldap.dll +;extension=php_mbstring.dll +;extension=php_exif.dll ; Must be after mbstring as it depends on it +;extension=php_mysqli.dll +;extension=php_oci8_12c.dll ; Use with Oracle Database 12c Instant Client +;extension=php_openssl.dll +;extension=php_pdo_firebird.dll +;extension=php_pdo_mysql.dll +;extension=php_pdo_oci.dll +;extension=php_pdo_odbc.dll +;extension=php_pdo_pgsql.dll +;extension=php_pdo_sqlite.dll +;extension=php_pgsql.dll +;extension=php_shmop.dll + +; The MIBS data available in the PHP distribution must be installed. +; See http://www.php.net/manual/en/snmp.installation.php +;extension=php_snmp.dll + +;extension=php_soap.dll +;extension=php_sockets.dll +;extension=php_sqlite3.dll +;extension=php_tidy.dll +;extension=php_xmlrpc.dll +;extension=php_xsl.dll + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[CLI Server] +; Whether the CLI web server uses ANSI color coding in its terminal output. +cli_server.color = On + +[Date] +; Defines the default timezone used by the date functions +; http://php.net/date.timezone +;date.timezone = + +; http://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; http://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; http://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.583333 + +; http://php.net/date.sunset-zenith +;date.sunset_zenith = 90.583333 + +[filter] +; http://php.net/filter.default +;filter.default = unsafe_raw + +; http://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +; Use of this INI entry is deprecated, use global input_encoding instead. +; If empty, default_charset or input_encoding or iconv.input_encoding is used. +; The precedence is: default_charset < intput_encoding < iconv.input_encoding +;iconv.input_encoding = + +; Use of this INI entry is deprecated, use global internal_encoding instead. +; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. +; The precedence is: default_charset < internal_encoding < iconv.internal_encoding +;iconv.internal_encoding = + +; Use of this INI entry is deprecated, use global output_encoding instead. +; If empty, default_charset or output_encoding or iconv.output_encoding is used. +; The precedence is: default_charset < output_encoding < iconv.output_encoding +; To use an output encoding conversion, iconv's output handler must be set +; otherwise output encoding conversion cannot be performed. +;iconv.output_encoding = + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING +;intl.use_exceptions = 0 + +[sqlite3] +;sqlite3.extension_dir = + +[Pcre] +;PCRE library backtracking limit. +; http://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +;PCRE library recursion limit. +;Please note that if you set this value to a high number you may consume all +;the available process stack and eventually crash PHP (due to reaching the +;stack size limit imposed by the Operating System). +; http://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +;Enables or disables JIT compilation of patterns. This requires the PCRE +;library to be compiled with JIT support. +;pcre.jit=1 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; http://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +;pdo_odbc.db2_instance_name + +[Pdo_mysql] +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/pdo_mysql.cache_size +pdo_mysql.cache_size = 2000 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/pdo_mysql.default-socket +pdo_mysql.default_socket= + +[Phar] +; http://php.net/phar.readonly +;phar.readonly = On + +; http://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[mail function] +; For Win32 only. +; http://php.net/smtp +SMTP = localhost +; http://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; http://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; http://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(). +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; The path to a log file that will log all mail() calls. Log entries include +; the full path of the script, line number, To address and headers. +;mail.log = +; Log mail to syslog (Event Log on Windows). +;mail.log = syslog + +[ODBC] +; http://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; http://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; http://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; http://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; http://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; http://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; http://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; http://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +;birdstep.max_links = -1 + +[Interbase] +; Allow or prevent persistent links. +ibase.allow_persistent = 1 + +; Maximum number of persistent links. -1 means no limit. +ibase.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +ibase.max_links = -1 + +; Default database name for ibase_connect(). +;ibase.default_db = + +; Default username for ibase_connect(). +;ibase.default_user = + +; Default password for ibase_connect(). +;ibase.default_password = + +; Default charset for ibase_connect(). +;ibase.default_charset = + +; Default timestamp format. +ibase.timestampformat = "%Y-%m-%d %H:%M:%S" + +; Default date format. +ibase.dateformat = "%Y-%m-%d" + +; Default time format. +ibase.timeformat = "%H:%M:%S" + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; http://php.net/mysqli.allow_local_infile +;mysqli.allow_local_infile = On + +; Allow or prevent persistent links. +; http://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; http://php.net/mysqli.max-links +mysqli.max_links = -1 + +; If mysqlnd is used: Number of cache slots for the internal result set cache +; http://php.net/mysqli.cache_size +mysqli.cache_size = 2000 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; http://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; http://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysql_connect() (doesn't apply in safe mode). +; http://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; http://php.net/mysqli.default-pw +mysqli.default_pw = + +; Allow or prevent reconnect +mysqli.reconnect = Off + +[mysqlnd] +; Enable / Disable collection of general statistics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_statistics +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statistics by mysqlnd which can be +; used to tune and monitor MySQL operations. +; http://php.net/mysqlnd.collect_memory_statistics +mysqlnd.collect_memory_statistics = Off + +; Records communication from all extensions using mysqlnd to the specified log +; file. +; http://php.net/mysqlnd.debug +;mysqlnd.debug = + +; Defines which queries will be logged. +; http://php.net/mysqlnd.log_mask +;mysqlnd.log_mask = 0 + +; Default size of the mysqlnd memory pool, which is used by result sets. +; http://php.net/mysqlnd.mempool_default_size +;mysqlnd.mempool_default_size = 16000 + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +; http://php.net/mysqlnd.net_cmd_buffer_size +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +; http://php.net/mysqlnd.net_read_buffer_size +;mysqlnd.net_read_buffer_size = 32768 + +; Timeout for network requests in seconds. +; http://php.net/mysqlnd.net_read_timeout +;mysqlnd.net_read_timeout = 31536000 + +; SHA-256 Authentication Plugin related. File with the MySQL server public RSA +; key. +; http://php.net/mysqlnd.sha256_server_public_key +;mysqlnd.sha256_server_public_key = + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; http://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; http://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; http://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; http://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle 11g Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; http://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables statement prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; http://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; http://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgreSQL] +; Allow or prevent persistent links. +; http://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; http://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; http://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; http://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; http://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Notice message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; http://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[bcmath] +; Number of decimal digits for all bcmath functions. +; http://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; http://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; http://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if +; your OS has problems with many files in one directory, and is +; a more efficient layout for servers that handle many sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; http://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use strict session mode. +; Strict session mode does not accept uninitialized session ID and regenerate +; session ID if browser sends uninitialized session ID. Strict mode protects +; applications from session fixation via session adoption vulnerability. It is +; disabled by default for maximum compatibility, but enabling it is encouraged. +; https://wiki.php.net/rfc/strict_sessions +session.use_strict_mode = 0 + +; Whether to use cookies. +; http://php.net/session.use-cookies +session.use_cookies = 1 + +; http://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combating +; session hijacking when not specifying and managing your own session id. It is +; not the be-all and end-all of session hijacking defense, but it's a good start. +; http://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; http://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; http://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; http://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; http://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; http://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; http://php.net/session.cookie-httponly +session.cookie_httponly = + +; Handler used to serialize data. php is the standard serializer of PHP. +; http://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started +; on every session initialization. The probability is calculated by using +; gc_probability/gc_divisor. Where session.gc_probability is the numerator +; and gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.gc-probability +session.gc_probability = 1 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using the following equation: +; gc_probability/gc_divisor. Where session.gc_probability is the numerator and +; session.gc_divisor is the denominator in the equation. Setting this value to 1 +; when the session.gc_divisor value is 100 will give you approximately a 1% chance +; the gc will run on any give request. Increasing this value to 1000 will give you +; a 0.1% chance the gc will run on any give request. For high volume production servers, +; this is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; http://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; http://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script would is the equivalent of +; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; find /path/to/sessions -cmin +24 -type f | xargs rm + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; http://php.net/session.referer-check +session.referer_check = + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; http://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; http://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users' security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publicly accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; http://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Set session ID character length. This value could be between 22 to 256. +; Shorter length than default is supported only for compatibility reason. +; Users should use 32 or more chars. +; http://php.net/session.sid-length +; Default Value: 32 +; Development Value: 26 +; Production Value: 26 +session.sid_length = 26 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +;
is special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. tag's action attribute URL will not be modified +; unless it is specified. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=" +; Development Value: "a=href,area=href,frame=src,form=" +; Production Value: "a=href,area=href,frame=src,form=" +; http://php.net/url-rewriter.tags +session.trans_sid_tags = "a=href,area=href,frame=src,form=" + +; URL rewriter does not rewrite absolute URLs by default. +; To enable rewrites for absolute pathes, target hosts must be specified +; at RUNTIME. i.e. use ini_set() +; tags is special. PHP will check action attribute's URL regardless +; of session.trans_sid_tags setting. +; If no host is defined, HTTP_HOST will be used for allowed host. +; Example value: php.net,www.php.net,wiki.php.net +; Use "," for multiple hosts. No spaces are allowed. +; Default Value: "" +; Development Value: "" +; Production Value: "" +;session.trans_sid_hosts="" + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; http://php.net/session.hash-bits-per-character +session.sid_bits_per_character = 5 + +; Enable upload progress tracking in $_SESSION +; Default Value: On +; Development Value: On +; Production Value: On +; http://php.net/session.upload-progress.enabled +;session.upload_progress.enabled = On + +; Cleanup the progress information as soon as all POST data has been read +; (i.e. upload completed). +; Default Value: On +; Development Value: On +; Production Value: On +; http://php.net/session.upload-progress.cleanup +;session.upload_progress.cleanup = On + +; A prefix used for the upload progress key in $_SESSION +; Default Value: "upload_progress_" +; Development Value: "upload_progress_" +; Production Value: "upload_progress_" +; http://php.net/session.upload-progress.prefix +;session.upload_progress.prefix = "upload_progress_" + +; The index name (concatenated with the prefix) in $_SESSION +; containing the upload progress information +; Default Value: "PHP_SESSION_UPLOAD_PROGRESS" +; Development Value: "PHP_SESSION_UPLOAD_PROGRESS" +; Production Value: "PHP_SESSION_UPLOAD_PROGRESS" +; http://php.net/session.upload-progress.name +;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" + +; How frequently the upload progress should be updated. +; Given either in percentages (per-file), or in bytes +; Default Value: "1%" +; Development Value: "1%" +; Production Value: "1%" +; http://php.net/session.upload-progress.freq +;session.upload_progress.freq = "1%" + +; The minimum delay between updates, in seconds +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; http://php.net/session.upload-progress.min-freq +;session.upload_progress.min_freq = "1" + +; Only write session data when session data is changed. Enabled by default. +; http://php.net/session.lazy-write +;session.lazy_write = On + +[Assertion] +; Switch whether to compile assertions at all (to have no overhead at run-time) +; -1: Do not compile at all +; 0: Jump over assertion at run-time +; 1: Execute assertions +; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1) +; Default Value: 1 +; Development Value: 1 +; Production Value: -1 +; http://php.net/zend.assertions +zend.assertions = -1 + +; Assert(expr); active by default. +; http://php.net/assert.active +;assert.active = On + +; Throw an AssertationException on failed assertions +; http://php.net/assert.exception +;assert.exception = On + +; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active) +; http://php.net/assert.warning +;assert.warning = On + +; Don't bail out by default. +; http://php.net/assert.bail +;assert.bail = Off + +; User-function to be called if an assertion fails. +; http://php.net/assert.callback +;assert.callback = 0 + +; Eval the expression with current error_reporting(). Set to true if you want +; error_reporting(0) around the eval(). +; http://php.net/assert.quiet-eval +;assert.quiet_eval = 0 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; http://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; http://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a components typlib on com_load() +; http://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; http://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; http://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +[mbstring] +; language for internal character representation. +; This affects mb_send_mail() and mbstring.detect_order. +; http://php.net/mbstring.language +;mbstring.language = Japanese + +; Use of this INI entry is deprecated, use global internal_encoding instead. +; internal/script encoding. +; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*) +; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. +; The precedence is: default_charset < internal_encoding < iconv.internal_encoding +;mbstring.internal_encoding = + +; Use of this INI entry is deprecated, use global input_encoding instead. +; http input encoding. +; mbstring.encoding_traslation = On is needed to use this setting. +; If empty, default_charset or input_encoding or mbstring.input is used. +; The precedence is: default_charset < intput_encoding < mbsting.http_input +; http://php.net/mbstring.http-input +;mbstring.http_input = + +; Use of this INI entry is deprecated, use global output_encoding instead. +; http output encoding. +; mb_output_handler must be registered as output buffer to function. +; If empty, default_charset or output_encoding or mbstring.http_output is used. +; The precedence is: default_charset < output_encoding < mbstring.http_output +; To use an output encoding conversion, mbstring's output handler must be set +; otherwise output encoding conversion cannot be performed. +; http://php.net/mbstring.http-output +;mbstring.http_output = + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; http://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; "auto" detect order is changed according to mbstring.language +; http://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; http://php.net/mbstring.substitute-character +;mbstring.substitute_character = none + +; overload(replace) single byte functions by mbstring functions. +; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(), +; etc. Possible values are 0,1,2,4 or combination of them. +; For example, 7 for overload everything. +; 0: No overload +; 1: Overload mail() function +; 2: Overload str*() functions +; 4: Overload ereg*() functions +; http://php.net/mbstring.func-overload +;mbstring.func_overload = 0 + +; enable strict encoding detection. +; Default: Off +;mbstring.strict_detection = On + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetype= + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; http://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 1 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; http://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; http://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; http://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; http://php.net/exif.encode-jis +;exif.encode_jis = + +; http://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; http://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; http://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; http://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; http://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; http://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; http://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[dba] +;dba.default_handler= + +[opcache] +; Determines if Zend OPCache is enabled +;opcache.enable=1 + +; Determines if Zend OPCache is enabled for the CLI version of PHP +;opcache.enable_cli=1 + +; The OPcache shared memory storage size. +;opcache.memory_consumption=128 + +; The amount of memory for interned strings in Mbytes. +;opcache.interned_strings_buffer=8 + +; The maximum number of keys (scripts) in the OPcache hash table. +; Only numbers between 200 and 100000 are allowed. +;opcache.max_accelerated_files=10000 + +; The maximum percentage of "wasted" memory until a restart is scheduled. +;opcache.max_wasted_percentage=5 + +; When this directive is enabled, the OPcache appends the current working +; directory to the script key, thus eliminating possible collisions between +; files with the same name (basename). Disabling the directive improves +; performance, but may break existing applications. +;opcache.use_cwd=1 + +; When disabled, you must reset the OPcache manually or restart the +; webserver for changes to the filesystem to take effect. +;opcache.validate_timestamps=1 + +; How often (in seconds) to check file timestamps for changes to the shared +; memory storage allocation. ("1" means validate once per second, but only +; once per request. "0" means always validate) +;opcache.revalidate_freq=2 + +; Enables or disables file search in include_path optimization +;opcache.revalidate_path=0 + +; If disabled, all PHPDoc comments are dropped from the code to reduce the +; size of the optimized code. +;opcache.save_comments=1 + +; If enabled, a fast shutdown sequence is used for the accelerated code +; Depending on the used Memory Manager this may cause some incompatibilities. +;opcache.fast_shutdown=0 + +; Allow file existence override (file_exists, etc.) performance feature. +;opcache.enable_file_override=0 + +; A bitmask, where each bit enables or disables the appropriate OPcache +; passes +;opcache.optimization_level=0xffffffff + +;opcache.inherited_hack=1 +;opcache.dups_fix=0 + +; The location of the OPcache blacklist file (wildcards allowed). +; Each OPcache blacklist file is a text file that holds the names of files +; that should not be accelerated. The file format is to add each filename +; to a new line. The filename may be a full path or just a file prefix +; (i.e., /var/www/x blacklists all the files and directories in /var/www +; that start with 'x'). Line starting with a ; are ignored (comments). +;opcache.blacklist_filename= + +; Allows exclusion of large files from being cached. By default all files +; are cached. +;opcache.max_file_size=0 + +; Check the cache checksum each N requests. +; The default value of "0" means that the checks are disabled. +;opcache.consistency_checks=0 + +; How long to wait (in seconds) for a scheduled restart to begin if the cache +; is not being accessed. +;opcache.force_restart_timeout=180 + +; OPcache error_log file name. Empty string assumes "stderr". +;opcache.error_log= + +; All OPcache errors go to the Web server log. +; By default, only fatal errors (level 0) or errors (level 1) are logged. +; You can also enable warnings (level 2), info messages (level 3) or +; debug messages (level 4). +;opcache.log_verbosity_level=1 + +; Preferred Shared Memory back-end. Leave empty and let the system decide. +;opcache.preferred_memory_model= + +; Protect the shared memory from unexpected writing during script execution. +; Useful for internal debugging only. +;opcache.protect_memory=0 + +; Allows calling OPcache API functions only from PHP scripts which path is +; started from specified string. The default "" means no restriction +;opcache.restrict_api= + +; Mapping base of shared memory segments (for Windows only). All the PHP +; processes have to map shared memory into the same address space. This +; directive allows to manually fix the "Unable to reattach to base address" +; errors. +;opcache.mmap_base= + +; Enables and sets the second level cache directory. +; It should improve performance when SHM memory is full, at server restart or +; SHM reset. The default "" disables file based caching. +;opcache.file_cache= + +; Enables or disables opcode caching in shared memory. +;opcache.file_cache_only=0 + +; Enables or disables checksum validation when script loaded from file cache. +;opcache.file_cache_consistency_checks=1 + +; Implies opcache.file_cache_only=1 for a certain process that failed to +; reattach to the shared memory (for Windows only). Explicitly enabled file +; cache is required. +;opcache.file_cache_fallback=1 + +; Enables or disables copying of PHP code (text segment) into HUGE PAGES. +; This should improve performance, but requires appropriate OS configuration. +;opcache.huge_code_pages=1 + +; Validate cached file permissions. +;opcache.validate_permission=0 + +; Prevent name collisions in chroot'ed environment. +;opcache.validate_root=0 + +[curl] +; A default value for the CURLOPT_CAINFO option. This is required to be an +; absolute path. +;curl.cainfo = + +[openssl] +; The location of a Certificate Authority (CA) file on the local filesystem +; to use when verifying the identity of SSL/TLS peers. Most users should +; not specify a value for this directive as PHP will attempt to use the +; OS-managed cert stores in its absence. If specified, this value may still +; be overridden on a per-stream basis via the "cafile" SSL stream context +; option. +;openssl.cafile= + +; If openssl.cafile is not specified or if the CA file is not found, the +; directory pointed to by openssl.capath is searched for a suitable +; certificate. This value must be a correctly hashed certificate directory. +; Most users should not specify a value for this directive as PHP will +; attempt to use the OS-managed cert stores in its absence. If specified, +; this value may still be overridden on a per-stream basis via the "capath" +; SSL stream context option. +;openssl.capath= + +; Local Variables: +; tab-width: 4 +; End: \ No newline at end of file diff --git a/docker/test-docker.sh b/docker/test-docker.sh new file mode 100755 index 0000000000..0ff78d1100 --- /dev/null +++ b/docker/test-docker.sh @@ -0,0 +1,250 @@ +#!/bin/bash +# Test script for Docker environment before Kubernetes deployment +# Supports testing multi-arch images + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default values +CHECK_ARCH=false +BUILD_MULTI_ARCH=false + +# Print usage information +function print_usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " --check-arch Check image architecture support" + echo " --build-multi-arch Build multi-arch images before testing" + echo " -h, --help Show this help message" + echo "" +} + +# Parse command-line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --check-arch) + CHECK_ARCH=true + shift + ;; + --build-multi-arch) + BUILD_MULTI_ARCH=true + shift + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "Unknown option: $1" + print_usage + exit 1 + ;; + esac +done + +echo -e "${YELLOW}Starting osTicket Docker environment test${NC}" +echo "----------------------------------------" + +# Check if docker and docker-compose are installed +if ! command -v docker &> /dev/null; then + echo -e "${RED}Docker is not installed. Please install Docker first.${NC}" + exit 1 +fi + +if ! command -v docker-compose &> /dev/null; then + echo -e "${RED}Docker Compose is not installed. Please install Docker Compose first.${NC}" + exit 1 +fi + +# Function to check image architecture support +check_image_architecture() { + local image="$1" + echo -e "${BLUE}Checking architecture support for image: ${image}${NC}" + + # Get architecture information using docker inspect + if ! arch_info=$(docker inspect --format '{{.Architecture}}' "$image" 2>/dev/null); then + echo -e "${RED}Image not found or cannot inspect: ${image}${NC}" + return 1 + fi + + # Get manifest info for multi-arch images + if manifest_info=$(docker manifest inspect "$image" 2>/dev/null); then + echo -e "${GREEN}Image supports multiple architectures:${NC}" + platforms=$(echo "$manifest_info" | grep -o '"platform": {[^}]*}' | grep -o '"architecture": "[^"]*"' | cut -d'"' -f4 | sort -u) + + for platform in $platforms; do + echo -e "${GREEN} - $platform${NC}" + done + + # Check specifically for amd64 and arm64 + if echo "$platforms" | grep -q "amd64" && echo "$platforms" | grep -q "arm64"; then + echo -e "${GREEN}✓ Image supports both amd64 and arm64 architectures${NC}" + return 0 + else + echo -e "${YELLOW}⚠ Image does not support both amd64 and arm64 architectures${NC}" + return 1 + fi + else + echo -e "${YELLOW}Single architecture image: ${arch_info}${NC}" + + if [ "$arch_info" = "amd64" ] || [ "$arch_info" = "arm64" ]; then + echo -e "${YELLOW}⚠ Image only supports ${arch_info} architecture${NC}" + else + echo -e "${RED}⚠ Image architecture ${arch_info} is not amd64 or arm64${NC}" + fi + return 1 + fi +} + +# Switch to docker directory +cd "$(dirname "$0")" + +# If build-multi-arch is requested, build the images first +if [ "$BUILD_MULTI_ARCH" = true ]; then + echo -e "${YELLOW}Building multi-architecture images...${NC}" + ../docker/build-for-k8s.sh --platforms "linux/amd64,linux/arm64" --repo "osticket-test" --tag "test" + + # Override the images in docker-compose.yml + export OSTICKET_IMAGE="osticket-test:test" + export OSTICKET_NGINX_IMAGE="osticket-test-nginx:test" +fi + +# Check architecture support if requested +if [ "$CHECK_ARCH" = true ]; then + echo -e "${YELLOW}Checking architecture support for images...${NC}" + check_image_architecture "osticket-test:test" || echo -e "${YELLOW}Multi-architecture check failed. Consider building with --build-multi-arch${NC}" + check_image_architecture "osticket-test-nginx:test" || echo -e "${YELLOW}Multi-architecture check failed. Consider building with --build-multi-arch${NC}" +fi + +# Clean up any existing containers to start fresh +echo -e "${YELLOW}Stopping and removing any existing containers...${NC}" +docker-compose down -v + +# Build and start the containers +echo -e "${YELLOW}Building and starting containers...${NC}" +docker-compose build +docker-compose up -d + +# Wait for services to be ready +echo -e "${YELLOW}Waiting for services to start up...${NC}" +sleep 10 + +# Check if containers are running +echo -e "${YELLOW}Checking container status...${NC}" +CONTAINERS=("osticket-mysql" "osticket-php-fpm" "osticket-nginx") +ALL_RUNNING=true + +for CONTAINER in "${CONTAINERS[@]}"; do + STATUS=$(docker inspect --format='{{.State.Status}}' "$CONTAINER" 2>/dev/null || echo "not_found") + + if [ "$STATUS" != "running" ]; then + echo -e "${RED}Container $CONTAINER is not running (status: $STATUS)${NC}" + ALL_RUNNING=false + else + echo -e "${GREEN}Container $CONTAINER is running${NC}" + fi +done + +if [ "$ALL_RUNNING" = false ]; then + echo -e "${RED}Not all containers are running. Check the logs with 'docker-compose logs'${NC}" + exit 1 +fi + +# Check container health +echo -e "\n${YELLOW}Checking container health...${NC}" +ALL_HEALTHY=true + +for CONTAINER in "${CONTAINERS[@]}"; do + HEALTH=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER" 2>/dev/null || echo "not_found") + + if [ "$HEALTH" != "healthy" ]; then + echo -e "${RED}Container $CONTAINER is not healthy (health: $HEALTH)${NC}" + ALL_HEALTHY=false + + # Show logs for unhealthy containers + echo -e "${YELLOW}Logs for $CONTAINER:${NC}" + docker logs "$CONTAINER" | tail -n 20 + echo "" + else + echo -e "${GREEN}Container $CONTAINER is healthy${NC}" + fi +done + +# Check if Nginx is responding +echo -e "\n${YELLOW}Testing Nginx connectivity...${NC}" +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/) + +if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 302 ]; then + echo -e "${GREEN}Nginx is responding with HTTP $HTTP_CODE${NC}" +else + echo -e "${RED}Nginx is not responding properly. HTTP code: $HTTP_CODE${NC}" + ALL_HEALTHY=false +fi + +# Check PHP-FPM status through Nginx +echo -e "\n${YELLOW}Testing PHP-FPM connectivity through Nginx...${NC}" +PHP_TEST=$(curl -s http://localhost:8080/api/http.php/status) + +if [[ "$PHP_TEST" == *"osTicket"* ]]; then + echo -e "${GREEN}PHP-FPM is processing requests correctly${NC}" +else + echo -e "${RED}PHP-FPM is not processing requests correctly${NC}" + echo "Response: $PHP_TEST" + ALL_HEALTHY=false +fi + +# Check for multi-arch support if requested +if [ "$CHECK_ARCH" = true ]; then + echo -e "\n${YELLOW}Checking container architecture:${NC}" + + CURRENT_ARCH=$(uname -m) + if [ "$CURRENT_ARCH" = "x86_64" ]; then + CURRENT_ARCH="amd64" + elif [ "$CURRENT_ARCH" = "aarch64" ]; then + CURRENT_ARCH="arm64" + fi + + echo -e "Current architecture: ${GREEN}$CURRENT_ARCH${NC}" + + for CONTAINER in "${CONTAINERS[@]}"; do + CONTAINER_IMAGE=$(docker inspect --format='{{.Config.Image}}' "$CONTAINER" 2>/dev/null || echo "unknown") + echo -e "Container $CONTAINER is using image: $CONTAINER_IMAGE" + + if [ "$BUILD_MULTI_ARCH" = true ]; then + echo -e "${GREEN}✓ $CONTAINER_IMAGE was built with multi-arch support${NC}" + fi + done +fi + +# Final summary +echo -e "\n${YELLOW}Test summary:${NC}" +echo "----------------------------------------" + +if [ "$ALL_RUNNING" = true ] && [ "$ALL_HEALTHY" = true ]; then + echo -e "${GREEN}All tests passed! The Docker environment is working correctly.${NC}" + echo -e "You can access osTicket at ${GREEN}http://localhost:8080/${NC}" + echo -e "The Docker environment matches the Kubernetes deployment structure." + + if [ "$BUILD_MULTI_ARCH" = true ]; then + echo -e "${GREEN}Multi-architecture images (amd64, arm64) were successfully built and tested.${NC}" + else + echo -e "${YELLOW}To build multi-architecture images for Kubernetes deployment:${NC}" + echo -e " ./build-for-k8s.sh --platforms linux/amd64,linux/arm64 --push" + fi + + echo -e "Ready to proceed with Kubernetes deployment." +else + echo -e "${RED}Some tests failed. Please fix the issues before deploying to Kubernetes.${NC}" +fi + +echo "" +echo "To clean up the test environment:" +echo " docker-compose down -v" +echo "" diff --git a/docker/test-multi-arch.sh b/docker/test-multi-arch.sh new file mode 100755 index 0000000000..2e0144a3ce --- /dev/null +++ b/docker/test-multi-arch.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# Test multi-architecture images for osTicket + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default values +IMAGE_REPO="your-registry/osticket" +IMAGE_TAG="latest" +BUILD=false +TEST=true +PLATFORMS="linux/amd64,linux/arm64" + +# Print usage information +function print_usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -r, --repo Docker image repository (default: your-registry/osticket)" + echo " -t, --tag Docker image tag (default: latest)" + echo " -b, --build Build multi-arch images before testing" + echo " --no-test Skip tests and only build" + echo " -p, --platforms Comma-separated list of platforms (default: linux/amd64,linux/arm64)" + echo " -h, --help Show this help message" + echo "" +} + +# Parse command-line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -r|--repo) + IMAGE_REPO="$2" + shift 2 + ;; + -t|--tag) + IMAGE_TAG="$2" + shift 2 + ;; + -b|--build) + BUILD=true + shift + ;; + --no-test) + TEST=false + shift + ;; + -p|--platforms) + PLATFORMS="$2" + shift 2 + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + print_usage + exit 1 + ;; + esac +done + +# Move to docker directory +cd "$(dirname "$0")" + +# Display current architecture +CURRENT_ARCH=$(uname -m) +if [ "$CURRENT_ARCH" = "x86_64" ]; then + CURRENT_ARCH="amd64" +elif [ "$CURRENT_ARCH" = "aarch64" ]; then + CURRENT_ARCH="arm64" +fi + +echo -e "${YELLOW}Current architecture: ${CURRENT_ARCH}${NC}" +echo -e "${YELLOW}Testing platforms: ${PLATFORMS}${NC}" + +# Build multi-arch images if requested +if [ "$BUILD" = true ]; then + echo -e "${YELLOW}Building multi-architecture images...${NC}" + + # Run the build-for-k8s.sh script with the specified parameters + ./build-for-k8s.sh --repo "$IMAGE_REPO" --tag "$IMAGE_TAG" --platforms "$PLATFORMS" + + echo -e "${GREEN}Multi-architecture images built successfully!${NC}" +fi + +# Only run tests if not skipped +if [ "$TEST" = true ]; then + echo -e "${YELLOW}Testing multi-architecture images...${NC}" + + # Set environment variables for docker-compose + export OSTICKET_IMAGE="${IMAGE_REPO}:${IMAGE_TAG}" + export OSTICKET_NGINX_IMAGE="${IMAGE_REPO}-nginx:${IMAGE_TAG}" + + # Stop any running containers + docker-compose -f docker-compose.multi-arch.yml down -v + + # Start containers using the multi-arch compose file + docker-compose -f docker-compose.multi-arch.yml up -d + + # Wait for services to start + echo -e "${YELLOW}Waiting for services to start up...${NC}" + sleep 10 + + # Check container status + echo -e "${YELLOW}Checking container status...${NC}" + CONTAINERS=("osticket-mysql" "osticket-php-fpm" "osticket-nginx") + ALL_RUNNING=true + + for CONTAINER in "${CONTAINERS[@]}"; do + STATUS=$(docker inspect --format='{{.State.Status}}' "$CONTAINER" 2>/dev/null || echo "not_found") + + if [ "$STATUS" != "running" ]; then + echo -e "${RED}Container $CONTAINER is not running (status: $STATUS)${NC}" + ALL_RUNNING=false + else + echo -e "${GREEN}Container $CONTAINER is running${NC}" + fi + done + + if [ "$ALL_RUNNING" = false ]; then + echo -e "${RED}Some containers are not running. Check the logs with 'docker-compose -f docker-compose.multi-arch.yml logs'${NC}" + exit 1 + fi + + # Check image architecture information + echo -e "\n${YELLOW}Image Architecture Information:${NC}" + + for CONTAINER in "${CONTAINERS[@]}"; do + CONTAINER_IMAGE=$(docker inspect --format='{{.Config.Image}}' "$CONTAINER" 2>/dev/null || echo "unknown") + CONTAINER_PLATFORM=$(docker inspect --format='{{.Architecture}}' "$CONTAINER" 2>/dev/null || echo "unknown") + + echo -e "${BLUE}Container: ${CONTAINER}${NC}" + echo -e " Image: ${CONTAINER_IMAGE}" + echo -e " Running on platform: ${CONTAINER_PLATFORM}" + + # Check if osTicket containers are multi-arch (skip MySQL, as it's from Docker Hub) + if [[ "$CONTAINER" != "osticket-mysql" ]]; then + echo -e " Multi-arch support: " + if docker manifest inspect "$CONTAINER_IMAGE" &>/dev/null; then + SUPPORTED_ARCHS=$(docker manifest inspect "$CONTAINER_IMAGE" | grep -o '"architecture": "[^"]*"' | cut -d'"' -f4 | sort -u) + + for ARCH in $SUPPORTED_ARCHS; do + echo -e " - ${ARCH}" + done + + # Check if both amd64 and arm64 are supported + if echo "$SUPPORTED_ARCHS" | grep -q "amd64" && echo "$SUPPORTED_ARCHS" | grep -q "arm64"; then + echo -e " ${GREEN}✓ Image supports both amd64 and arm64 architectures${NC}" + else + echo -e " ${YELLOW}⚠ Image does not support both amd64 and arm64 architectures${NC}" + fi + else + echo -e " ${YELLOW}Single architecture image${NC}" + fi + fi + echo "" + done + + # Test HTTP connectivity + echo -e "${YELLOW}Testing HTTP connectivity...${NC}" + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/) + + if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 302 ]; then + echo -e "${GREEN}✓ HTTP test successful (HTTP $HTTP_CODE)${NC}" + else + echo -e "${RED}✗ HTTP test failed (HTTP $HTTP_CODE)${NC}" + fi + + echo -e "\n${GREEN}Test completed!${NC}" + echo -e "You can access osTicket at http://localhost:8080" + echo -e "To stop the test environment: docker-compose -f docker-compose.multi-arch.yml down -v" +fi + +echo -e "\n${YELLOW}Summary:${NC}" +if [ "$BUILD" = true ]; then + echo -e "${GREEN}✓ Multi-architecture images built successfully${NC}" +fi +if [ "$TEST" = true ] && [ "$ALL_RUNNING" = true ]; then + echo -e "${GREEN}✓ Multi-architecture images tested successfully${NC}" +fi +echo -e "Images ready for Kubernetes deployment!" diff --git a/include/class.api.php b/include/class.api.php index 2ebac92d2a..cadee34962 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -98,19 +98,63 @@ static function add($vars, &$errors) { } static function validate($key, $ip) { - return ($key && $ip && self::getIdByKey($key, $ip)); + if (!$key || !$ip) + return false; + + // Get the API key entry + $sql='SELECT id, ipaddr FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key); + if (!($res=db_query($sql)) || !db_num_rows($res)) + return false; + + list($id, $stored_ip) = db_fetch_row($res); + + // No IP restriction if field is empty + if (!$stored_ip) + return $id; + + // Check if stored IP is CIDR notation + if (strpos($stored_ip, '/') !== false) { + if (Validator::check_ip($ip, $stored_ip)) + return $id; + } + // Direct IP comparison + elseif ($stored_ip == $ip) { + return $id; + } + + return false; } static function getIdByKey($key, $ip='') { - - $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key); - if($ip) - $sql.=' AND ipaddr='.db_input($ip); - - if(($res=db_query($sql)) && db_num_rows($res)) - list($id) = db_fetch_row($res); - - return $id; + // If no IP provided, just look up the key + if (!$ip) { + $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key); + if(($res=db_query($sql)) && db_num_rows($res)) + list($id) = db_fetch_row($res); + return $id; + } + + // We need to check IP constraints + $sql='SELECT id, ipaddr FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key); + if(($res=db_query($sql)) && db_num_rows($res)) { + list($id, $stored_ip) = db_fetch_row($res); + + // No IP restriction + if (!$stored_ip) + return $id; + + // Check if stored IP is CIDR notation + if (strpos($stored_ip, '/') !== false) { + if (Validator::check_ip($ip, $stored_ip)) + return $id; + } + // Direct IP comparison + elseif ($stored_ip == $ip) { + return $id; + } + } + + return null; } static function lookupByKey($key, $ip='') { @@ -120,11 +164,36 @@ static function lookupByKey($key, $ip='') { static function lookup($id) { return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null; } + + /** + * Determine if a given string is valid CIDR notation + * + * @param string $cidr String to check + * @return bool True if the string is valid CIDR notation + */ + static function is_valid_cidr($cidr) { + if (strpos($cidr, '/') === false) + return false; + + list($ip, $netmask) = explode('/', $cidr, 2); + + // Validate the IP part + if (!Validator::is_ip($ip)) + return false; + + // Check if netmask is valid + if (!is_numeric($netmask) || + (strpos($ip, ':') !== false && ($netmask < 1 || $netmask > 128)) || // IPv6 + (strpos($ip, '.') !== false && ($netmask < 0 || $netmask > 32))) // IPv4 + return false; + + return true; + } static function save($id, $vars, &$errors) { - if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr']))) - $errors['ipaddr'] = __('Valid IP is required'); + if(!$id && (!$vars['ipaddr'] || (!Validator::is_ip($vars['ipaddr']) && !self::is_valid_cidr($vars['ipaddr'])))) + $errors['ipaddr'] = __('Valid IP or CIDR notation required (e.g. 192.168.1.1 or 192.168.1.0/24)'); if($errors) return false; @@ -198,10 +267,29 @@ function requireApiKey() { // see getApiKey method. if (!($key=$this->getKey())) return $this->exerr(401, __('Valid API key required')); - elseif (!$key->isActive() || $key->getIPAddr() != $this->getRemoteAddr()) - return $this->exerr(401, __('API key not found/active or source IP not authorized')); - - return $key; + + if (!$key->isActive()) + return $this->exerr(401, __('API key not found or not active')); + + // Check IP restrictions + $ipAddr = $key->getIPAddr(); + $remoteAddr = $this->getRemoteAddr(); + + // No IP restriction if field is empty + if (!$ipAddr) + return $key; + + // Handle CIDR notation + if (strpos($ipAddr, '/') !== false) { + if (Validator::check_ip($remoteAddr, $ipAddr)) + return $key; + } + // Direct IP comparison + elseif ($ipAddr == $remoteAddr) { + return $key; + } + + return $this->exerr(401, __('Source IP not authorized')); } function getKey() { diff --git a/include/i18n/en_US/help/tips/manage.api_keys.yaml b/include/i18n/en_US/help/tips/manage.api_keys.yaml index f7bdbdae78..37c7d65bef 100644 --- a/include/i18n/en_US/help/tips/manage.api_keys.yaml +++ b/include/i18n/en_US/help/tips/manage.api_keys.yaml @@ -28,5 +28,6 @@ api_key: ip_addr: title: IP Address content: > - Client's network IP address. Each unique client IP address will - require separate API keys + Client's network IP address. You can specify either a single IP address (e.g., 192.168.1.1) or + a CIDR notation to allow a range of IP addresses (e.g., 192.168.1.0/24 for a whole subnet). + Leave empty to allow access from any IP address. diff --git a/setup/doc/api.md b/setup/doc/api.md index db9774af74..3e1d4d441a 100644 --- a/setup/doc/api.md +++ b/setup/doc/api.md @@ -9,11 +9,14 @@ Authentication -------------- Authentication via the API is done via API keys configured inside the -osTicket admin panel. API keys are created and tied to a source IP address, -which will be checked against the source IP of requests to the HTTP API. +osTicket admin panel. API keys are created and tied to a source IP address or +network range (using CIDR notation), which will be checked against the source +IP of requests to the HTTP API. API keys can be created and managed via the admin panel. Navigate to Manage --> API keys. Use *Add New API Key* to create a new API key. Currently, no +-> API keys. Use *Add New API Key* to create a new API key. You can specify +either a single IP address (e.g., 192.168.1.1) or use CIDR notation to allow +a range of IPs (e.g., 192.168.1.0/24 for a whole subnet). Currently, no special configuration is required to allow the API key to be used for the HTTP API. All API keys are valid for the HTTP API.