# Panduan Deploy ke Shared Hosting (cPanel)

Aplikasi ini adalah aplikasi Laravel biasa. Karena hosting yang tersedia hanya
shared hosting (PHP + MySQL, kemungkinan tanpa akses SSH), ikuti langkah berikut.

## 1. Upload Aplikasi

Ada dua cara umum:

**A. Jika domain/subdomain bisa diarahkan ke folder manapun (custom document root)**
Upload seluruh isi folder `Starterkit/` ke luar `public_html` (misalnya ke
`~/presensi-app/`), lalu arahkan document root domain/subdomain ke
`~/presensi-app/public`.

**B. Jika document root domain WAJIB `public_html`**
Upload seluruh isi folder `Starterkit/` ke sebuah folder di luar `public_html`
(misalnya `~/presensi-app/`), lalu:
1. Salin isi folder `public/` ke dalam `public_html/`.
2. Edit `public_html/index.php`, ubah path `require` agar menunjuk ke lokasi
   `presensi-app/vendor/autoload.php` dan `presensi-app/bootstrap/app.php` yang benar.

## 2. Konfigurasi `.env`

Salin `.env.example` menjadi `.env` di server, lalu isi:

```
APP_NAME="Presensi Kehadiran"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://domain-anda.ac.id
APP_TIMEZONE=Asia/Jakarta

# Wajib true di produksi (HTTPS) -- memaksa cookie sesi hanya terkirim
# lewat HTTPS, tidak pernah lewat HTTP biasa sama sekali.
SESSION_SECURE_COOKIE=true

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=nama_database_cpanel
DB_USERNAME=user_database_cpanel
DB_PASSWORD=password_database_cpanel
```

Generate `APP_KEY` baru (jangan pakai APP_KEY dari lokal):
```
php artisan key:generate
```

## 3. WAJIB: Aktifkan HTTPS (SSL)

Fitur **presensi berbasis lokasi (GPS) hanya bisa berjalan di browser HP jika
situs diakses lewat HTTPS** (`navigator.geolocation` diblokir browser modern di
halaman HTTP biasa — gagal dengan pesan "Izin akses lokasi ditolak" tanpa
pernah menampilkan dialog izin sama sekali, walau mahasiswa tidak pernah
menolak apa pun). Aktifkan AutoSSL/Let's Encrypt gratis dari menu cPanel
sebelum aplikasi digunakan mahasiswa.

**Mengaktifkan SSL saja tidak cukup** kalau situsnya tetap bisa diakses lewat
`http://` biasa tanpa dialihkan — mahasiswa yang membuka link lama/bookmark/
link WA tanpa `https://` akan tetap kena masalah GPS di atas walau SSL-nya
sudah aktif dan valid. `public/.htaccess` sudah memaksa redirect otomatis ke
HTTPS (`RewriteCond %{HTTPS} off`), jadi setelah SSL aktif, **cek ulang**
`http://domain-anda.ac.id/` di browser benar-benar dialihkan ke `https://`
(bukan tetap menampilkan halaman seperti biasa) sebelum dibagikan ke mahasiswa.

## 3b. Fitur QR Presensi butuh extension GD

Fitur opsional **QR Presensi** (tombol QR di menu Kegiatan Presensi) membuat
gambar QR di server memakai extension **GD**, yang hampir selalu sudah aktif
default di shared hosting cPanel. Kalau setelah deploy tombol "Tampilkan QR"
error, cek di cPanel > **Select PHP Version** > **Extensions** apakah `gd`
sudah dicentang.

## 3c. Fitur Export PDF Rekap butuh extension GD, DOM, dan Mbstring

Fitur **Export PDF** di halaman Rekap Presensi mahasiswa memakai library
`barryvdh/laravel-dompdf`, yang butuh extension PHP **gd**, **dom**, dan
**mbstring**. Ketiganya hampir selalu aktif default di shared hosting cPanel
(sama seperti GD untuk fitur QR di atas). Kalau tombol "Export PDF" error
setelah deploy, cek extension yang sama di cPanel > **Select PHP Version**.

