неділя, 28 жовтня 2018 р.

Трішки магії для ssh


Для підключення до віддаленого сервера, я використовую OpenSSH з додатковими налаштуваннями в .ssh/config
Host gw 
  HostName gw.extern.lan
  User gwuser
  IdentityFile ~/.ssh/keys/gw.extern.lan

Host remotehost 
  HostName remotehost.internal.lan
  User remoteuser
  IdentityFile ~/.ssh/keys/remotehost.internal.lan

Match Host remotehost !exec "ping -c 1 -W 1 ipaddrremote &>/dev/null"
  ProxyCommand ssh gw nc %h %p

І де ж тут магія, спитаєти ви?
А мвгія в тому, що якщо ви викоритовуєте наприклад ноутбук вдома чи на роботі, то для підключення до віддаленого сервера завжки використовується одна команда:
ssh remotehost

четвер, 21 червня 2018 р.

Переключение раскладки клавиатуры в Gnome 3

Собрано на просторах интернета. Мои настройки для переключения раскладки клавиатуры:
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'ru'), ('xkb', 'ua')]"
$ gsettings set org.gnome.desktop.input-sources xkb-options "['terminate:ctrl_alt_bksp', 'grp:alt_shift_toggle', 'grp_led:scroll']"
Думаю рассказывать что делают эти две команды не стоит, и так понятно все с контекста.

середа, 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 дня


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

четвер, 6 жовтня 2016 р.

Автодополнение для Docker

Всем доброго времени суток :)
Уже несколько месяце я полностью переполз на Manjaro Linux (по сути ArchLinux для домохозяек :)) на рабочем ПК, и что бы продолжить эксперименты,  установил я недавно на него Docker. Все хорошо, вот только беда, на моем домашнем сервере было настроено автодополнение команд. Собственно и займемся исправлением данной "не удобности".
Для начало нужно установить пакет bash-completion
$ sudo pacman -S bash-completion
Дальше настраиваем само авто дополнение. Для этого идем на https://github.com/docker/docker/blob/master/contrib/completion/bash/docker и скачиваем файл.
Настроить автодополнение можно двумя способами - глобально или только для себя.
Для глобальной настройки нужно положить файл в /etc/bash_completion.d
$ sudo curl -L https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker > /etc/bash_completion.d/docker 
Только для себя. Не забываем прописать в ~/.profile выполнение этого файла
$ sudo curl -L https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker > ~/.docker-completion.sh
$ vim ~/.profile
... cut ...
. ~/.docker-completion.sh
... cut ...

пʼятниця, 11 грудня 2015 р.

Тригеры в MySQL

Пришлось как то мне заниматься "свадебкой" - подружить Asterisk с UTM5 (биллинговая система). После нескольких безсонных ночей, мне все таки удалось кое как заставить работать эти системы вместе, но был один существенный недостаток в этой связке. Хотя Asterisk и отдавал данные в UTM5, но обратной связи он не имел с биллинговой системой. Что было очень неудобно, так как приходилось фактически вести две конфигурации, после того как в UTM5 например добавлялся новый абонент, его нужно было переносить руками в конфигурацию Asterisk… Что в конечном итоге могло создать кучу проблем и постоянно нужно было следить за тем что бы конфигурации были одинаковы. Так как базы оба продукта хранили в MySQL, то первое что мне пришло в голову, это использовать тригеры для синхронизации конфигураций. Конечно настроить Asterisk что бы он работал с базой, было еще той задачей, так как я раньше не видел это чудо, только слышал, что есть такой продукт.
Сообственно об этом я расскажу как то в другой раз, думаю это не последняя статья на телефонную тематику у меня :)
Немножко посмотрел документацию по MySQL и приступил, что тут может быть сложного то… сделать три тригера, на вставку, обновление и удаление записи в таблице:
-- Insert peer --
CREATE TRIGGER tel_add AFTER INSERT ON tel_numbers 
  FOR EACH ROW BEGIN 
    INSERT INTO tel_peers SET 
    id = NEW.id, name = NEW.login, 
    callerid = NEW.tel_number, md5secret = MD5(CONCAT(NEW.login,':',NEW.allowed_cid,':',NEW.password)); 
  END;

-- Update peer --
CREATE TRIGGER tel_update AFTER UPDATE ON tel_numbers 
  FOR EACH ROW BEGIN 
    UPDATE tel_peers SET 
    name = NEW.login, 
    callerid = NEW.tel_number, 
    md5secret = MD5(CONCAT(NEW.login,':',NEW.allowed_cid,':',NEW.password)) 
    WHERE id = NEW.id; 
  END;

-- Delete peer --
CREATE TRIGGER tel_del AFTER DELETE ON tel_numbers 
  FOR EACH ROW BEGIN 
    DELETE FROM tel_peers WHERE id = NEW.id; 
  END;
Но как оказалось, UTM5 не удаляет данные когда в GUI нажимаешь кнопку удалить, в таблице есть отдельное поле с признаком того что запись удалена (я так понимаю для того что бы была возможность просмотреть старые записи). Пришлось мне переделывать тригер на обновление, а на удаление просто выбросить.
-- Update peer --
CREATE TRIGGER tel_update AFTER UPDATE ON tel_numbers 
  FOR EACH ROW 
  BEGIN
    IF (OLD.is_deleted = 0 AND NEW.is_deleted = 1) THEN 
      BEGIN
        DELETE FROM tel_peers WHERE id = NEW.id;
      END;
    ELSE 
      BEGIN
        UPDATE tel_peers SET 
        name = NEW.login, 
        callerid = NEW.tel_number, 
        md5secret = MD5(CONCAT(NEW.login,':',NEW.allowed_cid,':',NEW.password)) 
        WHERE id = NEW.id;
      END;
    END IF;
  END;
Конечно это все наверное можно как то более изящно переписать, но так как я не сильно большой специалист в SQL, для решения моей задачи достаточно :)