공부하고

let's encrypt를 이용하면 SSL 인증서 발급과 갱신을 무료로 처리할 수 있습니다.




certbot 설치

cd /usr/bin
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto
ln -s /usr/bin/certbot-auto /usr/bin/certbot



서버 인증 허용 설정(nginx 기준) *인증 방식이 webroot가 아니라면 건너뛰세요.

location ~ /\.(?!well-known).* {
    access_log off;
    log_not_found off;
    deny all;
}

webroot(웹 경로)를 기준으로 인증서를 발급하기에 루트 경로의 .well-known 폴더 접근이 가능해야 합니다.

service nginx reload

변경사항 반영을 위해 nginx를 재실행 합니다.




인증서 발급 - webroot 방식

certbot certonly -a webroot --webroot-path=/var/www/example.com -d example.com -d www.example.com

-a : 인증 방식 설정
--webroot-path : 웹 경로 지정
-d : 도메인(멀티 도메인 설정 가능)




인증서 발급 - DNS 방식

certbot certonly dns -d example.com --manual --preferred-challenges

DNS 라운드 로빈, HAPROXY 등을 이용할 경우에는 DNS 인증 방식을 사용할 수 있습니다.




DH 인증서 발급
openssl dhparam -out dhparam.pem 4096
발급이 완료된 인증서(dhparam.pem)는 /etc/letsencrypt/live/example.com/ 경로로 이동합니다.



서버 인증서 설정

listen      443 ssl http2;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_dhparam /etc/letsencrypt/live/example.com/dhparam.pem;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;

ssl_stapling on;
ssl_stapling_verify on;

resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;



인증서 갱신

service nginx stop
certbot renew
service nginx start

위 커맨드를 스크립트로 만들어 cronjob에 등록하면 자동 갱신을 할 수 있습니다.


rsync는 파일 백업, 동기화 등에 사용되는 리눅스 명령어입니다.




로컬 내 폴더 동기화

rsync -avzrh /public/www /backup/www

/public/www 폴더를 /backup/www 폴더로 동기화합니다.




원격 서버 폴더를 로컬 서버로 동기화

rsync -avzrh root@123.123.123.123:/backup /backup

123.123.123.123 서버의 /backup 폴더를 로컬 내 /backup 폴더로 동기화합니다.




로컬 내 폴더 원격 서버로 동기화

rsync -avzrh /backup root@123.123.123.123:/backup

로컬내 /backup 폴더를 123.123.123.123 서버의 /backup 폴더로 동기화합니다.




자동 삭제

rsync -avzrh --delete /backup root@123.123.123.123:/backup

--delete 옵션 사용 시 소스 파일의 위치에 존재 하지 않는 파일을 삭제삽니다.




비밀번호, 포트 설정

rsync -avzrh --rsh='sshpass -p 비밀번호 ssh -p포트 -l root' 123.123.123.123:/backup /backup

접속할 서버에 22번 외 다른 포트, 비밀번호가 설정되어 있을 경우 포트("-p")와 비밀번호("-p ")를 설정할 수 있습니다. (공백 1개 차이 주의)
* sshpass를 이용하기 위해서는 sshpass를 설치해야 합니다. (yum install sshpass)




특정 확장자 제외

rsync -avzrh --exclude=".js" root@123.123.123.123:/backup /backup

js 확장자를 제외한 파일 동기화


오랜만에 모네로(xmr)를 채굴하기 위해 마풀허를 접속해보니 새로운 해시 프로토콜(pow)을 사용한다는 것을 알게 되었습니다.


새로운 pow는 바로 'monero7'인데 이는 기존 포스팅에서 안내드린 'xmr-stak-cpu' 마이너에서는 지원하지 않아 


Monero-v7와 Aeon-v7, Sumukoin-v3 등 새로운 pow를 지원하는 'xmr-stak' 마이너를 사용해보기로 하였습니다.



xmr-stak은 xmr-stak-cpu의 통합 버전이기 때문에 설치 과정은 거의 동일합니다.




필수 패키지 설치

yum install epel-release
yum install libmicrohttpd-devel autoconf cmake3 centos-release-scl hwloc-devel scl-utils git



devtoolset-4-gcc 설치 및 활성화

yum install devtoolset-4-gcc* 
scl enable devtoolset-4 bash

