adding docker-compose
This commit is contained in:
parent
5af8687ebb
commit
8779fee2af
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.idea/
|
||||||
|
./redis_conf/
|
||||||
|
./redis_data/
|
||||||
|
./node_logs/
|
||||||
|
./node_app./
|
||||||
|
node_modules/
|
||||||
|
./node_app/package-lock.json
|
10
README.md
10
README.md
@ -1,13 +1,13 @@
|
|||||||
# short_url
|
# short_url
|
||||||
This is a simple application to short you urls with your own web server.
|
This is a simple application to short you urls with your own web server.
|
||||||
It is based on Nodejs and Redis.
|
It is based on Nodejs and Redis.
|
||||||
You will have to change the IPs in the `index.js` and `./public/index.html` files to your public IP or domain.
|
You can change the public ports `8080` (web UI) and `8081` (websocket) in the `docker-compose.yml` file
|
||||||
To start the server simply execute `index.js`:
|
To start the server simply use `docker-compose`:
|
||||||
```
|
```
|
||||||
screen -A -m -d -S short_url nodejs index.js
|
docker-compose -p surl up -d
|
||||||
```
|
```
|
||||||
It's recommended to use a reverse proxy like nginx to manage SSL certificates etc.
|
It's recommended to use a reverse proxy like nginx to manage SSL certificates.
|
||||||
|
|
||||||
A test instance is running under [s.adb.sh](https://s.adb.sh).
|
A test instance is running at [s.adb.sh](https://s.adb.sh).
|
||||||
|
|
||||||
//good luck
|
//good luck
|
||||||
|
31
docker-compose.yml
Normal file
31
docker-compose.yml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
node:
|
||||||
|
image: node
|
||||||
|
container_name: surl_node
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "8081:8081"
|
||||||
|
volumes:
|
||||||
|
- ./node_app/:/home/node/app/
|
||||||
|
# - ./node_modules/:/home/node/app/node_modules/
|
||||||
|
- ./node_logs/:/var/log/
|
||||||
|
working_dir: /home/node/app/
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
command: sh -c 'npm i && nodejs index.js'
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
container_name: surl_redis
|
||||||
|
expose:
|
||||||
|
- 6379
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./redis_data/:/var/lib/redis/
|
||||||
|
- ./redis_conf/:/usr/local/etc/redis/
|
||||||
|
environment:
|
||||||
|
- REDIS_REPLICATION_MODE=master
|
@ -5,30 +5,34 @@ const ws = require('ws')
|
|||||||
const redis = require('redis')
|
const redis = require('redis')
|
||||||
const mime = require('mime')
|
const mime = require('mime')
|
||||||
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest
|
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest
|
||||||
|
|
||||||
const host = 'http://127.0.0.1:8080/'
|
const host = 'http://127.0.0.1:8080/'
|
||||||
|
const outpath = ['sym', '', 'manifest.json', 'admin', 'stats']
|
||||||
|
|
||||||
//redis client
|
//redis client
|
||||||
const redis_cli = redis.createClient()
|
const redis_cli = redis.createClient({
|
||||||
|
host: 'redis',
|
||||||
|
port: 6379
|
||||||
|
})
|
||||||
redis_cli.on("error", function (error) {
|
redis_cli.on("error", function (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
const outpath = ['sym', '', 'manifest.json']
|
|
||||||
|
|
||||||
//HTTP server
|
//HTTP server
|
||||||
http.createServer(function (req, res) {
|
http.createServer(function (req, res) {
|
||||||
const q = url.parse(req.url, true);
|
const q = url.parse(req.url, true);
|
||||||
let filename = "./public" + q.pathname;
|
let filename = "./public" + q.pathname;
|
||||||
if (filename === "./public/") filename = "./public/index.html";
|
let path_split = q.pathname.split("/", 3);
|
||||||
|
if (path_split[path_split.length - 1] === "") filename += "/index.html";
|
||||||
let file_type = mime.getType(filename)
|
let file_type = mime.getType(filename)
|
||||||
//q.pathname.split("/", 2)[1] === "sym" || )
|
if (function valid_path(){
|
||||||
if (function valid_path(){//
|
//outpath.forEach( path => {if (request_path === path) return true})
|
||||||
for (let i = 0; i < outpath.length; i++) if (q.pathname.split("/", 2)[1] === outpath[i]) return true
|
for (let i = 0; i < outpath.length; i++) if (path_split[1] === outpath[i]) return true
|
||||||
return false
|
return false
|
||||||
}() === true){
|
}() === true){
|
||||||
fs.readFile(filename, function(err, data) {
|
fs.readFile(filename, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.writeHead(404, {'Content-Type': file_type});
|
res.writeHead(404, {'Content-Type': "text/html"});
|
||||||
return res.end("404 Not Found");
|
return res.end("404 Not Found");
|
||||||
}
|
}
|
||||||
res.writeHead(200, {'Content-Type': file_type});
|
res.writeHead(200, {'Content-Type': file_type});
|
||||||
@ -42,7 +46,7 @@ http.createServer(function (req, res) {
|
|||||||
res.writeHead(302, {'Location': obj});
|
res.writeHead(302, {'Location': obj});
|
||||||
return res.end();
|
return res.end();
|
||||||
}else{
|
}else{
|
||||||
res.writeHead(404, {'Content-Type': file_type});
|
res.writeHead(404, {'Content-Type': "text/html"});
|
||||||
return res.end("404 this short-url does not exist :/");
|
return res.end("404 this short-url does not exist :/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -113,14 +117,13 @@ wss.on('connection', ws => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
//random key
|
//random key
|
||||||
const forbidden_array = ['sym', 'admin', 'stats']
|
|
||||||
function get_key(length) {
|
function get_key(length) {
|
||||||
let forbidden = false; let output = ''
|
let forbidden = false; let output = ''
|
||||||
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
do{
|
do{
|
||||||
for (let i = 0; i < length; i++ )
|
for (let i = 0; i < length; i++ )
|
||||||
output += characters.charAt(Math.floor(Math.random() * characters.length))
|
output += characters.charAt(Math.floor(Math.random() * characters.length))
|
||||||
for (let i = 0; i < forbidden_array.length; i++) if (output === forbidden_array[i]) forbidden = true
|
for (let i = 0; i < output.length; i++) if (output === outpath[i]) forbidden = true
|
||||||
redis_cli.hget("surl;"+output, "url", function(err, obj) {
|
redis_cli.hget("surl;"+output, "url", function(err, obj) {
|
||||||
if(err) console.log(err)
|
if(err) console.log(err)
|
||||||
if(obj) forbidden = true
|
if(obj) forbidden = true
|
57
node_app/package-lock.json
generated
Normal file
57
node_app/package-lock.json
generated
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "surl",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"denque": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
|
||||||
|
},
|
||||||
|
"mime": {
|
||||||
|
"version": "2.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
|
||||||
|
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==",
|
||||||
|
"requires": {
|
||||||
|
"denque": "^1.4.1",
|
||||||
|
"redis-commands": "^1.5.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redis-commands": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ=="
|
||||||
|
},
|
||||||
|
"redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
|
||||||
|
},
|
||||||
|
"redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
||||||
|
"requires": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
|
||||||
|
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
|
||||||
|
},
|
||||||
|
"xmlhttprequest": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
|
||||||
|
"integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "short_url",
|
"name": "surl",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime": "^2.4.6",
|
"mime": "^2.4.6",
|
||||||
"redis": "^3.0.2",
|
"redis": "^3.0.2",
|
@ -1,90 +1,90 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>sURL</title>
|
<title>sURL</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<!-- PWA support -->
|
<!-- PWA support -->
|
||||||
<link rel="manifest" href="/manifest.json"/>
|
<link rel="manifest" href="/manifest.json"/>
|
||||||
<meta name="theme-color" content="#2F3BA2"/>
|
<meta name="theme-color" content="#2F3BA2"/>
|
||||||
<meta name="description" content="short your url"/>
|
<meta name="description" content="short your url"/>
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'/>
|
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0'/>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="./sym/dark.css"/>
|
<link rel="stylesheet" type="text/css" href="./sym/dark.css"/>
|
||||||
<script src="./sym/script.js"></script>
|
<script src="./sym/script.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const wsurl = 'ws://127.0.0.1:8081'
|
const wsurl = 'ws://127.0.0.1:8081'
|
||||||
const socket = new WebSocket(wsurl)
|
const socket = new WebSocket(wsurl)
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
socket.send('new session')
|
socket.send('new session')
|
||||||
}
|
}
|
||||||
socket.onerror = (error) => {
|
socket.onerror = (error) => {
|
||||||
console.log(`WebSocket error: ${error}`)
|
console.log(`WebSocket error: ${error}`)
|
||||||
}
|
}
|
||||||
socket.onclose = error => show_error('session timed out (refresh)')
|
socket.onclose = error => show_error('session timed out (refresh)')
|
||||||
socket.onmessage = (e) => {
|
socket.onmessage = (e) => {
|
||||||
console.log(e.data)
|
console.log(e.data)
|
||||||
let msg = e.data.split(";", 2)
|
let msg = e.data.split(";", 2)
|
||||||
if (msg[0] === 'error') show_error(msg[1])
|
if (msg[0] === 'error') show_error(msg[1])
|
||||||
else if (msg[0] === 'surl') {
|
else if (msg[0] === 'surl') {
|
||||||
document.getElementById('surl-input').value = msg[1]
|
document.getElementById('surl-input').value = msg[1]
|
||||||
document.getElementById('surl-popup').style.display = 'block'
|
document.getElementById('surl-popup').style.display = 'block'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function copy_url() {
|
function copy_url() {
|
||||||
document.getElementById('surl-input').select()
|
document.getElementById('surl-input').select()
|
||||||
document.execCommand("copy")
|
document.execCommand("copy")
|
||||||
}
|
}
|
||||||
//document.surl_form.action = surl_submit()
|
//document.surl_form.action = surl_submit()
|
||||||
function lurl_submit() {
|
function lurl_submit() {
|
||||||
socket.send('long_url;'+document.getElementById('longurl-input').value)
|
socket.send('long_url;'+document.getElementById('longurl-input').value)
|
||||||
}
|
}
|
||||||
function show_error(msg) {
|
function show_error(msg) {
|
||||||
let error_style = document.getElementById('error-box').style
|
let error_style = document.getElementById('error-box').style
|
||||||
document.getElementById('error-message').innerText = msg
|
document.getElementById('error-message').innerText = msg
|
||||||
error_style.display = "block"
|
error_style.display = "block"
|
||||||
error_style.animation = "slide-from-left alternate 0.2s"
|
error_style.animation = "slide-from-left alternate 0.2s"
|
||||||
setTimeout(to => {error_style.animation = ""}, 200)
|
setTimeout(to => {error_style.animation = ""}, 200)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<div class="title">short your url</div>
|
<div class="title">short your url</div>
|
||||||
<form name="surl_form" action="javascript:lurl_submit()">
|
<form name="surl_form" action="javascript:lurl_submit()">
|
||||||
<div class="input-field" id="longurl">
|
<div class="input-field" id="longurl">
|
||||||
<input class="input" id="longurl-input" type="text" autocomplete="off" onblur="deselected('longurl');" onfocus="selected('longurl');" name="url" value="" maxlength="2000" placeholder="https://your.long.url/junk">
|
<input class="input" id="longurl-input" type="text" autocomplete="off" onblur="deselected('longurl');" onfocus="selected('longurl');" name="url" value="" maxlength="2000" placeholder="https://your.long.url/junk">
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" value="search" name="login">
|
<input type="hidden" value="search" name="login">
|
||||||
<a href="javascript:lurl_submit()">
|
<a href="javascript:lurl_submit()">
|
||||||
<div class="btn-blue" id="short-btn">
|
<div class="btn-blue" id="short-btn">
|
||||||
<div class="btn-text">short</div>
|
<div class="btn-text">short</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box-dark" id="surl-popup">
|
<div class="box-dark" id="surl-popup">
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<div class="title2">your sURL:</div>
|
<div class="title2">your sURL:</div>
|
||||||
<div class="input-field" id="surl">
|
<div class="input-field" id="surl">
|
||||||
<input class="input" id="surl-input" type="text" onblur="deselected('surl');" onfocus="selected('surl');" size="30" value="" maxlength="20" placeholder="surl">
|
<input class="input" id="surl-input" type="text" onblur="deselected('surl');" onfocus="selected('surl');" size="30" value="" maxlength="20" placeholder="surl">
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" value="search" name="login">
|
<input type="hidden" value="search" name="login">
|
||||||
<div class="btn-green" id="copy-btn" onclick="copy_url()">
|
<div class="btn-green" id="copy-btn" onclick="copy_url()">
|
||||||
<div class="btn-text">copy</div>
|
<div class="btn-text">copy</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div onclick="this.parentNode.style.display = 'none'" style="position: absolute; top:5px; right: 5px;" class="sym_btn-invisible">
|
<div onclick="this.parentNode.style.display = 'none'" style="position: absolute; top:5px; right: 5px;" class="sym_btn-invisible">
|
||||||
<img class="icon" src="./sym/ic_close_white_24px.svg" />
|
<img class="icon" src="./sym/ic_close_white_24px.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="error-box" class="error">
|
<div id="error-box" class="error">
|
||||||
<div onclick="this.parentNode.style.display = 'none'" style="position: absolute; top:5px; right: 5px;" class="sym_btn-invisible">
|
<div onclick="this.parentNode.style.display = 'none'" style="position: absolute; top:5px; right: 5px;" class="sym_btn-invisible">
|
||||||
<img class="icon" src="./sym/ic_close_white_24px.svg">
|
<img class="icon" src="./sym/ic_close_white_24px.svg">
|
||||||
</div>
|
</div>
|
||||||
<div id="error-message" class="btn-text">
|
<div id="error-message" class="btn-text">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,4 +1,4 @@
|
|||||||
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 265 B |
Loading…
Reference in New Issue
Block a user