Files
Gnuboard7/public/install/includes/config.php
T
HeuJung 01d319e74b fix(core,installer): 프로세스 출력 인코딩으로 인한 JSON 빈 응답 차단
한국어 Windows 에서 whoami 가 CP949 로 출력해 계정명에 한글이 있으면
invalid UTF-8 이 응답 배열에 실리고 json_encode 가 false 를 반환한다.
echo false 는 빈 문자열이라 HTTP 200 + 빈 본문이 나가는데 예외도 로그도
남지 않아, 설치 마법사 2단계가 진행 불가 상태로 멈춘다.

2026-07 수정(7.0.2)은 로그 축 4곳만 막았다. 출처에서 정규화하고(1차),
응답 경계를 단일 헬퍼로 닫고(2차), 실패 시 문제 필드 경로를 로그에
남기도록(3차) 세 층으로 처리해 다른 경로가 열린 채 남지 않게 했다.

정규화는 복원 가능한 코드페이지 출력을 먼저 되살리므로, 종전 mb_scrub
단독 처리와 달리 한글이 U+FFFD 로 훼손되지 않는다.

같은 원인으로 한국어 Windows 에서 관리자 환경설정의 시스템 정보가
500 이 되던 것도 함께 막았다.
2026-08-29 15:13:14 +09:00

233 lines
8.2 KiB
PHP

