Работа с secure shell; Tkinter: текстовый редактор

Git и ssh-ключи

Старый, но актуальный материал из ПСПО про ssh

Secure shell:

Асимметричное шифрование:

Сгенерить пару ключей

   1 $ ssh-keygen -t ed25519 -f tmpkey
   2 Generating public/private ed25519 key pair.
   3 Enter passphrase (empty for no passphrase):
   4 Enter same passphrase again:
   5 Your identification has been saved in tmpkey.
   6 Your public key has been saved in tmpkey.pub.
   7 The key fingerprint is:
   8 SHA256:2ylIy4YEDwM7Xwg8UkspUJ8lYaa10PFd3otVHedJR0k george@inspiron
   9 The key's randomart image is:
  10 +--[ED25519 256]--+
  11 |*o=oBo.   .   oE*|
  12 |oB.X.* . o . ..o=|
  13 |+.O = . . . o  ..|
  14 | o *       o .   |
  15 |  . o . S . .    |
  16 |   . + o o .     |
  17 |    . = o o      |
  18 |     .   .       |
  19 |                 |
  20 +----[SHA256]-----+
  21 $ ls tmpkey*
  22 tmpkey  tmpkey.pub
  23 $ ssh-keygen -v -l -f tmpkey.pub
  24 256 SHA256:2ylIy4YEDwM7Xwg8UkspUJ8lYaa10PFd3otVHedJR0k george@inspiron (ED25519)
  25 +--[ED25519 256]--+
  26 |*o=oBo.   .   oE*|
  27 |oB.X.* . o . ..o=|
  28 |+.O = . . . o  ..|
  29 | o *       o .   |
  30 |  . o . S . .    |
  31 |   . + o o .     |
  32 |    . = o o      |
  33 |     .   .       |
  34 |                 |
  35 +----[SHA256]-----+
  36 

За красивую картинку отвечает настройка «VisualHostKey yes» в файле $HOME/.ssh/config. Это визуализация отпечатка ключа (сам ключ может быть достаточно большим), а отпечаток фиксированного размера, его можно продиктовать или переслать фото в мессенджере, если всерьёз опасаешься MitM-атаки.

Запуск и пополнение агента

Агент — это программа, которая хранит в памяти распароленные закрытые ключи и предлагает удалённой машине использовать соответствующий открытый ключ для авторизации доступа.

Запуск:

   1 $ ssh-add -l
   2 Error connecting to agent: No such file or directory
   3 $ ssh-agent
   4 SSH_AUTH_SOCK=/tmp/.private/george/ssh-MAVKXDo58vzi/agent.2667630; export SSH_AUTH_SOCK;
   5 SSH_AGENT_PID=2667631; export SSH_AGENT_PID;
   6 echo Agent pid 2667631;
   7 $ SSH_AUTH_SOCK=/tmp/.private/george/ssh-MAVKXDo58vzi/agent.2667630; export SSH_AUTH_SOCK;
   8 $ ssh-add -l
   9 The agent has no identities.
  10 

Добавление ключа:

   1 $ ssh-add tmpkey
   2 Enter passphrase for tmpkey:
   3 Identity added: tmpkey (george@inspiron)
   4 $ ssh-add -l
   5 256 SHA256:2ylIy4YEDwM7Xwg8UkspUJ8lYaa10PFd3otVHedJR0k george@inspiron (ED25519)
   6 

Настройка доступа по ключу

