added deployment file
This commit is contained in:
1 parent
061d5fd8a4
commit
125eab3cd4
14 files changed
+704
-1
No files matched your search
@@ -0,0 +1,42 @@
|
||||
ServerName localhost
|
||||
ServerTokens Prod
|
||||
ServerSignature Off
|
||||
TraceEnable Off
|
||||
|
||||
# Only expose this HTTP listener to a trusted reverse proxy. The proxy must
|
||||
# overwrite X-Forwarded-Proto, not pass a client-supplied value through.
|
||||
# PHP uses HTTPS to decide whether participant/admin cookies need Secure.
|
||||
SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on
|
||||
|
||||
<Directory /var/www/html>
|
||||
Options -Indexes -MultiViews
|
||||
AllowOverride None
|
||||
DirectoryIndex index.php
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Internal code is still usable by PHP/CLI, never directly over HTTP.
|
||||
<DirectoryMatch "^/var/www/html/(?:includes|DB|dev|docker|dashboard/(?:tools|migrations))(?:/|$)">
|
||||
Require all denied
|
||||
</DirectoryMatch>
|
||||
|
||||
# Also deny dotfiles and backup/config files if accidentally added later.
|
||||
<LocationMatch "(?:^|/)\.">
|
||||
Require all denied
|
||||
</LocationMatch>
|
||||
<FilesMatch "(?i)\.(?:sql(?:\.gz|\.zip)?|ini|env|toml|ya?ml|log|bak)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
Alias /healthz /usr/local/share/recruitment/health.php
|
||||
<Directory /usr/local/share/recruitment>
|
||||
Options -Indexes
|
||||
AllowOverride None
|
||||
Require all denied
|
||||
<Files health.php>
|
||||
Require all granted
|
||||
</Files>
|
||||
</Directory>
|
||||
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set Referrer-Policy "same-origin"
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
// Docker-only replacement for includes/conn.php; local/server config is untouched.
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
foreach (['MYSQL_HOST', 'MYSQL_DATABASE', 'MYSQL_USER', 'MYSQL_PASSWORD'] as $key) {
|
||||
if (getenv($key) === false || getenv($key) === '') {
|
||||
throw new RuntimeException('Missing database environment variable: ' . $key);
|
||||
}
|
||||
}
|
||||
$conn = new mysqli(
|
||||
getenv('MYSQL_HOST'),
|
||||
getenv('MYSQL_USER'),
|
||||
getenv('MYSQL_PASSWORD'),
|
||||
getenv('MYSQL_DATABASE'),
|
||||
(int) (getenv('MYSQL_PORT') ?: 3306)
|
||||
);
|
||||
$conn->set_charset('utf8mb4');
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
try {
|
||||
require '/var/www/html/includes/conn.php';
|
||||
// A database connection alone is insufficient: the initial import and
|
||||
// migrations must also be present before this instance is ready for users.
|
||||
foreach (['in_daper', 'in_soal', 'in_jawab', 'rec_admin', 'rec_exam_attempt', 'rec_exam_event', 'rec_candidate_login_guard'] as $table) {
|
||||
$conn->query("SELECT 1 FROM `$table` LIMIT 0");
|
||||
}
|
||||
$conn->query('SELECT email, nama_de, nama_be, idkar, password_hash FROM rec_admin LIMIT 0');
|
||||
echo "ok\n";
|
||||
} catch (Throwable $error) {
|
||||
http_response_code(503);
|
||||
// Never expose connection details, credentials, or schema errors over HTTP.
|
||||
echo "not ready\n";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// Check Apache + PHP + database readiness without requiring curl in the image.
|
||||
$socket = @fsockopen('127.0.0.1', 80, $errno, $error, 3);
|
||||
if (!$socket) {
|
||||
exit(1);
|
||||
}
|
||||
stream_set_timeout($socket, 3);
|
||||
fwrite($socket, "GET /healthz HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n");
|
||||
$status = fgets($socket);
|
||||
fclose($socket);
|
||||
exit(is_string($status) && preg_match('/^HTTP\/\d\.\d 200\b/', $status) ? 0 : 1);
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
require '/var/www/html/includes/conn.php';
|
||||
foreach (['in_daper', 'in_soal', 'in_jawab'] as $table) {
|
||||
$conn->query("SELECT 1 FROM `$table` LIMIT 0");
|
||||
}
|
||||
|
||||
foreach (['001_admin.sql', '002_admin_identity.sql', '003_exam_attempts.sql'] as $file) {
|
||||
// Only migrate the historical rec_admin schema, preserving identities.
|
||||
if ($file === '002_admin_identity.sql'
|
||||
&& $conn->query("SHOW COLUMNS FROM rec_admin LIKE 'name'")->num_rows === 0) {
|
||||
continue;
|
||||
}
|
||||
$sql = file_get_contents('/var/www/html/dashboard/migrations/' . $file);
|
||||
$conn->multi_query($sql);
|
||||
do {
|
||||
$result = $conn->store_result();
|
||||
if ($result instanceof mysqli_result) {
|
||||
$result->free();
|
||||
}
|
||||
} while ($conn->more_results() && $conn->next_result());
|
||||
fwrite(STDOUT, "Applied: $file\n");
|
||||
}
|
||||
fwrite(STDOUT, "Database ready. Existing accounts and test data were preserved.\n");
|
||||
} catch (Throwable $error) {
|
||||
fwrite(STDERR, 'Migration failed: ' . $error->getMessage() . "\nImport the base database first; see docs/deployment.md.\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
expose_php = Off
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
log_errors = On
|
||||
error_log = /proc/self/fd/2
|
||||
error_reporting = E_ALL
|
||||
date.timezone = Asia/Jakarta
|
||||
|
||||
session.use_strict_mode = 1
|
||||
session.use_only_cookies = 1
|
||||
session.cookie_httponly = 1
|
||||
session.cookie_samesite = Lax
|
||||
session.save_path = /var/lib/php/sessions
|
||||
|
||||
; Secure cookies are set by the application when Apache receives the HTTPS
|
||||
; indication from the trusted proxy (see apache.conf).
|
||||
Reference in new issue
Block a user