2017年8月21日

記錄用 CentOS 7 安裝 Redmine 3.3.3

因機器搬遷,需要把舊的 server 的資料,移到新機器上。

用 docker 測試

docker run -d -p 10022:22 -p 10080:80 centosssh /usr/sbin/sshd -D

如果遇到 Failed to get D-Bus connection: Operation not permitted 的問題,就改用這個方式啟動。因為需要 ssh 及 web 的 port,啟動時先對應好。

ref [原创] 解决 CentOS7 容器 Failed to get D-Bus connection: Operation not permitted

docker run -d -p 10022:22 -p 10080:80 -e "container=docker" --privileged=true -v /sys/fs/cgroup:/sys/fs/cgroup --name centos7test centosssh /usr/sbin/init

docker exec -it centos7test /bin/bash

安裝Apache、MariaDB、PHP

yum install -y httpd php mariadb mariadb-server mariadb-devel systemd which wget

設定MariaDB的DB為utf8

vi /etc/my.cnf.d/server.cnf

[mysqld]
character-set-server=utf8

vi /etc/my.cnf.d/client.cnf

[client]
default-character-set=utf8

啟動 MariaDB

systemctl start mariadb
mysql_secure_installation

建立 redmine 資料庫及帳號

mysql -u root -p

CREATE DATABASE redmine CHARACTER SET utf8;
CREATE USER 'redmine'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';
flush privileges;
exit;

修改 MariaDB 密碼

mysqladmin -u root password 'password'

安裝 rvm

安裝RVM (Ruby管理工具)、Ruby、Rubygem

# Install Key
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
#Install RVM
\curl -sSL https://get.rvm.io | bash -s stable

測試過 ruby 2.4.0 有些問題,所以安裝 ruby 限制在 2.2.4 版。

source /etc/profile.d/rvm.sh
rvm requirements
rvm install ruby 2.2.4
#rvm install rubygem
#gem install rails --no-rdoc --no-ri

不使用 yum 安裝 ruby (現在是 2.0 版)

#yum list ruby 
yum install -y gcc libxml2-devel

# 包含ruby/gem/libyaml
#yum install -y ruby ruby-devel

gem install bundler

gem install rake --no-document
gem i nokogiri --no-document -v='1.6.8'
gem i mime-types --no-document

# ruby 2 與 rails 5 不相容
gem install rails --no-document -v='4.2.7'

gem install rbpdf --no-document
gem install rbpdf-font --no-document

安裝passenger

yum install -y libcurl-devel httpd-devel apr-devel apr-util-devel

gem install passenger
passenger-install-apache2-module

安裝完成後,會出現module passenger的設定檔文字

vi /etc/httpd/conf.d/passenger.conf

LoadModule passenger_module /usr/local/rvm/gems/ruby-2.2.4/gems/passenger-5.1.4/buildout/apache2/mod_passenger.so
<IfModule mod_passenger.c>
     PassengerRoot /usr/local/rvm/gems/ruby-2.2.4/gems/passenger-5.1.4
     PassengerDefaultRuby /usr/local/rvm/gems/ruby-2.2.4/wrappers/ruby
</IfModule>

設定 redmine httpd virtual host

vi /etc/httpd/conf.d/redmine.conf

RailsEnv production
RailsBaseURI /redmine

<Directory /home/redmine/redmine-3.3.3/public>
  Options FollowSymlinks
  AllowOverride none
  Require all granted
</Directory>

restart httpd

systemctl restart httpd

安裝 redmine

cd /home

mkdir redmine
cd redmine

wget http://www.redmine.org/releases/redmine-3.3.3.tar.gz

tar -xf redmine-3.3.3.tar.gz

ln -s /home/redmine/redmine-3.3.3/public /var/www/html/redmine

chown -R apache:apache redmine-3.3.3

設定 mysql

cd redmine-3.3.3/config
cp database.yml.example database.yml

