한국어 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 이 되던 것도 함께 막았다.
233 lines
8.2 KiB
PHP
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';
|
|
}
|