From 98066118a66c40a78a67729eddfde69036968293 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 12 Jul 2026 23:34:36 +0200 Subject: [PATCH] Expand terminal filesystem, add mainframe hint cron, redirect PHP logs - Game1 terminal: flesh out the virtual filesystem with a realistic spread of Linux directories/files (~70 dirs, ~140 files) so it no longer reads as an obviously small puzzle set, without touching any win-condition or rapport files. - Add app:hints:check command + a php-cron container (BusyBox crond) that nudges players who haven't contacted every teammate 5 minutes into a session, via a new 'hint' Mercure message type. - Log the cron command's output to var/log/cron/cron.log and rotate it (25MB / 90 days) via logrotate, run daily from the same crontab. - Redirect PHP's error_log and Symfony's prod app/deprecation logs from stderr-only into var/log/php/*.log (kept alongside stderr), with the same rotation policy. Co-Authored-By: Claude Sonnet 5 --- assets/game1.js | 1 + .../game1/filesystem/etc/apache2/apache2.conf | 8 + assets/game1/filesystem/etc/apt/sources.list | 3 + assets/game1/filesystem/etc/crontab | 8 + assets/game1/filesystem/etc/environment | 1 + assets/game1/filesystem/etc/fstab | 4 + assets/game1/filesystem/etc/hostname | 1 + assets/game1/filesystem/etc/hosts | 6 + assets/game1/filesystem/etc/issue | 2 + assets/game1/filesystem/etc/motd | 2 + .../game1/filesystem/etc/network/interfaces | 10 + assets/game1/filesystem/etc/nginx/nginx.conf | 14 ++ assets/game1/filesystem/etc/nsswitch.conf | 9 + assets/game1/filesystem/etc/os-release | 8 + assets/game1/filesystem/etc/passwd | 11 ++ assets/game1/filesystem/etc/resolv.conf | 3 + assets/game1/filesystem/etc/shadow | 7 + assets/game1/filesystem/etc/ssh/ssh_config | 4 + assets/game1/filesystem/etc/ssh/sshd_config | 7 + assets/game1/filesystem/etc/timezone | 1 + assets/game1/filesystem/opt/app/config.yml | 8 + assets/game1/filesystem/root/.bash_history | 5 + assets/game1/filesystem/root/.bashrc | 10 + .../filesystem/root/.ssh/authorized_keys | 1 + .../game1/filesystem/var/log/alternatives.log | 2 + assets/game1/filesystem/var/log/auth.log | 6 + assets/game1/filesystem/var/log/boot.log | 4 + assets/game1/filesystem/var/log/cron.log | 2 + assets/game1/filesystem/var/log/daemon.log | 2 + assets/game1/filesystem/var/log/dmesg | 4 + assets/game1/filesystem/var/log/dpkg.log | 4 + assets/game1/filesystem/var/log/kern.log | 4 + assets/game1/filesystem/var/log/mail.log | 2 + assets/game1/filesystem/var/log/syslog | 8 + assets/game1/filesystem/var/mail/root | 4 + assets/styles/game1.css | 5 + config/packages/monolog.yaml | 17 +- docker/compose.yaml | 33 ++++ docker/php/Dockerfile | 14 +- docker/php/crontab | 2 + docker/php/logrotate/cron-hints.conf | 11 ++ docker/php/logrotate/php-logs.conf | 11 ++ docker/php/php.ini | 2 +- docker/restart.sh | 4 +- docker/setup.sh | 4 + src/Command/SendMainframeHintsCommand.php | 134 +++++++++++++ src/Game/Service/GameResponseService.php | 176 ++++++++++++++++++ 47 files changed, 583 insertions(+), 6 deletions(-) create mode 100644 assets/game1/filesystem/etc/apache2/apache2.conf create mode 100644 assets/game1/filesystem/etc/apt/sources.list create mode 100644 assets/game1/filesystem/etc/crontab create mode 100644 assets/game1/filesystem/etc/environment create mode 100644 assets/game1/filesystem/etc/fstab create mode 100644 assets/game1/filesystem/etc/hostname create mode 100644 assets/game1/filesystem/etc/hosts create mode 100644 assets/game1/filesystem/etc/issue create mode 100644 assets/game1/filesystem/etc/motd create mode 100644 assets/game1/filesystem/etc/network/interfaces create mode 100644 assets/game1/filesystem/etc/nginx/nginx.conf create mode 100644 assets/game1/filesystem/etc/nsswitch.conf create mode 100644 assets/game1/filesystem/etc/os-release create mode 100644 assets/game1/filesystem/etc/passwd create mode 100644 assets/game1/filesystem/etc/resolv.conf create mode 100644 assets/game1/filesystem/etc/shadow create mode 100644 assets/game1/filesystem/etc/ssh/ssh_config create mode 100644 assets/game1/filesystem/etc/ssh/sshd_config create mode 100644 assets/game1/filesystem/etc/timezone create mode 100644 assets/game1/filesystem/opt/app/config.yml create mode 100644 assets/game1/filesystem/root/.bash_history create mode 100644 assets/game1/filesystem/root/.bashrc create mode 100644 assets/game1/filesystem/root/.ssh/authorized_keys create mode 100644 assets/game1/filesystem/var/log/alternatives.log create mode 100644 assets/game1/filesystem/var/log/auth.log create mode 100644 assets/game1/filesystem/var/log/boot.log create mode 100644 assets/game1/filesystem/var/log/cron.log create mode 100644 assets/game1/filesystem/var/log/daemon.log create mode 100644 assets/game1/filesystem/var/log/dmesg create mode 100644 assets/game1/filesystem/var/log/dpkg.log create mode 100644 assets/game1/filesystem/var/log/kern.log create mode 100644 assets/game1/filesystem/var/log/mail.log create mode 100644 assets/game1/filesystem/var/log/syslog create mode 100644 assets/game1/filesystem/var/mail/root create mode 100644 docker/php/crontab create mode 100644 docker/php/logrotate/cron-hints.conf create mode 100644 docker/php/logrotate/php-logs.conf mode change 100755 => 100644 docker/restart.sh mode change 100755 => 100644 docker/setup.sh create mode 100644 src/Command/SendMainframeHintsCommand.php diff --git a/assets/game1.js b/assets/game1.js index 7e383cc..a0edbb7 100644 --- a/assets/game1.js +++ b/assets/game1.js @@ -94,6 +94,7 @@ let currentLockedAt = null; function lockMessageClass(messageType) { if (messageType === 'virus') return 'message-virus'; if (messageType === 'mainframe') return 'message-mainframe'; + if (messageType === 'hint') return 'message-hint'; return ''; } diff --git a/assets/game1/filesystem/etc/apache2/apache2.conf b/assets/game1/filesystem/etc/apache2/apache2.conf new file mode 100644 index 0000000..8aaa5b5 --- /dev/null +++ b/assets/game1/filesystem/etc/apache2/apache2.conf @@ -0,0 +1,8 @@ +ServerRoot "/etc/apache2" +Listen 80 +User www-data +Group www-data +ErrorLog ${APACHE_LOG_DIR}/error.log +LogLevel warn +IncludeOptional mods-enabled/*.load +IncludeOptional sites-enabled/*.conf diff --git a/assets/game1/filesystem/etc/apt/sources.list b/assets/game1/filesystem/etc/apt/sources.list new file mode 100644 index 0000000..32faac3 --- /dev/null +++ b/assets/game1/filesystem/etc/apt/sources.list @@ -0,0 +1,3 @@ +deb http://deb.debian.org/debian bookworm main contrib non-free-firmware +deb http://deb.debian.org/debian bookworm-updates main contrib non-free-firmware +deb http://security.debian.org/debian-security bookworm-security main contrib non-free-firmware diff --git a/assets/game1/filesystem/etc/crontab b/assets/game1/filesystem/etc/crontab new file mode 100644 index 0000000..21da5a7 --- /dev/null +++ b/assets/game1/filesystem/etc/crontab @@ -0,0 +1,8 @@ +# /etc/crontab: system-wide crontab +SHELL=/bin/sh +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +17 * * * * root cd / && run-parts --report /etc/cron.hourly +25 6 * * * root test -x /usr/sbin/anacron || run-parts --report /etc/cron.daily +47 6 * * 7 root test -x /usr/sbin/anacron || run-parts --report /etc/cron.weekly +52 6 1 * * root test -x /usr/sbin/anacron || run-parts --report /etc/cron.monthly diff --git a/assets/game1/filesystem/etc/environment b/assets/game1/filesystem/etc/environment new file mode 100644 index 0000000..426f982 --- /dev/null +++ b/assets/game1/filesystem/etc/environment @@ -0,0 +1 @@ +PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" diff --git a/assets/game1/filesystem/etc/fstab b/assets/game1/filesystem/etc/fstab new file mode 100644 index 0000000..486f07c --- /dev/null +++ b/assets/game1/filesystem/etc/fstab @@ -0,0 +1,4 @@ +# /etc/fstab: static file system information. +UUID=8f14e45f-ceea-4a63-9b3f-1a2b3c4d5e6f / ext4 errors=remount-ro 0 1 +UUID=1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d /boot ext4 defaults 0 2 +/swapfile none swap sw 0 0 diff --git a/assets/game1/filesystem/etc/hostname b/assets/game1/filesystem/etc/hostname new file mode 100644 index 0000000..dd0e31d --- /dev/null +++ b/assets/game1/filesystem/etc/hostname @@ -0,0 +1 @@ +archive-node-04 diff --git a/assets/game1/filesystem/etc/hosts b/assets/game1/filesystem/etc/hosts new file mode 100644 index 0000000..a884f2d --- /dev/null +++ b/assets/game1/filesystem/etc/hosts @@ -0,0 +1,6 @@ +127.0.0.1 localhost +127.0.1.1 archive-node-04 +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +10.0.0.4 archive-node-04.internal diff --git a/assets/game1/filesystem/etc/issue b/assets/game1/filesystem/etc/issue new file mode 100644 index 0000000..d0506b2 --- /dev/null +++ b/assets/game1/filesystem/etc/issue @@ -0,0 +1,2 @@ +Debian GNU/Linux 12 \n \l + diff --git a/assets/game1/filesystem/etc/motd b/assets/game1/filesystem/etc/motd new file mode 100644 index 0000000..d9e4dea --- /dev/null +++ b/assets/game1/filesystem/etc/motd @@ -0,0 +1,2 @@ +Welcome to archive-node-04. +All connections are logged and monitored for internal review purposes. diff --git a/assets/game1/filesystem/etc/network/interfaces b/assets/game1/filesystem/etc/network/interfaces new file mode 100644 index 0000000..8479c68 --- /dev/null +++ b/assets/game1/filesystem/etc/network/interfaces @@ -0,0 +1,10 @@ +source /etc/network/interfaces.d/* + +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 10.0.0.4 + netmask 255.255.255.0 + gateway 10.0.0.1 diff --git a/assets/game1/filesystem/etc/nginx/nginx.conf b/assets/game1/filesystem/etc/nginx/nginx.conf new file mode 100644 index 0000000..06fc1c1 --- /dev/null +++ b/assets/game1/filesystem/etc/nginx/nginx.conf @@ -0,0 +1,14 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; + +events { + worker_connections 768; +} + +http { + sendfile on; + keepalive_timeout 65; + include /etc/nginx/mime.types; + include /etc/nginx/sites-enabled/*; +} diff --git a/assets/game1/filesystem/etc/nsswitch.conf b/assets/game1/filesystem/etc/nsswitch.conf new file mode 100644 index 0000000..7b122ae --- /dev/null +++ b/assets/game1/filesystem/etc/nsswitch.conf @@ -0,0 +1,9 @@ +passwd: files +group: files +shadow: files +hosts: files dns +networks: files +protocols: db files +services: db files +ethers: db files +rpc: db files diff --git a/assets/game1/filesystem/etc/os-release b/assets/game1/filesystem/etc/os-release new file mode 100644 index 0000000..8b5a9d3 --- /dev/null +++ b/assets/game1/filesystem/etc/os-release @@ -0,0 +1,8 @@ +PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" +NAME="Debian GNU/Linux" +VERSION_ID="12" +VERSION="12 (bookworm)" +VERSION_CODENAME=bookworm +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" diff --git a/assets/game1/filesystem/etc/passwd b/assets/game1/filesystem/etc/passwd new file mode 100644 index 0000000..7be1909 --- /dev/null +++ b/assets/game1/filesystem/etc/passwd @@ -0,0 +1,11 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +sshd:x:105:65534::/run/sshd:/usr/sbin/nologin +admin:x:1000:1000:admin,,,:/home/admin:/bin/bash +guest:x:1001:1001:guest,,,:/home/guest:/bin/bash diff --git a/assets/game1/filesystem/etc/resolv.conf b/assets/game1/filesystem/etc/resolv.conf new file mode 100644 index 0000000..6acd133 --- /dev/null +++ b/assets/game1/filesystem/etc/resolv.conf @@ -0,0 +1,3 @@ +nameserver 1.1.1.1 +nameserver 9.9.9.9 +options edns0 diff --git a/assets/game1/filesystem/etc/shadow b/assets/game1/filesystem/etc/shadow new file mode 100644 index 0000000..c209823 --- /dev/null +++ b/assets/game1/filesystem/etc/shadow @@ -0,0 +1,7 @@ +root:$6$rounds=656000$xJ2kLQmZ$aFq9zN3vQwErTyUiOpAsDfGhJkLzXcVbNm1234567890abcdefgh:19700:0:99999:7::: +daemon:*:19700:0:99999:7::: +bin:*:19700:0:99999:7::: +sys:*:19700:0:99999:7::: +sshd:*:19700:0:99999:7::: +admin:$6$rounds=656000$k3PqR8tW$bGr0oPqLmNbVcXzAsDfGhJkLqWeRtYuIoP0987654321zyxwvu:19700:0:99999:7::: +guest:*:19700:0:99999:7::: diff --git a/assets/game1/filesystem/etc/ssh/ssh_config b/assets/game1/filesystem/etc/ssh/ssh_config new file mode 100644 index 0000000..d4e3065 --- /dev/null +++ b/assets/game1/filesystem/etc/ssh/ssh_config @@ -0,0 +1,4 @@ +Host * + SendEnv LANG LC_* + HashKnownHosts yes + GSSAPIAuthentication yes diff --git a/assets/game1/filesystem/etc/ssh/sshd_config b/assets/game1/filesystem/etc/ssh/sshd_config new file mode 100644 index 0000000..935b19c --- /dev/null +++ b/assets/game1/filesystem/etc/ssh/sshd_config @@ -0,0 +1,7 @@ +Port 22 +PermitRootLogin no +PasswordAuthentication yes +PubkeyAuthentication yes +X11Forwarding no +PrintMotd no +Subsystem sftp /usr/lib/openssh/sftp-server diff --git a/assets/game1/filesystem/etc/timezone b/assets/game1/filesystem/etc/timezone new file mode 100644 index 0000000..3cfb6a1 --- /dev/null +++ b/assets/game1/filesystem/etc/timezone @@ -0,0 +1 @@ +Europe/Amsterdam diff --git a/assets/game1/filesystem/opt/app/config.yml b/assets/game1/filesystem/opt/app/config.yml new file mode 100644 index 0000000..2c2bed2 --- /dev/null +++ b/assets/game1/filesystem/opt/app/config.yml @@ -0,0 +1,8 @@ +app: + name: internal-archive-sync + version: 2.3.1 + log_level: info + port: 8080 + database: + driver: sqlite + path: /opt/app/data.db diff --git a/assets/game1/filesystem/root/.bash_history b/assets/game1/filesystem/root/.bash_history new file mode 100644 index 0000000..3261cd6 --- /dev/null +++ b/assets/game1/filesystem/root/.bash_history @@ -0,0 +1,5 @@ +apt update +apt upgrade -y +systemctl restart nginx +df -h +journalctl -xe diff --git a/assets/game1/filesystem/root/.bashrc b/assets/game1/filesystem/root/.bashrc new file mode 100644 index 0000000..f663046 --- /dev/null +++ b/assets/game1/filesystem/root/.bashrc @@ -0,0 +1,10 @@ +# ~/.bashrc: executed by bash for non-login shells + +case $- in + *i*) ;; + *) return;; +esac + +export PS1='\u@\h:\w\$ ' +alias ll='ls -alF' +alias la='ls -A' diff --git a/assets/game1/filesystem/root/.ssh/authorized_keys b/assets/game1/filesystem/root/.ssh/authorized_keys new file mode 100644 index 0000000..64d99a1 --- /dev/null +++ b/assets/game1/filesystem/root/.ssh/authorized_keys @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZ8pQxT2mN0vRkLwYb6cJhU3sEoAeKdVmXpZq7tRnBs admin@archive-node-04 diff --git a/assets/game1/filesystem/var/log/alternatives.log b/assets/game1/filesystem/var/log/alternatives.log new file mode 100644 index 0000000..7490924 --- /dev/null +++ b/assets/game1/filesystem/var/log/alternatives.log @@ -0,0 +1,2 @@ +update-alternatives 2026-05-30 03:10:04: link group editor updated to point to /usr/bin/vim.basic +update-alternatives 2026-05-30 03:10:04: link group pager updated to point to /usr/bin/less diff --git a/assets/game1/filesystem/var/log/auth.log b/assets/game1/filesystem/var/log/auth.log new file mode 100644 index 0000000..132cdc6 --- /dev/null +++ b/assets/game1/filesystem/var/log/auth.log @@ -0,0 +1,6 @@ +Jun 12 09:41:55 archive-node-04 sshd[10233]: Accepted publickey for admin from 10.0.0.7 port 51422 ssh2 +Jun 12 09:41:55 archive-node-04 sshd[10233]: pam_unix(sshd:session): session opened for user admin by (uid=0) +Jun 12 09:55:02 archive-node-04 sudo: admin : TTY=pts/0 ; PWD=/home/admin ; USER=root ; COMMAND=/usr/bin/apt update +Jun 12 10:12:40 archive-node-04 sshd[10233]: pam_unix(sshd:session): session closed for user admin +Jun 12 22:03:11 archive-node-04 sshd[15092]: Failed password for invalid user test from 203.0.113.44 port 39102 ssh2 +Jun 12 22:03:14 archive-node-04 sshd[15092]: Connection closed by 203.0.113.44 port 39102 [preauth] diff --git a/assets/game1/filesystem/var/log/boot.log b/assets/game1/filesystem/var/log/boot.log new file mode 100644 index 0000000..8f0373e --- /dev/null +++ b/assets/game1/filesystem/var/log/boot.log @@ -0,0 +1,4 @@ +[ OK ] Started Network Manager. +[ OK ] Started OpenSSH server daemon. +[ OK ] Started Nginx HTTP server. +[ OK ] Reached target Multi-User System. diff --git a/assets/game1/filesystem/var/log/cron.log b/assets/game1/filesystem/var/log/cron.log new file mode 100644 index 0000000..d46e676 --- /dev/null +++ b/assets/game1/filesystem/var/log/cron.log @@ -0,0 +1,2 @@ +Jun 12 04:00:11 archive-node-04 CRON[9021]: (root) CMD (test -x /usr/sbin/anacron || run-parts --report /etc/cron.daily) +Jun 12 18:30:02 archive-node-04 CRON[15544]: (root) CMD (test -x /usr/sbin/anacron || run-parts --report /etc/cron.hourly) diff --git a/assets/game1/filesystem/var/log/daemon.log b/assets/game1/filesystem/var/log/daemon.log new file mode 100644 index 0000000..776082c --- /dev/null +++ b/assets/game1/filesystem/var/log/daemon.log @@ -0,0 +1,2 @@ +Jun 12 03:00:05 archive-node-04 systemd-udevd[512]: Using default interface naming scheme 'v252'. +Jun 12 03:00:11 archive-node-04 dbus-daemon[601]: [system] Successfully activated service 'org.freedesktop.hostname1' diff --git a/assets/game1/filesystem/var/log/dmesg b/assets/game1/filesystem/var/log/dmesg new file mode 100644 index 0000000..ccfc96e --- /dev/null +++ b/assets/game1/filesystem/var/log/dmesg @@ -0,0 +1,4 @@ +[ 0.000000] Linux version 6.1.0-21-amd64 (debian-kernel@lists.debian.org) +[ 0.004211] Command line: BOOT_IMAGE=/boot/vmlinuz-6.1.0-21-amd64 root=UUID=8f14e45f +[ 0.512033] ACPI: Core revision 20221020 +[ 1.221004] usb 1-1: new high-speed USB device number 2 diff --git a/assets/game1/filesystem/var/log/dpkg.log b/assets/game1/filesystem/var/log/dpkg.log new file mode 100644 index 0000000..0d4cd59 --- /dev/null +++ b/assets/game1/filesystem/var/log/dpkg.log @@ -0,0 +1,4 @@ +2026-05-30 03:10:02 startup archives unpack +2026-05-30 03:10:04 install curl:amd64 8.4.0-2 +2026-05-30 03:10:05 status installed curl:amd64 8.4.0-2 +2026-06-02 09:44:11 upgrade openssh-server:amd64 1:9.2p1-2 1:9.2p1-2+deb12u2 diff --git a/assets/game1/filesystem/var/log/kern.log b/assets/game1/filesystem/var/log/kern.log new file mode 100644 index 0000000..db97f6d --- /dev/null +++ b/assets/game1/filesystem/var/log/kern.log @@ -0,0 +1,4 @@ +Jun 12 03:00:02 archive-node-04 kernel: [ 0.000000] Linux version 6.1.0-21-amd64 +Jun 12 03:00:02 archive-node-04 kernel: [ 0.004211] Command line: BOOT_IMAGE=/boot/vmlinuz-6.1.0-21-amd64 root=UUID=8f14e45f +Jun 12 03:00:03 archive-node-04 kernel: [ 1.221004] usb 1-1: new high-speed USB device number 2 +Jun 12 03:00:03 archive-node-04 kernel: [ 1.552210] eth0: link up, 1000Mbps, full-duplex diff --git a/assets/game1/filesystem/var/log/mail.log b/assets/game1/filesystem/var/log/mail.log new file mode 100644 index 0000000..b28c1ba --- /dev/null +++ b/assets/game1/filesystem/var/log/mail.log @@ -0,0 +1,2 @@ +Jun 12 05:11:02 archive-node-04 postfix/qmgr[812]: 3F2A1C0021: removed +Jun 12 05:11:02 archive-node-04 postfix/smtp[9944]: 3F2A1C0021: to=, status=sent diff --git a/assets/game1/filesystem/var/log/syslog b/assets/game1/filesystem/var/log/syslog new file mode 100644 index 0000000..75e6664 --- /dev/null +++ b/assets/game1/filesystem/var/log/syslog @@ -0,0 +1,8 @@ +Jun 12 03:12:01 archive-node-04 systemd[1]: Starting Daily apt download activities... +Jun 12 03:12:04 archive-node-04 systemd[1]: apt-daily.service: Deactivated successfully. +Jun 12 04:00:11 archive-node-04 CRON[9021]: (root) CMD (test -x /usr/sbin/anacron || run-parts --report /etc/cron.daily) +Jun 12 06:25:00 archive-node-04 anacron[1122]: Job `cron.daily' terminated +Jun 12 09:41:55 archive-node-04 sshd[10233]: Accepted publickey for admin from 10.0.0.7 port 51422 ssh2 +Jun 12 09:41:55 archive-node-04 sshd[10233]: pam_unix(sshd:session): session opened for user admin +Jun 12 12:03:19 archive-node-04 systemd[1]: Reloading nginx.service +Jun 12 18:30:02 archive-node-04 CRON[15544]: (root) CMD (test -x /usr/sbin/anacron || run-parts --report /etc/cron.hourly) diff --git a/assets/game1/filesystem/var/mail/root b/assets/game1/filesystem/var/mail/root new file mode 100644 index 0000000..0594ebe --- /dev/null +++ b/assets/game1/filesystem/var/mail/root @@ -0,0 +1,4 @@ +From cron@archive-node-04 Wed Jun 10 06:25:01 2026 +Subject: Cron run-parts --report /etc/cron.daily + +Daily housekeeping completed without errors. diff --git a/assets/styles/game1.css b/assets/styles/game1.css index 0e4eecf..1f85e09 100644 --- a/assets/styles/game1.css +++ b/assets/styles/game1.css @@ -66,6 +66,11 @@ div.message-mainframe { color: #0F0; } +div.message-hint { + color: #FF0; + font-weight: bold; +} + div#lock-banner { position: fixed; top: 68px; diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index 9db7d8a..0e8d3e5 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -47,6 +47,14 @@ when@prod: excluded_http_codes: [404, 405] buffer_size: 50 # How many messages should be saved? Prevent memory leaks nested: + type: group + members: [nested_file, nested_stderr] + nested_file: + type: stream + path: "%kernel.logs_dir%/php/prod.log" + level: debug + formatter: monolog.formatter.json + nested_stderr: type: stream path: php://stderr level: debug @@ -56,7 +64,14 @@ when@prod: process_psr_3_messages: false channels: ["!event", "!doctrine"] deprecation: - type: stream + type: group channels: [deprecation] + members: [deprecation_file, deprecation_stderr] + deprecation_file: + type: stream + path: "%kernel.logs_dir%/php/deprecation.log" + formatter: monolog.formatter.json + deprecation_stderr: + type: stream path: php://stderr formatter: monolog.formatter.json diff --git a/docker/compose.yaml b/docker/compose.yaml index 2c837b1..b876a66 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -66,6 +66,39 @@ services: # ipv4_address: 172.23.0.11 restart: unless-stopped + php-cron: + build: + context: .. + dockerfile: docker/php/Dockerfile + args: + USER_ID: ${USER_ID} + GROUP_ID: ${GROUP_ID} + container_name: escapepage-php-cron + volumes: + - ../:/var/www/html:delegated + - /etc/hosts:/etc/hosts:ro + environment: + APP_ENV: ${APP_ENV} + SITE_BASE_URL: ${SITE_BASE_URL} + MAILER_DSN: ${MAILER_DSN} + MAILER_FROM: ${MAILER_FROM} + DATABASE_URL: ${DATABASE_URL} + MERCURE_URL: ${MERCURE_URL} + MERCURE_PUBLIC_URL: ${MERCURE_PUBLIC_URL} + MERCURE_JWT_SECRET: ${MERCURE_JWT_SECRET} + MERCURE_CORS_ALLOWED_ORIGINS: ${MERCURE_CORS_ALLOWED_ORIGINS} + MERCURE_TOPIC_BASE: ${MERCURE_TOPIC_BASE} + RECAPTCHA3_KEY: ${RECAPTCHA3_KEY} + RECAPTCHA3_SECRET: ${RECAPTCHA3_SECRET} + depends_on: + - database + - mercure + command: ["crond", "-f", "-l", "2"] + # networks: + # backend: + # ipv4_address: 172.23.0.16 + restart: unless-stopped + nginx: image: nginx:1.29.4-alpine container_name: escapepage-nginx diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index fb43443..7e96740 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -12,7 +12,8 @@ RUN apk add --no-cache \ make \ nodejs \ npm \ - shadow + shadow \ + logrotate # Install PHP extension installer COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ @@ -41,6 +42,15 @@ COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # Configure PHP COPY docker/php/php.ini $PHP_INI_DIR/conf.d/zz-custom.ini +# Cron daemon (BusyBox's built-in crond) for the php-cron container. +# Harmless for the php/php-worker containers too, since they never invoke crond. +COPY docker/php/crontab /etc/crontabs/root +RUN chmod 0600 /etc/crontabs/root + +# Log rotation for the cron hint-check and PHP error logs: 25MB per file, kept for 3 months. +COPY docker/php/logrotate/cron-hints.conf /etc/logrotate.d/cron-hints +COPY docker/php/logrotate/php-logs.conf /etc/logrotate.d/php-logs + # Adjust www-data UID/GID to match host user (default 1000) ARG USER_ID=1000 ARG GROUP_ID=1000 @@ -56,7 +66,7 @@ RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \ WORKDIR /var/www/html # Set permissions for Symfony directories -RUN mkdir -p var/cache var/log var/sessions && \ +RUN mkdir -p var/cache var/log/cron var/log/php var/sessions && \ chown -R www-data:www-data var # Default command diff --git a/docker/php/crontab b/docker/php/crontab new file mode 100644 index 0000000..59dad61 --- /dev/null +++ b/docker/php/crontab @@ -0,0 +1,2 @@ +* * * * * php /var/www/html/bin/console app:hints:check >> /var/www/html/var/log/cron/cron.log 2>&1 +5 3 * * * logrotate -s /var/www/html/var/log/.logrotate.state /etc/logrotate.d/cron-hints /etc/logrotate.d/php-logs diff --git a/docker/php/logrotate/cron-hints.conf b/docker/php/logrotate/cron-hints.conf new file mode 100644 index 0000000..8a0815e --- /dev/null +++ b/docker/php/logrotate/cron-hints.conf @@ -0,0 +1,11 @@ +/var/www/html/var/log/cron/cron.log { + size 25M + rotate 100 + maxage 90 + missingok + notifempty + compress + delaycompress + dateext + dateformat -%Y%m%d-%s +} diff --git a/docker/php/logrotate/php-logs.conf b/docker/php/logrotate/php-logs.conf new file mode 100644 index 0000000..3bb2364 --- /dev/null +++ b/docker/php/logrotate/php-logs.conf @@ -0,0 +1,11 @@ +/var/www/html/var/log/php/*.log { + size 25M + rotate 100 + maxage 90 + missingok + notifempty + compress + delaycompress + dateext + dateformat -%Y%m%d-%s +} diff --git a/docker/php/php.ini b/docker/php/php.ini index 228659d..db56989 100644 --- a/docker/php/php.ini +++ b/docker/php/php.ini @@ -9,6 +9,6 @@ opcache.validate_timestamps=1 opcache.revalidate_freq=0 log_errors=On -error_log=/var/www/html/var/log/errorlog_php.log +error_log=/var/www/html/var/log/php/error.log session.gc_maxlifetime=1440 session.cookie_lifetime=0 diff --git a/docker/restart.sh b/docker/restart.sh old mode 100755 new mode 100644 index 4ca8d44..68d1a26 --- a/docker/restart.sh +++ b/docker/restart.sh @@ -12,7 +12,7 @@ echo "Stopping and removing containers..." docker network rm escapepage_network || true docker network rm $(docker network ls -q --filter name=escapepage) || true docker network prune -f || true -docker rm -f escapepage-db escapepage-php escapepage-nginx escapepage-mercure escapepage-mailer escapepage-php-worker || true +docker rm -f escapepage-db escapepage-php escapepage-nginx escapepage-mercure escapepage-mailer escapepage-php-worker escapepage-php-cron || true docker system prune -f || true echo "Clearing Docker build cache..." @@ -21,7 +21,7 @@ docker builder prune -af echo "Setting permissions for var/volumes/db and var directories..." sudo chown -R 1000:1000 "$ROOT_DIR/var/volumes/db" || true sudo chmod -R 777 "$ROOT_DIR/var/volumes/db" || true -sudo mkdir -p "$ROOT_DIR/var/cache" "$ROOT_DIR/var/log" "$ROOT_DIR/var/sessions" +sudo mkdir -p "$ROOT_DIR/var/cache" "$ROOT_DIR/var/log/cron" "$ROOT_DIR/var/log/php" "$ROOT_DIR/var/sessions" sudo chown -R 1000:1000 "$ROOT_DIR/var" || true sudo chmod -R 777 "$ROOT_DIR/var" || true diff --git a/docker/setup.sh b/docker/setup.sh old mode 100755 new mode 100644 index 5b9a91d..73b099b --- a/docker/setup.sh +++ b/docker/setup.sh @@ -143,6 +143,10 @@ Common commands: (cd "$DOCKER_DIR" && $DOCKER_COMPOSE logs -f nginx) (cd "$DOCKER_DIR" && $DOCKER_COMPOSE logs -f php) (cd "$DOCKER_DIR" && $DOCKER_COMPOSE logs -f php-worker) + (cd "$DOCKER_DIR" && $DOCKER_COMPOSE logs -f php-cron) # crond scheduler activity + tail -f "$ROOT_DIR/var/log/cron/cron.log" # hint-check command output + tail -f "$ROOT_DIR/var/log/php/error.log" # raw PHP errors + tail -f "$ROOT_DIR/var/log/php/prod.log" # Symfony app errors (prod only) (cd "$DOCKER_DIR" && $DOCKER_COMPOSE exec php bash) (cd "$DOCKER_DIR" && $DOCKER_COMPOSE exec php npm run watch) (cd "$DOCKER_DIR" && $DOCKER_COMPOSE down) diff --git a/src/Command/SendMainframeHintsCommand.php b/src/Command/SendMainframeHintsCommand.php new file mode 100644 index 0000000..368467a --- /dev/null +++ b/src/Command/SendMainframeHintsCommand.php @@ -0,0 +1,134 @@ +sessionRepository->findBy(['status' => SessionStatus::PLAYING]); + $hintsSent = 0; + + foreach ($sessions as $session) { + if ($this->checkContactHint($session)) { + $hintsSent++; + } + } + + $output->writeln(sprintf('Checked %d running session(s), sent %d hint(s).', count($sessions), $hintsSent)); + + return Command::SUCCESS; + } + + /** + * "Get in touch" hint: if 5 minutes into the game the players haven't messaged + * everyone (a private message to each other player, plus one broadcast), nudge them. + * Repeats every run of this command for as long as the condition still holds. + */ + private function checkContactHint(Session $session): bool + { + $elapsed = $this->getElapsedPlayingSeconds($session); + if ($elapsed === null || $elapsed < 300) { + return false; + } + + if ($this->allPlayersHaveContactedEveryone($session)) { + return false; + } + + $this->publishHint($session, 'Get in contact with your fellow agents to work together on defeating this AI virus.'); + + return true; + } + + private function getElapsedPlayingSeconds(Session $session): ?int + { + $timer = $session->getTimer(); + if ($timer === null) { + return null; + } + + $totalTimeSetting = $this->gameSettingRepository->getSetting($session->getGame(), GameSettingType::TOTAL_TIME); + $totalTime = $totalTimeSetting ? (int)$totalTimeSetting->getValue() : self::DEFAULT_TOTAL_TIME; + + $startedAt = $timer - $totalTime; + + return time() - $startedAt; + } + + private function allPlayersHaveContactedEveryone(Session $session): bool + { + $players = $session->getPlayers(); + $screens = []; + + foreach ($players as $player) { + if ($player->getScreen() === null) { + return false; + } + $screens[] = $player->getScreen(); + } + + foreach ($players as $player) { + $screen = $player->getScreen(); + $trackingSettingName = SessionSettingType::tryFrom('ChatTrackingForPlayer' . $screen); + if (!$trackingSettingName) { + return false; + } + + $setting = $this->sessionSettingRepository->getSetting($session, $trackingSettingName, $player); + $tracking = $setting ? (json_decode($setting->getValue() ?? '[]', true) ?? []) : []; + + if (!in_array(0, $tracking)) { + return false; + } + + foreach ($screens as $otherScreen) { + if ($otherScreen !== $screen && !in_array($otherScreen, $tracking)) { + return false; + } + } + } + + return true; + } + + private function publishHint(Session $session, string $message): void + { + $topic = '/game/hub/' . $session->getId(); + try { + $this->hub->publish(new Update($topic, json_encode([0, $message, 'hint']))); + } catch (\Exception $e) { + // Mercure might be down + } + } +} diff --git a/src/Game/Service/GameResponseService.php b/src/Game/Service/GameResponseService.php index dfee129..a5c0e53 100644 --- a/src/Game/Service/GameResponseService.php +++ b/src/Game/Service/GameResponseService.php @@ -998,6 +998,61 @@ class GameResponseService $paths[] = '/etc/handle'; $paths[] = '/etc/freak'; $paths[] = '/etc/host'; + $paths[] = '/etc/ssh'; + $paths[] = '/etc/nginx'; + $paths[] = '/etc/apache2'; + $paths[] = '/etc/systemd'; + $paths[] = '/etc/cron.d'; + $paths[] = '/etc/network'; + $paths[] = '/etc/apt'; + $paths[] = '/etc/default'; + $paths[] = '/etc/init.d'; + $paths[] = '/etc/security'; + $paths[] = '/etc/skel'; + $paths[] = '/etc/logrotate.d'; + + $paths[] = '/bin'; + $paths[] = '/boot'; + $paths[] = '/dev'; + $paths[] = '/home'; + $paths[] = '/home/admin'; + $paths[] = '/home/guest'; + $paths[] = '/home/backup'; + $paths[] = '/lib'; + $paths[] = '/lib64'; + $paths[] = '/media'; + $paths[] = '/mnt'; + $paths[] = '/opt'; + $paths[] = '/opt/app'; + $paths[] = '/proc'; + $paths[] = '/root'; + $paths[] = '/root/.ssh'; + $paths[] = '/run'; + $paths[] = '/sbin'; + $paths[] = '/srv'; + $paths[] = '/sys'; + $paths[] = '/tmp'; + $paths[] = '/usr'; + $paths[] = '/usr/bin'; + $paths[] = '/usr/sbin'; + $paths[] = '/usr/lib'; + $paths[] = '/usr/local'; + $paths[] = '/usr/local/bin'; + $paths[] = '/usr/local/sbin'; + $paths[] = '/usr/share'; + $paths[] = '/usr/share/doc'; + $paths[] = '/usr/share/man'; + $paths[] = '/usr/include'; + $paths[] = '/usr/src'; + + $paths[] = '/var/log'; + $paths[] = '/var/lib'; + $paths[] = '/var/cache'; + $paths[] = '/var/spool'; + $paths[] = '/var/backups'; + $paths[] = '/var/tmp'; + $paths[] = '/var/mail'; + $paths[] = '/var/run'; $paths[] = '/var/home'; @@ -1213,6 +1268,127 @@ class GameResponseService $files[] = '/var/rapports/011_130-62.txt'; $files[] = '/var/rapports/index.txt'; + $files[] = '/etc/passwd'; + $files[] = '/etc/shadow'; + $files[] = '/etc/hostname'; + $files[] = '/etc/hosts'; + $files[] = '/etc/os-release'; + $files[] = '/etc/motd'; + $files[] = '/etc/issue'; + $files[] = '/etc/timezone'; + $files[] = '/etc/resolv.conf'; + $files[] = '/etc/crontab'; + $files[] = '/etc/nsswitch.conf'; + $files[] = '/etc/environment'; + $files[] = '/etc/fstab'; + $files[] = '/etc/ssh/sshd_config'; + $files[] = '/etc/ssh/ssh_config'; + $files[] = '/etc/nginx/nginx.conf'; + $files[] = '/etc/apache2/apache2.conf'; + $files[] = '/etc/network/interfaces'; + $files[] = '/etc/apt/sources.list'; + $files[] = '/etc/cron.d/backup-cron.sh'; + $files[] = '/etc/init.d/nginx.sh'; + $files[] = '/etc/init.d/ssh.sh'; + $files[] = '/etc/init.d/cron.sh'; + $files[] = '/etc/logrotate.d/rsyslog.sh'; + $files[] = '/etc/logrotate.d/apt.sh'; + $files[] = '/etc/skel/bashrc.sh'; + $files[] = '/etc/skel/profile.sh'; + + $files[] = '/var/log/syslog'; + $files[] = '/var/log/auth.log'; + $files[] = '/var/log/kern.log'; + $files[] = '/var/log/boot.log'; + $files[] = '/var/log/dmesg'; + $files[] = '/var/log/dpkg.log'; + $files[] = '/var/log/cron.log'; + $files[] = '/var/log/mail.log'; + $files[] = '/var/log/daemon.log'; + $files[] = '/var/log/alternatives.log'; + $files[] = '/var/lib/dpkg-status.sh'; + $files[] = '/var/lib/apt-extended-states.sh'; + $files[] = '/var/cache/apt-archives.sh'; + $files[] = '/var/spool/cron-crontabs.sh'; + $files[] = '/var/spool/mail-root.sh'; + $files[] = '/var/backups/passwd.bak.sh'; + $files[] = '/var/backups/group.bak.sh'; + $files[] = '/var/mail/root'; + + $files[] = '/root/.bashrc'; + $files[] = '/root/.bash_history'; + $files[] = '/root/.ssh/authorized_keys'; + + $files[] = '/home/admin/notes.sh'; + $files[] = '/home/admin/todo.sh'; + $files[] = '/home/guest/readme.sh'; + $files[] = '/home/backup/backup.sh'; + + $files[] = '/opt/app/config.yml'; + $files[] = '/opt/app/app.sh'; + $files[] = '/opt/app/start.sh'; + + $files[] = '/usr/bin/ls.sh'; + $files[] = '/usr/bin/cat.sh'; + $files[] = '/usr/bin/grep.sh'; + $files[] = '/usr/bin/awk.sh'; + $files[] = '/usr/bin/sed.sh'; + $files[] = '/usr/bin/bash.sh'; + $files[] = '/usr/bin/python3.sh'; + $files[] = '/usr/bin/perl.sh'; + $files[] = '/usr/bin/curl.sh'; + $files[] = '/usr/bin/wget.sh'; + $files[] = '/usr/bin/ssh.sh'; + $files[] = '/usr/bin/scp.sh'; + $files[] = '/usr/bin/rsync.sh'; + $files[] = '/usr/bin/tar.sh'; + $files[] = '/usr/bin/gzip.sh'; + $files[] = '/usr/bin/vim.sh'; + $files[] = '/usr/bin/nano.sh'; + $files[] = '/usr/bin/top.sh'; + $files[] = '/usr/bin/ps.sh'; + $files[] = '/usr/bin/kill.sh'; + $files[] = '/usr/bin/chmod.sh'; + $files[] = '/usr/bin/chown.sh'; + $files[] = '/usr/bin/systemctl.sh'; + $files[] = '/usr/bin/docker.sh'; + $files[] = '/usr/bin/git.sh'; + $files[] = '/usr/bin/find.sh'; + $files[] = '/usr/bin/sort.sh'; + $files[] = '/usr/bin/uniq.sh'; + $files[] = '/usr/bin/head.sh'; + $files[] = '/usr/bin/tail.sh'; + + $files[] = '/bin/sh.sh'; + $files[] = '/bin/mount.sh'; + $files[] = '/bin/umount.sh'; + $files[] = '/bin/ping.sh'; + $files[] = '/bin/netstat.sh'; + $files[] = '/bin/ifconfig.sh'; + $files[] = '/bin/hostname.sh'; + $files[] = '/bin/date.sh'; + $files[] = '/bin/ln.sh'; + $files[] = '/bin/cp.sh'; + $files[] = '/bin/mv.sh'; + $files[] = '/bin/rm.sh'; + $files[] = '/bin/mkdir.sh'; + $files[] = '/bin/rmdir.sh'; + $files[] = '/bin/touch.sh'; + $files[] = '/bin/echo.sh'; + + $files[] = '/sbin/init.sh'; + $files[] = '/sbin/reboot.sh'; + $files[] = '/sbin/shutdown.sh'; + $files[] = '/sbin/fsck.sh'; + $files[] = '/sbin/ifup.sh'; + $files[] = '/sbin/ifdown.sh'; + $files[] = '/sbin/iptables.sh'; + $files[] = '/sbin/sysctl.sh'; + + $files[] = '/usr/local/bin/composer.sh'; + $files[] = '/usr/local/bin/node.sh'; + $files[] = '/usr/local/bin/npm.sh'; + if ($player === null) { return $files; }