середа, 23 листопада 2016 р.

Добавляем поддержку HTTPS для микросервиса 7dtd.moon

При написании предыдущей заметки Программное формирования изображения на Go, мне понадобилось вставить пример формируемого изображения. Можно конечно это было сделать статическими картинками, но я решил использовать реальный пример работы. Так вот при это я столкнулся с проблемой что Blogger ругался на то что я вставляю картинки которые доступны по http а не по https на котором он работает по умолчании.
Сертификат будем использовать свободный, который выдает Let’s Encrypt, для получения самого сертификата воспользуемся утилитой СertBot. Данный процесс хорошо расписан на этих ресурсах и повторять его тут не имеет смысла.
И так приступим...
Первое это мы добавляем переменные которые будут указывать сам сертификат и закрытый ключ, и запускаем https сервер в отдельном потоке - гороутине (goroutine)
...
  log.Printf("Loading Certificates %s/%s/{privkey.pem,fullchain.pem}\n", *cert, *host)
  keyPath := filepath.Join(*cert, *host, "privkey.pem")
  certPath := filepath.Join(*cert, *host, "fullchain.pem")
...
/*** HTTPS ***/
  log.Println("Listening on https port: ", int(*port)+363)
  go func() {
    if err := http.ListenAndServeTLS(":"+strconv.Itoa(int(*port)+363), certPath, keyPath, nil); err != nil {
      log.Fatal("HTTPS Listen and Serve: ", err)
      os.Exit(1)
    }
  }()
...
Вот и все, теперь у нас есть два веб-сервера, один на порту 26980 (http) и второй на порту 27343 (https)

понеділок, 21 листопада 2016 р.

Удобный доступ к контейнеру Docker и другие вкусности :)

Сегодня пару коротких заметок о alias которые добавляют удобства для меня как администратора системы.

Первый alias
Так как я часто работаю с контейнерами Docker, и каждый раз для подключения к контейнеру писать docker attach мне лень, по этому я добавил себе в .profile
# Docker shell
alias dsh='function target(){ CONTAINER=$(docker inspect --format {{.State.Pid}} $1); sudo nsenter --target $CONTAINER --mount --uts --ipc --net --pid; }; target'
Теперь что бы получить "root" доступ к контейнеру, достаточно выполнить команду dsh

Второй alias
Вся суть этой команды, это показать актуальный лог файл игрового сервера. При перезапуске сервер формирует новый лог файл "logs/`date +%Y%m%d.%H%M%S`.log" и как обычно, лень как двигатель прогресса 😜 привела до написания этого alias, который как обычно добавил себе в .profile
# 7 Days to Die tools
alias tail7='grc -c conf.7dtd tail -f $(GAMELOG="/mnt/data/games/7DaysToDie/steam/games/7dtd/logs" && ls -tc ${GAMELOG} | echo "$GAMELOG/$(head -n1)")'
Дополнительная "плюшка" этого alias, это то что он еще раскрашивает лог файл.
Так как я иногда играя в игру, очень боюсь там собак, то я сделал себе такой вот "чит", перед тем как я подключаюсь к игре, я подключаюсь к серверу и запускаю эту команду и когда появляются собаки, консоль начинает "бибикать" 😎
Для раскраски лога, я использую утилиту Generic Colouriser, для которой я написал простенький конфигурационный файл conf.7dtd который нужно положить в ~/.grc/
# Spawned any zombies
regexp=.+Spawned.+|.+AIDirector.+
colours=yellow 
-
# Spawned Zombie Dog
regexp=.+Spawned.+Dog.+
colours=red beep
-
# Player info
regexp=.+Player.+
colours=green
-
regexp=.+ERR.+|.+EXC.+
colours=red
-
regexp=.+WRN.+|Warning.+|.+UNET.+
colours=magenta
-
regexp=(\d{4}\-\d{2}\-\d{2}).(\d{2}:\d{2}:\d{2})
colours=black, blue, blue bold
count=once
-
# Time: 6.00m FPS: 37.77 Heap: 250.0MB Max: 250.0MB
regexp=Time\: (\d{1,4}\.\d\dm) FPS\: (\d{1,3}\.\d\d) Heap\: (\d{1,4}\.\dMB) Max\: (\d{1,4}\.\dMB)
colours=default bold, blue bold, yellow bold, green bold, red bold
count=once
-
regexp=Ply\: (\d{1,2}) Zom\: (\d{1,2}) Ent\: (\d{1,2}) \((\d{1,2})\) Items\: (\d{1,2})
colours=default bold, green bold, red bold, yellow bold, yellow bold, blue bold
count=once
Вот так теперь радужно выглядит лог 😐

