7 Ответы
На самом деле это отдельная реализация удалённой консоли, использующая SSH только для авторизации.

Основное преимущество ET - восстановление сессии при перебоях доступа к серверу. Более того, можно отправить клиент в сон, разбудить его через несколько часов, а сессия будет продолжена с того же места. Никаких сообщений об отключении или переподключении не будет.

В ET на данный момент не реализован проброс иксов, но лично мне оно и не нужно.

https://eternalterminal.dev
Нет ответов
Эти несколько строк помогут немного снизить нагрузку на сервер, обрубив множество медленных клиентов, которые занимают канал. В моём случае netstat -nalt | grep :80 | wc -l говорил примерно о 1500 клиентах, зависших в статусе TIME_WAIT, и даже статика отдавалась очень медленно.

Эти настройки - всего лишь пример, для вашего сервера подбирайте свои лимиты.
Код:
http {
    limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m; # Ограничение на количество соединений с одного IP
    limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=5r/s; # Ограничение на количество соединений в рамках одной сессии, максимальное количество - 5 запросов в секунду
}

server {
    location / {
        limit_conn conn_limit_per_ip 10;
        limit_req zone=req_limit_per_ip burst=10 nodelay;
    }
    client_body_timeout 5s; # Таймаут на получение тела запроса от клиента
    client_header_timeout 5s; # Таймаут на получение заголовков запроса от клиента
}
Нет ответов
Небольшой скрипт для CVE-2022-44268, для быстрого извлечения профиля из PNG файла. Требуется модуль PIL.
Код:
from PIL import Image
import sys

if (len(sys.argv) < 2):
    sys.exit(-1)

img = Image.open(sys.argv[1])
img.load()

try:
    pf = img.info['Raw profile type']
    print (bytes.fromhex(pf).decode())
except:
    print("There is no raw profile")

img.close()

Использование: script.py image.png

Если сервер затронула эта уязвимость, то данный скрипт выведет украденные данные читабельным текстом. identify -verbose, как оказалось, отображает профиль не всегда (например, когда в нём данные в YAML).
16 Ответы
Паранойя не дремлет, поэтому всегда лучше иметь бэкап всех своих проектов, чтобы в случае какого-либо казуса ничего не потерялось. А лучше два бэкапа....

В качестве одного у меня есть свой сервер Gitea, на котором хранятся разные проекты или просто скрипты и заметки, он же выкачивает с GitHub в режиме зеркала те проекты, которые изначально хостятся там.

В качестве второго бэкапа рулят обычные архивы с данными где-нибудь на полочке. 

Для этого можно овспользоваться простым скриптом, который по API дернет список всех реп, а потом просто клонирует их в каталог.

Я его подцепил на каком-то форуме, и с тех пор довольно часто пользуюсь.

Создаем каталог, куда будем клонировать, и переходим в него:

Код:
mkdir my_projects
cd my_projects

А теперь запускаем скрипт:

Код:
curl -s "https://api.github.com/users//repos?per_page=100&page=1" | jq -r '.[] | select(.name ) | select(.fork == false).ssh_url' | xargs -L1 git clone --mirror

Единственный нюанс — он не сможет выкачать боле 100 реп, но я редко у кого столько видел.

Обновить репы можно так:

Код:
find . -maxdepth 1 -type d -exec sh -c '(cd {} && echo ">>> Pull repo \"$(basename "$(pwd)")\"" && git pull --all | grep -v "Fetching origin" | grep -v "Already up to date")' ';'

Можно добавить всю эту магию в crontab на домашнем сервере, чтобы, скажем, раз в сути в 12 ночи он делал бэкап. Можно добавить немного улучшайзеров: упаковывать в архивы по дате и так далее.
Нет ответов
Часто бывает нужно подсчитать, сколько времени занимает выполнение той или иной функции. Например, когда на это повешены метрики, отдаваемые в систему алертов, или просто для себя, когда оцениваешь эффективность своего кода.

