SteamServer::createDefaultServerCfgFile()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 2
eloc 8
nc 2
nop 0
1
<?php
2
3
/**
4
 * This file is part of Dedipanel project
5
 *
6
 * (c) 2010-2015 Dedipanel <http://www.dedicated-panel.net>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace DP\GameServer\SteamServerBundle\Entity;
13
14
use Dedipanel\PHPSeclibWrapperBundle\Connection\Exception\ScreenNotExistException;
15
use Doctrine\ORM\Mapping as ORM;
16
use DP\GameServer\GameServerBundle\Entity\GameServer;
17
use DP\Core\GameBundle\Entity\Plugin;
18
use DP\Core\CoreBundle\Exception\InstallAlreadyStartedException;
19
use DP\Core\CoreBundle\Exception\MissingPacketException;
20
use Dedipanel\PHPSeclibWrapperBundle\Crontab\Crontab;
21
use Dedipanel\PHPSeclibWrapperBundle\Crontab\CrontabItem;
22
23
/**
24
 * DP\GameServer\SteamServerBundle\Entity\SteamServer
25
 *
26
 * @ORM\Table(name="steam_server")
27
 * @ORM\Entity(repositoryClass="DP\GameServer\GameServerBundle\Entity\GameServerRepository")
28
 * 
29
 * @todo: refacto phpseclib
30
 * @todo: refacto domain logic
31
 */