Программное формирования изображения на Go

Понадобилось мне при разработке мода на 7 Days to Die программно формировать изображение круга в зависимости от количества дней в игре. Так как я давно порывался посмотреть на Go, решил я использовать его для данной задачи. Да и подходит он для такого микро-сервиса в самый раз.
Сделать веб сервер на Go просто :) для этого используем стандартную библиотеку "net/http"
func main() {
  flag.Parse()
  log.Println("Starting tiny webserver for 7DTD moon texture...")
  log.Println("Document root path: ", *root)
  http.HandleFunc("/moon", moonHandler)
  log.Println("Listening on port: ", *port)
  if err := http.ListenAndServe(":"+strconv.Itoa(*port), nil); err != nil {
    log.Fatal("ERROR Listen and Serve: ", err)
  }
}
Дальше, формируем изображение круга с использование библиотеки "image/draw",
func moonHandler(w http.ResponseWriter, r *http.Request) {
  d := getDay(r)
  /*** Create colored image ***/
  dst := image.NewRGBA(image.Rect(0, 0, cXY.X+cR+1, cXY.Y+cR+1))
  draw.Draw(dst, dst.Bounds(), &circle{cXY, cR}, image.ZP, draw.Src)
  writeImg(w, dst)
}
и отдаем ее как картинку "image/png"
func writeImg(w http.ResponseWriter, img image.Image) {
  buffer := new(bytes.Buffer)
  if err := png.Encode(buffer, img); err != nil {
    log.Println("Unable to encode image: ", err)
    http.Error(w, err.Error(), http.StatusNotFound)
    return
  }

  w.Header().Set("Content-Type", "image/png")
  w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))

  if _, err := w.Write(buffer.Bytes()); err != nil {
    log.Println("Unable to write image: ", err)
    http.Error(w, err.Error(), http.StatusNotFound)
    return
  }
}
А теперь самое интересное, как же сделать эту картинку :)
Для этого описываем структуру:
type circle struct {
  P image.Point
  R int
}
И определяем методы которые описываю данную структуру в разрезе "image.Image"
func (c *circle) ColorModel() color.Model {
  return color.AlphaModel
}
func (c *circle) Bounds() image.Rectangle {
  return image.Rect(c.P.X-c.R, c.P.Y-c.R, c.P.X+c.R, c.P.Y+c.R)
}
func (c *circle) At(x, y int) color.Color {
  xx, yy, rr := float64(x-c.P.X), float64(y-c.P.Y), float64(c.R)
  if xx*xx+yy*yy < rr*rr {
    return color.Alpha{255}
  }
  return color.Alpha{0}
}
Все это прекрасно работает, но мне нужно было получить картинку с определенной прозрачностью, по этому пришлось немного изменить код функции "At" и структуры "circle". Добавляем поля для цвета и типа изображения (сплошная заливка или градиентная)
type circle struct {
  P image.Point
  R int
  C color.NRGBA
  S bool
}
... и теперь сама процедура отрисовки круга:
func (c *circle) At(x, y int) color.Color {
  xx, yy, rr := float64(x-c.P.X), float64(y-c.P.Y), float64(c.R)
  if xx*xx+yy*yy < rr*rr {
    xr := math.Abs(float64(c.P.X - x))
    yr := math.Abs(float64(c.P.Y - y))
    ar := uint8((1 - math.Sqrt(xr*xr+yr*yr)/float64(c.R)) * float64(c.C.A))
    if c.S {
      ar = c.C.A
    }
    return color.NRGBA{c.C.R, c.C.G, c.C.B, ar}
  }
  return color.Alpha{0}
}
В итоге, мы можем получить два вида кружкок :) задав параметры при обращении к веб серверу.
Градиентная заливка для 7 дня Сплошная заливка для 7 дня Градиентная заливка для 5-6 дня Сплошная заливка для 5-6 дня


Конечный результат как это все выглядит в игре :)