Merge branch 'edgarogh-safe-shutdown'
This commit is contained in:
commit
c1d3a42d22
10 changed files with 126 additions and 3 deletions
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.git
|
||||||
|
.cache
|
||||||
|
node_modules
|
|
@ -4,4 +4,4 @@ WORKDIR /usr/src
|
||||||
COPY app/ /usr/src/
|
COPY app/ /usr/src/
|
||||||
RUN npm install --production
|
RUN npm install --production
|
||||||
EXPOSE 2222
|
EXPOSE 2222
|
||||||
CMD npm run start
|
ENTRYPOINT [ "/usr/local/bin/node", "index.js" ]
|
||||||
|
|
|
@ -228,6 +228,8 @@ docker run --name webssh2 -d -p 2222:2222 -v `pwd`/app/config.json:/usr/src/conf
|
||||||
|
|
||||||
* **accesslog** - _boolean_ - http style access logging to console.log, default: false
|
* **accesslog** - _boolean_ - http style access logging to console.log, default: false
|
||||||
|
|
||||||
|
* **safeShutdownDuration** - _integer_ - maximum delay, in seconds, given to users before the server stops when doing a safe shutdown
|
||||||
|
|
||||||
# Experimental client-side logging
|
# Experimental client-side logging
|
||||||
Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first.
|
Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first.
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="footer"></div>
|
<div id="footer"></div>
|
||||||
<div id="status"></div>
|
<div id="status"></div>
|
||||||
|
<div id="countdown"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/ssh/webssh2.bundle.js" defer></script>
|
<script src="/ssh/webssh2.bundle.js" defer></script>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -246,6 +246,30 @@ body, html {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
#countdown {
|
||||||
|
display: none;
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(50, 50, 50);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none solid none solid;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
#countdown.active {
|
||||||
|
display: inline-block;
|
||||||
|
animation: countdown infinite alternate 200ms;
|
||||||
|
}
|
||||||
|
@keyframes countdown {
|
||||||
|
from {
|
||||||
|
background-color: rgb(255, 255, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
#menu {
|
#menu {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="footer"></div>
|
<div id="footer"></div>
|
||||||
<div id="status"></div>
|
<div id="status"></div>
|
||||||
|
<div id="countdown"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/ssh/webssh2.bundle.js" defer></script>
|
<script src="/ssh/webssh2.bundle.js" defer></script>
|
||||||
|
|
|
@ -75,6 +75,30 @@ body, html {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
#countdown {
|
||||||
|
display: none;
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(50, 50, 50);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none solid none solid;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
#countdown.active {
|
||||||
|
display: inline-block;
|
||||||
|
animation: countdown infinite alternate 200ms;
|
||||||
|
}
|
||||||
|
@keyframes countdown {
|
||||||
|
from {
|
||||||
|
background-color: rgb(255, 255, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
#menu {
|
#menu {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
|
@ -26,6 +26,7 @@ var status = document.getElementById('status')
|
||||||
var header = document.getElementById('header')
|
var header = document.getElementById('header')
|
||||||
var dropupContent = document.getElementById('dropupContent')
|
var dropupContent = document.getElementById('dropupContent')
|
||||||
var footer = document.getElementById('footer')
|
var footer = document.getElementById('footer')
|
||||||
|
var countdown = document.getElementById('countdown')
|
||||||
var fitAddon = new FitAddon()
|
var fitAddon = new FitAddon()
|
||||||
var terminalContainer = document.getElementById('terminal-container')
|
var terminalContainer = document.getElementById('terminal-container')
|
||||||
term.loadAddon(fitAddon)
|
term.loadAddon(fitAddon)
|
||||||
|
@ -136,6 +137,7 @@ socket.on('disconnect', function (err) {
|
||||||
'WEBSOCKET SERVER DISCONNECTED: ' + err
|
'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||||
}
|
}
|
||||||
socket.io.reconnection(false)
|
socket.io.reconnection(false)
|
||||||
|
countdown.classList.remove('active')
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('error', function (err) {
|
socket.on('error', function (err) {
|
||||||
|
@ -149,6 +151,18 @@ socket.on('reauth', function () {
|
||||||
(allowreauth) && reauthSession()
|
(allowreauth) && reauthSession()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// safe shutdown
|
||||||
|
var hasCountdownStarted = false
|
||||||
|
|
||||||
|
socket.on('shutdownCountdownUpdate', function (remainingSeconds) {
|
||||||
|
if (!hasCountdownStarted) {
|
||||||
|
countdown.classList.add('active')
|
||||||
|
hasCountdownStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
countdown.innerText = 'Shutting down in ' + remainingSeconds + 's'
|
||||||
|
})
|
||||||
|
|
||||||
term.onTitleChange(function (title) {
|
term.onTitleChange(function (title) {
|
||||||
document.title = title
|
document.title = title
|
||||||
})
|
})
|
||||||
|
|
|
@ -83,7 +83,8 @@ let config = {
|
||||||
server: false
|
server: false
|
||||||
},
|
},
|
||||||
accesslog: false,
|
accesslog: false,
|
||||||
verify: false
|
verify: false,
|
||||||
|
safeShutdownDuration: 300
|
||||||
}
|
}
|
||||||
|
|
||||||
// test if config.json exists, if not provide error message but try to run
|
// test if config.json exists, if not provide error message but try to run
|
||||||
|
@ -120,6 +121,7 @@ var expressOptions = require('./expressOptions')
|
||||||
var favicon = require('serve-favicon');
|
var favicon = require('serve-favicon');
|
||||||
|
|
||||||
// express
|
// express
|
||||||
|
app.use(safeShutdownGuard)
|
||||||
app.use(session)
|
app.use(session)
|
||||||
app.use(myutil.basicAuth)
|
app.use(myutil.basicAuth)
|
||||||
if (config.accesslog) app.use(logger('common'))
|
if (config.accesslog) app.use(logger('common'))
|
||||||
|
@ -199,4 +201,56 @@ io.use(function (socket, next) {
|
||||||
// bring up socket
|
// bring up socket
|
||||||
io.on('connection', socket)
|
io.on('connection', socket)
|
||||||
|
|
||||||
|
// safe shutdown
|
||||||
|
var shutdownMode = false
|
||||||
|
var shutdownInterval = 0
|
||||||
|
var connectionCount = 0
|
||||||
|
|
||||||
|
function safeShutdownGuard (req, res, next) {
|
||||||
|
if (shutdownMode) res.status(503).end('Service unavailable: Server shutting down')
|
||||||
|
else return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
io.on('connection', function (socket) {
|
||||||
|
connectionCount++
|
||||||
|
|
||||||
|
socket.on('disconnect', function () {
|
||||||
|
if ((--connectionCount <= 0) && shutdownMode) {
|
||||||
|
stop('All clients disconnected')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const signals = ['SIGTERM', 'SIGINT']
|
||||||
|
signals.forEach(signal => process.on(signal, function () {
|
||||||
|
if (shutdownMode) stop('Safe shutdown aborted, force quitting')
|
||||||
|
else if (connectionCount > 0) {
|
||||||
|
var remainingSeconds = config.safeShutdownDuration
|
||||||
|
shutdownMode = true
|
||||||
|
|
||||||
|
var message = (connectionCount === 1) ? ' client is still connected'
|
||||||
|
: ' clients are still connected'
|
||||||
|
console.error(connectionCount + message)
|
||||||
|
console.error('Starting a ' + remainingSeconds + ' seconds countdown')
|
||||||
|
console.error('Press Ctrl+C again to force quit')
|
||||||
|
|
||||||
|
shutdownInterval = setInterval(function () {
|
||||||
|
if ((remainingSeconds--) <= 0) {
|
||||||
|
stop('Countdown is over')
|
||||||
|
} else {
|
||||||
|
io.sockets.emit('shutdownCountdownUpdate', remainingSeconds)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
} else stop()
|
||||||
|
}))
|
||||||
|
|
||||||
|
// clean stop
|
||||||
|
function stop (reason) {
|
||||||
|
shutdownMode = false
|
||||||
|
if (reason) console.log('Stopping: ' + reason)
|
||||||
|
if (shutdownInterval) clearInterval(shutdownInterval)
|
||||||
|
io.close()
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { server: server, config: config }
|
module.exports = { server: server, config: config }
|
||||||
|
|
Loading…
Reference in a new issue