Merge branch 'edgarogh-safe-shutdown'

This commit is contained in:
Bill Church 2020-03-19 10:31:58 -04:00
commit c1d3a42d22
10 changed files with 126 additions and 3 deletions

3
.dockerignore Normal file
View file

@ -0,0 +1,3 @@
.git
.cache
node_modules

View file

@ -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" ]

View file

@ -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.

View file

@ -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

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -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
}) })

View file

@ -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 }