Если ключ не зарегистрирован, доступ будет только парольным (когда разрешён)

   1 george@inspiron ~$ ssh user@host-241.local
   2 user@host-241.local's password:
   3 Last login: Thu Mar 18 15:01:54 2021 from 192.168.122.1
   4 user@host-241$ mkdir repo
   5 user@host-241 ~$ cd repo
   6 user@host-241 repo$ git-init
   7 Initialized empty Git repository in /home/user/repo/.git/
   8 user@host-241 repo$ cal > calend
   9 user@host-241 repo$ git add .
  10 user@host-241 repo$ git commit -a -m initial
  11 [master (root-commit) ab3b91d] initial
  12  1 file changed, 8 insertions(+)
  13  create mode 100644 calend
  14 user@host-241 repo$ выход
  15 Connection to host-241.local closed.
  16 george@inspiron ~$ git clone user@host-241.local:repo
  17 Cloning into 'repo'...
  18 user@host-241.local's password:
  19 remote: Enumerating objects: 3, done.
  20 remote: Counting objects: 100% (3/3), done.
  21 remote: Compressing objects: 100% (2/2), done.
  22 remote: Total 3 (delta 0), reused 0 (delta 0)
  23 Receiving objects: 100% (3/3), done.
  24 george@inspiron ~$ cd repo
  25 george@inspiron repo$ git pull
  26 user@host-241.local's password:
  27 Already up to date.
  28 

Регистрация ключа совсем вручную:

   1 george@inspiron ~$ ssh-add tmpkey.pub
   2 Enter passphrase for tmpkey.pub:
   3 Identity added: tmpkey.pub (george@inspiron)
   4 george@inspiron ~$ ssh-add -L
   5 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJI/ZphlcwJOg23Q6U0b1pXxZC3LFcjYuE9ggIfz9bUs george@inspiron
   6 george@inspiron: ~$ ssh user@host-241.local
   7 user@host-241.local's password:
   8 Last login: Thu Mar 18 15:40:34 2021 from 192.168.122.1
   9 user@host-241 ~$ echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJI/ZphlcwJOg23Q6U0b1pXxZC3LFcjYuE9ggIfz9bUs george@inspiron" >> .ssh/authorized_keys
  10 user@host-241 ~$ выход
  11 Connection to host-241.local closed.
  12 george@inspiron ~$ ssh user@host-241.local
  13 Last login: Thu Mar 18 15:41:44 2021 from 192.168.122.1
  14 user@host-241 ~$

Создание репозитория

