- PVSM.RU - https://www.pvsm.ru -
Начну с того, что однажды мне захотелось создать приложение. Желание такое возникло из-за того, что я люблю читать, а нормальных книжных агрегаторов на просторах русского интернета просто нет. Собственно из боли поиска чего бы почитать и попыток вспомнить как называлась та книжка, которую я недавно читал и на какой же главе я остановился, родилось желание сделать веб-приложение, в котором всё это было бы возможно и удобно. Стоит отметить, что никакого опыта разработки, программирования и т.п. у меня не было, моя работа вообще с этим не связана. Тем не менее желание перебороло лень и переросло в конкретные действия, своеобразное хобби.
Не буду рассказывать как я изучал javascript, node.js, react, html, css и т.п., перейдём к тому, к чему я пришел на данный момент, чем хотел бы с вами поделится и, конечно, послушать конструктивную критику специалистов.
Как и многие я тренировался на собственном ПК на localhost:3000, создавал front/back-end'ы, верстал, работал с api и т.д., но меня всегда тревожила мысль а том, как же всё это потом перенести на ? Будет ли оно работать? Нужно ли будет переписывать из-за этого код? И самое главное, нельзя ли всё настроить так, чтобы я мог работать над приложением с любого ПК и легко переносить всё на
На своё хобби я готов был тратить 10$ в месяц, поэтому выбирал тот
Jelastic: красивый и удобный интерфейс, вроде всё интуитивно, масштабируемо и понятно. Тем не менее столкнулся с трудностями при настройке (nginx почему-то из
Облачный
AWS и Google облака: попробовал, всё вроде хорошо, но вспомнил про наши «замечательные» законы и требование размешать данные пользователей на сервера в РФ. У этих ребят, к сожалению, серверов в РФ не оказалось. Не юрист, но от греха решил поискать облака с серверами в РФ. Если же ваше приложение маловероятно будет иметь проблемы с законом, то хороший выбор.
Облака с серверами в РФ хоть и были, но хотелось всё же что-то, что избавит меня от необходимости погружаться в администрирование PostgreSQL. Порыв немного наткнулся на не так давно ставшие доступными Яндекс.Облака [2], попробовал, вроде всё просто и удобно, поэтому остановился пока на них. Стоит отметить, что
1) Создаём каталог под свой сайт:
![]()
2) Создаём Virtual Private Cloud:
Главное что он даёт для меня на текущем этапе — это IP для доступа к созданному ресурсу из вне. С подсетями, зонами, изоляцией и отказоустойчивостью ознакомился поверхностно, при необходимости наверстаю.
3) Создаём подсеть и присваиваем ей внутренний IP (как я понял, это примерно как локальная сеть)

4) Переходим на вкладку IP и резервируем себе статический IP.
По нему мы будем подключаться из дома и других мест. Вероятно можно и с динамическим работать, но я не разбирался в каких случаях он меняется.

Тут у нас будут происходить вычисления :) То есть мы создадим виртуальную машину с Linux (я выбрал ubuntu 18.04), установим node.js приложения и postgreSQL.

Жмём создать ВМ, выкручиваем все настройки на минимум, так как при разработке нагрузки не будет (когда наше приложение выйдет в свет, тогда и подкрутим побольше, ну и будем мониторить по графикам).
Проблемный момент, с которым я столкнулся на этом этапе, это SSH:

Что это и зачем понятия не имел, поэтому пошел изучать. Оказалось — это просто метод доступа, но не по паролю, а по сгенерированному SSH ключу. Чтобы собственно его сгенерировать, скачиваем и устанавливаем Putty [3] как нам советуют.
Запускаем C:Program FilesPuTTYputtygen.exe

Нажимаем кнопку Generate и водим мышкой, чтобы придать случайности сгенерированному ключа (как я понял). Далее копируем появившуюся строку начинающуюся с ssh-rsa куда-нибудь в текстовый файл и жмём Save private key, Save public key. Скопированный в текстовый файл ключ вставляем в поле SSH ключ открытой страницы Яндекса. Логин указываем root, иначе у вас не будет доступа при работе с графической файловой системой приложения по которому будете подключаться к облаку из дома/работы (возможно и есть способ, но я не разбирался).
Как заметил andreymal, лучше не использовать root, чтобы китайские боты не подобрали пароль к вашему облаку, но так как в яндекс.облаке доступ только по SSH, то жить вроде можно.
Приложение на
Стандартный Putty позволяет работать только командной строкой, а так как мне пользователю windows это непривычно, то я начал искать клиент с псевдо-проводником. Сначала я попробовал Mobaxterm, но он после какого-то времени простоя отключается, проводник вообще зависает, поэтому сейчас работаю с bitvise ssh [4] и пока проблем как у Mobaxterm не наблюдаю.