В некоторых языках, например, в Java, для этого нужно городить свой велосипед: брать UTC в милисекунда в начале, потом в конце и руками вычитать значение, переводя его потом в удобоваримую форму.

В Go учли этот момент и сделали штатный механизм.

В начале нужной функции берем время начала:

Код:
start := time.Now()

Затем выполняем все нужные действия и в конце берем время окончания:

Код:
duration := time.Since(start)

Теперь просто выводим результат:

Код:
fmt.Println(duration)

В выводе будет показана понятная всем читабельная человеком цифра в наносекундах, микросекундах, миллисекундах, секундах и так далее.
13 Ответы
Вот и всё что я хотел вам сказать!

Убиваю остаток сегодняшнего дня!

Гарри Поттер и тайная комната на freebsd

сабж

[Изображение: DKVvvnM.png]
2 Ответы
Я давно присматривался к оконному менеджеру ctwm. После кропотливого изучения мануала и просмотра чужих конфигов, понял, что это очень мощный wm. Очень мощный и гибкий. Думаю, он гибче и мощнее fvwm.

Установил я его (версия в Debian Bullseye 3.7-5, а в unstable уже новая-4.0.3-2), и настроил так, чтобы напоминало мне BeOS.

Также настроил хоткей, наподобие вимовских....
Эскизы(ов)
   
3 Ответы
Почти во всех 3D играх воспроизводится разная анимация ног в зависимости от направления движения. Т.е. если персонаж смотрит вперёд, но идёт назад - то будет воспроизводиться анимация ходьбы назад.

Один из самых простых способов реализовать это в Unity:

Код:
public static Vector2 getAnimationDirection(Vector3 referenceVectorUp, Vector3 referenceVectorRight, Vector2 movementVector, Transform objectTransform)
{
    Vector2 animationFinalDirection = Vector2.zero;

    Vector3 dirForward = Vector3.Scale(referenceVectorUp, new Vector3(1f, 0f, 1f)).normalized;
    Vector3 animMoveDir = movementVector.y * dirForward + movementVector.x * referenceVectorRight;

    if (animMoveDir.magnitude > 1)
        animMoveDir.Normalize();

    Vector3 localMove = objectTransform.transform.InverseTransformDirection(animMoveDir);

    animationFinalDirection.x = localMove.x;
    animationFinalDirection.y = localMove.z;

    return animationFinalDirection;
}

Данная функция готова для использования без MonoBehaviour.

Пояснения по коду

referenceVectorUp и referenceVectorRight - transform.up и transform.right объекта, от которого будет отталкиваться функция для поворота. Например, в данный момент мы разрабатываем Top Down Shooter, и в них передаются трансформы камеры.

movementVector - нажатия клавиш или левого стика геймпада. Значения от -1 до 1 по осям X и Y.

objectTransform - трансформ объекта персонажа.

В итоге в зависимости от поворота персонажа будут разные значения вектора.

Как использовать

Пример: есть 2D дерево смешивания. В нём есть две переменные: run и strafe, которые получают float от -1 до 1. И четыре анимации (но можно и больше, если есть отдельные анимации для бега по диагонали).

В нашем проекте это выглядит так:
Код:
Vector2 animDir = getAnimationDirection(playerCamera.GetComponent<Transform>().up, playerCamera.GetComponent<Transform>().right, movementVector, transform)

animator.SetFloat("run", animDir.y);
animator.SetFloat("strafe", animDir.x);

Также можно вместо камеры можно передавать transform.forward игрока, чтобы корректировать анимации. Подойдёт для игр от третьего лица или ботов.
6 Ответы
Сразу скажу, что использование Python для написания простых скриптов - оверкилл. Потому что, в отличие от скриптовых языков (Bash/SH), это полноценный язык программирования. Тем не менее в этом есть свои плюсы. Я так и не осилил скриптовые языки, и каждая попытка воспользоваться оными оборачивалась болью.

Если Вы хорошо знаете скриптовые языки и у Вас есть огромный опыт - то этот пост не для Вас. А всем остальным - добро пожаловать.

Для понимания статьи у Вас уже должны быть базовые знания питона.