32
class SteamServer extends GameServer
33
{
34
    /**
35
     * @var \DateTime $rebootAt
36
     *
37
     * @ORM\Column(name="rebootAt", type="time", nullable=true)
38
     */
39
    private $rebootAt;
40
41
    /**
42
     * @var boolean $munin
43
     *
44
     * @ORM\Column(name="munin", type="boolean", nullable=true)
45
     */
46
    private $munin;
47
48
    /**
49
     * @var string $svPassword
50
     *
51
     * @ORM\Column(name="sv_passwd", type="string", length=16, nullable=true)
52
     */
53
    private $svPassword;
54
    
55
    /**
56
     * @var string $mode
57
     * 
58
     * @ORM\Column(name="mode", type="string", nullable=true)
59
     */
60
    private $mode;
61
    
62
    
63
    /**
64
     * Set rebootAt
65
     *
66
     * @param \DateTime $rebootAt
67
     * 
68
     * @return SteamServer
69
     */
70
    public function setRebootAt($rebootAt)
71
    {
72
        $this->rebootAt = $rebootAt;
73
        
74
        return $this;
75
    }
76
77
    /**
78
     * Get rebootAt
79
     *
80
     * @return \DateTime
81
     */
82
    public function getRebootAt()
83
    {
84
        return $this->rebootAt;
85
    }
86
87
    /**
88
     * Set munin
89
     *
90
     * @param boolean $munin
91
     * 
92
     * @return SteamServer
93
     */
94
    public function setMunin($munin)
95
    {
96
        $this->munin = $munin;
97
        
98
        return $this;
99
    }
100
101
    /**
102
     * Get munin
103
     *
104
     * @return boolean
105
     */
106
    public function getMunin()
107
    {
108
        return $this->munin;
109
    }
110
111
    /**
112
     * Set svPassword
113
     *
114
     * @param string $svPassword
115
     * 
116
     * @return SteamServer
117
     */
118
    public function setSvPassword($svPassword)
119
    {
120
        $this->svPassword = $svPassword;
121
        
122
        return $this;
123
    }
124
125
    /**
126
     * Get svPassword
127
     *
128
     * @return string
129
     */
130
    public function getSvPassword()
131
    {
132
        return $this->svPassword;
133
    }
134
    
135
    /**
136
     * Set game server mode 
137
     * 
138
     * @param string $mode
139
     * 
140
     * @return SteamServer
141
     */
142
    public function setMode($mode)
143
    {
144
        $this->mode = $mode;
145
        
146
        return $this;
147
    }
148
    
149
    /**
150
     * Get game server mode
151
     * 
152
     * @return string
153
     */
154
    public function getMode()
155
    {
156
        return $this->mode;
157
    }
158
    
159
    public static function getModeList()
160
    {
161
        return array(
162
            '0;0' => 'Classic Casual', 
163
            '0;1' => 'Classic Competitive', 
164
            '1;0' => 'Arms Race', 
165
            '1;1' => 'Demolition', 
166
            '1;2' => 'Deathmatch', 
167
        );
168
    }
169
170
    /**
171
     * Upload & launch game server installation
172
     *
173
     * @param \Twig_Environment $twig Used for generate shell script
174
     */
175
    public function installServer(\Twig_Environment $twig)
176
    {
177
        $conn = $this->getMachine()->getConnection();
178
179
        // S'il s'agit d'un serveur 64 bits on commence par vérifier si le paquet ia32-libs est présent
180
        // (nécessaire pour l'utilisation de l'installateur steam)
181
        if ($this->machine->is64Bit() === true && $conn->hasCompatLib() == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
182
            throw new MissingPacketException(array('ia32-libs/', 'libc6:i386'));
183
        }
184
185
        $installDir = $this->getAbsoluteDir();
186
        $scriptPath = $installDir . 'install.sh';
187
        $screenName = $this->getInstallScreenName();
188
        $steamCmd = $this->getGame()->getSteamCmd();
0 ignored issues
show
Unused Code introduced by
$steamCmd is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
189
        $bin = $this->getGame()->getBin();
190
        $appId = $this->game->getappId();
191
        $appMod =  $this->game->getappMod();
192
        $conn->mkdir($installDir);
193
194
        $installScript = $twig->render(
195
            'DPSteamServerBundle:sh:install.sh.twig',
196
            array('installDir'  => $installDir)
197
        );
198
199
        $conn->upload($scriptPath, $installScript);
200
        
201
        $pgrep = '`ps aux | grep SCREEN | grep "' . $screenName . ' " | grep -v grep | wc -l`';
202
        $screenCmd  = 'if [ ' . $pgrep . ' = "0" ]; then ';
203
        $screenCmd .= 'screen -dmS "' . $screenName . '" ' . $scriptPath . ' "' . $appId . '" "' . $appMod . '"  "' . $bin . '"; ';
204
        $screenCmd .= 'else echo "Installation is already in progress."; fi; ';
205
        $result = $conn->exec($screenCmd);
206
207
        if ($result == 'Installation is already in progress.') {
208
            throw new InstallAlreadyStartedException();
209
        }
210
211
        $this->installationStatus = 0;
212
213
        return true;
214
    }
215
216
    public function removeInstallationFiles()
217
    {
218
        $installDir = $this->getAbsoluteDir();
219
        $scriptPath = $installDir . 'install.sh';
220
        $logPath = $installDir . 'install.log';
221
222
        return $this->getMachine()->getConnection()->exec('rm -f ' . $scriptPath . ' ' . $logPath);
223
    }
224
225
    public function getInstallationProgress()
226
    {
227
        $absDir = $this->getAbsoluteDir();
228
        $logPath = $absDir . 'install.log';
229
        $logCmd = 'if [ -f ' . $logPath . ' ]; then cat ' . $logPath . '; else echo "File not found exception."; fi; ';
230
231
        $conn = $this->getMachine()->getConnection();
232
        $installLog = $conn->exec($logCmd);
233
234
        if (strpos($installLog, 'Install ended') !== false) {
235
            // Si l'installation est terminé, on supprime le fichier de log et le script
236
            $conn->exec('rm -f ' . $absDir . 'install.log ' . $absDir . 'install.sh');
237
           // 100 = serveur installé
238
           // 101 = serveur installé + config uploadé
239
           return 100;
240
        }
241
        elseif (strpos($installLog, 'Install failed') !== false) {
242
            return null;
243
        }
244
        elseif (strpos($installLog, 'Game install') !== false) {
245
            $screenContent = false;
246
247
            try {
248
                $screenContent = $conn->getScreenContent($this->getInstallScreenName());
249
            }
250
            catch (ScreenNotExistException $e) {}
251
252
            if ($screenContent == false || $screenContent == 'No screen session found.') return null;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $screenContent of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
253
            else {
254
                // Si on a réussi à récupérer le contenu du screen,
255
                // On recherche dans chaque ligne en commencant par la fin
256
                // Un signe "%" afin de connaître le % le plus à jour
257
                $lines = array_reverse(explode("\n", $screenContent));
258
259
                foreach ($lines AS $line) {
260
                    $line = trim($line);
261
                    // On passe à la ligne suivante si l'actuelle est vide
262
                    if (empty($line)) continue;
263
264
                    $matches = array();
265
266
                    if (preg_match('#^(App|Update) state \(0x\d+\) (downloading|installed), progress: ([\d]+.[\d]+)#', $line ,$matches)) {
267
                        return ($matches[1] == 'downloading') ? ($matches[2] / 2) : (50 + $matches[2] / 2);
268
                    }
269
                }
270
271
                // Si arrivé à ce stade aucun pourcentage n'a été détecté
272
                // C'est surement que l'installation est en train de vérifier les fichiers locaux
273
                return 3;
274
            }
275
        }
276
        elseif (strpos($installLog, 'Steam updating') !== false) {
277
            return 2;
278
        }
279
        elseif (strpos($installLog, 'DL hldsupdatetool.bin') !== false || strpos($installLog, 'Download steamcmd') !== false) {
280
            return 1;
281
        }
282
        elseif ($installLog == 'File not found exception.') {
283
            return null;
284
        }
285
        else {
286
            throw new \ErrorException('Impossible de définir le statut de l\'installation du serveur.');
287
        }
288
    }
289
290
    public function uploadShellScripts(\Twig_Environment $twig)
291
    {
292
        // Upload du script de gestion du serveur de jeu
293
        $uploadHlds = $this->uploadHldsScript($twig);
294
295
        // Création d'un ficier server.cfg vide (si celui-ci n'existe pas)
296
        $this->createDefaultServerCfgFile();
297
        
298
        if ($this->getGame()->getLaunchName() == 'csgo') {
299
            $this->modifyGameModesCfg();
300
        }
301
302
        $this->installationStatus = 101;
303
304
        return $uploadHlds;
305
    }
306
307
    public function uploadHldsScript(\Twig_Environment $twig)
308
    {
309
        $conn = $this->getMachine()->getConnection();
310
        $game = $this->getGame();
311
        $hostName = $this->getName();
0 ignored issues
show
Unused Code introduced by
$hostName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
312
313
        $scriptPath = $this->getAbsoluteHldsScriptPath();
314
        $isCsgo = $this->getGame()->getLaunchName() == 'csgo';
315
        $isJustCause = $this->getGame()->getLaunchName() == 'justcause';
316
        $isNs2 = $this->getGame()->getLaunchName() == 'ns2';
317
        $isKF = $this->getGame()->getLaunchName() == 'KFmod.KFGameType';
318
        $gameType = '';
319
        $gameMode = '';
320
        $mapGroup = '';
321
        
322
        if ($isCsgo) {
323
            $mode = $this->getMode();
324
            $mode = !(empty($mode)) ? $mode : '0;0';
325
            
326
            list($gameType, $gameMode) = explode(';', $mode);
327
            
328
            if ($gameType == 0 && $gameMode == 0) {
329
                $mapGroup = 'mg_bomb';
330
            }
331
            elseif ($gameType == 0 && $gameMode == 1) {
332
                $mapGroup = 'mg_bomb_se';
333
            }
334
            elseif ($gameType == 1 && $gameMode == 0) {
335
                $mapGroup = 'mg_armsrace';
336
            }
337
            elseif ($gameType == 1 && $gameMode == 1) {
338
                $mapGroup = 'mp_demolition';
339
            }
340
            elseif ($gameType == 1 && $gameMode == 2) {
341
                $mapGroup = 'mg_allclassic';
342
            }
343
        }
344
345
        $hldsScript = $twig->render('DPSteamServerBundle:sh:hlds.sh.twig', array(
346
            'screenName'        => $this->getScreenName(),
347
            'bin'               => $game->getBin(),
348
            'name'              => $this->getName(),
349
            'launchName'        => $game->getLaunchName(),
350
            'ip'                => $this->getMachine()->getPublicIp(),
351
            'port'              => $this->getPort(),
352
            'maxplayers'        => $this->getMaxplayers(),
353
            'startMap'          => $game->getMap(),
354
            'binDir'            => $this->getAbsoluteBinDir(),
355
            'core'              => implode(',', $this->getCore()),
356
            'isCsgo'            => $isCsgo,
357
            'isJustCause'       => $isJustCause,
358
            'isNs2'             => $isNs2,
359
            'isKF'              => $isKF,
360
            'gameType'          => $gameType,
361
            'gameMode'          => $gameMode,
362
            'mapGroup'          => $mapGroup,
363
        ));
364
365
        return $conn->upload($scriptPath, $hldsScript, 0750);
366
    }
367
368
    public function createDefaultServerCfgFile()
369
    {
370
        $conn = $this->getMachine()->getConnection();
371
        $cfgPath = $this->getServerCfgPath();
372
373
        if ($this->getGame()->getLaunchName() == 'csgo') {
374
            $file = $this->getAbsoluteGameContentDir() . 'gamemodes_server.txt';
375
            
376
            return $conn->exec('if [ ! -e ' . $file . ' ] && [ -e ' . $file . '.example ]; then mv ' . $file . '.example ' . $file . '; fi');
377
        }
378
        else {
379
            // On créer un fichier server.cfg si aucun n'existe
380
            return $conn->exec('if [ ! -e ' . $cfgPath . ' ]; then touch ' . $cfgPath . '; fi');
381
        }
382
    }
383
384
    public function uploadDefaultServerConfigurationFile()
385
    {
386
        $template = $this->getGame()->getConfigTemplate();
387
388
        if (!empty($template)) {
389
            $conn = $this->getMachine()->getConnection();
390
            $cfgPath = $this->getServerCfgPath();
391
392
            $env = new \Twig_Environment(new \Twig_Loader_String());
0 ignored issues
show
Deprecated Code introduced by
The class Twig_Loader_String has been deprecated with message: since 1.18.1 (to be removed in 2.0)

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
393
            $cfgFile = $env->render($template, array(
394
                'hostname' => $this->getFullName(),
395
                'rconPassword' => $this->getRconPassword(), 
396
                'svPassword' => $this->getSvPassword(), 
397
            ));
398
399
            return $conn->upload($cfgPath, $cfgFile, 0750);
400
        }
401
402
        return false;
403
    }
404
405
    public function modifyServerCfgFile()
406
    {
407
        $conn = $this->getMachine()->getConnection();
408
        $cfgPath = $this->getServerCfgPath();
409
410
        $remoteFile = $conn->download($cfgPath);
411
        $fileLines = explode("\n", $remoteFile);
412
        
413
        $patterns = array(
414
            '#^hostname#' => 'hostname "' . $this->getFullName() . '"',
415
            '#^rcon_password#' => 'rcon_password "' . $this->getRconPassword() . '"', 
416
            '#^sv_password#' => 'sv_password "' . $this->getSvPassword() . '"', 
417
        );
418
        $matched = array();
419
420
        foreach ($fileLines AS &$line) {
421
            if ($line == '' || substr($line, 0, 2) == '//') continue;
422
423
            // Vérifie tous les patterns fournis
424
            foreach ($patterns AS $pattern => $replacement) {
425
                // Si le pattern est trouvé, le replacement est effectué
426
                // Et la ligne est ajouté à l'array des lignes détectés
427
                if (preg_match($pattern, $line)) {
428
                    $line = $replacement;
429
                    
430
                    $matched[$pattern] = $replacement;
431
                }
432
            }
433
        }
434
        // Suppression de la référence
435
        unset($line);
436
        
437
        // Ajoute les lignes non matchées
438
        $delta = array_diff($patterns, $matched);
439
        foreach ($delta AS $toAdd) {
440
            $fileLines[] = $toAdd;
441
        }
442
443
        // Upload du nouveau fichier
444
        return $conn->upload($cfgPath, implode("\n", $fileLines));
445
    }
446
447
    /**
448
     * @var string $state
449
     */
450
    public function changeState($state)
451
    {
452
        return $this
453
                ->getMachine()
454
                ->getConnection()
455
                ->exec($this->getAbsoluteHldsScriptPath() . ' ' . $state)
456
        ;
457
    }
458
    
459
    public function installPlugin(\Twig_Environment $twig, Plugin $plugin)
460
    {
461
        return $this->execPluginScript($twig, $plugin, 'install');
462
    }
463
    
464
    public function uninstallPlugin(\Twig_Environment $twig, Plugin $plugin)
465
    {
466
        return $this->execPluginScript($twig, $plugin, 'uninstall');
467
    }
468
469
    /**
470
     * @param string $action
471
     */
472
    public function execPluginScript(\Twig_Environment $twig, Plugin $plugin, $action)
473
    {
474
        if ($action != 'install' && $action != 'uninstall' && $action != 'activate' && $action != 'deactivate') {
475
            throw new \BadMethodCallException('Only actions available for SteamServers plugin scripts are : install, uninstall, activate and deactivate.');
476
        }
477
478
        $conn = $this->getMachine()->getConnection();
479
480
        // En cas d'installation, vérification des dépendances du plugin
481
        if ($action == 'install') {
482
            $packetDependencies = $plugin->getPacketDependencies();
483
484
            if (!empty($packetDependencies)) {
485
                $missingPackets = array();
486
487
                foreach ($packetDependencies AS $dep) {
488
                    if (!$conn->isInstalled($dep)) {
489
                        $missingPackets[] = $dep;
490
                    }
491
                }
492
493
                if (!empty($missingPackets)) {
494
                    throw new MissingPacketException($missingPackets);
495
                }
496
            }
497
        }
498
499
        $dir = $this->getAbsoluteGameContentDir();
500
        $scriptName = $plugin->getScriptName();
501
        $scriptPath = $dir . $scriptName . '.sh';
502
503
        $pluginScript = $twig->render(
504
            'DPSteamServerBundle:sh:Plugin/' . $scriptName . '.sh.twig', array('gameDir' => $dir));
505
        $conn->upload($scriptPath, $pluginScript);
506
507
        $screenName = $this->getPluginInstallScreenName($scriptName);
508
        $screenCmd  = 'screen -dmS ' . $screenName . ' ' . $scriptPath . ' ' . $action;
509
510
        if ($action == 'install') {
511
            $screenCmd .= ' "' . $plugin->getDownloadUrl() . '"';
512
        }
513
514
        $conn->exec($screenCmd);
515
    }
516
517
    public function getAbsoluteGameContentDir()
518
    {
519
        return $this->getAbsoluteBinDir() . $this->game->getLaunchName() . '/';
520
    }
521
522
    public function getAbsoluteHldsScriptPath()
523
    {
524
        return $this->getAbsoluteDir() . 'hlds.sh';
525
    }
526
527
    public function getRebootCommand()
528
    {
529
        return $this->getAbsoluteHldsScriptPath() . ' restart >> ' .
530
            $this->getAbsoluteDir() . 'cron-dp.log';
531
    }
532
533 View Code Duplication
    public function addAutoReboot()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
534
    {
535
        $rebootTime = $this->getRebootAt();
536
        $script = $this->getRebootCommand();
537
        $item =  new CrontabItem($script, $rebootTime->format('i'), $rebootTime->format('H'));
538
        $crontab =  new Crontab($this->getMachine()->getConnection());
539
        $crontab->addItem($item);
540
541
        return $crontab->update();
542
543
    }
544
545 View Code Duplication
    public function removeAutoReboot($rebootTime)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
546
    {
547
548
        $script = $this->getRebootCommand();
549
        $item =  new CrontabItem($script, $rebootTime->format('i'), $rebootTime->format('H'));
550
        $crontab =  new Crontab($this->getMachine()->getConnection());
551
        $crontab->removeItem($item);
552
553
        return $crontab->update();
554
    }
555
556
    public function getServerCfgPath()
557
    {
558
        return $this->getAbsoluteBinDir() . $this->getGame()->getCfgPath();
559
    }
560
561 View Code Duplication
    public function removeFromServer()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
562
    {
563
        $screenName = $this->getScreenName();
564
        $scriptPath = $this->getAbsoluteHldsScriptPath();
565
        $serverPath = $this->getAbsoluteDir();
566
567
        $conn = $this->getMachine()->getConnection();
568
569
        // On commence par vérifier que le serveur n'est pas lancé (sinon on l'arrête)
570
        $pgrep   = '`ps aux | grep SCREEN | grep "' . $screenName . ' " | grep -v grep | wc -l`';
571
        $stopCmd = 'if [ ' . $pgrep . ' != "0" ]; then ' . $scriptPath . ' stop; fi; ';
572
        $conn->exec($stopCmd);
573
574
        // Puis on supprime complètement le dossier du serveur
575
        $delCmd  = 'rm -Rf ' . $serverPath;
576
577
        return $conn->exec($delCmd);
578
    }
579
    
580
    public function modifyGameModesCfg()
581
    {
582
        $conn = $this->getMachine()->getConnection();
583
        $file = $this->getAbsoluteGameContentDir() . 'gamemodes_server.txt';
584
        
585
        $content = $conn->download($file);
586
        $fileLines = explode("\r\n", $content);
587
        
588
        foreach ($fileLines AS &$line) {
589
            // On ignore la ligne vide et les commentaires
590
            if (empty($line) || substr($line, 0, 2) == '//') continue;
591
            
592
            if (preg_match('#"maxplayers"[ \t]+"[\d]+"#', $line)) {
593
                $line = preg_replace('#^([ \t]+)"maxplayers"([ \t]+)"[\d]+"(.*)$#', '$1"maxplayers"$2"' . $this->maxplayers . '"$3', $line);
594
            }
595
        }
596
        
597
        // Upload du nouveau fichier
598
        return $conn->upload($file, implode("\r\n", $fileLines));
599
    }
600
    
601
    public function regenerateScripts(\Twig_Environment $twig)
602
    {
603
        $this->uploadHldsScript($twig);
604
        
605
        if ($this->getGame()->getLaunchName() == 'csgo') {
606
            $this->modifyGameModesCfg();
607
        }
608
    }
609
}
610