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 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Game\Entity\Session;
|
||||
use App\Game\Enum\GameSettingType;
|
||||
use App\Game\Enum\SessionSettingType;
|
||||
use App\Game\Enum\SessionStatus;
|
||||
use App\Game\Repository\GameSettingRepository;
|
||||
use App\Game\Repository\SessionRepository;
|
||||
use App\Game\Repository\SessionSettingRepository;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:hints:check',
|
||||
description: 'Checks running game sessions and sends mainframe hints to players who are behind schedule.'
|
||||
)]
|
||||
final class SendMainframeHintsCommand extends Command
|
||||
{
|
||||
private const DEFAULT_TOTAL_TIME = 3600;
|
||||
|
||||
public function __construct(
|
||||
private readonly SessionRepository $sessionRepository,
|
||||
private readonly SessionSettingRepository $sessionSettingRepository,
|
||||
private readonly GameSettingRepository $gameSettingRepository,
|
||||
private readonly HubInterface $hub,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$sessions = $this->sessionRepository->findBy(['status' => SessionStatus::PLAYING]);
|
||||
$hintsSent = 0;
|
||||
|
||||
foreach ($sessions as $session) {
|
||||
if ($this->checkContactHint($session)) {
|
||||
$hintsSent++;
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('<info>Checked %d running session(s), sent %d hint(s).</info>', 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user