xmr-stak 컴파일 시 필요한 GCC v5.1+을 위해 devtoolset-4을 활성화 합니다.




xmr-stak 클론

git clone https://github.com/fireice-uk/xmr-stak.git




클론 받은 폴더로 이동

cd xmr-stak



xmr-stak 개발자 기부 비활성화(선택사항)

sed -i 's/constexpr double fDevDonationLevel.*/constexpr double fDevDonationLevel = 0.0;/' ./xmrstak/donate-level.hpp



빌드 및 설치

cmake3 -DCUDA_ENABLE=OFF -DOpenCL_ENABLE=OFF
make

만약, 그래픽 카드 채굴을 활성화 하고 싶으시다면 cmake3 시 -DCUDA_ENABLE=OFF -DOpenCL_ENABLE=OFF 옵션을 제거해주세요.




xmr-stak 초기 설정

./bin/xmr-stak Please enter: - Do you want to use the HTTP interface? Unlike the screen display, browser interface is not affected by the GPU lag. If you dont want to use it, please enter 0, otherwise enter port number that the miner should listen on HTTP 리포터 활성화를 원할 경우 포트 번호를 입력, 그렇지 않을 경우 0 입력 0 Configuration stored in file 'config.txt' Please enter: - Please enter the currency that you want to mine: - aeon7 - bbscoin - croat - cryptonight - cryptonight_heavy - cryptonight_lite - cryptonight_lite_v7 - cryptonight_v7 - edollar - electroneum - graft - haven - intense - karbo - monero7 - stellite - sumokoin 마이닝 할 화폐의 pow 방식 입력 monero7 - Pool address: e.g. pool.usxmrpool.com:3333 마이닝 주소 입력 asia.cryptonight-hub.miningpoolhub.com:20580 - Username (wallet address or pool login): 유저명 입력(지갑 주소 또는 pool 계정), 마이닝풀허브는 계정명.마이너명(id.worker)

wi-fi.worker1 - Password (mostly empty or x): 마이너 비밀번호 입력 x - Rig identifier for pool-side statistics (needs pool support). Can be empty: pool 관련 설정(입력 필요x) - Does this pool port support TLS/SSL? Use no if unknown. (y/N) 사용하는 pool의 TLS/SSL 지원 여부 y - Do you want to use nicehash on this pool? (y/n) 나이스해시 pool 사용 설정 여부 n - Do you want to use multiple pools? (y/n) 다중 pool 사용 설정 여부 n




xmr-stak 실행

./bin/xmr-stak

CentOS 6에서 Node.js를 설치하는 방법으로,


  1. 소스 설치
  2. 패키지 매니저(yum)를 통한 설치


등이 있습니다.



특정 버전을 설치할 때에는 소스 설치를 할 필요가 있지만


그렇지 않고 일반적인 프로덕션 환경에 맞춰 개발 또는 운영을 할 경우에는


패키지 매니저를 이용해 설치하는 것이 쉽고 안전하며, 시간을 단축할 수도 있어 효율적입니다.



그런데, CentOS 6에서 단순히 nodejs를 패키지 매니저로 설치하려고 한다면 0.10.x 버전대가 설치되기 때문에


최신 버전(현재 9버전)을 설치하기 위해서는 아래의 방법을 순서대로 진행해보시기 바랍니다.




native 애드온 도구를 빌드하기 위한 의존성 패키지 설치

yum install -y gcc-c++ make


Node.js 최신버전 저장소 설치

curl -sL https://rpm.nodesource.com/setup_9.x | sudo -E bash -

2018.04.16 기준 최신버전(9.x)입니다.

참조 : https://github.com/nodesource/distributions



만약, 안정화 버전 저장소를 설치하고 싶으시다면 아래의 구문을 사용해주세요.

curl -sL https://rpm.nodesource.com/setup_8.x | sudo -E bash - 

2018.04.16 기준 안정화 버전(8.x)입니다.

참조 : https://github.com/nodesource/distributions



Node.js 설치

yum install nodejs


Node.js 버전 확인

node -v

v9.11.1


일반적으로 JS 라이브러리는 각 개발 사이트에서 다운로드 후 "/lib"과 같은 특정 폴더에 넣어 관리를 합니다.