<?php
/**
* 그누보드7 웹 인스톨러 설정 파일
*
* 인스톨러의 기본 상수와 설정을 정의합니다.
*/
// 프로젝트 루트 경로 (public/install/includes에서 3단계 상위)
if (! defined('BASE_PATH')) {
define('BASE_PATH', realpath(dirname(__DIR__, 3)) ?: dirname(__DIR__, 3));
}
// UTF-8 정규화 / JSON 출력 헬퍼 (의존성 0 — 가장 먼저 로드)
require_once __DIR__.'/utf8.php';
// 인스톨러 기본 URL
$installerPath = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
// install이 없으면 추가
if (substr($installerPath, -8) !== '/install') {
$installerPath .= '/install';
}
// 다음 define 들은 가드 필수 — 테스트 환경에서 같은 프로세스가 config.php 를 직접/간접
// 재진입(예: Laravel bootstrap + 테스트 require)할 수 있어 PHP 9 에서 fatal 이 되는
// 중복 정의 warning 을 차단.
if (! defined('INSTALLER_BASE_URL')) {
define('INSTALLER_BASE_URL', $installerPath);
}
// 소프트웨어 최초 출시 연도 (저작권 표시용)
if (! defined('APP_RELEASE_YEAR')) {
define('APP_RELEASE_YEAR', '2026');
}
// 최소 PHP 버전
if (! defined('MIN_PHP_VERSION')) {
define('MIN_PHP_VERSION', '8.2.0');
}
// 필수 PHP 모듈
if (! defined('REQUIRED_EXTENSIONS')) {
define('REQUIRED_EXTENSIONS', [
'pdo',
'mbstring',
'openssl',
'tokenizer',
'xml',
'ctype',
'json',
'fileinfo',
'curl',
'dom',
'filter',
'hash',
'pcre',
'session',
]);
}
// 선택적 PHP 모듈 (설치를 권장하지만 필수는 아님)
if (! defined('OPTIONAL_EXTENSIONS')) {
define('OPTIONAL_EXTENSIONS', [
'zlib', // gzip 압축 지원 (응답 압축에 사용)
'gd', // 이미지 처리
'imagick', // 고급 이미지 처리
'redis', // Redis 캐시 지원
'intl', // 국제화 지원
]);
}
// 최소 디스크 공간 (MB)
if (! defined('MIN_DISK_SPACE_MB')) {
define('MIN_DISK_SPACE_MB', 500);
}
// DB 테이블 접두사 최대 길이.
// MySQL identifier 한도(64자) 안에서 자동 생성 인덱스명이 안전하도록 제한한다.
// 가장 긴 자동 생성 인덱스명(접두사 제외)이 58자이므로 58 + 6 = 64 로 정확히 한도에 맞는다.
// 7자 이상 접두사는 일부 인덱스명이 65자가 되어 마이그레이션이 실패한다.
if (! defined('MAX_DB_PREFIX_LENGTH')) {
define('MAX_DB_PREFIX_LENGTH', 6);
}
// 디렉토리 권한 설정 (8진수)
// 업계 표준 755 (WordPress/Drupal/Joomla/Laravel 공통) — 실제 통과 기준은 is_writable() && is_readable()
if (! defined('REQUIRED_DIRECTORY_PERMISSIONS')) {
define('REQUIRED_DIRECTORY_PERMISSIONS', 0755);
}
// 권한 표시용 문자열 (사용자에게 보여줄 형식)
if (! defined('REQUIRED_DIRECTORY_PERMISSIONS_DISPLAY')) {
define('REQUIRED_DIRECTORY_PERMISSIONS_DISPLAY', '755');
}
// 권한 검증이 필요한 디렉토리 목록
// 값이 true인 경우 하위 디렉토리까지 재귀적으로 체크
if (! defined('REQUIRED_DIRECTORIES')) {
define('REQUIRED_DIRECTORIES', [
'storage' => true,
'bootstrap/cache' => false,
'vendor' => false,
'modules' => false,
'modules/_pending' => false,
'plugins' => false,
'plugins/_pending' => false,
'templates' => false,
'templates/_pending' => false,
'lang-packs' => false, // 언어팩 활성 디렉토리 (코어/모듈/플러그인/템플릿 lang-packs)
'lang-packs/_pending' => false, // 외부 ZIP/URL/GitHub 다운로드 staging
'storage/app/core_pending' => false, // 코어 업데이트 _pending (기본값, 커스텀 경로는 Step 3에서 설정)
]);
}
// 인스톨러 기본 설정값
if (! defined('DEFAULT_INSTALL_CONFIG')) {
define('DEFAULT_INSTALL_CONFIG', [
// Write DB 설정
'db_write_host' => 'localhost',
'db_write_port' => '3306',
'db_write_database' => '',
'db_write_username' => '',
'db_write_password' => '',
'db_prefix' => 'g7_',
// Read DB 설정
'use_read_db' => false,
'db_read_host' => 'localhost',
'db_read_port' => '3306',
'db_read_database' => '',
'db_read_username' => '',
'db_read_password' => '',
// 사이트 설정
'app_name' => '그누보드7',
'app_env' => 'production',
// 관리자 계정 설정
'admin_name' => '관리자',
'admin_email' => '',
'admin_password' => '',
'admin_language' => 'ko',
// 코어 업데이트 설정
'core_update_pending_path' => '', // 빈 값 = 기본값(storage/app/core_pending) 사용
'core_update_github_url' => 'https://github.com/gnuboard/g7',
'core_update_github_token' => '', // GitHub Personal Access Token (프라이빗 저장소용)
// PHP CLI / Composer 경로 설정
'php_binary' => 'php', // PHP CLI 바이너리 경로 (예: /usr/local/php82/bin/php)
'composer_binary' => '', // Composer 바이너리 경로 (빈 값 = 시스템 PATH 사용)
// Vendor 설치 모드 (auto|composer|bundled)
// - auto: composer 사용 가능 시 composer, 불가 시 vendor-bundle.zip 추출
// - composer: 강제 composer 실행
// - bundled: 강제 vendor-bundle.zip 추출 (공유 호스팅 환경)
'vendor_mode' => 'auto',
]);
}
// 설치 단계별 파일 매핑
// Step 5 (installation)에서 완료/실패/중단 화면까지 모두 처리
if (! defined('STEP_FILE_MAP')) {
define('STEP_FILE_MAP', [
0 => 'welcome',
1 => 'license',
2 => 'requirements',
3 => 'configuration',
4 => 'extension-selection',
5 => 'installation',
]);
}
// 인스톨러 기본 상태 정의
if (! defined('DEFAULT_INSTALLATION_STATE')) {
define('DEFAULT_INSTALLATION_STATE', [
'current_step' => 0,
'step_status' => [
'0' => 'pending',
'1' => 'pending',
'2' => 'pending',
'3' => 'pending',
'4' => 'pending',
'5' => 'pending',
],
'completed_tasks' => [],
'current_task' => null,
'current_task_name' => null,
'installation_status' => 'not_started',
'config' => [],
'selected_extensions' => [
'admin_templates' => [],
'user_templates' => [],
'modules' => [],
'plugins' => [],
'language_packs' => [],
],
'extension_names' => [], // 확장 이름 매핑 (identifier → {ko: '...', en: '...'})
]);
}
// 지원 언어 목록 (언어 추가 시 이 한 곳만 수정)
if (! defined('SUPPORTED_LANGUAGES')) {
define('SUPPORTED_LANGUAGES', [
'ko' => '한국어 (Korean)',
'en' => 'English',
]);
}
// 설치 완료 후 인스톨러 파일 삭제 여부
if (! defined('DELETE_INSTALLER_AFTER_COMPLETE')) {
define('DELETE_INSTALLER_AFTER_COMPLETE', true);
}
// 코어와 공유하는 판정 SSoT 클래스 로드.
//
// 인스톨러는 Laravel 오토로드 없이 도는 순수 PHP 이므로 파일을 직접 require 한다.
// 두 클래스는 프레임워크 의존성이 0 이라 이 방식이 성립하며, 인스톨러와 코어가
// 서로 다른 판정을 내리는 것을 구조적으로 차단한다.
// class_exists 가드: Laravel 부트가 선행된 테스트 환경에서 중복 로드 방지.
//
// 오토로드를 **막지 않는다**(2번째 인자 생략). `false` 를 주면 오토로드 가능한
// 클래스를 못 본 채 require 로 내려가는데, BASE_PATH 를 임시 디렉토리로 바꿔 두는
// 인스톨러 단위 테스트에서는 그 경로에 app/Support 가 없어 fatal 이 된다.
// 순수 인스톨러 실행 시에는 등록된 오토로더가 없어 false 를 반환하므로 require 가 그대로 돈다.
if (! class_exists('App\\Support\\PrivilegedDatabaseAccounts')) {
require_once BASE_PATH.'/app/Support/PrivilegedDatabaseAccounts.php';
}
if (! class_exists('App\\Support\\OpcacheStatus')) {
require_once BASE_PATH.'/app/Support/OpcacheStatus.php';
}