# 設定資料庫使用者名稱、密碼。

vi database.yml

把
production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: root
  password: ""
  encoding: utf8

改為

production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: redmine
  password: "dbpassword"
  encoding: utf8
cd ..

bundle install --without development test rmagick

### 這個是生成redmine的什麼token,不生成的話瀏覽器會連接不上的,每次生成,以前的cookie內容就會失效
bundle exec rake generate_secret_token

如果有舊的 DB 要先 restore redmine db

mysql -uroot -p redmine < redmine_noplugins.sql

升級 DB schema

RAILS_ENV=production bundle exec rake db:migrate

有舊DB 就不需要 loaddefaultdata

# 生成數據庫對象

RAILS_ENV=production REDMINE_LANG=zh bundle exec rake redmine:load_default_data

可用 webrick 內建 web server 測試,也可以跳過不做

bundle exec rails server webrick -e production
wget http://localhost:3000
  > 測試安裝

為以後apache服務器對應(redmine/public目錄)做準備

cd public

cp htaccess.fcgi.example htaccess.fcgi
cp dispatch.fcgi.example dispatch.fcgi

設定 email

cd /home/redmine/redmine-3.3.3/config
cp configuration.yml.example configuration.yml
vi configuration.yml

修改 configuration.yml 前面的 email_delivery,要注意不能修改縮排的格式。

  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      enable_starttls_auto: true
      address: "smtp.gmail.com"
      port: 587
      domain: "maxkit.com.tw"
      authentication: :login
      user_name: "maxkit@maxkit.com.tw"
      password: "youremailpassword"

references

Installing Redmine

CentOS 7 安裝 Redmine

Centos 7安裝 redmine 3.X

最小化安装centos7.3 redmine3.3.3 passenger


Upgrading redmine

Redmine 2.6.3 to 3.0.1 upgrade

redmine_bak to git repository

2017年8月14日

以 docker 安裝一個可以遠端 ssh 登入的 centos 7 image

以下紀錄如何產生一個基本的 docker image,安裝了 openssh-server 可以用 ssh 遠端登入。

設定 docker image 以及 openssh-server

docker run -it --name c1 centos:latest /bin/bash

安裝一些基本工具,以及 openssh-server

#yum provides ifconfig

yum install -y net-tools telnet iptables sudo initscripts
yum install -y passwd openssl openssh-server

測試 sshd

/usr/sbin/sshd -D
Could not load host key: /etc/ssh/ssh_host_rsa_key
Could not load host key: /etc/ssh/ssh_host_ecdsa_key
Could not load host key: /etc/ssh/ssh_host_ed25519_key

缺少了一些 key

ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
#直接 enter 即可

ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
#直接 enter 即可

ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N ""

ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""

修改 UsePAM 設定

vi /etc/ssh/sshd_config
# UsePAM yes 改成 UsePAM no
UsePAM no

再測試看看 sshd

/usr/sbin/sshd -D

修改 root 密碼

passwd root

離開 docker

exit

以 docker ps -l 找到剛剛那個 container 的 id

$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
107fb9c3fc0d        centos:latest       "/bin/bash"         7 minutes ago       Exited (0) 2 seconds ago                       c1

將 container 存成另一個新的 image

docker commit 107fb9c3fc0d centosssh

以新的 image 啟動另一個 docker instance

docker run -d -p 10022:22 centosssh /usr/sbin/sshd -D

現在可以直接 ssh 登入新的 docker machine

ssh root@localhost -p 10022

如果遇到 Failed to get D-Bus connection: Operation not permitted 的問題:ref [原创] 解决 CentOS7 容器 Failed to get D-Bus connection: Operation not permitted

docker run -d -p 10022:22 -e "container=docker" --privileged=true -v /sys/fs/cgroup:/sys/fs/cgroup --name centos7test centosssh /usr/sbin/init

docker exec -it centos7test /bin/bash

gitolite 測試

在新的 docker 機器上安裝 gitolite 測試