그런데, 프로젝트가 계속 발전해가는 상황에서 위 방식으로 계속 관리를 하게 된다면


  1. 의존성 관리
    "각 JS 라이브러리 간의 의존성/호환성 문제는 없는지?"

  2. 버전 관리
    "내가 사용하고 있는 JS 라이브러리가 결함이 있는 버전은 아닌지, 최신 버전은 맞는지?"



등에 있어 문제점을 겪게될 수 있습니다.



이러한 문제점을 해소하기 위해 node.js의 package.json을 이용해 JS 라이브러리(모듈) 관리를 해보기로 하였습니다.





package.json

{
    "name": "app",
    "version": "0.0.1",
    "private": true,
    "scripts": {
        "start": "node ./bin/www"
    },
    "dependencies": {
        "JSON": "^1.0.0",
        "backbone": "https://github.com/jashkenas/backbone",
        "backbone-validation": "^0.11.5",
        "bootstrap": "4.0.0",
        "cookie-parser": "~1.4.3",
        "debug": "~2.6.9",
        "ejs": "~2.5.7",
        "express": "~4.16.0",
        "express-ejs-layouts": "^2.4.0",
        "http-errors": "~1.6.2",
        "jquery": "^1.12.4",
        "moment": "^2.22.0",
        "morgan": "~1.9.0",
        "redis": "^2.8.0",
        "requirejs": "^2.3.5",
        "requirejs-text": "^2.0.15",
        "socket.io": "^2.1.0",
        "underscore": "^1.8.3"
    }
}

name : 프로젝트 명

version : 프로젝트 버전

private : 개인 프로젝트 여부

scripts : npm run 단축 명령어, 예) npm run start

dependencies : 의존성 모듈


dependencies 버전 종류

  • version(=version) : 동일한 버전 
  • >version : 상위 버전 
  • >=version : 상위 버전이거나 동일한 버전 
  • <version : 하위 버전 
  • <=version : 하위 버전이거나 동일한 버전 
  • ~version : 해당 버전의 minor 버전 범위, 예) ~1.3.0 => 1.3.1, 1.3.2, .., 1.3.9, 1.4.0(x)
  • ^version : 호환 버전



app.js

..
app.use('/javascripts', express.static(__dirname + '/node_modules'));
..

node_modules 폴더가 public 폴더와 같은 레벨에 있어 위 코드를 추가하였습니다.



app/config.js

require.config({
    urlArgs: 'version=0.0.1',
    baseUrl: "/javascripts",
    paths: {
        jquery : 'jquery/dist/jquery.min',
        underscore : 'underscore/underscore-min',
        backbone : 'backbone/backbone-min',
        backbone_validation : 'backbone-validation/backbone-validation-min',
        moment: 'momen/min/moment.min',
        bootstrap: 'bootstrap/dist/js/bootstrap.min',
        text: 'requirejs-text/text',
        "socket.io": 'socket.io-client/dist/socket.io'
    },
    shim : {
        underscore : {
            exports : '_'
        },
        backbone: {
            exports: 'Backbone',
            deps: ['jquery', 'underscore']
        },
        backbone_validation: {
            exports: 'BackboneValidation',
            deps: ['backbone']
        },
        bootstrap: {
            deps: ['jquery']
        }
    }
});


모듈 설치
npm install


모듈 업데이트 확인
npm outdated


초기 세팅 시 app/config.js에서는 모듈 마다 파일 위치가 달라 약간의 시간이 걸리지만 한 번 세팅 후에는 


호환성/의존성 관리 뿐만 아니라 버전 관리도 할 수 있어 훨씬 견고한 프로젝트를 개발하실 수 있게 됩니다.


app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var redis = require('redis');
var client = redis.createClient(6379, "127.0.0.1");
var JSON = require('JSON');
var _ = require('underscore');

var app = express();

var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(3001, "127.0.0.1");

function addZero(i) {
    if (i < 10) {
        i = "0" + i;
    }
    return i;
}

io.on('connection', function(socket){

    var userName = socket.id;

    io.to(socket.id).emit('change name', userName);

    socket.on('changed name', function(receivedUserName) {
        userName = receivedUserName;
        io.to(socket.id).emit('change name', userName);
    });

    socket.on('disconnect', function(){
        io.emit('leave', socket.id);
    });

    socket.on('send message', function(text){
        var date = new Date();
        client.rpush('chatLogs', JSON.stringify({
            userName: socket.id,
            message: text,
            date: addZero(date.getHours()) + ":" + addZero(date.getMinutes())
        }));
        io.emit('receive message', {
            userName: socket.id,
            message: text,
            date: addZero(date.getHours()) + ":" + addZero(date.getMinutes())
        });
    });
});

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')))