Тут в поле Server>Host указываем наш внешний IP облака. Порт 22. Нажимаем client key manager>import указываем в нём сгенерированный Putty ранее private ключ. Может ещё потребоваться ключевая фраза, выберите что-нибудь что не забудете. Закрываем это окошко и в поле authentication указываем username: root, method publick key, client key — выбираем импортированный ранее. Жмём login и если мы всё сделали правильно, то подключимся к облаку:

Тут я рекомендую пользоваться инструкциями digitalocean.com, они очень подробны и многие есть на русском. Обычно я так и гуглю «digitalocean ubuntu 18.04 node.js» или что вы там захотите установить или настроить.
Как установить Node.js можно почитать тут [5].
Если коротко, то заходим на nodesource [6](тут последние версии node.js можно установить), листаем сюда:

Копируем и по-очереди запускаем команды:
curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
sudo apt-get install -y nodejs
Проверяем как установилось командой
nodejs -v
Нам покажет версию node.js
npm -v
Нам покажет версию менеджера пакетов для node.js.
Далее идём в папку /opt/mysuperapp (my_super_app_name — эту папку вы должны создать). Каталог opt был выбран в качестве места расположения приложения после долгих гуглений «где уместно положить файлы node.js приложения в ubuntu».
Наконец-таки создаём файл server.js, который будет входной точкой приложения и вставляем туда код простого сервера на node.js:
const http = require('http');
const hostname = 'localhost';
const port = 80;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World!n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Порт 80 — это для http запросов, 443 — для https. Пока у нас сервер на http.
Сохраняем всё и запускаем командной:
node server.js
В консоли должно появиться строка 'Server running at localhost [7]:80/'
Теперь можно открыть браузер, ввести внешний IP (тот что в облаке яндекса у вашей ВМ ubuntu) и мы увидим «Hello World!»
Всё вроде работает, но мы же не будем работать всё время подключаясь к облаку. К тому же вдруг мы будем в будущем работать не одни.
Github — это место, где будет лежать код нашего приложения. Если коротко, принцип работы для одного человека следующий:
Всё быстро, просто и удобно.
Собственно регистрируемся на Github и создаём private репозиторий для нашего приложения (он будет доступен только нам):

Копируем строку github.com/ReTWi/mysuperapp.git [8] для скачивания приложения.

Git- это то, с помощью чего мы будем загружать наше приложение на github, а оттуда на
Устанавливаем git на хостинге [9] командами:
sudo apt update
sudo apt install git
Проверяем всё ли хорошо установилось:
git --version
Должна показаться версия git.
Заполняем данные git (так и не понял зачем, но видимо могут быть какие-то занудные предупреждения).
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
Наконец-таки загружаем наше приложение на
(тут должна быть ссылка на ваше приложение )
git clone https://github.com/ReTWi/mysuperapp.git
В каталоге /opt появится новый mysuperapp, где будут находится файлы нашего приложения загруженные из github.
Теперь пора повторить тоже самое для ПК и замкнуть цепочку ПК(разные) --> Github -->
Устанавливаем node.js на ПК [10].
Для начала выберем редактор исходного кода, где будем работать. Я выбрал Visual studio code, так он прост, удобен, в нём много плагинов и можно настроить синхронизацию настроек если вы работаете с нескольких устройств. Собственно скачиваем, устанавливаем, запускаем, выбираем общую папку приложений, так как git clone создаст нам свою.
Плагины я использую следующие:

