shopt -s nullglob nocaseglob; for f in *.heic; do convert "$f" "${f%.*}.jpg"; done
Tips Paket Internet Pomo Special
4G Ceria Telkomsel - Agar Tidak Habis Pulsa..
4G Ceria Telkomsel - Agar Tidak Habis Pulsa..
Cara Install driver printer Epson L321 di Ubuntu Linux
Cara Install driver Printer Canon di Ubuntu Linux
shopt -s nullglob nocaseglob; for f in *.heic; do convert "$f" "${f%.*}.jpg"; done
Ya, benar. Perintah itu dijalankan di database MariaDB, tepatnya pada database sekolahku.
Contohnya:
mysql sekolahku
Lalu di prompt MariaDB:
ALTER TABLE galleries
ADD COLUMN updated_at DATETIME
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
AFTER created_at;
Perintah ini hanya perlu dijalankan sekali untuk setiap database yang di-upgrade ke v3.1.0 dan masih memiliki struktur lama.
Menurut saya ini merupakan bug pada paket upgrade v3.1.0.
Karena:
Sitemap.php mengharapkan kolom galleries.updated_at.
Tetapi database hasil upgrade tidak memiliki kolom tersebut.
Akibatnya sitemap.xml gagal.
Jadi seharusnya pada UPGRADE.sql ada tambahan seperti:
ALTER TABLE galleries
ADD COLUMN updated_at DATETIME
DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
AFTER created_at;
Kalau nanti Anda meng-upgrade CMS Sekolahku v3.1.0 di server lain (HG680P maupun Debian), setelah menjalankan UPGRADE.sql, tambahkan juga perintah tersebut pada database jika kolom updated_at belum ada di tabel galleries.
Dengan begitu, bug sitemap.xml tidak akan muncul lagi.
Menurut saya, temuan ini layak dilaporkan ke pengembang CMS Sekolahku agar dimasukkan ke UPGRADE.sql atau diperbaiki pada rilis berikutnya.
Nah, ketemu penyebabnya. 👍
File belum diubah. Baris 54 masih berisi:
<textarea name="content"
class="form-control"
rows="16"
placeholder="Write your article content here..."
required
style="display:none"><?= $item['content'] ?? '' ?></textarea>
Itulah yang menyebabkan browser selalu menolak submit.
<textarea name="content"
class="form-control"
rows="16"
placeholder="Write your article content here..."><?= $item['content'] ?? '' ?></textarea>
Atau dalam satu baris:
<textarea name="content" class="form-control" rows="16" placeholder="Write your article content here..."><?= $item['content'] ?? '' ?></textarea>
Artinya hapus:
required
style="display:none"
php8.5 spark cache:clear
php8.5 spark optimize
systemctl restart php8.5-fpm
Kemudian lakukan hard refresh di browser:
Ctrl + Shift + R
Jalankan lagi:
grep -n "textarea name=\"content\"" -A3 -B2 /var/www/cms-sekolahku/app/Views/admin/news/form.php
Hasilnya tidak boleh lagi mengandung:
required
style="display:none"
Menurut saya ini memang bug pada rilis v3.1.0.
Karena kalau menggunakan Quill, seharusnya <textarea> memang disembunyikan, tetapi tidak boleh diberi required. Validasi harus dilakukan di JavaScript atau di controller, bukan oleh browser terhadap elemen yang disembunyikan.
Kalau setelah menghapus dua atribut itu tombol masih belum bekerja, saya akan bantu membuat patch yang benar agar Quill dan validasi bekerja bersama tanpa perlu menghilangkan editor.
spark harus dijalankan di folder CMScd /var/www/cms-sekolahku
php8.5 spark cache:clear
php8.5 spark optimize
systemctl bisa dijalankan dari mana sajaMisalnya masih di folder CMS juga tidak masalah:
systemctl restart php8.5-fpm
atau dari /root juga boleh.
bisa login menggunakan user biasa maupun root jika diperlukan.
sudo apt update
sudo apt install openssh-server -y
Pastikan aktif:
sudo systemctl enable ssh
sudo systemctl start ssh
sudo systemctl status ssh
Secara default Debian membuat akun root, tetapi kadang belum memiliki password.
sudo passwd root
Masukkan password root.
sudo nano /etc/ssh/sshd_config
Cari atau tambahkan baris berikut:
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
UsePAM yes
Jika ada baris yang diawali #, hilangkan tanda #.
Contoh konfigurasi minimal:
Port 22
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
UsePAM yes
X11Forwarding no
sudo systemctl restart ssh
Sebagai user biasa:
ssh guru@192.168.1.10
Sebagai root:
ssh root@192.168.1.10
Saya menyarankan akses root hanya melalui jaringan Tailscale, sedangkan dari jaringan lokal atau internet gunakan user biasa lalu sudo jika diperlukan.
Misalnya interface Tailscale adalah tailscale0, Anda dapat mengatur firewall (UFW atau nftables) sehingga login root hanya dapat dilakukan melalui jaringan Tailscale. Ini lebih aman jika nanti server diakses dari luar.
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
UsePAM yes
Kemudian:
✔ Login sebagai user biasa (guru, admin, dll.) untuk pekerjaan sehari-hari.
✔ Login sebagai root hanya saat konfigurasi sistem atau keadaan darurat.
✔ Setelah Tailscale dan Cloudflared selesai dipasang, pertimbangkan mengubah PermitRootLogin menjadi prohibit-password atau without-password dan gunakan SSH key untuk akun root. Dengan begitu login root tetap bisa dilakukan, tetapi jauh lebih aman daripada menggunakan password.
Baik. Kita pasang GIMP 3.x menggunakan Flatpak, karena ini adalah cara resmi untuk mendapatkan versi terbaru di Ubuntu 24.04 tanpa mengganggu paket sistem.
sudo apt update
sudo apt install flatpak
Agar aplikasi Flatpak muncul dan terintegrasi dengan GNOME:
sudo apt install gnome-software-plugin-flatpak
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install flathub org.gimp.GIMP
Saat diminta:
Proceed with these changes to the system installation? [Y/n]:
ketik:
y
Tunggu hingga selesai (unduhan pertama bisa cukup besar karena runtime juga ikut dipasang).
flatpak info org.gimp.GIMP
atau
flatpak run org.gimp.GIMP --version
Seharusnya tampil versi 3.x.
Dari terminal:
flatpak run org.gimp.GIMP
Atau buka menu Activities → cari GIMP.
Semua aplikasi Flatpak dapat diperbarui dengan:
flatpak update
Setelah GIMP selesai terpasang, jalankan:
flatpak install flathub org.gimp.GIMP.Plugin.GMic
Plugin ini menambahkan ratusan filter dan efek (AI denoise, artistic, sharpen, repair, dll.) yang sangat berguna untuk mengolah foto dan gambar.
Saran: Setelah instalasi selesai, kirimkan hasil dari:
flatpak info org.gimp.GIMP
Saya akan memastikan Anda benar-benar mendapatkan GIMP 3.x dan, jika perlu, membantu mengaktifkan plugin G'MIC atau font tambahan.
run-parts /etc/update-motd.d/
_ _ _ _ _
/_\ _ _ _ __ | |__(_)__ _ _ _ __ ___ _ __ _ __ _ _ _ _ (_) |_ _ _
/ _ \| '_| ' \| '_ \ / _` | ' \ / _/ _ \ ' \| ' \ || | ' \| | _| || |
/_/ \_\_| |_|_|_|_.__/_\__,_|_||_| \__\___/_|_|_|_|_|_\_,_|_||_|_|\__|\_, |
|__/
v26.8 rolling for aml-s9xx-box running Armbian Linux 6.18.33-current-meson64
Packages: Debian stable (trixie)
Updates: Kernel upgrade enabled and 10 packages available for upgrade
Support: for advanced users (rolling release)
IPv4: (LAN) 192.168.10.196, 100.73.52.27, 192.168.0.111 (WAN) 140.213.133.19
Performance:
Load: 30% Uptime: 14h 21m Local users: 3
Memory usage: 44% of 1.87G
CPU temp: 59°C Usage of /: 63% of 5.7G
Tips:
What’s new in Armbian v26.5? https://tinyurl.com/5cwr7wfh
Commands:
Configuration: armbian-config
Upgrade : armbian-upgrade
Monitoring : htop
Menurut saya alur akhirnya menjadi seperti ini:
Dengan tiga proses itu, hampir seluruh riwayat akademik akan tercatat otomatis tanpa perlu mengedit satu per sat
*convert logo, pilih 1 perintah saja
convert logo.jpg -resize 256x256 favicon.png
magick logo.jpg -resize 256x256 favicon.png
*buat file ico
convert favicon.png -define icon:auto-resize=16,32,48,64,128,256 favicon.ico
magick favicon.png -define icon:auto-resize=16,32,48,64,128,256 favicon.ico
*salin ke server
scp favicon.ico root@stb:/var/www/cms-sekolahku/public/
*jika sudah di server
cp favicon.ico /var/www/cms-sekolahku/public/favicon.ico
chown www-data:www-data /var/www/cms-sekolahku/public/favicon.ico
*bersihkan cache cloudflared
Masuk ke Cloudflare Dashboard:
Caching → Cache → Purge Cache
Purge single file:
https://al-ihsan.jurnaldo.my.id/favicon.ico
*kirim file ke stb
scp ~/Downloads/cms-sekolahku-v3.0.1.zip root@stb:/root/
*ekstrak
cd /root
mkdir /root/cms-sekolahku
unzip cms-sekolahku-v3.0.1.zip -d /root/cms-sekolahku
*Buat database
mysql
CREATE DATABASE sekolahku
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'sekolahku'@'localhost'
IDENTIFIED BY 'PasswordKuat';
GRANT ALL PRIVILEGES
ON sekolahku.*
TO 'sekolahku'@'localhost';
FLUSH PRIVILEGES;
EXIT;
*Import database
mysql sekolahku < sekolahku_db.sql
*Edit .env, pastikan:
CI_ENVIRONMENT = production
app.baseURL = 'https://ihsan.jurnaldo.my.id/'
database.default.hostname = localhost
database.default.database = sekolahku
database.default.username = sekolahku
database.default.password = PasswordKuat
database.default.DBDriver = MySQLi
*
nano /etc/nginx/sites-available/cms-sekolahku
isikan:
server {
listen 8084;
server_name ihsan.jurnaldo.my.id;
root /var/www/cms-sekolahku/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
*aktifkan situs
ln -s /etc/nginx/sites-available/cms-sekolahku \
/etc/nginx/sites-enabled/
*cek konfigurasi
nginx -t
*reload
systemctl reload nginx
*pindahkan ke web root
mv /root/cms-sekolahku /var/www/
*set permission
chown -R www-data:www-data /var/www/cms-sekolahku
chmod -R 755 /var/www/cms-sekolahku
chmod -R 775 /var/www/cms-sekolahku/writable
chmod -R 775 /var/www/cms-sekolahku/public/uploads
chown -R www-data:www-data /var/www/cms-sekolahku
find /var/www/cms-sekolahku -type d -exec chmod 755 {} \;
find /var/www/cms-sekolahku -type f -exec chmod 644 {} \;
chmod -R 775 /var/www/cms-sekolahku/writable
chmod -R 775 /var/www/cms-sekolahku/public/uploads
*edit composer
nano /var/www/cms-sekolahku/composer.json
ubah "php": "^8.5", menjadi "php": "^8.2",
ubah:
"platform": {
"php": "8.5.0"
}
menjadi:
"platform": {
"php": "8.2"
}
*hapus vendor lama
cd /var/www/cms-sekolahku
rm -rf vendor composer.lock
*jalankan pasang composer
cd /var/www/cms-sekolahku
composer install
bila
/var/www/cms-sekolahku# composer install Do not run Composer as root/super user! See https://getcomposer.org/root for details Continue as root/super user [yes]?
ketik yes dan [enter]
*bersihkan cache Code Igniter
cd /var/www/cms-sekolahku
php spark cache:clear
*reload nginx
systemctl reload nginx
*login
https://ihsan.jurnaldo.my.id/admin
username: administrator
password: 12345
**reset password jika diperlukan
buat hash baru misal:
php -r "echo password_hash('Admin123', PASSWORD_DEFAULT), PHP_EOL;"
update ke database dengan:
UPDATE users
SET password='HASIL_HASH_DARI_PERINTAH_DI_ATAS'
WHERE username='administrator';
Cara paling cepat (tanpa copy-paste hash)
Anda juga bisa menjalankan satu perintah berikut dari shell Linux:
HASH=$(php -r "echo password_hash('Admin123', PASSWORD_DEFAULT);")
mysql sekolahku -e "UPDATE users SET password='$HASH' WHERE username='administrator';"
Audit keamanan source code jauh lebih mendalam daripada sekadar mencari eval(base64_decode()). Tujuannya adalah menjawab pertanyaan: "Apakah CMS ini aman dipasang di server yang terhubung ke internet?"
Untuk CMS cms-sekolahku-v3.0.1, saya biasanya melakukan audit dalam beberapa tahap.
| Tahap | Yang diperiksa | Risiko |
|---|---|---|
| 1 | Struktur proyek | File asing, backdoor |
| 2 | Konfigurasi | Password, API key, debug mode |
| 3 | Login & Session | Bypass login, session hijacking |
| 4 | SQL | SQL Injection |
| 5 | Upload file | Upload shell PHP |
| 6 | XSS | Script berbahaya |
| 7 | CSRF | Form tanpa token |
| 8 | Hak akses | Privilege escalation |
| 9 | Library pihak ketiga | Versi rentan/CVE |
| 10 | Kualitas kode | Error handling, sanitasi |
Kode yang berbahaya misalnya:
$id = $_GET['id'];
$sql = "SELECT * FROM siswa WHERE id=$id";
$db->query($sql);
Seharusnya menggunakan prepared statement atau Query Builder.
Misalnya:
move_uploaded_file($_FILES['file']['tmp_name'],
"uploads/".$_FILES['file']['name']);
tanpa validasi.
Saya akan cek:
hanya jpg/png/pdf?
MIME type
ukuran
rename file
blokir php
simpan di luar public_html bila memungkinkan
Misalnya
echo $_POST['nama'];
harusnya
esc($nama)
atau
htmlspecialchars(...)
Saya akan cek
password di-hash?
pakai bcrypt/Argon2?
session_regenerate_id()
remember me aman?
brute force protection
Apakah setiap POST mempunyai token.
Misalnya URL
/admin/user/delete/5
Apakah user biasa bisa membuka URL itu.
Misalnya
CI_ENVIRONMENT = development
di server produksi.
Atau
display_errors = On
Saya cari
password=
apikey=
secret=
Bearer
JWT
smtp
Misalnya CodeIgniter 4.1
padahal sekarang sudah ada patch keamanan.
Saya cek:
framework
composer
vendor
CVE yang diketahui
Selain
eval(base64_decode())
saya juga mencari
assert()
preg_replace('/e')
create_function()
call_user_func()
Reflection
file_get_contents("http://")
curl_exec()
exec()
shell_exec()
passthru()
proc_open()
dan memastikan penggunaannya memang sah.
Saya biasanya membuat laporan seperti ini:
Security Audit CMS Sekolahku
★★★★★ Critical
- Tidak ada
★★★★ High
- 1 SQL Injection pada modul Alumni
★★★ Medium
- Upload gambar belum memeriksa MIME Type
★★ Low
- Debug masih aktif
★ Info
- Framework CodeIgniter 4.5.2
- Composer dependency OK
lalu saya sertakan
lokasi file
nomor baris
penyebab
dampak
cara memperbaikinya
contoh kode yang benar.
Karena CMS ini akan dipasang pada server sekolah dan diakses dari internet, saya menyarankan audit penuh. Saya bisa memeriksa seluruh proyek (termasuk folder app, public, vendor, dan konfigurasi) dan menghasilkan laporan keamanan yang cukup lengkap, bukan hanya mencari malware. Dengan begitu kita bisa mengetahui apakah ada celah seperti SQL Injection, XSS, upload file berbahaya, atau masalah otorisasi sebelum sistem digunakan di lingkungan produksi.
Menurut saya, keputusan yang paling tepat untuk plugin Anda adalah berhenti menggunakan binaan.csv dan menggantinya dengan satu tabel master Guru Wali.
Saya menyarankan struktur berikut:
CREATE TABLE mdl_local_jurnalmengajar_guruwali (
id BIGINT(10) NOT NULL AUTO_INCREMENT,
guruid BIGINT(10) NOT NULL,
muridid BIGINT(10) NOT NULL,
timecreated BIGINT(10) NOT NULL,
timemodified BIGINT(10) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_murid (muridid),
KEY idx_guru (guruid),
KEY idx_murid (muridid)
);
Karena sekarang saya sudah melihat keseluruhan plugin Anda.
Semua memakai
mdl_user.id
Semua modul juga memakai
muridid = mdl_user.id
Misalnya:
✅ Jurnal Guru Wali
✅ Riwayat Individu
✅ Pembinaan BK
✅ Layanan BK
✅ Pembinaan Guru Mapel
✅ Rekap Kehadiran
Semuanya sudah menggunakan muridid.
Jadi Guru Wali tinggal mengikuti arsitektur yang sudah ada, bukan membuat konsep baru.
Tambahkan tabel SQL.
Belum mengubah halaman apa pun.
Buat script migrasi sekali saja.
binaan.csv
↓
cari userid murid dari NIS
↓
INSERT
Setelah selesai tabel akan berisi:
| guruid | muridid |
|---|---|
| 1172 | 821 |
| 1172 | 845 |
| 34 | 902 |
Ubah halaman satu per satu.
Urutannya saya sarankan:
guruwali_manage.php
guruwali_add.php
guruwali_delete.php
guruwali_view.php
jurnalguruwali.php
rekap_kehadiran_muridwali.php
lib_notifikasi.php
Semuanya cukup membaca tabel baru.
Hapus
binaan.csv
beserta semua fungsi:
jw_load_binaan_csv()
jw_find_user_by_nis()
jw_get_murid_options_from_csv()
Karena sudah tidak diperlukan lagi.
Nanti saat awal tahun ajaran:
Import User
Import Cohort
Guru Wali tetap
Tidak ada lagi edit CSV.
Tidak ada lagi masalah:
XII masih muncul
kelas lama masih tampil
nama murid tidak berubah
Karena setiap halaman tinggal melakukan:
Guru
↓
local_jurnalmengajar_guruwali
↓
muridid
↓
mdl_user
↓
cohort aktif
Sehingga nama dan kelas selalu mengikuti kondisi terbaru di Moodle.
Karena Anda memang sedang mengerjakan proses import cohort tahunan, saya akan menganggap ini sebagai versi 2.0 dari fitur Guru Wali.
Perubahan yang dilakukan hanya pada master data Guru Wali. Seluruh tabel jurnal dan riwayat yang sudah ada tetap dipertahankan, sehingga risiko terhadap data lama sangat kecil, sementara manfaatnya besar untuk pemeliharaan plugin di tahun-tahun berikutnya.
Saya sudah meninjau struktur fitur Guru Wali pada plugin local_jurnalmengajar. Dari file yang ada, fiturnya sudah cukup lengkap dan membentuk satu alur kerja yang jelas.
Import CSV binaan
│
▼
binaan.csv (dataroot)
│
├── guruwali_manage.php
├── guruwali_view.php
├── jurnalguruwali.php
├── rekap_kehadiran_muridwali.php
└── lib_notifikasi.php
Artinya seluruh fitur Guru Wali bergantung pada satu file binaan.csv, bukan tabel database.
Administrator dapat:
Import CSV
Download format CSV
Tambah murid binaan
Update murid binaan
Hapus murid binaan
Melihat daftar murid binaan setiap guru
File:
import_binaan.php
download_format_binaan.php
guruwali_add.php
guruwali_manage.php
guruwali_delete.php
guruwali_view.php
Guru wali dapat:
✔ memilih beberapa murid sekaligus
✔ mengisi
Topik
Tindak lanjut
Keterangan
kemudian otomatis dibuat satu record untuk setiap murid.
Misalnya memilih:
Ahmad
Budi
Cici
maka langsung menjadi tiga record jurnal.
Ini sudah cukup efisien.
Sudah tersedia.
Guru dapat mengubah:
topik
tindak lanjut
keterangan
tanpa membuat data baru.
Di bawah form terdapat:
Riwayat 10 jurnal terakhir.
Bisa diedit kembali.
Halaman:
rekap_kehadiran_muridwali.php
Fitur:
filter tanggal
mode per hari
mode per jam
rekap
Hadir
Sakit
Ijin
Alpa
Dispensasi
dan bisa membuka detail per murid.
Sesudah jurnal disimpan:
Guru wali cukup menginput sekali.
Plugin akan:
mengelompokkan berdasarkan kelas
membuat pesan
mengirim ke tujuan sesuai setting
menggunakan template
template_guru_wali
yang placeholdernya sudah lengkap.
Sudah tersedia template yang bisa diubah admin.
Placeholder:
waktu
murid
kelas
topik
tindak lanjut
keterangan
guru wali
Semua sudah dipakai ketika mengirim WA.
Kalau melihat keseluruhan plugin, fitur Guru Wali sebenarnya sudah sekitar 75–80% lengkap. Yang masih bisa dikembangkan agar menjadi lebih berguna bagi wali kelas dan BK adalah:
Saat guru membuka menu Guru Wali, langsung tampil:
Jumlah murid binaan : 18
Jurnal bulan ini : 24
Belum pernah dibina : 5
Sudah dibina : 13
Tidak perlu membuka rekap dahulu.
Klik nama murid
↓
Muncul seluruh histori
10 Januari
Topik ...
20 Januari
Topik ...
15 Februari
Topik ...
sehingga perkembangan siswa mudah dipantau.
Misalnya
Ahmad
█████████ 9
Budi
██ 2
Cici
██████ 6
langsung terlihat siapa yang sering dibina.
Saat murid binaan sudah banyak (30–40 orang),
lebih nyaman jika ada kotak pencarian nama.
Misalnya
Disiplin 14
Terlambat 9
Motivasi 6
Bullying 1
berguna untuk evaluasi.
Misalnya sekolah menentukan
setiap murid minimal mendapat pembinaan 1 kali per semester.
Plugin dapat menampilkan:
Sudah dibina
16
Belum dibina
4
Ini akan sangat membantu monitoring.
Saat membuka profil murid:
Riwayat Guru Wali
Riwayat BK
Riwayat Pembinaan Guru Mapel
Riwayat Surat Izin
Semua muncul pada satu halaman sehingga menjadi semacam profil pembinaan siswa yang terintegrasi.
Jika kita mengembangkan fitur ini secara bertahap, saya menyarankan urutannya:
Riwayat lengkap per murid (paling bermanfaat dan relatif mudah).
Dashboard Guru Wali dengan statistik ringkas.
Monitoring target pembinaan per semester.
Integrasi seluruh riwayat pembinaan (Guru Wali, BK, Guru Mapel, Surat Izin) menjadi satu profil siswa.
Menurut saya, empat pengembangan tersebut akan membuat modul Guru Wali terasa jauh lebih matang dan menjadi pusat pemantauan perkembangan siswa, bukan sekadar tempat mencatat jurnal.
Ya, rsync sangat cocok untuk kebutuhan itu. Yang Anda inginkan adalah:
✅ File .php di root → masuk ke /var/www/shared/local/jurnalmengajar/
✅ File .php di subfolder → masuk ke subfolder yang sesuai di STB
✅ File non-.php yang sudah ada di STB tetap utuh
✅ File non-.php dari laptop tidak dikirim
✅ Tidak menghapus file apa pun di STB
Gunakan perintah berikut dari laptop:
rsync -av \
--include='*/' \
--include='*.php' \
--exclude='*' \
./ \
root@stb:/var/www/shared/local/jurnalmengajar/
Karena sumbernya adalah ./, maka struktur folder akan dipertahankan.
Misalnya di laptop:
all_php_shared_stb/
├── lib.php
├── index.php
├── classes/
│ ├── helper.php
│ └── api.php
├── db/
│ └── install.php
└── cli/
└── cron.php
Maka di STB akan menjadi:
/var/www/shared/local/jurnalmengajar/
├── lib.php ← diperbarui
├── index.php ← diperbarui
├── classes/
│ ├── helper.php ← diperbarui
│ ├── api.php ← diperbarui
│ └── style.css ← tetap ada
├── db/
│ ├── install.php ← diperbarui
│ └── upgrade.xml ← tetap ada
└── cli/
└── cron.php ← diperbarui
File seperti:
style.css
logo.png
icon.svg
template.mustache
lang/id/local_jurnalmengajar.php (jika bukan PHP tidak akan terkirim)
tidak akan dihapus karena Anda tidak menggunakan --delete.
Saya sarankan cek dulu dengan mode simulasi:
rsync -avn \
--include='*/' \
--include='*.php' \
--exclude='*' \
./ \
root@stb:/var/www/shared/local/jurnalmengajar/
Jika daftar file yang akan disalin sudah benar, jalankan kembali tanpa -n:
rsync -av \
--include='*/' \
--include='*.php' \
--exclude='*' \
./ \
root@stb:/var/www/shared/local/jurnalmengajar/
Perintah ini aman untuk sinkronisasi kode PHP karena hanya memperbarui file .php dan tidak menghapus file lain yang sudah ada di STB.
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
enable
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0
Berikut semua alias yang ada di .bashrc Anda beserta fungsinya.
| Alias | Perintah | Fungsi |
|---|---|---|
ls | ls --color=auto | Menampilkan daftar file/folder dengan warna agar lebih mudah dibaca. |
grep | grep --color=auto | Menyorot hasil pencarian teks dengan warna. |
fgrep | fgrep --color=auto | Versi grep untuk pencarian string literal dengan warna. |
egrep | egrep --color=auto | Versi grep untuk regular expression extended dengan warna. |
ll | ls -alF | Menampilkan daftar file lengkap (izin, pemilik, ukuran, file tersembunyi, dll.). |
la | ls -A | Menampilkan semua file termasuk yang tersembunyi, kecuali . dan ... |
l | ls -CF | Menampilkan daftar file dalam format ringkas dengan indikator tipe file. |
alert | notify-send ... | Menampilkan notifikasi desktop setelah perintah yang lama selesai dijalankan. |
cetakizin | ssh root@100.106.40.93 print_remote_surat | Login ke server melalui SSH dan menjalankan perintah print_remote_surat untuk mencetak surat izin dari server. |
yta | mpv --no-video --ytdl-raw-options=force-ipv4= --ytdl-format=best | Memutar audio dari YouTube menggunakan MPV tanpa menampilkan video. |
ytv | mpv --ytdl-raw-options=force-ipv4= --ytdl-format=best | Memutar video YouTube menggunakan MPV. |
serveron | sudo systemctl start docker && cd /home/dhwan/immich && docker compose up -d | Menyalakan layanan Docker kemudian menjalankan stack Docker Immich. |
serveroff | cd /home/dhwan/immich && docker compose down && sudo systemctl stop docker | Menghentikan stack Docker Immich lalu mematikan layanan Docker. |
moodleon | sudo systemctl start nginx php8.2-fpm mariadb | Menyalakan layanan Moodle (Nginx, PHP-FPM, dan MariaDB). |
moodleoff | sudo systemctl stop nginx php8.2-fpm mariadb | Mematikan layanan Moodle (Nginx, PHP-FPM, dan MariaDB). |
ls
ll
la
l
grep
fgrep
egrep
alert
cetakizin
yta → Dengarkan audio YouTube.
ytv → Putar video YouTube.
serveron
serveroff
moodleon
moodleoff
Selain alias tersebut, ada satu baris yang bukan alias:
neofetch
Baris ini akan menjalankan neofetch setiap kali Anda membuka terminal untuk menampilkan informasi sistem (OS, kernel, CPU, RAM, dll.).
Mantap, Guru Duan! 🎉
php tools/fet2csv.php "/home/dhwan/FET SMA2 CONTOH/2026-2027/HASIL/jadwal-sman2kandangan-26juni-0910-6/jadwal-sman2kandangan-26juni-0910_activities_time_horizontal.html"
Menurut saya kita sudah mencapai target awal dengan baik.
Kalau nanti sumbernya pindah folder?
Tidak masalah.
****************************************
Misalnya hasil FET tahun depan ada di:
/home/dhwan/JADWAL/2027/HASIL/
cukup ganti path-nya.
php tools/fet2csv.php \
"/home/dhwan/JADWAL/2027/HASIL/jadwal_2027_activities_time_horizontal.html"
Script tidak perlu diubah sama sekali.
*****************************************
## Yang sudah berhasil dibuat
✅ Membaca file hasil FET:
```text
activities_time_horizontal.html
```
✅ Parsing seluruh aktivitas.
✅ Mengambil:
* Guru
* Hari
* Kelas
* Mata Pelajaran
* Jam
✅ Mengelompokkan aktivitas menjadi satu record per sesi.
✅ Mengekspor ke CSV UTF-8 (dengan BOM) sehingga langsung terbuka rapi di LibreOffice Calc maupun Excel.
Format akhirnya:
```csv
guru,userid,hari,kelas,jamke,mapel
Dwiyani Lestari M.Pd,,Senin,XI-B,"2,3","Bahasa Indonesia"
```
---
## Alur kerja nantinya menjadi sederhana
```text
FET Generate
│
▼
activities_time_horizontal.html
│
▼
php tools/fet2csv.php activities_time_horizontal.html
│
▼
jadwal.csv
│
▼
LibreOffice Calc
│
├── isi userid (VLOOKUP/XLOOKUP)
▼
Import ke Moodle Jurnal Mengajar
```
---
Dari daftar yang Anda tampilkan, terdapat 141 file berekstensi .php di direktori:
/var/www/html/moodle/local/jurnalmengajar
Perhitungan tersebut tidak menghitung:
Direktori (amd, assets, classes, cli, css, db, lang, pix, templates, vendor, dll.)
File non-PHP seperti:
composer.json
composer.lock
jadwal_pengawas.json
File backup seperti:
export_berita_acara_pdf.php.save
tv.php.save
tv.php.save.1
Jika yang dimaksud adalah semua file PHP secara rekursif (termasuk di dalam classes/, cli/, db/, dll.), maka jumlahnya akan lebih banyak. Anda bisa mengeceknya langsung di Debian dengan:
find /var/www/html/moodle/local/jurnalmengajar -type f -name "*.php" | wc -l
atau untuk melihat daftarnya:
find /var/www/html/moodle/local/jurnalmengajar -type f -name "*.php" | sort
tail -100 /var/moodledata/logs/wa_debug.log
Karena yang muncul adalah HTTP 500 Internal Server Error, berarti ada fatal error PHP, bukan error routing. File izin_guru.php memang ada di plugin Anda, sehingga penyebabnya hampir pasti berasal dari kode yang baru diubah.
Langkah tercepat adalah melihat error sebenarnya.
Jalankan di server:
tail -50 /var/log/apache2/error.log
atau jika memakai PHP-FPM:
tail -50 /var/log/php8.2-fpm.log
atau jika menggunakan Nginx:
tail -50 /var/log/nginx/error.log
Kalau Moodle mengaktifkan debugging, Anda juga bisa:
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = 1;
atau melalui Site administration → Development → Debugging.
Kemungkinan terbesar ada pada blok notifikasi WA yang baru ditambahkan, misalnya:
ada koma yang hilang pada array,
ada kurung } atau ) yang tidak seimbang,
memanggil fungsi yang belum ada,
atau ada syntax error setelah penambahan:
jm_kirim_template_auto(...)
atau
$datawa = [
...
];
Tolong kirim salah satu dari berikut:
Isi file izin_guru.php terbaru, atau minimal bagian yang baru Anda ubah (terutama blok notifikasi WA), atau
Hasil:
php -l /var/www/html/moodle/local/jurnalmengajar/izin_guru.php
Perintah php -l biasanya langsung menunjukkan:
Parse error:
Fatal error:
Unexpected ...
on line ...
sehingga kita bisa menemukan letak error dalam hitungan menit.
Menurut saya, pengembangan yang kita lakukan kali ini bukan sekadar menambah fitur, tetapi menambahkan satu modul baru yang terintegrasi dengan sistem reminder jurnal.
Dibuat tabel baru (tanpa upgrade plugin), misalnya:
mdl_local_jurnalmengajar_kehadiran
Fungsinya menyimpan data guru yang tidak hadir.
Status yang didukung:
Sakit
Izin
Cuti
Tugas Luar
Status Alpa sengaja tidak dibuat, karena guru yang alpa tetap harus masuk ke reminder jurnal.
Dibuat halaman administrasi:
guru_takhadir.php
beserta CRUD:
✅ guru_takhadir.php
✅ guru_takhadir_add.php
✅ guru_takhadir_edit.php
✅ guru_takhadir_delete.php
Fitur:
pilih guru (role local/jurnalmengajar:submit)
status
periode mulai
periode selesai
keterangan
lib.phpDibuat fungsi:
jurnalmengajar_get_status_takhadir($userid, $tanggal)
Fungsinya:
mengecek apakah guru sedang tidak hadir
mengembalikan:
false
sakit
izin
cuti
tugasluar
Sehingga logika pengecekan cukup dipanggil dari satu fungsi.
notif_jurnal.phpAlur lama:
Jadwal
↓
Jam terlewat
↓
Sudah isi?
↓
Reminder
Sekarang menjadi:
Jadwal
↓
Cutoff
↓
Jam terlewat
↓
Guru Tidak Hadir?
│
├── Ya
│ ↓
│ Rekap Guru Tidak Hadir
│
└── Tidak
↓
Sudah isi jurnal?
│
├── Ya
└── Belum → Reminder
Akibatnya:
Guru sakit tidak menerima reminder.
Guru izin tidak menerima reminder.
Guru cuti tidak menerima reminder.
Guru tugas luar tidak menerima reminder.
Guru alpa tetap menerima reminder.
Rekap yang sebelumnya hanya berisi:
Guru Belum Mengisi Jurnal
sekarang menjadi:
Rekap Jurnal Mengajar
Belum Mengisi Jurnal
...
Guru Tidak Hadir
...
Sehingga admin dapat membedakan:
siapa yang memang lupa mengisi jurnal,
siapa yang memang tidak masuk.
Ditambahkan placeholder baru:
{tidakhadir}
Template default juga diperbarui agar menampilkan dua bagian:
Belum Mengisi Jurnal
Guru Tidak Hadir
Ditambahkan debug baru:
FILLED:
TAKHADIR:
Contoh:
TAKHADIR: Ahmad | XI-A | Sakit
TAKHADIR: Ahmad | XI-B | Sakit
Sehingga proses pengecekan lebih mudah ditelusuri saat menjalankan:
php local/jurnalmengajar/cli/notif_jurnal.php
Sekarang sistem reminder jurnal mampu membedakan tiga kondisi guru:
| Kondisi Guru | Reminder WA | Rekap Admin |
|---|---|---|
| Sudah mengisi jurnal | ❌ Tidak | ❌ Tidak |
| Belum mengisi jurnal | ✅ Ya | ✅ Ya |
| Sakit / Izin / Cuti / Tugas Luar | ❌ Tidak | ✅ Ya |
Menurut saya, ini adalah peningkatan yang cukup signifikan karena sebelumnya sistem hanya mengenal dua kondisi:
Sudah mengisi jurnal.
Belum mengisi jurnal.
Sekarang sistem mengenal tiga kondisi operasional:
Sudah mengisi jurnal → tidak perlu tindakan.
Belum mengisi jurnal → dikirim reminder.
Guru tidak hadir (sakit, izin, cuti, tugas luar) → tidak dikirim reminder, tetapi tetap tercatat dalam rekap admin.
Dengan demikian, reminder menjadi lebih akurat dan rekap yang diterima admin lebih informatif serta sesuai dengan kondisi pembelajaran di sekolah.
<?php
define('CLI_SCRIPT', true);
require_once(__DIR__.'/../../../config.php');
require_once(__DIR__.'/../jam_pelajaran_lib.php');
require_once(__DIR__.'/../jadwal_acuan_lib.php');
require_once(__DIR__.'/../lib.php');
require_once(__DIR__.'/../lib_notifikasi.php');// fungsi kirim WA
global $DB;
$cohortmap = [];
$cohorts = $DB->get_records('cohort', null, '', 'id,name');
foreach ($cohorts as $c) {
$cohortmap[$c->id] = $c->name;
}
$today = date('Y-m-d');
$hariIndo = jurnalmengajar_get_hari_ini();
$current = time();
$todayLabel = tanggal_indo(time());
$jamrekap = '19:50';
$jamsekarang = date('H:i');
//$isrekap = ($jamsekarang >= $jamrekap);
$isrekap = true; //test mode rekap
// ===== Cek hari sekolah =====
$hariSekolah = get_config('local_jurnalmengajar', 'harisekolah');
if (empty($hariSekolah)) {
$hariSekolah = 'Senin,Selasa,Rabu,Kamis,Jumat';
}
$hariSekolah = array_map('trim', explode(',', $hariSekolah));
if (!in_array($hariIndo, $hariSekolah)) {
mtrace("Hari $hariIndo bukan hari sekolah.");
exit(0);
}
// ===== Cek tanggal libur =====
if (jurnalmengajar_cek_libur($today)) {
mtrace("Hari ini tanggal libur.");
exit(0);
}
// ===== Cek tanggal asesmen =====
$tanggalasesmen = trim(get_config('local_jurnalmengajar', 'tanggalasesmen'));
if (!empty($tanggalasesmen)) {
if (preg_match('/(\d{4}-\d{2}-\d{2})\s*s\/d\s*(\d{4}-\d{2}-\d{2})/i',
$tanggalasesmen,
$match)) {
$mulai = strtotime($match[1]);
$selesai = strtotime($match[2]);
$hariini = strtotime($today);
if ($hariini >= $mulai && $hariini <= $selesai) {
mtrace("Hari ini berada dalam rentang asesmen.");
exit(0);
}
}
}
mtrace("=== Notifikasi Jurnal Rekap ===");
mtrace("Hari: $hariIndo");
// ===== Ambil jam pelajaran =====
$jam_pelajaran = jurnalmengajar_generate_jam();
// ===== Tentukan jam yang sudah selesai =====
$jam_terlewat = [];
foreach ($jam_pelajaran as $jamke => $jam) {
$selesai = $jam['selesai'];
if ($current > strtotime("$today $selesai")) {
$jam_terlewat[] = $jamke;
}
}
if (empty($jam_terlewat)) {
mtrace("Belum ada jam pelajaran yang terlewat.");
exit(0);
}
mtrace("Jam terlewat: " . implode(',', $jam_terlewat));
// ===== Ambil jurnal hari ini =====
$starttoday = strtotime("$today 00:00:00");
$endtoday = strtotime("$today 23:59:59");
$jurnaltoday = $DB->get_records_sql("
SELECT id, userid, kelas, jamke
FROM {local_jurnalmengajar}
WHERE timecreated BETWEEN :starttoday AND :endtoday
", [
'starttoday' => $starttoday,
'endtoday' => $endtoday
]);
$filled = [];
foreach ($jurnaltoday as $row) {
foreach (explode(',', $row->jamke) as $j) {
$j = (int)trim($j);
// Samakan kelas dengan jadwal
$kelas = $row->kelas;
if (isset($cohortmap[$kelas])) {
$kelas = $cohortmap[$kelas];
}
$key = $row->userid . '-' . $kelas . '-' . $j;
$filled[$key] = true;
// Debug
mtrace("FILLED: " . $key);
}
}
//
// ===== Ambil jadwal dari database =====
$jadwal_db = $DB->get_records_sql("
SELECT j.id, j.userid, j.kelas, j.jamke, u.lastname
FROM {local_jurnalmengajar_jadwal} j
JOIN {user} u ON u.id = j.userid
WHERE j.hari = :hari
", [
'hari' => $hariIndo
]);
$jadwal = [];
foreach ($jadwal_db as $j) {
$jadwal[] = [
'userid' => $j->userid,
'lastname' => $j->lastname,
'kelas' => $j->kelas,
'jamke' => $j->jamke
];
}
if (empty($jadwal)) {
mtrace("Tidak ada jadwal di database untuk hari $hariIndo");
exit(0);
}
mtrace("=== JADWAL ===");
foreach ($jadwal as $j) {
$k = $j['userid'].'-'.$j['kelas'].'-'.$j['jamke'];
mtrace("JADWAL: " . $k);
}
// ===== Group jurnal yang belum diisi =====
$pending = [];
$tidakhadir = [];
$cutoff_cache = [];
foreach ($jadwal as $j) {
// 🔥 FILTER CUT OFF MULTI KELAS
$kelas_level = null;
// deteksi kelas (VI, IX, XII)
if (preg_match('/\b(VI|IX|XII)\b/i', $j['kelas'], $match)) {
$kelas_level = strtoupper($match[1]);
}
if ($kelas_level) {
if (!isset($cutoff_cache[$kelas_level])) {
$cutoff_cache[$kelas_level] = jurnalmengajar_get_cutoff_by_kelas($kelas_level, $current);
}
$cutoff = $cutoff_cache[$kelas_level];
if ($cutoff && $current >= $cutoff) {
continue;
}
}
// ===== Lewati jika jam belum selesai =====
if (!in_array((int)$j['jamke'], $jam_terlewat)) {
continue;
}
// ===== Cek Guru Tidak Hadir =====
$status = jurnalmengajar_get_status_takhadir(
$j['userid'],
$today
);
if ($status !== false) {
if (!isset($tidakhadir[$j['userid']])) {
$tidakhadir[$j['userid']] = [
'lastname' => $j['lastname'],
'status' => $status
];
}
// Debug: tampilkan semua kelas yang sudah terlewat
mtrace(
"TAKHADIR: {$j['lastname']} | {$j['kelas']} | " .
ucfirst($status)
);
continue;
}
$key = $j['userid'] . '-' . $j['kelas'] . '-' . (int)$j['jamke'];
if (isset($filled[$key])) {
continue;
}
if (!isset($pending[$j['userid']])) {
$pending[$j['userid']] = [
'lastname' => $j['lastname'],
'kelasjam' => []
];
}
if (!isset($pending[$j['userid']]['kelasjam'][$j['kelas']])) {
$pending[$j['userid']]['kelasjam'][$j['kelas']] = [];
}
$pending[$j['userid']]['kelasjam'][$j['kelas']][] = (int)$j['jamke'];
}
if (empty($pending) && empty($tidakhadir)) {
mtrace("Semua jurnal sudah diisi.");
exit(0);
}
// ===== Kirim WA per guru =====
$mengirim = 0;
if (!$isrekap) {
mtrace("Mode: Reminder Guru");
foreach ($pending as $userid => $info) {
// $user = $DB->get_record('user', ['id'=>$userid], 'id, firstname, lastname');
// Ambil nomor WA
$nowa = $DB->get_field_sql("
SELECT d.data
FROM {user_info_data} d
JOIN {user_info_field} f ON f.id = d.fieldid
WHERE d.userid = :userid AND f.shortname = 'nowa'
", ['userid' => $userid]);
if (empty($nowa)) {
mtrace("Tidak ada nomor WA untuk {$info['lastname']}");
continue;
}
$nomor = preg_replace('/[^0-9]/', '', $nowa);
// Urutkan berdasarkan jam pertama
$urut = [];
foreach ($info['kelasjam'] as $kelas => $jamlist) {
$jamlist = array_unique($jamlist);
sort($jamlist);
$urut[$kelas] = $jamlist;
}
// Sort berdasarkan jam pertama
uasort($urut, function($a, $b) {
return $a[0] <=> $b[0];
});
$listkelas = "";
$ringkasParts = [];
foreach ($urut as $kelas => $jamlist) {
$listkelas .= "$kelas jam ke " . implode(',', $jamlist) . "\n";
$ringkasParts[] = $kelas . ':' . implode(',', $jamlist);
}
$ringkas = implode('; ', $ringkasParts);
$datawa = [
'{guru}' => $info['lastname'],
'{tanggal}' => $todayLabel,
'{kelasjam}' => trim($listkelas)
];
$res = jm_kirim_template(
'reminder_jurnal',
$nomor,
$datawa
);
$pending[$userid]['ringkas'] = $ringkas;
mtrace("Kirim ke $nomor ({$info['lastname']}) -> $res");
if ($res) {
$mengirim++;
}
// ===== Log TXT =====
$logtxt = __DIR__ . '/notif_log_' . date('Y-m-d') . '.txt';
$logstatus = $res ? 'BERHASIL' : 'GAGAL';
$line = date('Y-m-d H:i:s')
. " | Guru: {$info['lastname']}"
. " | Nomor: $nomor"
. " | Kelas/Jam: $ringkas"
. " | Status: $logstatus"
. "\n";
file_put_contents($logtxt, $line, FILE_APPEND);
}
} else {
// Mode rekap: tidak kirim WA ke guru,
// hanya membuat ringkasan.
mtrace("Mode: Rekap Admin");
foreach ($pending as $userid => $info) {
$urut = [];
foreach ($info['kelasjam'] as $kelas => $jamlist) {
$jamlist = array_unique($jamlist);
sort($jamlist);
$urut[$kelas] = $jamlist;
}
uasort($urut, function($a, $b) {
return $a[0] <=> $b[0];
});
$ringkasParts = [];
foreach ($urut as $kelas => $jamlist) {
$ringkasParts[] = $kelas . ':' . implode(',', $jamlist);
}
$pending[$userid]['ringkas'] = implode('; ', $ringkasParts);
}
}
if ($isrekap) {
$daftar = '';
$daftartakhadir = '';
if (empty($tidakhadir)) {
$daftartakhadir = '-';
}
foreach ($pending as $info) {
$daftar .= "• {$info['lastname']} - {$info['ringkas']}\n";
}
foreach ($tidakhadir as $info) {
$daftartakhadir .=
"• {$info['lastname']} - " .
ucfirst($info['status']) .
"\n";
}
$datawa = [
'{tanggal}' => $todayLabel,
'{daftar}' => trim($daftar),
'{jumlah}' => count($pending),
'{tidakhadir}' => trim($daftartakhadir)
];
// DEBUG
$config = get_config(
'local_jurnalmengajar',
'tujuan_rekap_reminder'
);
mtrace("CONFIG TUJUAN : " . $config);
$nomor = jm_get_nomor_tujuan(
'rekap_reminder',
$datawa
);
mtrace("NOMOR TUJUAN:");
print_r($nomor);
$template = jm_get_template('rekap_reminder');
mtrace("TEMPLATE:");
mtrace(empty($template) ? "(KOSONG)" : "ADA");
$res = jm_kirim_template_auto(
'rekap_reminder',
$datawa
);
if ($res) {
mtrace("Rekap reminder dikirim.");
} else {
mtrace("Rekap reminder gagal.");
}
}
mtrace("Selesai. Total notifikasi dikirim: $mengirim");
Karena kolom program di database menggunakan ENUM, maka ada 2 langkah.
Cek sekarang:
DESC jamaah;
Lalu ubah menjadi:
ALTER TABLE jamaah
MODIFY program ENUM(
'3_hari',
'10_hari',
'15_hari',
'40_hari',
'4_bulan',
'lainnya'
);
<select name="program">Di tambah.php dan edit.php, cari bagian:
<option value="10_hari"
<?= (($_POST['program'] ?? '') == '10_hari') ? 'selected' : '' ?>>
10 Hari
</option>
<option value="40_hari"
<?= (($_POST['program'] ?? '') == '40_hari') ? 'selected' : '' ?>>
40 Hari
</option>
Sisipkan di tengah:
<option
value="15_hari"
<?= (($_POST['program'] ?? '') == '15_hari') ? 'selected' : '' ?>>
15 Hari
</option>
Sehingga urutannya menjadi:
3 Hari
10 Hari
15 Hari
40 Hari
4 Bulan
Lainnya
Kalau ada halaman lain yang menampilkan program (misalnya index.php, rute.php, rekap.php), tidak perlu diubah karena yang disimpan tetap string 15_hari. Yang berubah hanya pilihan pada form dan nilai ENUM di database.
Bagus, sekarang sudah jelas. Anda tidak menggunakan Chrome + Chromium, melainkan dua profil Google Chrome:
Default → kemungkinan WhatsApp pribadi
Profile_1 → kemungkinan WhatsApp Business
Ini justru lebih bagus daripada memakai Chromium.
Lihat isi masing-masing launcher:
grep '^Name=' ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Default.desktop
grep '^Name=' ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Profile_1.desktop
Sekarang ubah namanya.
nano ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Default.desktop
Ubah:
Name=WhatsApp
nano ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Profile_1.desktop
Ubah:
Name=WhatsApp Business
Simpan, lalu jalankan:
update-desktop-database ~/.local/share/applications
Logout-login, atau tekan Alt+F2, ketik r (jika menggunakan X11), atau cukup tutup dan buka kembali menu aplikasi.
Mari kita lihat isi launcher yang kedua:
cat ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Profile_1.desktop
Terutama saya ingin melihat baris:
Icon=
Kalau Chrome menggunakan ikon lokal, kita bisa menggantinya dengan logo WhatsApp Business sehingga nanti di Dock tampil seperti ini:
🟢🅱 WhatsApp Business
Jadi tidak akan pernah tertukar.
Menurut saya ini adalah konfigurasi yang paling rapi untuk penggunaan sehari-hari.
pkill 5050
dhwan@dhwan-Aspire-A514-51K:~$ mocp
Running the server...
It seems that the server is already running with pid 5050.
If it is not true, remove the pid file (/home/dhwan/.moc/pid) and try again.
FATAL_ERROR: Exiting!
FATAL_ERROR: Server exited!
Pesan itu berarti proses MOC server masih dianggap berjalan dengan PID 5050, tetapi kemungkinan prosesnya sudah mati (stale PID).
Coba periksa dulu apakah proses 5050 benar-benar masih ada:
ps -p 5050
PID TTY TIME CMD
atau tidak ada proses, hapus file PID:
rm ~/.moc/pid
Lalu jalankan lagi:
mocp
Matikan dengan benar:
kill 5050
Jika masih membandel:
kill -9 5050
Lalu jalankan lagi:
mocp
kill sudah dilakukan tetapi tetap munculCek apakah ada beberapa proses MOC:
pgrep -a mocp
pgrep -a moc
ps aux | grep moc
Tolong kirimkan hasil dari ketiga perintah berikut:
ps -p 5050
ls -l ~/.moc/pid
cat ~/.moc/pid
Dari situ bisa diketahui apakah ini proses yang masih hidup atau hanya file PID yang tertinggal.
Kurang Guru
1 Al Quran = 42 JP
2 Sejarah = 27 JP
3 Bahasa Inggris = 19 JP
4 PJOK = 19 JP
5 Bahasa Indonesia = 18 JP
6 KWU = 18 JP
7 Matematika = 13 JP
8 Biologi = 10 JP
9 Ekonomi = 8 JP
10 Fisika = 7 JP
11 Sosiologi = 6 JP
12 Seni = 4 JP
13 Geografi = 4 JP
14 PKN = 4 JP
15 Informatika = 2 JP
bila dipakai untuk wakasek
Seni 6
Matematika 6
Kewirausahaan (KWU) 7
Sosiologi 4
Ekonomi 6
menjadi
1 Al Quran = 42 JP
2 Sejarah = 27 JP
3 Bahasa Inggris = 19 JP
4 PJOK = 19 JP
5 Bahasa Indonesia = 18 JP
6 KWU = 18 JP - seni 2 = 16 JP - KWU 7 = 11 JP
7 Matematika = 13 JP - matematika 6 = 7 JP
8 Biologi = 10 JP
9 Ekonomi = 8 JP - ekonomi 6 = 2 JP
10 Fisika = 7 JP
11 Sosiologi = 6 JP - sosiologi 4 = 2 JP
12 Seni = 4 JP - seni 4 = 0
13 Geografi = 4 JP
14 PKN = 4 JP
15 Informatika = 2 JP
Kesimpulan
Kelebihan jam
1 Al Quran = 42 JP
2 Sejarah = 27 JP
3 Bahasa Inggris = 19 JP
4 PJOK = 19 JP
5 Bahasa Indonesia = 18 JP
6 KWU = 11 JP
7 Matematika = 7 JP
8 Biologi = 10 JP
9 Ekonomi = 2 JP
10 Fisika = 7 JP
11 Sosiologi = 2 JP
12 Geografi = 4 JP
13 PKN = 4 JP
14 Informatika = 2 JP
Menu Timetable → Locking digunakan setelah FET berhasil membuat jadwal.
Fungsinya untuk mengunci hasil jadwal supaya ketika Generate ulang, aktivitas tertentu tidak dipindah lagi.
Lock all activities of the current timetable
Mengunci seluruh jadwal yang sudah dihasilkan.
Contoh:
Senin JP1 Matematika X-A
Senin JP2 Fisika X-A
...
semuanya dikunci.
Saat Generate lagi, FET harus mempertahankan posisi tersebut.
Unlock all activities of the current timetable
Membuka semua kunci.
FET bebas menyusun ulang dari awal.
Lock all activities of a specified day
Mengunci seluruh aktivitas pada hari tertentu.
Misalnya:
Senin
maka semua jadwal Senin tetap.
FET hanya boleh mengubah Selasa–Jumat.
Kebalikan dari nomor 3.
Lock all activities which end students day
Mengunci aktivitas yang berada pada jam terakhir siswa.
Jarang dipakai.
Misalnya Anda punya tag:
Praktikum
Agama
Lab
Semua aktivitas dengan tag tersebut dikunci.
Ini yang paling berguna.
Misalnya Anda ingin mengunci semua:
Matematika
atau
Guru tertentu
atau
Kelas X-A
tanpa mengunci yang lain.
Misalnya:
Jadwal sudah bagus 95%.
Hanya guru Matematika yang perlu diperbaiki.
Maka:
Lock all activities of current timetable
lalu buka (unlock) hanya aktivitas Matematika.
Kemudian Generate lagi.
FET hanya mengubah Matematika, yang lain tetap.
Saya sarankan jangan gunakan Locking dulu.
Karena Anda masih:
Menambah constraint MGMP
Mengatur jam pengawas
Mengatur batas jam guru
Biarkan FET menyusun ulang secara bebas sampai semua constraint final.
Locking biasanya dipakai setelah jadwal hampir selesai 100% dan hanya ada sedikit perbaikan.
Jumlah keseluruhan = 42 guru.
Guru yang memiliki aktivitas 2 JP dan juga 3 JP (minimal satu aktivitas 2 JP dan minimal satu aktivitas 3 JP) adalah:
Total: 24 guru.
Guru yang hanya memiliki aktivitas 2 JP
Total: 12 guru.
Guru yang seluruh aktivitasnya hanya berdurasi 3 JP (tidak memiliki aktivitas 1 JP, 2 JP, atau durasi lain) adalah:
=======================================
Drs. Muhammad Rafi'i M.Pd 35
Muhammad Saubari S.Pd.I 35
Nini Rahmini S.Pd.I 35
Ainayya Almadiyati S.Pd 34
Fathah Arsyad S.Pd 33
Muhammad Wildan Ariandi S.Pd 33
Ahmad Hafie S.Pd 32
Fajri Wahyudi S.Pd 32
Indah Wiratesta Manik S.Th 30
Rakhmad Taufik S.Pd 30
Sri Muliyatini S.Pd 30
Hj. Silfiana Hani S.Pd 30
Virawati S.Pd 29
Dra. Hj. Marfuah M.Pd 28
Dwiyani Lestari M.Pd 28
Hartati Jum'ah S.Pd 28
M. Riduan S.Pd 28
Risyandi S.Pd 28
Muhammad Naufal Akbar M.Pd 27
Hj. Maliyani S.Pd 26
Hj. Samnah Hayati M.Pd 26
Mariatul Qibtiah S.Pd 26
Sri Heldawati S.Pd 26
Sri Hidayati S.Pd 26
Anna Herlina S.Pd 25
Noor Ridhwan S.Pd M.A 25
Dra. Hj. Tri Krisnawati M.Pd 24
Ema Susi Darmawati S.Pd 24
Faridra Ramadhan P.S.Pd 24
Hj. St. Faridah S.Pd 24
Yekti Hanani S.Pd MA 24
Mukhtar Fuaddi S.Pd 18
Fatia Elfira Luthfyari S.Pd 16
Hj. Rianawati S.Pd 16
Muhammad Supiani S.IP 16
Nadya Rahmi Sari S.Pd 16
Khusnul Khotimah S.Pd 15
Muhammad Noor S.Pd 3
Misan S.Pd 2
Syamdani S.Pd 1
Ari Deviyanti S.Pd 1
Namun saya menyarankan jangan langsung memasukkan semua constraint ini.
Mulailah dari yang berat dulu:
35 JP -> max 7
34 JP -> max 7
33 JP -> max 7
32 JP -> max 7
Generate.
Kalau masih sukses, lanjut kelompok 30 JP (max 6).
Kalau langsung diberi constraint ketat untuk 42 guru sekaligus, FET bisa menjadi sulit menemukan solusi. Biasanya saya menambahkan bertahap sampai ketemu batas yang masih feasible.
| Hari | Guru |
|---|---|
| Senin | guru Seni, PJOK, Ekonomi |
| Selasa | guru Sosiologi, Sejarah, PPKn, Bahasa Indonesia |
| Rabu | guru Inggris, Geografi, Matematika, Agama |
| Kamis | guru Fisika, Kimia, Biologi, BK |
Saat kenaikan kelas
simpan_riwayatkelas.php.firstname dan cohort sesuai kelas baru.
2026/2027.
simpan_riwayatkelas.php.
Ya, karena Anda sudah berhasil mengekspor 461 siswa aktif ke CSV, maka Anda sudah punya cadangan data keanggotaan kelas saat ini.
Urutan yang saya sarankan sekarang:
Jangan diubah dulu. Simpan sebagai arsip:
siswa_aktif_20260618.csv
Saat ini cohort yang masih berisi siswa adalah:
75 X-A
76 X-B
77 X-C
78 X-D
79 X-E
80 X-F
81 X-G
82 XI-A
83 XI-B
84 XI-C
85 XI-D
86 XI-E
87 XI-F
88 XI-G
Cek dulu jumlah yang akan dihapus:
SELECT COUNT(*)
FROM mdl_cohort_members
WHERE cohortid IN (
75,76,77,78,79,80,81,
82,83,84,85,86,87,88
);
Harusnya hasil:
461
Kalau benar, lanjut:
DELETE FROM mdl_cohort_members
WHERE cohortid IN (
75,76,77,78,79,80,81,
82,83,84,85,86,87,88
);
SELECT COUNT(*)
FROM mdl_cohort_members;
Target:
0
Artinya seluruh siswa sudah tidak punya cohort.
Misalnya:
X-A → XI-C
X-B → XI-A
XI-A → XII-B
XI-B → XII-D
Isi kolom:
firstname
cohort1
dengan kelas baru.
Moodle:
Site administration
→ Users
→ Upload users
Pilih:
Update existing users only
dan aktifkan:
Allow cohort assignments
Maka:
firstname berubah menjadi kelas baru.
User masuk ke cohort baru.
Tidak ada sisa cohort lama karena sudah dikosongkan.
Sebelum menghapus 461 anggota cohort, pastikan cohort X-A s.d XII-E tetap ada (yang dikosongkan hanya anggotanya).
Cek:
SELECT id,name
FROM mdl_cohort
ORDER BY name;
Kalau cohortnya masih ada, maka aman untuk mengosongkan mdl_cohort_members dan mengisi ulang melalui CSV.
find . -maxdepth 1 -name "*.pdf" -size +2M -print0 |
while IFS= read -r -d '' f; do
echo "Memproses: $f"
gs \
-sDEVICE=pdfwrite \
-dCompatibilityLevel=1.4 \
-dPDFSETTINGS=/screen \
-dNOPAUSE \
-dQUIET \
-dBATCH \
-sOutputFile="${f%.pdf}-2mb.pdf" \
"$f"
done
Kalau Firefox normal terus reload (loading berputar terus) tetapi Firefox Private Mode bisa membuka 192.168.8.1 dengan normal, hampir pasti masalahnya bukan di modem E3276s, melainkan di browser.
Penyebab yang paling sering:
Firefox menyimpan JavaScript dan file CSS lama dari WebUI Huawei.
Coba:
Buka:
about:preferences#privacy
Cari Cookies and Site Data
Klik Manage Data
Cari:
192.168.8.1
Hapus data situs tersebut.
Atau lebih cepat:
Tekan Ctrl+Shift+Delete
Pilih Cached Web Content
Clear.
WebUI Huawei suka menyimpan data lokal yang membuat halaman looping.
Tekan:
F12
→ Storage
→ Hapus:
Local Storage
Session Storage
IndexedDB
untuk 192.168.8.1
Karena Private Mode biasanya menonaktifkan sebagian add-on.
Coba:
about:addons
sementara nonaktifkan:
uBlock Origin
Privacy Badger
NoScript
AdGuard
Cookie AutoDelete
Lalu buka lagi 192.168.8.1.
Kadang Firefox mencoba melakukan hal aneh pada alamat lokal.
Buka:
about:preferences#general
Network Settings → Settings
Matikan:
Enable DNS over HTTPS
Jika hanya WebUI Huawei yang bermasalah:
Buka:
Ctrl+Shift+R
(Hard Reload)
atau
Ctrl+F5
Di Firefox normal, buka:
http://192.168.8.1/api/device/information
atau
http://192.168.8.1/html/home.html
Jika tetap looping tetapi Private Mode lancar, kemungkinan cache/cookie WebUI Huawei yang korup.
Karena tadi Guru sempat mengubah band dan modem menggunakan WebUI modifikasi (ada tombol WatchCat), saya paling curiga cache JavaScript WebUI lama tersimpan di Firefox.
Coba hapus data situs khusus 192.168.8.1 dulu, tidak perlu menghapus cache seluruh Firefox. Biasanya langsung normal kembali.
Jika Guru ingin melihat:
find /var/www/html/moodle/local/jurnalmengajar -type f -daystart -mtime 0
find /var/www/html/moodle/local/jurnalmengajar -type f -daystart -mtime 1
find /var/www/html/moodle/local/jurnalmengajar -type f -daystart -mtime -2
find /var/www/html/moodle/local/jurnalmengajar -type f -mtime -2
Untuk audit perubahan plugin Moodle, saya biasanya lebih memilih:
find /var/www/html/moodle/local/jurnalmengajar \
-type f \
-daystart \
-mtime -2 \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort
karena langsung terlihat file mana yang kita ubah hari ini dan kemarin berdasarkan tanggal kalender, yang biasanya lebih mudah dicocokkan dengan riwayat pekerjaan Guru Duan.
cd "/mnt/GURU/GURU ONE/Surat Berharga/Ijazah"
for f in *.jpg; do
convert "$f" \
-resize 1200x1200\> \
-density 100 \
-quality 40 \
"${f%.jpg}.pdf"
done
Tentu Guru Duan. Script tersebut akan mengubah setiap file JPG menjadi PDF secara otomatis.
cd "/mnt/GURU/GURU ONE/Surat Berharga/Ijazah"
for f in *.jpg; do
convert "$f" \
-resize 1200x1200\> \
-density 100 \
-quality 40 \
"${f%.jpg}.pdf"
done
cd "/mnt/GURU/GURU ONE/Surat Berharga/Ijazah"
Masuk ke folder tempat file JPG berada.
for f in *.jpg; do
Artinya:
Cari semua file yang berakhiran .jpg
Simpan nama file satu per satu ke variabel f
Contoh:
Putaran pertama:
f="IJAZAH.jpg"
Putaran kedua:
f="IJAZAH MTs.jpg"
Putaran ketiga:
f="nem_SD.jpg"
dan seterusnya sampai semua file selesai.
convert "$f"
Misalnya:
convert "IJAZAH.jpg"
ImageMagick membaca gambar tersebut.
-resize 1200x1200\>
Tanda > sangat penting.
Artinya:
Jika ukuran gambar lebih besar dari 1200×1200, perkecil.
Jika sudah lebih kecil, jangan dibesarkan.
Contoh:
| Ukuran asli | Hasil |
|---|---|
| 4000×3000 | 1200×900 |
| 2400×1700 | 1200×850 |
| 800×600 | tetap 800×600 |
Tujuannya agar ukuran PDF lebih kecil.
-density 100
Menentukan resolusi PDF.
Umumnya:
| Density | Kualitas |
|---|---|
| 72 | sangat kecil |
| 100 | cukup jelas |
| 150 | bagus |
| 300 | kualitas scan tinggi |
Untuk arsip dan upload online, 100 biasanya cukup.
-quality 40
Kompresi JPEG.
| Quality | Hasil |
|---|---|
| 100 | hampir tanpa kompresi |
| 80 | bagus |
| 60 | cukup |
| 40 | kecil |
| 20 | sangat kecil |
Semakin kecil angkanya, ukuran file semakin kecil tetapi tulisan bisa mulai buram.
"${f%.jpg}.pdf"
Bagian:
${f%.jpg}
berarti:
Hapus
.jpgdari nama file.
Contoh:
| File JPG | PDF hasil |
|---|---|
| IJAZAH.jpg | IJAZAH.pdf |
| nem_SD.jpg | nem_SD.pdf |
| IJAZAH MTs.jpg | IJAZAH MTs.pdf |
Saat:
f="IJAZAH.jpg"
Perintah yang dijalankan menjadi:
convert "IJAZAH.jpg" \
-resize 1200x1200\> \
-density 100 \
-quality 40 \
"IJAZAH.pdf"
Kemudian lanjut ke file berikutnya.
Misalnya:
| JPG Asli | PDF Hasil |
|---|---|
| 3.6 MB | 300–700 KB |
| 2.8 MB | 250–600 KB |
| 1.1 MB | 100–300 KB |
Jadi seluruh dokumen ijazah/transkrip kemungkinan akan berada jauh di bawah batas 2 MB per file setelah dikonversi dengan pengaturan tersebut.
nano /var/www/html/moodle/local/jurnalmengajar/classes/form/jurnal_form.php
Mudah Guru Duan.
Cari:
$mform->addElement(
'textarea',
'catatan_pembinaan',
'Catatan',
'rows="3" cols="60"'
);
Ganti menjadi:
$mform->addElement(
'textarea',
'catatan_pembinaan',
'Masalah',
'rows="3" cols="60"'
);
Cari:
$mform->addElement(
'textarea',
'tindaklanjut_pembinaan',
'Tindak Lanjut',
'rows="3" cols="60"'
);
Ganti menjadi:
$mform->addElement(
'textarea',
'tindaklanjut_pembinaan',
'Tindak Lanjut / Solusi',
'rows="3" cols="60"'
);
Sekarang kemungkinan seperti ini:
$jenispembinaan = [
'' => '-- Pilih Jenis --',
'disiplin' => 'Disiplin',
'sikap' => 'Sikap',
'akademik' => 'Akademik',
'kerapian' => 'Kerapian',
'lainnya' => 'Lainnya'
];
Tinggal tambah item baru:
$jenispembinaan = [
'' => '-- Pilih Jenis --',
'disiplin' => 'Disiplin',
'sikap' => 'Sikap',
'akademik' => 'Akademik',
'kerapian' => 'Kerapian',
'kehadiran' => 'Kehadiran',
'etika' => 'Etika',
'ibadah' => 'Ibadah',
'pergaulan' => 'Pergaulan',
'motivasi' => 'Motivasi Belajar',
'tugas' => 'Tidak Mengerjakan Tugas',
'gadged' => 'Penggunaan HP/Gadget',
'lainnya' => 'Lainnya'
];
Kalau saya melihat kebutuhan Log Perkembangan Murid di sekolah, dropdown yang lebih berguna:
$jenispembinaan = [
'' => '-- Pilih Jenis --',
'kehadiran' => 'Kehadiran',
'disiplin' => 'Disiplin',
'kerapian' => 'Kerapian',
'sikap' => 'Sikap',
'akademik' => 'Akademik',
'tugas' => 'Tugas',
'motivasi' => 'Motivasi Belajar',
'pergaulan' => 'Pergaulan',
'ibadah' => 'Ibadah',
'lainnya' => 'Lainnya'
];
Karena nanti saat direkap di Log Perkembangan akan lebih mudah melihat pola masalah siswa:
Kehadiran : 5 kali
Disiplin : 3 kali
Akademik : 2 kali
Pergaulan : 1 kali
dan ini sangat membantu wali kelas serta BK.
teks gelap background terang
<a class="btn fw-bold w-100" style="background:#7fc7f5;color:#000;"
EKSTRA BG KUNING
<a class="btn fw-bold w-100" style="background:#fde668;color:#000;"
teks putih background gelap
BG BIRU TUA TEKS PUTIH WALI KELAS
<a class="btn text-white fw-bold w-100" style="background:#0a1347;"
BG HIJAU TEKS PUTIH
<a class="btn text-white fw-bold w-100" style="background:#2e7d32;"
SURAT IZIN MURID DI PENGAWAS [MERAH BG]
<a class="btn btn-danger btn-block w-100"
href="izin_murid.php">
📝 Isi Surat Izin Murid
shopt -s nullglob nocaseglob; for f in *.heic; do convert "$f" "${f%.*}.jpg"; done