<commit_before>###############################################################################
#
#    CoCalc: Collaborative Calculation in the Cloud
#
#    Copyright (C) 2016, Sagemath Inc.
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################


#################################################################
#
# console_server -- a node.js tty console server
#
#   * the server, which runs as a command-line daemon (or can
#     be used as a library)
#
#   * the client, which e.g. gets imported by hub and used
#     for communication between hub and the server daemon.
#
#################################################################

path           = require('path')
async          = require('async')
fs             = require('fs')
net            = require('net')
child_process  = require('child_process')
winston        = require('winston')
assert         = require('assert')

message        = require('smc-util/message')
misc_node      = require('smc-util-node/misc_node')
{secret_token_filename} = require('./common')

port_manager = require('./port_manager')
misc = require('smc-util/misc')
{to_json, from_json, defaults, required} = misc

abspath = (path) ->
    if path.length == 0
        return process.env.HOME
    if path[0] == '/'
        return path  # already an absolute path
    return process.env.HOME + '/' + path

if not process.env.SMC?
    process.env.SMC = path.join(process.env.HOME, '.smc')

DATA = path.join(process.env['SMC'], 'console_server')

if not fs.existsSync(process.env['SMC'])
    fs.mkdirSync(process.env['SMC'])
if not fs.existsSync(DATA)
    fs.mkdirSync(DATA)

##################################################################
# Read the secret token file.
#
# This file is created by the local_hub process, which is started at
# the same time as the console_server. So, we try for up to 5 seconds
# until this file appears.
##################################################################

misc = require('smc-util/misc')


secret_token = undefined

read_token = (cb) ->
    f = (cb) ->
        fs.exists secret_token_filename, (exists) ->
            if not exists
                cb("secret token file does not exist")
            else
                secret_token = fs.readFileSync(secret_token_filename).toString()
                cb()
    misc.retry_until_success
        f        : f
        max_time : 30000
        cb       : cb

start_session = (socket, mesg) ->
    winston.info "start_session #{to_json(mesg)}"

    if not mesg.params?  # for connecting to an existing session.
        mesg.params = {}

    opts = defaults mesg.params,
        rows     : 24
        cols     : 80
        command  : 'bash'
        args     : []
        path     : required
        filename : required

    opts.path     = abspath(opts.path)  # important since console server is started in some random location
    opts.filename = abspath(opts.filename)

    init_fn = misc.console_init_filename(opts.filename)
    if fs.existsSync(init_fn) and opts.command == 'bash'
        opts.args = ['--init-file', "#{init_fn}"]

    if process.env['USER'] == 'root'
        if not mesg.project_id? or mesg.project_id.length != 36
            winston.debug("suspicious project_id (=#{mesg.project_id}) -- bailing")
            return

    winston.debug "start_session opts = #{to_json(opts)}"

    # Ensure that the given user exists.  If not, send an error.  The
    # hub should always ensure the user exists before starting a session.
    async.series([
        (cb) ->
            # Fork off a child process that does all further work to
            # handle a connection.
            child = child_process.fork(__dirname + '/console_server_child.coffee', [])

            # Send the pid of the child to the client (the connected hub)
            socket.write_mesg('json', message.session_description(pid:child.pid))

            # Disable use of the socket for sending/receiving messages, since
            # it will be only used for raw xterm stuff hence.
            misc_node.disable_mesg(socket)

            # Give the socket to the child, along with the options.
            child.send(opts, socket)
<commit_msg>console server; prefer .js files is available.<commit_after>###############################################################################
#
#    CoCalc: Collaborative Calculation in the Cloud
#
#    Copyright (C) 2016, Sagemath Inc.
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################


#################################################################
#
# console_server -- a node.js tty console server
#
#   * the server, which runs as a command-line daemon (or can
#     be used as a library)
#
#   * the client, which e.g. gets imported by hub and used
#     for communication between hub and the server daemon.
#
#################################################################

path           = require('path')
async          = require('async')
fs             = require('fs')
net            = require('net')
child_process  = require('child_process')
winston        = require('winston')
assert         = require('assert')

message        = require('smc-util/message')
misc_node      = require('smc-util-node/misc_node')
{secret_token_filename} = require('./common')

port_manager = require('./port_manager')
misc = require('smc-util/misc')
{to_json, from_json, defaults, required} = misc

abspath = (path) ->
    if path.length == 0
        return process.env.HOME
    if path[0] == '/'
        return path  # already an absolute path
    return process.env.HOME + '/' + path

if not process.env.SMC?
    process.env.SMC = path.join(process.env.HOME, '.smc')

DATA = path.join(process.env['SMC'], 'console_server')

if not fs.existsSync(process.env['SMC'])
    fs.mkdirSync(process.env['SMC'])
if not fs.existsSync(DATA)
    fs.mkdirSync(DATA)

##################################################################
# Read the secret token file.
#
# This file is created by the local_hub process, which is started at
# the same time as the console_server. So, we try for up to 5 seconds
# until this file appears.
##################################################################

misc = require('smc-util/misc')


secret_token = undefined

read_token = (cb) ->
    f = (cb) ->
        fs.exists secret_token_filename, (exists) ->
            if not exists
                cb("secret token file does not exist")
            else
                secret_token = fs.readFileSync(secret_token_filename).toString()
                cb()
    misc.retry_until_success
        f        : f
        max_time : 30000
        cb       : cb

start_session = (socket, mesg) ->
    winston.info "start_session #{to_json(mesg)}"

    if not mesg.params?  # for connecting to an existing session.
        mesg.params = {}

    opts = defaults mesg.params,
        rows     : 24
        cols     : 80
        command  : 'bash'
        args     : []
        path     : required
        filename : required

    opts.path     = abspath(opts.path)  # important since console server is started in some random location
    opts.filename = abspath(opts.filename)

    init_fn = misc.console_init_filename(opts.filename)
    if fs.existsSync(init_fn) and opts.command == 'bash'
        opts.args = ['--init-file', "#{init_fn}"]

    if process.env['USER'] == 'root'
        if not mesg.project_id? or mesg.project_id.length != 36
            winston.debug("suspicious project_id (=#{mesg.project_id}) -- bailing")
            return

    winston.debug "start_session opts = #{to_json(opts)}"

    # Ensure that the given user exists.  If not, send an error.  The
    # hub should always ensure the user exists before starting a session.
    async.series([
        (cb) ->
            # Fork off a child process that does all further work to
            # handle a connection.  We prefer the .js file if it is there,
            # e.g., in kucalc, but can also work purely with .coffee, e.g.,
            # for smc-in-smc development.  NOTE: things will break if
            # just console_server_child.js exists, but not all other .js files.
            module = __dirname + '/console_server_child'
            if fs.existsSync(module + '.js')
                module += '.js'
            else
                module += '.coffee'
            child = child_process.fork(module, [])

            # Send the pid of the child to the client (the connected hub)
            socket.write_mesg('json', message.session_description(pid:child.pid))

            # Disable use of the socket for sending/receiving messages, since
            # it will be only used for raw xterm stuff hence.
            misc_node.disable_mesg(socket)

            # Give the socket to the child, along with the options.
            child.send(opts, socket)

{'fraction_non_alphanumeric': 0.17428121307601419, 'fraction_numerical': 0.0037416305632138637, 'mean_word_length': 2.731447465099192, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7233418', 'n_tokens_mistral': 2852, 'n_tokens_neox': 2724, 'n_words': 933}