Устанавливаем git для ПК [11].
Открываем консоль в VScode с помощью ctrl+shift+` или terminal>new terminal
Отступление:
В консоли windows плохо с русскими символами и чтобы не было крякозяблов нужно открыть file>preferences>settings, ввести в поле terminal.integrated.shellArgs.windows, нажать

И добавить строку «terminal.integrated.shellArgs.windows»: ["-NoExit", "/c", «chcp 65001»],

Повторяем команду для загрузки файлов с github:
git clone https://github.com/ReTWi/mysuperapp.git
В VScode нажимаем File>Open Folder и открываем папку нашего приложения.
Создаём файл server.js с тем же кодом простого сервера:
const http = require('http');
const hostname = 'localhost';
const port = 80;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World!n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Устанавливаем nodemon для автоматической перезагрузки сервера при изменениях в коде:
npm i nodemon -g
i — сокращение от install
g — глобальная установка (чтобы было доступно в консоли), а не только для нашего приложения.
Запускаем командой:
nodemon server.js
Открываем в браузере localhost [7]:80/ или просто localhost:80 и видим Hello World.
Теперь настало время проверить нашу цепочку ПК>Github>Hosting.
Скачиваем Github desktop для большего удобства, подключаем свой github аккаунт, затем нажимаем файл add local repository и указываем каталог нашего приложения.
В приложении мы видим изменения которые мы сделали по сравнению с загруженной с Github версией (мы добавили server.js):

Жмём «commit to master»>«push origin», таким образом загружая файлы с ПК на Github.

Заходим в браузере на наш github аккаунт и видим загруженный файл server.js:

Потренируемся ещё немного, в VScode заменим строку «res.end('Hello World !n');» на «res.end('OmNomNom');». Увидим, что сервер сам перезагрузился:

Проверим в браузере и увидим там сделанные нами изменения «OmNomNom».
Desktop github тоже покажет нам что мы поменяли строку:

Опять жмём «commit to master»>«push origin», чтобы отправить файлы на github.
Переключаемся на командную строку
Останавливаем наше приложение, если оно ещё запущено (ctrl+c).
Скачиваем наше обновлённое приложение командами:
git config credential.helper store
git pull
Первая сохранит наши данные, чтобы постоянно не вводить логин и пароль. В дальнейшем нам достаточно будет git pull.
Установим pm2 — нечто похожее на nodemon, только для
npm i pm2 -g
Запустим приложение с помощью pm2, которые при следующем git pull на
pm2 start server.js --watch
Откроем браузер по нашему внешнему IP облака и увидим наше «OmNomNom».
Таким образом мы замкнули цепочку работы с приложением и быстрым его развёртыванием на
Заходим на сайт zerossl.com [12]

В поле domains, ip… вписываем сначала localhost, нажимаем generate и скачиваем 2 файла по кнопке:

Сохраняем их у нас в проекте в папке ssl/localhost.
Повторяем процедуру для внешнего IP Облака и сохраняем в ssl/myapp.
Структура приложения:

Тут мы указываем те файлы/папки, которые мы не хотим выгружать на github. Они будут только на данном устройстве и git не будет отслеживать/показывать их изменения. Открываем и вставляем:
/node_modules/
/logs/*
# exception to the rule
!logs/.gitkeep
/public/react_bundle.js
/public/isProd.js
Так как github не выгружает пустые папки, то можно внутрь что-нибудь положить, к примеру пустой файл .gitkeep. Сохраняем файл и закрываем.
Открываем и вставляем следующее (после // добавил комментарии)
{
"name": "myapp", // название вашего приложения
"version": "1.0.0",
"description": "OmNomNom",
"main": "server.js",
"scripts": {
"server": "pm2 start pm2-watch.json", // командой npm run server можно запустить этот скрипт
"client": "webpack -w --mode development", // командой npm client можно запустить этот скрипт. Собирает приложение реакт и отслеживает изменения в коде, автоматически обновляя сборку.
"client-prod": "webpack --mode production", // собирает сжатый вариант для production
"client-analyze": "webpack --mode production --analyze true" // собирает сжатый вариант для production и позволяет посмотреть размеры разных модулей приложения. Полезно для оптимизации
},
"repository": {
"type": "git",
"url": "git+https://github.com/myapp/knigam.git" // ссылка на ваш репозиторий github
},
"author": "rtw",
"license": "UNLICENSED", // запрет на любое использование (личное приложение)
"bugs": {
"url": https://github.com/myapp/knigam.git"
},
"homepage": "https://github.com/myapp/knigam.git#readme",
"dependencies": {
"@babel/core": "^7.2.2", // Современный js для frontend
"@babel/plugin-transform-runtime": "^7.2.0", // Современный js для frontend
"@babel/preset-env": "^7.3.1", // Современный js для frontend
"@babel/preset-react": "^7.0.0", // Современный js для frontend
"ajv": "^6.8.1", // Проверка типов переменных
"babel-loader": "^8.0.5", // Современный js для frontend
"babel-plugin-styled-components": "^1.10.0", // Работа со styled-components
"css-loader": "^2.1.0", // Для сборки webpack'om css
"fastify": "^2.0.0-rc.6", // аналог express, более живой и активно развивающийся
"fastify-cookie": "^2.1.6", // Работа с куки
"fastify-static": "^2.2.0", // Работа со статичными файлами
"moment": "^2.24.0", // Работа со временем
"pg": "^7.8.0", // Работа со временем
"pino": "^5.11.1", // Работа с postgreSQL из node.js
"pino-pretty": "^2.5.0", // Читаемые логи в консоли
"react": "^16.8.1", // Frontend библиотека. Выбор был между ней и Vue.js, но второй больше фрэймворк. В реакте больше нужно делать руками, что полезно для обучения
"react-dom": "^16.8.1", // React для работы с браузером
"style-loader": "^0.23.1", // Для сборки webpack'om стилей, уже не помню
"styled-components": "^4.1.3", // CSS in JS, очень удобно для динамических стилей и локализации стилей в компонентах
"webpack": "^4.29.3", // Сборщик реакт приложения
"webpack-bundle-analyzer": "^3.0.3", // Анализатор размеров модулей собранного реакт приложения
"webpack-cli": "^3.2.3" // Консоль сборщик реакт приложения, не помню уже зачем
}
}
Остановлюсь на двух основных фрэймворках/библиотеках выбранных для приложения:
Fastify был выбран в качестве альтернативы express.js, так как в первом уже есть экспериментальная поддержка htpp2, он активно развивается и мне кажется у него больше будущего, нежели у express.js, который стал очень неповоротлив и кое-как развивается. С другой стороны express.js уже долгое время в работе и по нему вам легче будет найти информацию.
React был выбран так как мне с ним было проще работать, понять и пробовать всё своими руками. Vue — показался уже чем-то со своими правилами, направлением. Хотя во Vue может и меньше придётся писать что-то своими руками, но так как приоритет сделан на обучении и для человека ранее не программировавшего react пошел как-то легче.
Сохраняем файл package.json и устанавливаем все модули указанные в dependencies командой:
npm i
У нас появится папка node_modules, в которой будут все модули для нашего приложения.
client — пока пустая папка
logs — внутри лежит файл .gitkeep, чтобы папка перекочевала на
Тут статические файлы нашего сайта будут лежать, изображения там, фавиконки и т.д.
Отдельно остановимся на двух файлах:
index.html:
<!DOCTYPE html>
<html>
<head>
<base href="/" />
<meta charset="UTF-8" />
<title>MyApp</title>
</head>
<body>
<div id="cookies">Этот текст заменится содержимым react_bundle после его загрузки</div>
<noscript
>Система: онлайн подключение возможно только при наличии Javscript</noscript
>
<script src="react_bundle.js"></script>
</body>
</html>
— тут у нас подгружается react-фронтэнд и рендерится в тег по его id.
isProd.js содержит единственную строку «module.exports = false»
Так как он находится в исключениях .gitignore, то не переносится. Соответственно на ПК мы устанавливаем его в false, а на
ssl — там лежат сохранённые ранее сертификаты в папках localhost и myapp
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [">0.25%", "not ie 11", "not op_mini all"]
}
}
],
"@babel/preset-react"
],
"plugins": [
"babel-plugin-styled-components",
"@babel/plugin-transform-runtime"
]
}
Настройки для создания нашего react_bundle с поддержкой браузеров используемых более >0.25% пользователей.
import React from 'react'
import { render } from 'react-dom'
render(<div>Реакт!!</div>, document.getElementById('cookies'))
Рендерит наш фронтэнд в div с тегом cookies.
pm2-watch.json — позволяет на
Сборщик реакт приложения:
const webpack = require('webpack'),
path = require('path'),
BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = (env, argv) => {
let prod = argv.mode == 'production'
let config = {
entry: './client.js',
output: {
path: path.resolve('./public'),
filename: 'react_bundle.js'
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /.css$/,
use: ['style-loader', 'css-loader']
}
]
},
resolve: {
alias: {
client: path.resolve('./client/shared'),
public: path.resolve('./public')
}
},
plugins: [
argv.analyze ? new BundleAnalyzerPlugin() : false,
prod ? new webpack.optimize.AggressiveMergingPlugin() : false,
new webpack.ContextReplacementPlugin(/moment[/\]locale$/, /ru/)
].filter(Boolean),
optimization: {
minimize: prod ? true : false
},
performance: {
hints: false
}
}
return config
}
Если коротко, то он открывает файл client.js и все что у него внутри, собирая react_bundle и помещая его в папку public, откуда потом через открытый index.html он будет загружен.
const isProd = require('./public/isProd'),
fs = require('fs'),
log = require('./server/logger'),
path = require('path')
// Ошибки среды node.js, чтобы приложение никогда не падало
process.on('unhandledRejection', (reason, promise) => {
log.error({ reason, promise }, 'серверный процесс unhandledRejection')
})
process.on('uncaughtException', err => {
log.error({ err }, 'серверный процесс uncaughtException')
})
// Redirect server from http port 80 to https 443
const fastifyHttp = require('fastify')({
logger: log,
ignoreTrailingSlash: true
})
fastifyHttp.listen(80, '::', (err, address) => {
if (err) {
log.error({ err, address }, 'Ошибка при запуске HTTP сервера')
} else {
log.warn('Http cервер запущен')
}
})
// Let's Encrypt challenge
fastifyHttp.get('/.well-known/acme-challenge/:file', (req, res) => {
let stream = fs.createReadStream(
path.join(__dirname + '/ssl/.well-known/acme-challenge/' + req.params.file)
)
res.type('text/html').send(stream)
})
fastifyHttp.get('/*', (req, res) => {
res.redirect(301, 'https://' + req.headers.host + req.raw.url)
})
fastifyHttp.get('/', (req, res) => {
res.redirect(301, 'https://' + req.headers.host + req.raw.url)
})
// Сервер
let fastifyOptions = {
logger: log,
ignoreTrailingSlash: true,
http2: true
}
fastifyOptions.https = isProd
? {
allowHTTP1: true,
key: fs.readFileSync('./ssl/myapp/key.txt'),
cert: fs.readFileSync('./ssl/myapp/crt.txt')
}
: {
allowHTTP1: true,
key: fs.readFileSync('./ssl/localhost/cert.key'),
cert: fs.readFileSync('./ssl/localhost/cert.pem')
}
const fastify = require('fastify')(fastifyOptions)
fastify.listen(443, '::', (err, address) => {
if (err) {
log.error({ err, address }, 'Ошибка при запуске сервера')
} else {
log.warn(
`Сервер запущен в ${
isProd ? 'продакшен' : 'режиме разработки'
}`
)
}
})
// Валидатор
fastify.setSchemaCompiler(schema => {
return ajv.compile(schema)
})
// Ошибки fastify
fastify.setErrorHandler((err, req, res) => {
log.error({ err, req }, 'fastify errorHandler')
// Ошибка валидации данных запроса
if (err.validation) {
return res.send({
error: 'Ошибка валидации данных запроса'
})
} else {
return res.send({
error:
'Ошибка errorHandler'
})
}
})
// Статические файлы
fastify.register(require('fastify-static'), {
root: path.join(__dirname, './public')
})
// Куки
fastify.register(require('fastify-cookie'), err => {
if (err) log.error({ err }, 'fastify-cookie')
})
// Ответ на любой запрос исключая апи / несуществующая страница
// Тут мы на любой запрос отдаём index.html, по сути наш фронэнд
// Запросы фронтэнда у нас принимаются с префиксом api, то есть GET /api/userdata
fastify.setNotFoundHandler((req, res) => {
res.sendFile('index.html')
})
// Routes
fastify.register(
async openRoutes => {
// Пути доступные всем
openRoutes.register(require('./server/api/open'))
openRoutes.register(async withSession => {
// Пути доступные только после авторизации и проверки сессии
// Проверяем прямо тут хуком, пример:
///withSession.addHook('preHandler', async (req, res) => {
// if (!(await sessionManagerIsOk(req, res))) return
// })
withSession.register(require('./server/api/with_session'))
})
},
{ prefix: '/api' } // префикс всех путей
)
Тут лежит на бэкэнд и все пути.
logger.js — в зависимости от среды isProd логирует или в консоль или в errors.log
'use strict'
const pino = require('pino'),
isProd = require('../public/isProd')
let logOptions = isProd
? {
level: 'warn', // уровень логирования
timestamp: () => {
return ',"time":"' + new Date() + '"'
}
}
: {
level: 'warn',
prettifier: require('pino-pretty'),
prettyPrint: {
levelFirst: true,
translateTime: true
}
}
let dest = isProd ? pino.destination('./logs/errors.log') : pino.destination(1)
let log = pino(logOptions, dest)
module.exports = log
server/api/
open.js — сюда добавляем наши пути.
'use strict'
module.exports = function(fastify, options, next) {
fastify.route({
method: 'GET',
url: '/',
handler: async (req, res) => {
res.send('api / route')
}
})
fastify.route({
method: 'GET',
url: '/hi',
handler: async (req, res) => {
res.send('api / route hi')
}
})
next()
}
После настройки и проверки всего на Localhost, просто выгружаем всё на github, а оттуда git pull на
Когда вы купите себе домен и привяжете его к IP облака, пример инструкции для REG.RU [13], можно установить на сервере автоматически обновляемый бесплатный SSL для работы сайта по https.
Наш сервер работает без nginx. Возможно он понадобится нам в будущем как балансировщик нагрузки или более быстрый HTTP-сервер для раздачи статических файлов, но пока необходимости я в нём не вижу. Балансировка нагрузки пока нам не требуется, а касательно скорости раздачи статики сравнений я не нашел.
Перед установкой в папке ssl создадим папку .well-known, а в ней acme-challenge. Получится /opt/myapp/ssl/.well-known/acme-challenge
Для установке на сервере с node.js без nginx автоматически обновляемого SSL переходим по ссылке [14]. По очереди выполняем команды в консоли
sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot
sudo certbot certonly
Выбираем второй способ проверки, который разместит в папке /opt/myapp/ssl/.well-known/acme-challenge определённый файл, а после подтверждения владельца сервера его удалит.
Указываем по запросу наш домен, к примеру: «example.com» и путь к ssl папке нашего приложения (сервер настроен так, что отдаст созданный ботом файл) "/opt/myapp/ssl".
Бот сам настроит cron-задачу для обновления сертификата до срока его истечения в течение 90-дней.
Не думал, что займёт столько времени всё написать, к 4 часам ночи уже мог что-то упустить :/
Интересно мнение хабравчан и специалистов, кто осилил это полотно или прочитал какие-то отдельные моменты. Как у вас устроен цикл разработки? Есть ли какие-то моменты в которых я ошибаюсь или поступаю не лучшим образом?
Автор: Максим
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/node-js/308575
Ссылки в тексте:
[1] хостинг: https://www.reg.ru/?rlink=reflink-717
[2] Яндекс.Облака: https://cloud.yandex.ru/
[3] Putty: https://www.putty.org/
[4] bitvise ssh: https://www.bitvise.com/download-area
[5] тут: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04
[6] nodesource : https://github.com/nodesource/distributions
[7] localhost: http://localhost
[8] github.com/ReTWi/mysuperapp.git: https://github.com/ReTWi/mysuperapp.git
[9] git на хостинге: https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-18-04-quickstart
[10] node.js на ПК: https://nodejs.org/en/
[11] Устанавливаем git для ПК: https://git-scm.com/download
[12] zerossl.com: https://zerossl.com/free-ssl/#self
[13] инструкции для REG.RU: https://www.reg.ru/support/domains/dns_servery_i_nastroika_zony/Nastroika-zony/kak-dobavit-zapis-a-poddomen
[14] по ссылке: https://certbot.eff.org/lets-encrypt/ubuntubionic-other
[15] Источник: https://habr.com/ru/post/439952/?utm_source=habrahabr&utm_medium=rss&utm_campaign=439952
Нажмите здесь для печати.