04 октября 2023 Linux docker ftp proFTPD
Современный backend может не содержать на физическом сервере своих привычных файлов, там может даже не было СУБД и Веб-сервера потому что вся система размещается в контейнерах Docker
Для того чтобы предоставить доступ к файлам в таком контейнере можно воспользовать образом proFTPD и включить его в конфигурацию docker-compose
Ниже представлена настройка такого контейнера
ftp:
image: instantlinux/proftpd
container_name: ${APP_NAME}-ftp
ports:
- "2100:21"
- "30091-30100:30091-30100"
env_file: .env
volumes:
- ./ftp/secrets:/run/secrets
- ./site:/home/site/
environment:
- PASV_ADDRESS=0.0.0.0
- ANONYMOUS_DISABLE=on
- TZ=Europe/Moscow
- SFTP_ENABLE=off
- PASV_MAX_PORT=30100
- PASV_MIN_PORT=30091
так же предполагается наличие .env файла в котором необходимо определить следующие переменные
FTPUSER_UID=1000
FTPUSER_NAME=ftp_user
FTPUSER_PASSWORD_SECRET=Yhatztna7%$4A8hag
Переменная FTPUSER_UID должна быть равна ID текущего пользовать от имени которого запускается контейнер
Переменные FTPUSER_NAME и FTPUSER_PASSWORD_SECRET ипользуется для генерации пароля и поиска этого пароля в специальном файле в директории /run/secrets
Дело в том, что proFTPD использует пароли в определённом формате, для нормальной работы необходимо сгенерировать пароль и положить в специальный файл, который будет подмотирован в образ proFTPD
python3 -c "import crypt,random,string; print(crypt.crypt('$FTPUSER_PASSWORD_SECRET', '\$6\$' + ''.join( [random.choice(string.ascii_letters + string.digits) for _ in range(16)])))" > ftp/secrets/$FTPUSER_PASSWORD_SECRET
в результате получится вот такой файл с паролем
cat ftp/secrets/Yhatztna7%\$4A8hag
$6$XyHVN6aqgQvgj7Vv$Ac/9hKk0WYOmYPPh/hcG/yLvMAAgi91.k5lC2U4Dx/1PEe0KtW8NsLOhN6GBzcX8TKQPF51JHmyBX580pZ9.A0
Если хочется дополнительных опцией автозапуска то достаточно изучить файл инициализации контейнера
docker-compose exec ftp cat /usr/local/bin/entrypoint.sh
29 сентября 2023 СуБД Postgres RECURSIVE
Рекурсивных запрос к Postgres состоит из двух, объединённых запросов
Рекурсия начинает с добавляния в результат данных от первой выборки и будет продолждать до тех пока второй запрос будет возвращать данные
WITH RECURSIVE family_children as (
select id, 0 as level, fio from family where fio ~ '^Иванов'
union
select family.id, level+1 as level, family.fio from family join parent on family.id=parent.child join family on family.id=parent.id
) select * from family_children;
Приведённы в примере запрос начинается с поиска в Таблице Имён записей всех Ивановых. В результат подмешивается переменная level, level определяет уровень родства
Затем запрос продолжает поиском записей родителей любых Ивановых. При этом уровень родства увеличился единицу
Запросы будут продолжаться пока будут находиться родители для предыдущей порции детей
29 августа 2023 Bash awk haproxy
Задача, выделить из логов haproxy статусы запросов, бакенды обработчиков и урлы запросов, так же выводить количество счётчик статусов запросов
tail -f /var/log/haproxy-traffic.log |awk -F' ' '{if (NF == 20) print $11,'\t', ++count[$11],'\t', $9, $19 }'
Программа awk состоит из условия и инкремента счётчика
if (NF == 20) print $11,'\t', ++count[$11],'\t', $9, $19 }
здесь отбираются строки состоящие из 20 слов, выводится status_code запроса, а так же результат инкремента счётчика статусов
подобным образом можно организовать подсчёт количества обращений к бакенду и ссылкам
if (NF == 20) print $11,'\t', ++count[$11],'\t',++count[$19],'\t', $9, ++count[$19],'\t', $19 }
28 августа 2023 Python Docker Django
Задача, извлеч значение настроек запущенного контейнера Django.
Например, необходимо сбросить локальный кешь Django, настройки этого кеша находятся в settings.py файле в словаре
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": "/var/tmp/django_cache-{}".format(SITE_PREFIX),
"TIMEOUT": 360,
"OPTIONS": {
"MAX_ENTRIES": 1000
}
},
"redis": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_LOCATION,
"OPTIONS": {
"CONNECTION_POOL_KWARGS": {"max_connections": 100}
}
},
}
В данном случае необходимо извлеч значение CACHES -> default -> LOCATION
Если Django запущено в контейнере то извлечь это значение можно вот таким образом
docker-compose exec -e DJANGO_SETTINGS_MODULE=centrsvet.settings django poetry run python -c 'from django.conf import settings;print(settings.CACHES["default"]["LOCATION"])'
/var/tmp/django_cache-T1
Представленная команда состоит из целого ряда компонентов
08 августа 2023 Python Django Form Validator custom
Если необходимо выделить поле формы содержащее ошибку то необходимо переопредить форму так
class FeedbackForm(forms.Form):
name = forms.CharField(label="Имя", max_length=50)
email = forms.EmailField(label="E-mail", max_length=50)
phone = forms.CharField(label="Телефон", max_length=20)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
if visible.errors: # при наличии ошибки поля, добавляем css-класс и меняем title
visible.field.widget.attrs["class"] = "form__input has-error"
visible.field.widget.attrs["title"] = "".join(visible.errors)
else:
visible.field.widget.attrs["class"] = "form__input"
visible.field.widget.attrs["required"] = True
def clean_email(self):
return check_email(self.cleaned_data["email"]).lower()
def clean_name(self):
return self.cleaned_data["name"].title()
def clean_phone(self):
_phone = self.cleaned_data["phone"]
_phone = "".join(re.findall("([\\d]+)", _phone))
if re.match( "^\\+?[1-9][0-9]{7,14}$", _phone):
return f"+{_phone}"
raise ValidationError("Телефон не соответствует формату")
Конструктор формы позволяет переопредить настройки виджета поля формы, а так же предоставляет список ошибок обрануженных при валидации формы
07 июля 2023 Python
У Django есть возможность расширить функционал списка объектов дополнительными функциюми через управление списком функций actions
Например вот так выглядит расширение для чистки кеша
def update_cdn_cache(modeladmin, request, queryset):
for obj in queryset:
obj.file.clean_cache()
А затем эта функция добавляется в класс админки
class ImageAdmin(admin.ModelAdmin):
actions = (update_cdn_cache, show_cdn_url)
По умолчанию эти функции отображаются над списком объектов в выпадающем списке и имена формируются из названий
Видно, что выглядит для наглядности необходимо добавить описание и делается это путём добавления свойства short_description для каждой функции actions, но выглядит этот вариант не красиво, особенно когда расширений админки много
def update_cdn_cache(modeladmin, request, queryset):
for obj in queryset:
obj.file.clean_cache()
update_cdn_cache.short_description = _("Remove CDN file")
Чтобы избежать такого способа можно применить параметризованный декоратор
def add_short_description(short_description: str):
def decorator(admin_action):
def wrapper(*args, **kwargs):
return admin_action(*args, **kwargs)
wrapper.__name__ = admin_action.__name__ # принудительная смена названия функции
wrapper.short_description = _(short_description) # перевод описания
return wrapper
return decorator
с таким декоратором код выглядит более читаемым
@add_short_description("Remove CDN file")
def update_cdn_cache(modeladmin, request, queryset):
for obj in queryset:
obj.file.clean_cache()
@add_short_description("Show CDN file")
def show_cdn_url(modeladmin, request, queryset):
for obj in queryset:
print(obj.file.image())
Результат будет явно приятнее
Есть ещё один способ, встроенный в Django
@admin.action(description=_("Enable user"))
def make_published(modeladmin, request, queryset):
queryset.update(is_active=True)
06 июля 2023 Python
Имеются две модели: UploadFile и Image
Модель UploadFile содержит данные о загруженных файла FileField, md5hash
class UploadFile(models.Model):
file = models.FileField(max_length=255)
md5hash = models.CharField(max_length=32, editable=False, unique=True)
Модель Image содержит ссылку на UploadFile, а так же дополнительные поля Alt, Name и Tags
class Image(TranslatableModel):
translations = TranslatedFields(
alt=models.CharField(max_length=50)
)
name = models.CharField(_("Name"), max_length=50)
file = models.ForeignKey(
UploadFile,
related_name="images",
on_delete=models.DO_NOTHING,
)
То-есть, объект Image может быть связан только с одним UploadFile, а объект UploadFile может быть связан с несколькими Image, а так же File, это необходимо для того чтобы получить централизованое хранилище всех загружаемых файлов, исключить дублирование файлов, а так же позвонить создавать наборы различных описаний файлов.
Необходимо в админ панели объекта Image встроить форму добавления объекта UploadFile, стандартными методами можно получит переопределив классы admin.ModelAdmin, но проще воспользовать готовыми переопределениями в пакете django-reverse-admin
Для создания такой вывернутой наизнанку админки необходимо определить такой класс
class ImageAdmin(TranslatableAdmin, ReverseModelAdmin):
inline_type = "tabular"
inline_reverse = ("file", )
form = ImageForm
list_display = ("alt", "thumb", "name", "tags_str", "created_at")
list_filter = (MultiSelectFilter, )
search_fields = ("translations__alt", "name", "tags")
21 апреля 2023 22 августа 2023 Linux bind dnssec
export DOMAINNAME=breys.ru
cd /var/cache/bind
dnssec-keygen -L 3600 -a RSASHA256 -b 2048 $DOMAINNAME
dnssec-keygen -L 3600 -f KSK -a RSASHA256 -b 4096 $DOMAINNAME
for key in `ls K$DOMAINNAME*.key`; do
echo "\$INCLUDE $key" /etc/bind/zones/$DOMAINNAME.conf ;
done
salt=$(head -c 1000 /dev/urandom | sha1sum | cut -b 1-16)
dnssec-signzone -A -3 $salt -N INCREMENT -o $DOMAINNAME -t /etc/bind/zones/$DOMAINNAME.conf
В результате подписанный файл зоны будет в файле /etc/bind/zones/$DOMAINNAME.conf
этот файл необходимо указать в настройках зоны и перезапустить bind9
zone "breys.ru" {
type master;
// file "/etc/bind/zones/breys.ru.conf";
file "/etc/bind/zones/breys.ru.conf.signed";
};
При этом утилита dnssec-signzone подписывает каждую запись зоны /etc/bind/zones/$DOMAINNAME.conf
в который мы добавили созданные ключи через $INCLUDE
Ключи должны находиться в /var/cache/bind
В дальнейшем с зоной необходимо работать по такой схеме
dnssec-signzone -A -3 $salt -N INCREMENT -o $DOMAINNAME -t /etc/bind/zones/$DOMAINNAME.conf
21 апреля 2023 Nginx
После покупки коммерческого SSL сертификата должны появится следующие файлы
Для установки полученного сертификата необходимо публичный ключ сертификата с цепочкой корневых сертификатов в pem контейнер. Важно не перепутать последовательность: сначала сертификат домена, а затем цепочка корневых сертификатов
cat domanname.ru.crt domanname.ru.ca-bundle > domanname.ru.pem
Затем добавить в файл настройки домена NGINX необходимо добавить (можно сразу после директивы listen 80;)
listen 443 ssl;
ssl_certificate /home/domainname/ssl/domainname.ru.pem;
ssl_certificate_key /home/domainname/ssl/domainname.ru.nokey;
Затем перезапустить NGINX
03 апреля 2023 12 октября 2023 Всякое vim
Чтобы включить отбражение warning сообщений достаточно добавить в ~/.vimrc
let g:lsp_diagnostics_echo_cursor = 1
Узнать состояние переменной vim можно с помощью команды echo
:echo g:lsp_diagnostics_echo_cursor
весь список можно посмотреть использую <C+D>, то-есть, начинаете писать :echo g:lsp<C+D> отбразит список переменных начинающихся на g:lsp
03 апреля 2023 12 октября 2023 Всякое vim
Для замены текст по всему файлу нужно использовать команду
:s/найти/заменить/g
опция g обязывает заменить всё, без неё будет проведена только 1 замена
Для замены текст в отдельном участке кода необходимо выделить участок в визуальном режиме, для выделения выходим в командный режим с помощью Esc, затем жмём v и перемещаем курсор до конца или начала блока текста
затем жмём :
в командной строке vim появится :'<,'>
дописываем s/найти/заменить/g так что получается
:'<,'>s/найти/заменить/g
и жмём enter
в результате текст будет заменён только в выделенном блоке
03 апреля 2023 12 октября 2023 vim
Для подключения протоколов языковых серверов в vim необходимо добавить два плагина, а чтобы это всё происходило автоматически добавить плагин установки плагинов
Установка плагина установки плагинов vim
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
затем в конфиг vim добавить конструкцию
call plug#begin()
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
call plug#end()
для этого необходимо отрыть vim и файл ~/.vimrc
после сохранения файла настроек, можно не выходя из vim применить новые настройки вот так
:source % или сокращённо :so %
после применения настроек необходимо установить плагины командой
:PlugInstall
появится буфер отображающий установку плагинов, закрыть буфер :q
Затем можно запустить менеджер LSP с помощью команды
:LspManageServers
откроется буфер-диалог в котором можно выбрать необходимые LSP сервера для установки с помощью кнопки i, выйти из буфера-диалог как всегда :q
То-есть, теперь осталось лишь по одному инсталировать необходимые сервера, после чего vim начнёт использовать их для работы с исходниками
Затем, при открытии файла исходного текста vim может выдать сообщение, которое означает что для файла можно активировать поддержку LSP командой :LspInstallServer
If you want to enable Language Server, please do :LspInstallServer
Но далее необходимо провести поднастройку связки vim+LSP под свои потребности, но это в следующей части
ссылки
https://github.com/mattn/vim-lsp-settings
https://github.com/prabirshrestha/vim-lsp
https://github.com/junegunn/vim-plug
ps: следует обратить внимание на размещённые выше ссылки на плагины vim, а так же на код в секции call plug#begin/end()
08 марта 2023 Hardware
=======================================================
glmark2 2021.02
=======================================================
OpenGL Information
GL_VENDOR: NVIDIA Corporation
GL_RENDERER: NVIDIA GeForce GTX 1080 Ti/PCIe/SSE2
GL_VERSION: 4.6.0 NVIDIA 525.85.05
=======================================================
[build] use-vbo=false: FPS: 14976 FrameTime: 0.067 ms
[build] use-vbo=true: FPS: 45821 FrameTime: 0.022 ms
[texture] texture-filter=nearest: FPS: 38343 FrameTime: 0.026 ms
[texture] texture-filter=linear: FPS: 38579 FrameTime: 0.026 ms
[texture] texture-filter=mipmap: FPS: 38700 FrameTime: 0.026 ms
[shading] shading=gouraud: FPS: 37012 FrameTime: 0.027 ms
[shading] shading=blinn-phong-inf: FPS: 36705 FrameTime: 0.027 ms
[shading] shading=phong: FPS: 35755 FrameTime: 0.028 ms
[shading] shading=cel: FPS: 35650 FrameTime: 0.028 ms
[bump] bump-render=high-poly: FPS: 24752 FrameTime: 0.040 ms
[bump] bump-render=normals: FPS: 40782 FrameTime: 0.025 ms
[bump] bump-render=height: FPS: 39765 FrameTime: 0.025 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 33161 FrameTime: 0.030 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 24136 FrameTime: 0.041 ms
[pulsar] light=false:quads=5:texture=false: FPS: 39287 FrameTime: 0.025 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 10239 FrameTime: 0.098 ms
[desktop] effect=shadow:windows=4: FPS: 21362 FrameTime: 0.047 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 2841 FrameTime: 0.352 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 3714 FrameTime: 0.269 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 3181 FrameTime: 0.314 ms
[ideas] speed=duration: FPS: 26779 FrameTime: 0.037 ms
[jellyfish] <default>: FPS: 29440 FrameTime: 0.034 ms
[terrain] <default>: FPS: 1808 FrameTime: 0.553 ms
[shadow] <default>: FPS: 23509 FrameTime: 0.043 ms
[refract] <default>: FPS: 9068 FrameTime: 0.110 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 37475 FrameTime: 0.027 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 36949 FrameTime: 0.027 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 36338 FrameTime: 0.028 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 37287 FrameTime: 0.027 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 36778 FrameTime: 0.027 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 36874 FrameTime: 0.027 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 36889 FrameTime: 0.027 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 36393 FrameTime: 0.027 ms
=======================================================
glmark2 Score: 28798
=======================================================
06 марта 2023 Linux
dig TXT mail._domainkey.centersvet.com
dig +nocmd centrsvet.com any +multiline +noall +answer
14 февраля 2023 Всякое Python JavaScript 1C Rust PHP C++
Python |
JavaScript |
1C |
Rust |
PHP |
C++ |
32 |
34 |
35 |
39 |
67 |
84 |
False |
break |
и |
as |
__halt_compiler |
alignas |
True |
case |
из |
async |
abstract |
alignof |
None |
class |
или |
await |
and |
and |
and |
catch |
не |
break |
array |
and_eq |
with |
const |
для |
const |
as |
asm |
assert |
continue |
если |
continue |
break |
auto |
break |
debugger |
иначе |
crate |
callable |
Bооl |
class |
default |
как |
dyn |
case |
Bitand |
continue |
delete |
когда |
else |
catch |
bltor |
def |
do |
пока |
enum |
class |
break |
del |
else |
по |
extern |
clone |
case |
elif |
export |
вконце |
false |
const |
catch |
else |
extends |
возврат |
fn |
continue |
char |
except |
finally |
попытка |
for |
declare |
char16_t |
finally |
for |
поймать |
if |
default |
char32_t |
for |
function |
исключение |
impl |
die |
compl |
from |
if |
прервать |
in |
do |
const |
global |
import |
продолжить |
let |
echo |
const_cast |
if |
in |
импорт |
loop |
else |
constexpr |
import |
instanceof |
экспорт |
match |
elseif |
continue |
in |
let |
выбросить |
mod |
empty |
dass |
is |
new |
выбор |
move |
enddeclare |
decltype |
lambda |
return |
новый |
mut |
endfor |
default |
nonlocal |
super |
метод |
pub |
endforeach |
delete |
not |
switch |
это |
ref |
endif |
do |
or |
this |
исп |
return |
endswitch |
double |
pass |
throw |
конст |
Self |
endwhile |
dynamic_cast |
raise |
try |
конструктор |
self |
eval |
else |
return |
typeof |
любой |
static |
exit |
enum |
try |
var |
область |
struct |
extends |
explicit |
while |
void |
знч |
super |
final |
export |
yield |
while |
пер |
trait |
finally |
extern |
|
with |
перечисление |
true |
for |
false |
|
yield |
структура |
type |
foreach |
float |
|
|
умолчание |
union |
function |
for |
|
|
|
unsafe |
global |
friend |
|
|
|
use |
goto |
goto |
|
|
|
where |
if |
if |
|
|
|
while |
implements |
inline |
|
|
|
|
include |
int |
|
|
|
|
include_once |
long |
|
|
|
|
instanceof |
mutable |
|
|
|
|
insteadof |
namespace |
|
|
|
|
interface |
new |
|
|
|
|
isset |
noexcept |
|
|
|
|
list |
not |
|
|
|
|
namespace |
not_eq |
|
|
|
|
new |
nullptr |
|
|
|
|
or |
operator |
|
|
|
|
|
ОR |
|
|
|
|
private |
or_eq |
|
|
|
|
protected |
private |
|
|
|
|
public |
protected |
|
|
|
|
require |
public |
|
|
|
|
require_once |
register |
|
|
|
|
return |
reinterpret_cast |
|
|
|
|
static |
return |
|
|
|
|
switch |
short |
|
|
|
|
throw |
signed |
|
|
|
|
trait |
sizeof |
|
|
|
|
try |
static |
|
|
|
|
unset |
static_assert |
|
|
|
|
use |
static_cast |
|
|
|
|
var |
struct |
|
|
|
|
while |
switch |
|
|
|
|
xor |
template |
|
|
|
|
yield |
this |
|
|
|
|
|
thread_local |
|
|
|
|
|
throw |
|
|
|
|
|
true |
|
|
|
|
|
try |
|
|
|
|
|
typedef |
|
|
|
|
|
typeid |
|
|
|
|
|
typename |
|
|
|
|
|
union |
|
|
|
|
|
unsigned |
|
|
|
|
|
using |
|
|
|
|
|
virtual |
|
|
|
|
|
void |
|
|
|
|
|
volatile |
|
|
|
|
|
wchar_t |
|
|
|
|
|
while |
|
|
|
|
|
xor |
|
|
|
|
|
xor_eq |