app.get('/', function(req, res, next) {
    var chatLogs;
    client.lrange('chatLogs', -10, -1, (err, charArr) => {
        chatLogs = _.map(charArr, function(char){ return JSON.parse(char); });
        res.render('index', {
            title: 'Chat App',
            chatLogs: chatLogs
        });
    });
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;


index.ejs

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=0,maximum-scale=10">
        <title><%= title %></title>
        <link rel="stylesheet" href="/stylesheets/lib/bootstrap.min.css">
        <link rel="stylesheet" href="/stylesheets/custom.ui.css">
    </head>
    <body>
        <main id="boxApp"></main>
        <script src="/javascripts/lib/require.min.js"></script>
        <script src="/javascripts/app/config.js"></script>
        <script src="/javascripts/app/chat.js"></script>
        <script>
        var chatLogs = [];
        <% chatLogs.forEach(function(chatLog) { %>
        chatLogs.push({
            userName: '<%= chatLog.userName %>',
            message: '<%= chatLog.message %>',
            date: '<%= chatLog.date %>'
        });
        <% }); %>
        </script>
    </body>
</html>


config.js

require.config({
    urlArgs: 'version=0.0.0',
    baseUrl: "/javascripts",
    paths: {
        jquery : 'lib//jquery-1.12.4.min',
        underscore : 'lib/underscore.min',
        backbone : 'lib/backbone.min',
        backbone_validation : 'lib/backbone-validation.min',
        moment: 'lib/moment.min',
        bootstrap: 'lib/bootstrap.min',
        text: 'lib/text',
        io: 'lib/socket.io'
    },
    shim : {
        underscore : {
            exports : '_'
        },
        backbone: {
            exports: 'Backbone',
            deps: ['jquery', 'underscore']
        },
        backbone_validation: {
            exports: 'BackboneValidation',
            deps: ['backbone']
        },
        bootstrap: {
            deps: ['jquery']
        }
    }
});


app/chat.js

require([
    'view/chat'
], function(
    ChatView
) {
    var chatView = new ChatView({el: '#boxApp'});
    chatView.render();
})


collection/chat_logs.js

define([
    'backbone',
    'model/chat_log'
], function(
    Backbone,
    ChatLog
) {
    return Backbone.Collection.extend({
        model: ChatLog
    })
});


model/chat_log.js

define([
    'backbone'
], function(
    Backbone
) {
    return Backbone.Model.extend({
        defaults: {
            userName: null,
            message: null,
            date: null
        }
    })
});


template/chat.html

<div class="">
    <textarea id="chatLog" class="form-control" readonly><% _.each(chatLogs, function(chatLog){ %>
[<%=chatLog.date%>]<%=chatLog.userName%> : <%=chatLog.message%><% }); %>
</textarea>
    <form id="chat">
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span id="userName" class="input-group-text"></span>
            </div>
            <input type="text" name="message" class="form-control" aria-label="message">
            <div class="input-group-append">
                <button type="submit" class="input-group-text">전송</button>
            </div>
        </div>
    </form>
</div>


view/chat.js

define([
    'backbone',
    'collection/chat_logs',
    'text!template/chat.html',
    'io'
], function(
    Backbone,
    ChatLogs,
    ChatTemplate,
    io
) {
    return Backbone.View.extend({
        events: {
            "submit #chat": "public"
        },
        template: ChatTemplate,
        initialize: function() {
            _.bindAll(this, 'appendMessage', 'setName');
            this.collection = new ChatLogs(chatLogs);
            this.socket = io.connect('http://localhost:3001');
            this.socket.on("receive message", this.appendMessage);
            this.socket.on("change name", this.setName);
        },
        render: function() {
            this.$el.html(_.template(this.template)(this.collection.toJSON()));
        },
        public: function(e) {
            var $message = this.$el.find("[name=message]");
            this.socket.emit('send message', $message.val());
            $message.val("").focus();
            e.preventDefault();
        },
        appendMessage: function(result) {
            var $chatLog = this.$el.find("#chatLog");
            $chatLog.append("[" + result.date + "]" + result.userName + " : " + result.message + '\n');
            $chatLog.scrollTop($chatLog.prop("scrollHeight"));
        },
        setName: function(userName) {
            var $userName = this.$el.find("#userName");
            $userName.text(userName);
        }
    })
});


현재 매우 간단한 App이기 때문에 Express의 Route 기능을 사용하지는 않았습니다.


파일 업로드 기능이 추가된다면 Route를 통해 Controller를 분할하는 작업이 필요할 것 같습니다.


기존 채팅 이력은 Redis를 통해 넘겨받은 Object Array를 chatLogs 변수 안에 Push를 하고 


chatView render 시 채팅 이력이 보여지도록 하였습니다.



채팅방 구현에 필요한 각 요소들은 다음 포스팅에서 순차적으로 다뤄보도록 하겠습니다.



국내 무료 실시간 채팅방 서비스로는 '문챗', '유챗', '우하하챗', '가가라이브', '미니톡' 등이 존재합니다.


스킨과 기능적인 면에서 다양한 커스터마이징을 지원해 간단하게 사용한다면 더할 나위 없이 좋은 서비스들이지만 


일반화 되어 있는 스킨과 기능들로 인해 내 입맛에 맞는 채팅방을 구현하는데에는 무리가 있었습니다.


그래서, 직접 내 입맛에 맞는 실시간 채팅방을 만들어보면 어떨까 싶어 NodeJS 기반으로 제작해보기로 하였습니다.





1. 채팅방 구현에 필요한 요소 정리


  1. 접속자 정보
    • 고유 아이디
    • 닉네임
    • 레벨
    • 권한(일반/부관리자/관리자)
    • 접속 시간
    • 아이피 주소
    • 차단 여부

  2. 채팅 제한
    • 아이피
    • 단어

  3. 채팅 기능
    • 특정 단어/유저 무시하기
    • 호출하기
    • 이미지 업로드
    • 이모티콘
    • 귓속말
    • 글자 스타일
    • 링크
    • 공지사항

  4. 채팅 내역 남기기





2. 채팅 서버 구성도


  1. NodeJS
  2. Express
  3. Redis
  4. Socket
  5. MySQL



NodeJS <-> Socket : 서버와 클라이언트 간의 소켓 통신

Express : 웹 서버 프레임워크 용도

Mysql <-> Redis <-> NodeJS : 실시간 채팅 내역은 Redis에 저장, 일정 주기별로 DB로 저장





3. 설치



yum으로 nodejs 안정 버전 설치하기

yum install epel-release
curl -sL https://rpm.nodesource.com/setup_8.x | sudo -E bash -
yum install nodejs gcc-c++ make



package.json

{
  "name": "app",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "JSON": "^1.0.0",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.9",
    "ejs": "~2.5.7",
    "express": "~4.16.0",
    "http-errors": "~1.6.2",
    "morgan": "~1.9.0",
    "redis": "^2.8.0",
    "socket.io": "^2.0.3",
    "underscore": "^1.8.3"
  }
}


노드 모듈 설치

npm install


사용자에게 안정적인 속도로 콘텐츠를 제공하기 위해서 CDN(Contents Delivery Network) 서비스를 사용하곤 합니다.



이 서비스는 존재를 몰라 쓰지 못하는 경우도 있지만 비용적인 문제로 사용을 하지 못하는 경우가 상당합니다.

* 국내 CDN 서비스 중 저렴한 업체가 1TB 당 5만원 정도 꼴입니다.



3~4년 전에는 네임서버만 연결하면 무료로 CDN 서비스를 이용할 수 있는 CloudFlare가 국내에 알려지면서 개인 블로그나 사이트에서 사용을 하게 되었는데요.


하지만 국내 3사 통신사의 비싼 네트워크 비용 정책으로 인해 현재 CloudFlare에서는 수백만원에 이르는 Enterprise 플랜을 사용하지 않고서는 서울 PoP을 이용할 수 없게 하였고 대신 미국, 홍콩 등에 위치한 PoP으로 연결되어 오히려 전체적인 속도가 느려지는 문제가 발생해 더이상 사용하지 않는 서비스가 되었습니다.



그래서, 위와 같은 비용적인 문제를 해소하면서 CDN의 이점을 잡아보기 위해 CDN 서비스를 직접 만들어 보았습니다.




index.php : src 파라메터로 원격지 파일을 전달하면 $cache_dir에 해당 파일명으로 저장합니다.

* 참고 : 길호넷

<?php
$base_url = urldecode($_GET['src']);
if (!$base_url) return; // wrong source

$allowed_domains = ['example.com', 'www.example.com'];
$allowed_image_type = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp'];

if (isset($_SERVER['HTTP_REFERER'])) {
    $referer = parse_url($_SERVER['HTTP_REFERER']);
    if (in_array($referer['host'], $allowed_domains) == false) {
        return; // wrong access
    }
}

$base_url = base64_decode($base_url);
$url = @parse_url($base_url);
$cache_dir = "/var/www/html/cache";

if (in_array($url['host'], $allowed_domains) == false) {
    return; // wrong host
}

switch ($_SERVER['REQUEST_METHOD']) {
    case 'GET':
        $filename = $cache_dir . basename($url['path']);
        $dirname = dirname($filename);

        if (is_file($filename) == false) {
            $ch = curl_init();
            $fp = fopen($filename, 'w');
            curl_setopt($ch, CURLOPT_URL, $base_url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_FILE, $fp);
            curl_exec($ch);
            fclose($fp);

            switch ($code = curl_getinfo($ch, CURLINFO_HTTP_CODE)) {
                case 200:
                    break;

                default:
                    touch($filename, $code);
            }
            curl_close($ch);
        }

        if (is_file($filename)) {
            $filetype = mime_content_type($filename);
            if (in_array($filetype, $allowed_image_type) == false) {
                @unlink($filename);
                return; // wrong extension
            }

            $lastmodified = filemtime($filename);
            $etag = md5_file($filename);

            header('Content-Type: ' . $filetype);
            header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 77760000) . ' GMT');
            header('Cache-Control: public, max-age=77760000');
            header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastmodified) . ' GMT');
            header('Etag: ' . $etag);
            header('Access-Control-Allow-Origin: *');

            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
                if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == gmdate('D, d M Y H:i:s', $lastmodified) . ' GMT' || $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
                    header('HTTP/1.0 304 Not Modified');
                    exit;
                }
            }

            if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
                if (in_array($filetype, array('text/plain', 'text/html', 'text/css', 'text/javascript', 'application/json', 'application/xml'))) {
                    ob_start('ob_gzhandler');
                }
            }

            $fp = fopen($filename, 'rb');
            fpassthru($fp);
            fclose($fp);

            exit;
        }

        break;

    case 'POST':

    case 'PUT':

    case 'DELETE':
        break;
}