## 3d. Fitur Import/Export Excel butuh extension ZIP (paling sering jadi penyebab 500 di hosting)

Fitur **Import Data Mahasiswa** dan **Export Excel** (template contoh,
Rekap Ketidakhadiran) memakai library `maatwebsite/excel`, yang di baliknya
butuh extension PHP **zip**, selain **xml**, **dom**, **mbstring**, **gd**
(yang beberapa sudah dicek di 3b/3c). Beda dari extension lain di atas,
**zip tidak selalu aktif default** di sebagian konfigurasi shared hosting
cPanel yang lebih minim — ini penyebab paling umum kalau tombol
**"Import"** di menu Data Mahasiswa langsung menampilkan **500 Internal
Server Error** padahal fitur lain berjalan normal.

Cara cek & aktifkan: cPanel > **Select PHP Version** > **Extensions** >
pastikan `zip` (dan `xml`, `dom`, `mbstring`, `gd`) tercentang, lalu coba
import ulang. Kalau `zip` tidak muncul di daftar Extensions sama sekali,
hubungi penyedia hosting untuk mengaktifkannya di level server (php.ini),
karena beberapa hosting mengunci extension tertentu dari kontrol cPanel.

**Cara memastikan penyebabnya benar dari sini** (bukan sekadar menebak):
lihat pesan error asli di `storage/logs/laravel.log` (lewat File Manager
cPanel) tepat setelah percobaan import yang gagal — kalau penyebabnya
memang extension `zip`, pesannya akan menyebut `ZipArchive` atau
`ext-zip`.

## 3e. Import ratusan baris mahasiswa BARU sekaligus bisa kena timeout 30 detik

Kalau error yang muncul justru **"Maximum execution time of 30 seconds
exceeded"** (`Symfony\Component\ErrorHandler\Error\FatalError`) -- ini BUKAN
soal extension, tapi batas waktu eksekusi PHP bawaan hosting (umumnya 30
detik) yang terlampaui. Penyebabnya: setiap mahasiswa BARU yang di-import
butuh `Hash::make()` (bcrypt) untuk password-nya, dan bcrypt SENGAJA dibuat
lambat (~50-100ms per panggilan, ciri keamanannya) -- untuk ratusan baris
baru sekaligus, itu bisa menumpuk sampai puluhan detik, apalagi di CPU
hosting yang biasanya lebih terbatas dibanding komputer lokal.

`Admin\MahasiswaController::import()` sudah menaikkan batas ini sendiri
lewat `set_time_limit(300)` khusus untuk proses import (tidak mengubah
`php.ini` server secara global), jadi kalau kode ini sudah dideploy,
seharusnya tidak lagi kena batas 30 detik bawaan itu.

**Kalau setelah update kode ini masih tetap timeout** (mis. tetap berhenti
persis di detik yang sama), kemungkinan besar batasnya ada di level
**PHP-FPM sendiri** (`request_terminate_timeout` di pool config), yang
terpisah dari `max_execution_time` PHP dan **tidak bisa diubah dari kode
PHP manapun** -- ini murni pengaturan server. Solusinya:
- Minta penyedia hosting menaikkan `request_terminate_timeout` untuk akun
  Anda, ATAU
- Pecah file Excel-nya jadi beberapa bagian lebih kecil (mis. per 100-150
  baris) dan import bertahap, sampai penyedia hosting menaikkan batas itu.

Catatan: batas ini HANYA relevan untuk baris mahasiswa yang benar-benar
BARU. Re-import file yang sama untuk memperbarui data mahasiswa yang
SUDAH ADA jauh lebih cepat (hitungan detik, bukan puluhan detik) karena
password tidak dihash ulang kalau tanggal lahirnya tidak berubah.

## 3f. WAJIB untuk hosting produksi: aktifkan queue worker (email pendaftaran perangkat, kode OTP admin)

Email tautan pendaftaran perangkat (lihat `app/Mail/PendaftaranPerangkatMail.php`)
dan fitur **Kirim Massal Link Pendaftaran** dikirim lewat `Mail::queue()`,
bukan `Mail::send()` langsung -- supaya kirim ke banyak mahasiswa sekaligus
tidak kena timeout seperti kasus import di atas. Tapi ini artinya email
TIDAK benar-benar terkirim sampai ada **queue worker** yang memprosesnya.