Работа с репозиторием тоже беспарольная:

   1 george@inspiron ~$ rm -rf repo
   2 george@inspiron ~$ git clone user@host-241.local:repo
   3 Cloning into 'repo'...
   4 remote: Enumerating objects: 3, done.
   5 remote: Counting objects: 100% (3/3), done.
   6 remote: Compressing objects: 100% (2/2), done.
   7 remote: Total 3 (delta 0), reused 0 (delta 0)
   8 Receiving objects: 100% (3/3), done.
   9 george@inspiron ~$ cd repo
  10 george@inspiron ~/repo$ cat .git/config
  11 [core]
  12         repositoryformatversion = 0
  13         filemode = true
  14         bare = false
  15         logallrefupdates = true
  16 [remote "origin"]
  17         url = user@host-241.local:repo
  18         fetch = +refs/heads/*:refs/remotes/origin/*
  19 [branch "master"]
  20         remote = origin
  21         merge = refs/heads/master
  22 george@inspiron ~/repo$ git pull
  23 Already up to date.
  24 

Однако поскольку удалённый репозиторий имеет свою собственную рабочую копию, возможны конфликты если обе копии изменятся. Что делать с рабочей копией удалённого репозитория при push (ответ «ничего» — наихудший, догадайтесь, почему)?

  1. Надо было заводить удалённый репозиторий с помощью git init --bare

    • Тогда на удалённой машине непосредственно в репозитории работать нельзя нельзя (но clone/push/pull делать можно)

    • Именно так работают разные git-движки
  2. Заставить git перебивать содержимое удалённой рабочей копии актуальным содержимым ветки, на которой она развёрнута

    • Да, если при этом там кто-то работает (другая копия вас, ага), у него будут проблемы
         1 george@inspiron ~/repo$ date >> calend
         2 george@inspiron ~/repo$ git commit -a -m "Next commit"
         3 [master c790bf1] Next commit
         4  1 file changed, 1 insertion(+)
         5 george@inspiron ~/repo$ git push
         6 Enumerating objects: 5, done.
         7 Counting objects: 100% (5/5), done.
         8 Delta compression using up to 12 threads
         9 Compressing objects: 100% (2/2), done.
        10 Writing objects: 100% (3/3), 321 bytes | 321.00 KiB/s, done.
        11 Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
        12 remote: error: refusing to update checked out branch: refs/heads/master
        13 remote: error: By default, updating the current branch in a non-bare repository
        14 remote: is denied, because it will make the index and work tree inconsistent
        15 remote: with what you pushed, and will require 'git reset --hard' to match
        16 remote: the work tree to HEAD.
        17 remote:
        18 remote: You can set the 'receive.denyCurrentBranch' configuration variable
        19 remote: to 'ignore' or 'warn' in the remote repository to allow pushing into
        20 remote: its current branch; however, this is not recommended unless you
        21 remote: arranged to update its work tree to match what you pushed in some
        22 remote: other way.
        23 remote:
        24 remote: To squelch this message and still keep the default behaviour, set
        25 remote: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
        26 To host-241.local:repo
        27  ! [remote rejected] master -> master (branch is currently checked out)
        28 error: failed to push some refs to 'host-241.local:repo'
        29 george@inspiron ~/repo$ ssh user@host-241.local
        30 Last login: Thu Mar 18 15:42:00 2021 from 192.168.122.1
        31 user@host-241 ~$ cd repo/
        32 user@host-241 repo$ git config receive.denyCurrentBranch ignore
        33 user@host-241 repo$ cat .git/config
        34 [core]
        35         repositoryformatversion = 0
        36         filemode = true
        37         bare = false
        38         logallrefupdates = true
        39 [receive]
        40         denyCurrentBranch = ignore
        41 user@host-241 repo$ выход
        42 Connection to host-241.local closed.
        43 george@inspiron ~/repo$ git push
        44 Enumerating objects: 5, done.
        45 Counting objects: 100% (5/5), done.
        46 Delta compression using up to 12 threads
        47 Compressing objects: 100% (2/2), done.
        48 Writing objects: 100% (3/3), 321 bytes | 321.00 KiB/s, done.
        49 Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
        50 To host-241.local:repo
        51    ab3b91d..c790bf1  master -> master
        52 george@inspiron ~/repo$
      

Ключи и git-порталы

Именно такие ключи требуют от вас порталы типа SourceHut, GitHub, GitLab и многие другие

Никогда и никуда не копируйте закрытые ключи! Кроме вас они нужны только злоумышленникам!

Tkinter: Text

Text — довольно развесистый текстовый редактор, упрятанный в один Tk-виджет

Пример

   1 import tkinter as tk
   2 import inspect
   3 
   4 class Application(tk.Frame):
   5     '''Sample tkinter application class'''
   6 
   7     def __init__(self, master=None, title="<application>", **kwargs):
   8         '''Create root window with frame, tune weight and resize'''
   9         super().__init__(master, **kwargs)
  10         self.master.title(title)
  11         self.master.columnconfigure(0, weight=1)
  12         self.master.rowconfigure(0, weight=1)
  13         self.grid(sticky="NEWS")
  14         self.create_widgets()
  15         for column in range(self.grid_size()[0]):
  16             self.columnconfigure(column, weight=1)
  17         for row in range(self.grid_size()[1]):
  18             self.rowconfigure(row, weight=1)
  19 
  20     def create_widgets(self):
  21         '''Create all the widgets'''
  22 
  23 class App(Application):
  24     def create_widgets(self):
  25         self.T = tk.Text(self, undo=True, wrap=tk.WORD, font="fixed",
  26                 inactiveselectbackground="MidnightBlue")
  27         self.mytext()
  28         self.T.grid(row=0, column=0, sticky="NEWS")
  29         self.B = tk.LabelFrame(self, text="Controls")
  30         self.B.grid(row=1, column=0, sticky="NEWS")
  31         self.LS = tk.StringVar()
  32         self.IS = tk.StringVar()
  33         self.I = tk.Entry(self.B, textvariable=self.IS)
  34         self.S = tk.Button(self.B, text="Go", command=self.setI)
  35         self.MF = tk.Button(self.B, text="From",
  36                 command=lambda: self.T.mark_set("From", tk.INSERT))
  37         self.MT = tk.Button(self.B, text="To",
  38                 command=lambda: self.T.mark_set("To", tk.INSERT))
  39         self.G = tk.Button(self.B, text="Generate", command=self.mytext)
  40         self.Q = tk.Button(self.B, text="Quit", command=self.master.quit)
  41         self.L = tk.Label(self.B, textvariable=self.LS)
  42         for O in self.I, self.S, self.MF, self.MT, self.G, self.Q, self.L:
  43             O.grid(row=0, column=self.B.grid_size()[0])
  44 
  45         self.T.bind("<Motion>", self.showmarks)
  46         self.T.tag_config("keyword", foreground="limegreen")
  47         self.T.tag_bind("keyword", "<Motion>", print)
  48 
  49     def showmarks(self, event):
  50         self.LS.set(f"[{self.T.index('From')}:{self.T.index('To')}] {self.T.index('current')}")
  51 
  52     def setI(self):
  53         beg, *end = self.IS.get().split(":")
  54         try:
  55             self.T.mark_set(tk.INSERT, beg)
  56             if end:
  57                 self.T.tag_add(tk.SEL, beg, end[0])
  58             self.T.focus_force()
  59         except Exception as E:
  60             print(E)
  61 
  62     def mytext(self):
  63         self.T.delete(1.0, tk.END)
  64         mytext = open(inspect.getsourcefile(type(self))).read()
  65         self.T.insert("1.0", mytext)
  66         self.T.mark_set("From", "1.0")
  67         self.T.mark_set("To", "end")
  68         pos, res = 0.0, []
  69         while pos := self.T.search("self", f"{pos} + 1 chars", stopindex=tk.END):
  70             res.append(pos)
  71         for i0 in res:
  72             self.T.tag_add("keyword", i0, f"{i0} + 4 chars")
  73 
  74 app = App(title="Sample application")
  75 app.mainloop()

Пояснения к примеру:

Tkinter: Canvas и turtle

Не успели! Ну и ладно, можно и самому разобраться.

Д/З

  1. Прочитать в справочнике
  2. (если ещё не) Сгенерировать и аккуратно сохранить пару открытый/закрытый ключ и научиться пользоваться ей для доступа к отчётному репозиторию Git
  3. Написать вырожденный графический редактор. Минимальные требования
    • Имеет два окна — текстовое и графическое
    • В графическом окне:
      • Нажатие мышью в пустое место и движение с нажатой кнопкой создаёт овал и меняет его размер
      • Нажатие на существующий овал и движение с нажатой кнопкой перемещает этот овал
    • В текстовом окне параллельно формируется описание картинки на некотором языке. Синтаксис языка придумайте сами, можно взять из семинара.
      • Обязаны присутствовать координаты, ширина границы, цвет границы и цвет заливки
      • При изменении текста

        • Если в нём нет ошибок, рисуются все фигуры
        • Если ошибки есть, ошибочные строчки раскрашиваются в красный цвет
          • (подумайте, как сделать так, чтобы индикация не мешала редактированию)
    • Допустимо для обновления одного окна из другого использовать две кнопки
    • Остальное — необязательно.

      alt text

  4. В зарегистрированном вами репозитории создать подкаталог 05_SshAndSmartWidgents (совпадает с финальной частью URL данной лекции), всё, что вы написали, положить туда

LecturesCMC/PythonDevelopment2021/05_SshAndSmartWidgents (последним исправлял пользователь FrBrGeorge 2021-03-28 18:58:53)