Много кода было взято отсюда, не забудьте посетить ссылку: https://github.com/ninjaaron/replacing-b...ith-python

Причины

Начну с того, что этот пост не является агитацией за тот или иной язык, а является, скажем так, наглядным сравнением. Выбор остаётся за читателем.

Заголовок немного лукавит. Питон не является равноценной заменой скриптовым языкам, т.к. они состоят в разных весовых категориях. Однако питон имеет замену большинству возможностей Bash/SH. Разумеется, ни о какой замене всего Bash/SH на Python (в т.ч. для установки по умолчанию в терминале) речи не идёт.

Для себя я выбрал питон по следующим причинам:
  • Работа со строками. Т.к. это полноценный язык программирования, то в нём присутствует полноценная работа со строками. Парсинг, форматирование, разделение и т.д. Никакого жонглирования с grep и awk.
  • Различные типы переменных. Строки являются строками, числа - числами. Это позволяет нормально сравнивать переменные и проводить с ними операции.
  • Возможность писать код полностью без функций. Т.е. прямо как Bash/SH.
  • Полноценные циклы и итераторы
  • Обработка данных средствами языка, без вызова внешних программ. Bash/SH являются всего лишь клеем для внешних приложений.
  • Наличие API у различных штук, что позволяет не парсить выхлоп программы, а взаимодействовать с ней по-человечески
  • Да и в целом приятнее писать, благодаря синтаксическому сахару

А ещё питон позволяет избежать подобных нюансов:
Код:
foo = 'test   test1  test2'
echo $foo
# test test1 test2
echo "$foo"
# test   test1   test2

Можно вообще на любом языке писать, на самом деле, хоть на сишке. Но питон - это современный BASIC, не требующий предварительной компиляции, и входящий в поставку во многих дистрибутивах Linux, так что почему бы и не воспользоваться им.


Примеры

Некоторые примеры будут притянуты за уши, т.к... эээ... когда я садился за написание статьи, я не задумывался о том, что у меня уже давно не возникало каких-то задач по теме. Так что задачи будут абстрактными, но показанный код может быть применим в реальных задачах.

Начнём с простого. Есть модуль shutil, который реализовывает многие низкоуровневые вещи. Ещё есть модуль os.
Код:
shutil.move(src, dest) //mv
shutil.copy2(src, dest) //cp
shutil.copytree(src, dest) //cp -r
shutil.rmtree(dir) //rm -r
shutil.chown(file, user, group) //chown
shutil.which(file) //which
os.remove(file) //rm
os.rename(old_name, new_name) //mv
os.mkdir(dir) //mkdir
os.makedirs(dir) //mkdir -p

Например, вам надо произвести некую каталогизацию. В данном случае это будут директории с годом в названии, а в них - директории с номером месяца.
Код:
import os

base_path = "."

for year in range(2000, 2011):
    for month in range(1, 13):
        os.makedirs(f"{base_path}/{year}/{month}", exist_ok=True)

Я не знаю, почему верхняя граница диапазона требует указать на единицу больше. Тем не менее всё работает. Аргумент exist_ok=True указывает не выдавать ошибку, если директория уже существует.

Скрипт можно сделать чуть гибче:
Код:
import os
import sys

if len(sys.argv) < 2:
    print("Укажите базовый путь")
    sys.exit(-1)

base_path = sys.argv[1]

for year in range(2000, 2011):
    for month in range(1, 13):
        os.makedirs(f"{base_path}/{year}/{month}")

Теперь можно указать директорию, в которой будут создаваться другие директории, аргументом для скрипта. Что-то вроде python yourscript.py /home/user/your_folder/.

На самом деле у питона в стандартной библиотеке есть мощный модуль для работы с аргументами командной строки, но в этот раз обойдёмся без него.

В Python есть средства для получения информации. Давайте напишем небольшой скрипт, отображающий информацию о системе (что-то вроде screenfetch):
Код:
import os
import platform
import psutil


def get_uptime():
    with open("/proc/uptime", "r") as f:
        uptime = f.read().split(" ")[0].strip()

    uptime = int(float(uptime))

    return uptime