header('HTTP/1.0 404 File Not Found.');
?>


delete.php : 일정 기간이 지난 파일은 CDN 서버에서 삭제되도록 합니다.

<?php
$cache_dir = "/var/www/html/cache";
$delete_date = 7;

recursive_file_delete($cache_dir);

function recursive_file_delete($dir) {
    if(is_dir($dir)) {
        if($dh = opendir($dir)) {
            while(($entry = readdir($dh)) !== false) {
                if($entry == '.' || $entry == '..') continue;
                $subdir = $dir.'/'.$entry;
                if(is_dir($subdir)) {
                    recursive_file_delete($subdir);
                } else {
                    if($entry == 'index.php') continue;
                    $sfile = $dir.'/'.$entry;
                    $mtime = @filemtime($sfile);
                    if(file_exists($sfile) && (time() - $mtime <= 24 * 60 * 60 * $delete_date)) continue;
                    @unlink($sfile);
                }
            }
            closedir($dh);
        }
    }
}



사용법 안내


1. CDN 서버 웹 root 경로에 index.php를 위치시킵니다.

2. 호스트 서버에서 이미지 경로를 CDN 웹서버 도메인으로 변경합니다. 예) http://cdn-server/?src=http://example.com/image.png

3. CDN 서버에서 cron job에 delete.php가 주기적으로 작동하도록 등록합니다.