yum install -y autoconf git

useradd git
passwd git

產生管理員的 key

ssh-keygen

Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
01:93:46:03:17:6e:e2:06:ec:d6:07:db:2e:13:a3:92 root@1f01b0c5ad69
The key's randomart image is:
+--[ RSA 2048]----+
|    .oBo         |
| .   oo+         |
|  o o.o .        |
| . + *   .       |
|  o B o S        |
| o o =           |
|E . o .          |
| .   o           |
|                 |
+-----------------+
cp /root/.ssh/id_rsa.pub /home/git/admin.pub

以 scp 遠端測試 key

sshpass -p "password" scp -p -P 10022 git@localhost:/home/git/admin.pub .

在本機上安裝 gitolite

su - git

mkdir ~/bin

git clone git://github.com/sitaramc/gitolite

gitolite/install -ln ~/bin

把 admin.pub 放入 gitolite

gitolite setup -pk admin.pub

Initialized empty Git repository in /home/git/repositories/gitolite-admin.git/
Initialized empty Git repository in /home/git/repositories/testing.git/
WARNING: /home/git/.ssh missing; creating a new one
    (this is normal on a brand new install)
WARNING: /home/git/.ssh/authorized_keys missing; creating a new one
    (this is normal on a brand new install)

回到 root 身份

exit

以 git clone gitolite-admin 進行 local git 測試

mkdir test
cd test
git config --global user.email "charley@maxkit.com.tw"
git config --global user.name "charley"

git clone ssh://git@localhost/gitolite-admin

現在就可以利用 gitolite-admin 進行 git 帳號及 repo 維護

放入新的 user key: test.pub 放到 keydir 目錄中

git add keydir/test.pub

修改 conf/gitolite.conf

repo gitolite-admin
    RW+     =   admin
    RW+     =   test

repo testing
    RW+     =   admin
    RW+     =   test

將新的 test 增加到 gitolite-admin 裡面

git add keydir/test.pub
git add conf/gitolite.conf
git commit -m 'add test key'
git push origin master

也可以用遠端的方式存取 git

git clone ssh://git@localhost:10022/gitolite-admin

How to install Gitolite in CentOS 7

Linux 使用 Gitolite 架設 Git Server

使用Gitolite搭建Git服務器

gitolite basic administration

References

centos7中安裝一個可以ssh登陸的docker容器

Docker安裝SSH【Ubuntu、CentOS】

2017年8月7日

python tornado websocket server and client

tornado 是一個用Python語言寫成的Web服務器兼Web應用框架,以下記錄如何用 tornado framework 撰寫 websocket Echo Server & Client。

安裝 tornado

在 debian 安裝 python library

wget https://bootstrap.pypa.io/get-pip.py
sudo python get-pip.py

sudo pip install tornado

在 mac 安裝 tornado

sudo port install py27-tornado

Echo Server

# -*- coding: utf-8 -*-

import datetime
import sys
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

class WSHandler(tornado.websocket.WebSocketHandler):
    clients = []

    def check_origin(self, origin):
        return True

    def open(self):
        print "New client connected"
        #self.write_message("You are connected")
        WSHandler.clients.append(self)

    def on_message(self, message):
        self.write_message(message)

    def on_close(self):
        print "Client disconnected"
        WSHandler.clients.remove(self)

    @classmethod
    def write_to_clients(cls):
        print "Writing to clients"
        for client in cls.clients:
            client.write_message("Hi there!")

application = tornado.web.Application([
    (r"/", WSHandler),
])

if __name__ == "__main__":
    try:
        http_server = tornado.httpserver.HTTPServer(application)
        http_server.listen(9000)
        main_loop = tornado.ioloop.IOLoop.instance()

        # Schedule event (5 seconds from now)
        #main_loop.add_timeout(datetime.timedelta(seconds=5), WSHandler.write_to_clients)

        # background update every x seconds
        # 固定每 5 秒鐘就呼叫一次 WSHandler.write_to_clients 廣播訊息
        task = tornado.ioloop.PeriodicCallback(
                WSHandler.write_to_clients,
                5 * 1000)
        task.start()

        # Start main loop
        #main_loop.start()
        main_loop.make_current()
    except KeyboardInterrupt:
        #print("KeyboardInterrupt")
        sys.exit()