**Kode OTP login dua faktor** (`app/Mail/KodeOtpMail.php`, dipakai siapa
pun yang mengaktifkan dua faktor di menu **Akun Saya**) dan notifikasi
`DuaFaktorStatusDiubahMail` juga lewat `Mail::queue()` -- kalau queue
worker tidak aktif, admin yang 2FA-nya sudah aktif akan **terkunci total**
dari aplikasi (kode tidak akan pernah sampai sama sekali, bukan cuma
tertunda seperti kasus pendaftaran perangkat di atas). Pastikan langkah di
bawah ini benar-benar aktif SEBELUM ada admin yang mengaktifkan dua faktor.

**Default `.env` masih `QUEUE_CONNECTION=sync`** (email langsung terkirim
seketika, cocok untuk lokal/testing tanpa setup tambahan apa pun). Untuk
produksi, WAJIB ganti supaya email benar-benar terkirim di latar belakang:

1. Ubah di `.env`: `QUEUE_CONNECTION=database`.
2. Pastikan migration sudah dijalankan (`php artisan migrate`) -- tabel
   `jobs` dan `failed_jobs` sudah termasuk di migration bawaan aplikasi ini.
3. Di cPanel, buka **Cron Jobs**, tambahkan tugas baru yang menjalankan
   perintah ini **setiap 1 menit** (atau interval terkecil yang diizinkan
   paket hosting Anda):
   ```
   cd /home/namauser/public_html && php artisan queue:work --stop-when-empty >> /dev/null 2>&1
   ```
   (sesuaikan path `public_html` dengan lokasi aplikasi di akun hosting Anda).

Tanpa langkah ini, baris di tabel `jobs` akan menumpuk tanpa pernah
terproses -- email tidak akan pernah benar-benar terkirim ke mahasiswa,
walau halaman admin tetap menampilkan "berhasil" (karena memang berhasil
masuk antrian, cuma belum ada yang memprosesnya).

## 4. Menjalankan Migration

Jika cPanel menyediakan fitur **Terminal**:
```
cd ~/presensi-app
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan db:seed --force
php artisan config:cache
php artisan route:cache
```

Jika **tidak ada akses Terminal/SSH**, jalankan migration lewat route sementara.
Tambahkan baris berikut di `routes/web.php` (di baris paling atas, sebelum rute
lain), ganti `RAHASIA123` dengan string acak yang hanya Anda tahu:

```php
Route::get('/jalankan-migrasi-RAHASIA123', function () {
    Artisan::call('migrate', ['--force' => true]);
    Artisan::call('db:seed', ['--force' => true]);
    return 'Migrasi selesai.';
});
```

Upload perubahan ini, buka URL tersebut sekali di browser, lalu **segera hapus
kembali route ini dan upload ulang** agar tidak bisa diakses orang lain.

## 5. Izin Folder

Pastikan folder berikut dapat ditulis oleh web server (biasanya 755, kadang
perlu 775 tergantung konfigurasi hosting):
- `storage/`
- `bootstrap/cache/`

## 6. Setelah Live

- Login admin pertama kali dengan kredensial dari seeder (lihat
  `database/seeders/DatabaseSeeder.php` — **segera ganti password** lewat
  `Forgot password?` atau `php artisan tinker` di Terminal cPanel jika tersedia).
- Tambahkan satu atau lebih lokasi presensi di menu **Lokasi Presensi** (bisa lebih dari
  satu, misalnya per gedung/lapangan/jurusan).
- Import data mahasiswa lewat menu **Data Mahasiswa > Import**.
- Buat kegiatan presensi di menu **Kegiatan Presensi**, lalu pilih lokasi presensinya
  masing-masing.
- (Opsional, per kegiatan) Tekan ikon QR di daftar Kegiatan Presensi untuk menampilkan
  QR presensi di layar/proyektor lokasi — mahasiswa wajib scan QR ini dulu sebelum bisa
  lanjut ke form presensi GPS. Kegiatan yang QR-nya tidak pernah ditampilkan tetap bisa
  dipresensi seperti biasa tanpa scan (fitur ini opsional, tidak wajib dipakai).