이번에 만든 CDN 서비스는 단순히 네트워크를 분산시켜주는 기능 밖에 없습니다.


추후에는 콘텐츠를 요청한 지역과 지리적으로 실제 가까운 위치의 서버에서 콘텐츠를 전달받을 수 있는 구성을 만들어보도록 하겠습니다.

xmr-stak-cpu 채굴기를 이용하면 CentOS(6, 7)에서도 CPU 채굴을 할 수 있습니다.



필수 패키지를 설치합니다. 

yum install epel-release
yum install libmicrohttpd-devel autoconf cmake3 centos-release-scl hwloc-devel scl-utils git



devtoolset-4-gcc 설치 및 활성화 합니다.

yum install devtoolset-4-gcc*
scl enable devtoolset-4 bash

xmr-stak-cpu 컴파일 시 GCC 5.1 이상 버전을 이용합니다.



xmr-stak-cpu를 git을 이용해 clone 받습니다.

cd /opt 
git clone https://github.com/fireice-uk/xmr-stak-cpu


clone 받은 폴더로 이동합니다.

cd xmr-stak-cpu
 


기본적으로 xmr-stak-cpu는 개발자 기부 옵션이 활성화 되어있는데, 만약 기부를 하고 싶지 않다면 donation level을 아래와 같이 변경할 수 있습니다.