# Переменные

fetch_user = os.environ["USER"]
fetch_host = os.environ["HOSTNAME"]
fetch_de = os.environ["DESKTOP_SESSION"]
fetch_shell = os.environ["SHELL"]
fetch_home = os.path.expanduser("~")
fetch_os = platform.system();
fetch_kernel = platform.uname()
fetch_cpu = platform.processor()
fetch_dist = platform.freedesktop_os_release()
fetch_ram = psutil.virtual_memory()
fetch_swap = psutil.swap_memory()


print(f"{fetch_user}@{fetch_host}")
print(f"Shell: {fetch_shell}")
print(f"Home: {fetch_home}")
print(f"OS: {fetch_os}")
print(f"Distro: {fetch_dist['PRETTY_NAME']}")
print(f"Kernel: {fetch_kernel.release}")
print(f"Desktop: {fetch_de}")
print(f"CPU: {fetch_cpu}")
print(f"Memory (GB): {fetch_ram[0] // 1000000000} (total), {fetch_ram[3] // 1000000000} (used)")
print(f"Swap (GB): {fetch_swap[0] // 1000000000} (total), {fetch_swap[3] // 1000000000} (used)")
print(f"Uptime: {get_uptime() // 3600} hours, {(get_uptime() % 3600) // 60} minutes")
Пример выхлопа команды:
Код:
user@desktop
Shell: /usr/bin/zsh
Home: /home/user
OS: Linux
Distro: Fedora Linux 36 (KDE Plasma)
Kernel: 6.0.8-xm1.0.fc36.x86_64
Desktop: plasma
CPU: x86_64
Memory (GB): 16 (total), 9 (used)
Swap (GB): 17 (total), 0.0 (used)
Uptime: 9 hours, 51 minutes

Все модули отработали, кроме отображения модели процессора. Не знаю, только ли у меня такой косяк. Но раз модуль не отработал, то можно пропарсить /proc/cpuinfo, благо что в питоне это делается очень легко:
Код:
def get_cpu():
    with open("/proc/cpuinfo", "r") as f:
        for line in f:
            if "model name" in line:
                return(line.split(": ")[1].strip())


Здесь мы проверили строки на предмет содержания "model name" и разделили найденную строку по двоеточию с пробелом. В данном случае процессор у меня один, и я просто сделал возврат первого совпадения. Но что, если установлены два разных процессора?

Мы не будем учитывать количество ядер (т.к. я не знаю, как), только разные модели процессоров.

Поменяем функцию:
Код:
def get_cpu():
    cpu_list = []
    with open("/proc/cpuinfo", "r") as f:
        for line in f:
            if "model name" in line:
                cpu_model = line.split(": ")[1].strip()
                if cpu_model not in cpu_list:
                    cpu_list.append(cpu_model)
                   
    return cpu_list

И поменяем код вывода модели процессора:
Код:
if len(fetch_cpu) == 1:
    print(f"CPU: {fetch_cpu[0]}")
else:
    for cpu in fetch_cpu:
        print(f"CPU {fetch_cpu.index(cpu) + 1}: {cpu}")

Мы просто проверяем длину списка с процессорами, и в зависимости от неё выводим разный текст. Прибавление единицы можно и убрать, просто мне так больше нравится.

А вот пример реальной задачи, которая у меня возникла пару месяцев назад. Мне нужно было сжать образы игр .ISO для PSP. И вроде бы ничего особенного, всего лишь запустить бинарь с нужными аргументами. Но игр было довольно много.

Я уже писал выше, что Bash/SH - это клей для внешних приложений. И питон тоже может быть им!
Код:
import os
import subprocess
import sys

ISO_LIST = os.listdir(sys.argv[1])

FOLDER_NAME = sys.argv[2]

os.makedirs(FOLDER_NAME, exist_ok=True)

for iso in ISO_LIST:
    if iso.endswith(".iso"):
        print(f"Now compressing: {iso}")
        out_file = os.path.splitext(iso)[0] + '.cso'
        sp = subprocess.run(["./ciso", "9", iso, f"{FOLDER_NAME}/{out_file}"])