- (Opsional) Buat template ekspor di menu **Template Ekspor** (logo, nama institusi,
  judul dokumen) lalu jadikan aktif — dipakai sebagai kop dokumen PDF rekap presensi
  yang bisa diunduh mahasiswa. Tanpa template aktif, PDF tetap bisa dibuat dengan kop
  bawaan (nama aplikasi saja, tanpa logo).
- Setelah suatu kegiatan selesai dan datanya sudah dicek, klik ikon verifikasi (perisai)
  di daftar Kegiatan Presensi. Mahasiswa hanya bisa meng-export kegiatan yang sudah
  diverifikasi — kegiatan yang belum diverifikasi tetap tampil di rekap web mahasiswa
  tapi tidak ikut di file PDF export.
- Bagikan URL login mahasiswa: `https://domain-anda.ac.id/portal/login`.

## 7. Backup Database (Cron Job)

Shared hosting sering sudah punya backup otomatis bawaan (cPanel Backup/
JetBackup) — cek dulu di menu **Backup** apakah itu sudah aktif dan
retensinya cukup (berapa hari ke belakang disimpan). Sebagai tambahan
(atau kalau fitur itu tidak ada/tidak cukup), skrip `scripts/backup-database.sh`
sudah disediakan untuk dijadwalkan lewat **Cron Job**:

1. Pastikan skrip bisa dieksekusi (sekali saja, lewat Terminal cPanel kalau
   ada, atau File Manager > klik kanan file > Permissions > centang
   "Execute"):
   ```
   chmod +x ~/presensi-app/scripts/backup-database.sh
   ```
2. Di cPanel, buka **Cron Jobs**, tambahkan tugas baru — disarankan jam
   dini hari (mis. jam 2 pagi WIB) supaya tidak bersamaan dengan jam sibuk
   presensi:
   ```
   0 2 * * * /home/namauser/presensi-app/scripts/backup-database.sh >> /home/namauser/backup-db-presensi/cron.log 2>&1
   ```
   (sesuaikan `namauser` dan path `presensi-app` dengan lokasi aplikasi di
   akun hosting Anda).
3. Hasil backup (`.sql.gz`, otomatis terhapus setelah 14 hari) tersimpan di
   `~/backup-db-presensi/` — folder ini **di luar `public_html`**, jadi
   tidak bisa diunduh siapa pun lewat browser. Sesekali unduh manual lewat
   File Manager/FTP ke tempat LAIN (komputer sendiri, Google Drive, dst) —
   backup yang cuma tersimpan di server yang sama tidak banyak membantu
   kalau server itu sendiri yang bermasalah.
4. Setelah cron berjalan minimal sekali, cek `~/backup-db-presensi/cron.log`
   dan pastikan ada baris "Backup selesai" tanpa error -- skrip ini belum
   pernah dites di lingkungan cPanel sungguhan, jadi verifikasi hasilnya
   benar-benar jalan sebelum diandalkan.

## Catatan Keamanan

- Login mahasiswa (NIM + tanggal lahir) sengaja tidak memakai password
  terpisah sesuai permintaan, sehingga entropinya rendah. Aplikasi sudah
  membatasi percobaan login (rate limiting), tapi pertimbangkan untuk tidak
  mempublikasikan NIM secara luas.
- Registrasi akun admin baru (`/register`) sudah DINONAKTIFKAN
  (`Auth::routes(['register' => false])` di `routes/web.php`) dan
  `RegisterController` bawaan scaffold Laravel sudah dihapus dari kode --
  tidak perlu tindakan tambahan apa pun untuk ini.
- Jalankan `composer audit` secara berkala (idealnya tiap kali sebelum
  deploy) untuk mengecek celah keamanan yang baru ditemukan di
  dependency pihak ketiga, lalu `composer update` (tanpa mengubah batas
  versi di `composer.json`, supaya tidak ikut lompat ke versi major yang
  bisa breaking) untuk menariknya.