sed -i 's/constexpr double fDevDonationLevel.*/constexpr double fDevDonationLevel = 0.0;/' donate-level.h



빌드를 진행합니다.

cmake3 . 
make


cpu, pool 주소 등을 설정할 수 있는 config.txt를 수정합니다.

vi config.txt


cpu 쓰레드 설정

"cpu_threads_conf" : null,


위 내용을 아래와 같이 변경합니다.

"cpu_threads_conf" : 

{ "low_power_mode" : false, "no_prefetch" : true, "affine_to_cpu" : 0 },

{ "low_power_mode" : false, "no_prefetch" : true, "affine_to_cpu" : 2 }

],

쓰레드 개수에 따라 { "low_power_mode" : false, "no_prefetch" : true, "affine_to_cpu" : 0 } 코드 단락을 추가할 수 있습니다.

* affine_to_cpu 값은 0, 2, 4, 6.. 순으로 늘려주셔야 합니다.

채굴 속도는 cpu 쓰레드 개수에 반드시 비례하지는 않기에 개수는 직접 조절하면서 자신의 cpu에 맞는 값을 찾아보시기 바랍니다.



pool 주소 설정


"pool_address" : "pool.usxmrpool.com:3333", 

"wallet_address" : "", 

"pool_password" : "",

국내 pool로는 miningpoolhub가 있습니다.


miningpoolhub에서 ETN 채굴 시 설정 예)

"pool_address" : "asia.cryptonight-hub.miningpoolhub.com:20596",

"wallet_address" : "user_id.worker1", 

"pool_password" : "x",


pool_address : 풀 주소

wallet_address : 아이디.마이너이름

pool_password : 마이너 추가 시 입력한 비밀번호



채굴 시작

./bin/xmr-stak-cpu



docker는 Dockerfile에 명세를 작성해 이미지를 생성할 수 있습니다.

Dockerfile은 파일명이 "Dockerfile"인 파일입니다.



Dockerfile 명세 작성

FROM centos:6
LABEL description="Production Server"
LABEL version="1.0"

RUN yum install -y epel-release
RUN yum update -y
ADD http://rpms.famillecollet.com/enterprise/remi.repo /etc/yum.repos.d/remi.repo
RUN rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
RUN rpm -Uvh https://mirror.webtatic.com/yum/el6/latest.rpm
RUN rpm -ivh https://dev.mysql.com/get/mysql57-community-release-el6-11.noarch.rpm
RUN yum install -y nginx
RUN yum install -y php70w php70w-mysqlnd php70w-fpm
RUN yum install -y mysql-community-server

VOLUME ["/data", "/home/user1/data"]

EXPOSE 80

FROM : FROM <name>[:tag]

Dockerfile의 기반이 되는 이미지


LABEL : LABEL <key>=<value>

이미지 metadata


RUN : RUN <command>

패키지 설치, 치환 등에 사용


ADD : ADD <src> <dest>

호스트 또는 원격지로 부터 파일 복사


VOLUME : VOLUME [<dir> <dir> <dir> ...]

컨테이너 내 디렉토리 생성 및 마운트


EXPOSE : EXPOSE <port>

호스트와 연결한 포트 번호


이 외 옵션은 여기를 참고하시기 바랍니다.



새 이미지 생성

docker build --tag production-server:0.1

docker build --tag <name>[:version]



이미지 생성이 완료 되었다면 docker 가이드 - 컨테이너 생성 및 실행하기를 참고해 컨테이너를 생성해보시기 바랍니다.