В данном скрипте в цикле запускается внешний бинарь, а у выходных файлов указывается расширение .CSO. За запуск внешних процессов отвечает модуль subprocess.
Код:
user@desktop ~/G/ciso> python ciso_all_isos.py . cso_files                                                 master?
Now compressing: From Russia with Love - 007.iso
Compressed ISO9660 converter Ver.1.02 by BOOSTER
Compress 'From Russia with Love - 007.iso' to 'cso_files/From Russia with Love - 007.cso'
Total File Size 732495872 bytes
block size      2048  bytes
index align     1
compress level  9
ciso compress completed , total size = 565886491 bytes , rate 77%
Now compressing: Family Guy.iso
Compressed ISO9660 converter Ver.1.02 by BOOSTER
Compress 'Family Guy.iso' to 'cso_files/Family Guy.cso'
Total File Size 1186463744 bytes
block size      2048  bytes
index align     1
compress level  9
ciso compress completed , total size = 1101765517 bytes , rate 92%

Вообще, пакетная обработка в питоне сделана довольно приятно. Вот ещё один пример. В нём я хочу залить несколько картинок на сервер https://0x0.st.
Код:
import os
import requests

FILE_LIST = os.listdir()

def upload_pic(filename):
    with open(filename, "rb") as fname:
        r = requests.post("https://0x0.st", files={"file": fname})
        return r.text.strip()

for f in FILE_LIST:
    if f.endswith(".jpg"):
        print(upload_pic(f))

Запускаем:
Код:
# python upload_pics.py
https://0x0.st/ohHP.jpg
https://0x0.st/ohHZ.jpg
https://0x0.st/ohHN.jpg
https://0x0.st/ohHq.jpg
https://0x0.st/ohHb.jpg

requests - неофициальный, но очень популярный модуль реализующий HTTP клиент.

Или наоборот, сделаем качалку:
Код:
import sys
import requests

with open(sys.argv[1], "r") as fname:
    FILE_LIST = fname.readlines()

    for line in FILE_LIST:
        line = line.strip()

        r = requests.get(line)

        outfile = line.split("/")[-1]
       
        with open(outfile, "wb") as ofname:
            ofname.write(r.content)

Закидываем ссылки в текстовик и запускаем:
Код:
python download_files.py files.txt

Пример парсинга файла. Подсчитаем количество уникальных IP адресов в логе веб-сервера Nginx.
Код:
import sys

LOG_FILE = sys.argv[1]
UNIQUE_IPS = []
ALL_IPS = 0

logfile = open(LOG_FILE, "r")

for line in logfile:
    ip = line.split(" ")[0]
    ALL_IPS += 1
    if ip not in UNIQUE_IPS:
        UNIQUE_IPS.append(ip)

logfile.close()

print(f"Unique IPs: {len(UNIQUE_IPS)}, all IPs: {ALL_IPS}")

Пример работы:
Код:
# python unique_ips.py access.log
Unique IPs: 215, all IPs: 4373

Можно было бы воспользоваться регуляркой, или добавить дополнительные проверки... но сам формат лог-файла простой. Хотя... А вдруг нам подсунули не лог-файл?
Код:
import sys
import re

LOG_FILE = sys.argv[1]
UNIQUE_IPS = []
ALL_IPS = 0
IP_REGEX = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}"

logfile = open(LOG_FILE, "r")

for line in logfile:
    ip = re.findall(IP_REGEX, line)[0]
    ALL_IPS += 1
    if ip not in UNIQUE_IPS:
        UNIQUE_IPS.append(ip)

logfile.close()

print(f"Unique IPs: {len(UNIQUE_IPS)}, all IPs: {ALL_IPS}")


Заключение

Python вполне может быть клеем для внешних программ. Благодаря средствам языка и своей довольно богатой библиотеке, можно писать скрипты, зачастую не прибегая к вызову внешних программ, полагаясь на обработку данных средствами самого языка.