EchoServer in aother Thread

將 Server 放在另一個 Thread 啟動,保留 main thread 用在其他的用途上。

# -*- coding: utf-8 -*-

import datetime
import sys
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import os
from threading import Thread

class WSHandler(tornado.websocket.WebSocketHandler):
    clients = []

    def check_origin(self, origin):
        return True

    def open(self):
        print "New client connected"
        #self.write_message("You are connected")
        WSHandler.clients.append(self)

    def on_message(self, message):
        self.write_message(message)

    def on_close(self):
        print "Client disconnected"
        WSHandler.clients.remove(self)

    @classmethod
    def write_to_clients(cls):
        print "Writing to clients"
        for client in cls.clients:
            client.write_message("Hi there!")

class WebThread(Thread):
    def __init__(self):
        Thread.__init__(self, name='WebThread')

    def run(self):
        curdir = os.path.dirname(os.path.realpath(__file__))

        application = tornado.web.Application([
            (r"/", WSHandler),
        ])

        http_server = tornado.httpserver.HTTPServer(application)
        http_server.listen(9000)
        main_loop = tornado.ioloop.IOLoop.instance()

        # Schedule event (5 seconds from now)
        #main_loop.add_timeout(datetime.timedelta(seconds=5), WSHandler.write_to_clients)

        # background update every x seconds
        # 固定每 5 秒鐘就呼叫一次 WSHandler.write_to_clients 廣播訊息
        task = tornado.ioloop.PeriodicCallback(
                WSHandler.write_to_clients,
                5 * 1000)
        task.start()

        main_loop.start()


if __name__ == "__main__":
    try:
        webThread = WebThread()
        webThread.daemon = True
        webThread.start()

        while True:
            pass

    except KeyboardInterrupt:
        #print("KeyboardInterrupt")
        sys.exit()

EchoClient.html


<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script>

$(document).ready(function(){
    var socket = new WebSocket('ws://127.0.0.1:9000/');

    socket.onopen = function(event){
        socket.send('Hi');
    }

    socket.onmessage = function(event){
        console.log(event.data);
    };

    $(window).unload(function(event){
        socket.close();
    });
});

</script>

Echo Client with tornado framework

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

from tornado.ioloop import IOLoop, PeriodicCallback
from tornado import gen
from tornado.websocket import websocket_connect


class Client(object):
    def __init__(self, url, timeout):
        self.url = url
        self.timeout = timeout
        self.ioloop = IOLoop.instance()
        self.ws = None
        self.connect()

        # 每 20 秒發送一次 ping
        PeriodicCallback(self.keep_alive, 20000, io_loop=self.ioloop).start()

        self.ioloop.start()

    @gen.coroutine
    def connect(self):
        print "trying to connect"
        try:
            self.ws = yield websocket_connect(self.url)
        except Exception, e:
            print "connection error"
        else:
            print "connected"
            self.run()

    @gen.coroutine
    def run(self):
        while True:
            msg = yield self.ws.read_message()
            if msg is None:
                print "connection closed"
                self.ws = None
                break
            else:
                print msg

    def keep_alive(self):
        if self.ws is None:
            self.connect()
        else:
            self.ws.write_message("ping")

if __name__ == "__main__":
    try:
        client = Client("ws://localhost:9000", 5)
    except KeyboardInterrupt:
        #print("KeyboardInterrupt")
        sys.exit()

References

SIMPLE WEB SOCKET CLIENT IMPLEMENTATION USING TORNADO FRAMEWORK.

tornado-websocket-client-example/client.py