<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Widowan</title><author><name>Widowan</name></author><id>https://teletype.in/atom/widowan</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/widowan?offset=0"></link><link rel="alternate" type="text/html" href="https://blog.wido.dev/?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=widowan"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/widowan?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-30T11:36:40.552Z</updated><entry><id>widowan:wireplumber-scripting</id><link rel="alternate" type="text/html" href="https://blog.wido.dev/wireplumber-scripting?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=widowan"></link><title>Wireplumber's lua scripting sucks; here's how to use it</title><published>2024-12-13T17:01:26.827Z</published><updated>2024-12-13T17:04:23.307Z</updated><category term="it" label="IT"></category><summary type="html">Recently steam rolled out a beta of their game recording feature, but it captures entire system audio on linux. Sounds like a job for wireplumber and pipewire! Oh no, it sucks.</summary><content type="html">
  &lt;h2 id=&quot;2fHg&quot;&gt;The problem&lt;/h2&gt;
  &lt;p id=&quot;btVc&quot;&gt;Recently steam rolled out a beta preview of their game recording feature. It&amp;#x27;s amazing! But, since it&amp;#x27;s a beta, one small problem: on linux, at the time of writing, you can not choose which audio source it&amp;#x27;s recording from, it captures entire system audio. So I wanted to write a relatively simple script to unlink all sinks from steam and link wine64-preloader apps to it instead. Sounds like a job for pipewire!&lt;/p&gt;
  &lt;h2 id=&quot;6vQz&quot;&gt;Gotchas&lt;/h2&gt;
  &lt;h3 id=&quot;3P8R&quot;&gt;Wireplumber doesn&amp;#x27;t have any (good) overview documentation&lt;/h3&gt;
  &lt;p id=&quot;ZlQG&quot;&gt;Here&amp;#x27;s how it works in Pipewire world:&lt;/p&gt;
  &lt;ul id=&quot;ReNk&quot;&gt;
    &lt;li id=&quot;6smU&quot;&gt;You have nodes that produce/consume sound&lt;/li&gt;
    &lt;li id=&quot;cjRP&quot;&gt;You have ports which regulate settings of how said nodes produce/consume sound&lt;/li&gt;
    &lt;li id=&quot;nTiF&quot;&gt;You have links that connect the ports&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Ac8d&quot;&gt;Wireplumber doesn&amp;#x27;t have any (good) scripting documentation&lt;/h3&gt;
  &lt;p id=&quot;Qg2S&quot;&gt;Oh, you want to know all possible types for &amp;#x60;Interest&amp;#x60; object? Too bad.&lt;/p&gt;
  &lt;blockquote id=&quot;87tP&quot;&gt;The type of an interest must be a valid &lt;code&gt;GType&lt;/code&gt;, written as a string without the “Wp” prefix and with the first letter optionally being lowercase. The type may match any wireplumber object or interface.&lt;/blockquote&gt;
  &lt;p id=&quot;77NO&quot;&gt;No comprehensive list, go read the possible values from C library.&lt;/p&gt;
  &lt;p id=&quot;phdt&quot;&gt;Oh, you somehow found that wireplumber has eventhooks? Too bad, there&amp;#x27;s only one mention of it in &amp;quot;Wireplumber&amp;#x27;s design&amp;quot; page and no examples whatsoever. Go read source code to find example usage.&lt;/p&gt;
  &lt;p id=&quot;zv4W&quot;&gt;Oh, you want to read examples on github that documentation explicitly mentions? Too bad, they&amp;#x27;re outdated and use legacy api.&lt;/p&gt;
  &lt;p id=&quot;lqqg&quot;&gt;Hell, it doesn&amp;#x27;t even describe all the objects there are! The only circulating snippet around the internet for linking ports has this:&lt;/p&gt;
  &lt;pre id=&quot;m0Qv&quot; data-lang=&quot;lua&quot;&gt;local link = Link(&amp;quot;link-factory&amp;quot;, link_args)
link:activate(1)&lt;/pre&gt;
  &lt;p id=&quot;dble&quot;&gt;Link object is not mentioned ANYWHERE in lua scripting documentation. It &lt;em&gt;looks&lt;/em&gt; to be a wrapper against &lt;code&gt;wp_link_new_from_factory&lt;/code&gt;, but why the hell do I have to guess?&lt;/p&gt;
  &lt;p id=&quot;zg9x&quot;&gt;&lt;strong&gt;&lt;em&gt;Awesome&lt;/em&gt;&lt;/strong&gt;, guys. &lt;/p&gt;
  &lt;h3 id=&quot;G2fc&quot;&gt;Wireplumber doesn&amp;#x27;t run &amp;quot;real&amp;quot; lua, it&amp;#x27;s sandboxed context&lt;/h3&gt;
  &lt;p id=&quot;J0aq&quot;&gt;Okay, you found ObjectManager, managed to give it interest and connect it to callback. Great! It works!&lt;/p&gt;
  &lt;pre id=&quot;85cq&quot; data-lang=&quot;lua&quot;&gt;local node_om = ObjectManager {
  type = &amp;quot;node&amp;quot;,
  Constraint { &amp;quot;media.class&amp;quot;, &amp;quot;equals&amp;quot;, &amp;quot;Stream/Output/Audio&amp;quot;, type = &amp;quot;pw&amp;quot; }
}

node_om:connect(&amp;quot;object-added&amp;quot;, function(om, object)
  print(&amp;quot;Object exists!&amp;quot;)
end)&lt;/pre&gt;
  &lt;p id=&quot;2FB5&quot;&gt;You run this script via wpexec, and it prints a bunch of stuff! Cool! Now you launch a VLC or something to check that it will print once again and.... it doesn&amp;#x27;t?&lt;/p&gt;
  &lt;p id=&quot;nle9&quot;&gt;Saving you some headache, it&amp;#x27;s because of local. As far as I understand, once it&amp;#x27;s done with first run, wireplumber switches context from script back to the main app and garbage collects all &lt;code&gt;local&lt;/code&gt;s (you&amp;#x27;ll also see some &amp;quot;proxy destroyed&amp;quot; errors along the way).&lt;/p&gt;
  &lt;p id=&quot;SJLV&quot;&gt;Do not use locals for ANY ObjectManager objects, global or not.  &lt;/p&gt;
  &lt;h3 id=&quot;bela&quot;&gt;Quick bits&lt;/h3&gt;
  &lt;ul id=&quot;S8G8&quot;&gt;
    &lt;li id=&quot;zihE&quot;&gt;Wireplumber&amp;#x27;s sandbox doesn&amp;#x27;t expose much if at all from standard lua features. Here&amp;#x27;s comprehensive list from the doc. Don&amp;#x27;t expect to be able to make pid tree here:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;6lJE&quot; data-lang=&quot;lua&quot;&gt;_VERSION  assert       error    ipairs    next       pairs   tonumber
pcall     select       print    tostring  type       xpcall  require
table     string       math     package   utf8       debug   coroutine
os.clock  os.difftime  os.time  os.date   os.getenv&lt;/pre&gt;
  &lt;ul id=&quot;aRI0&quot;&gt;
    &lt;li id=&quot;WXrP&quot;&gt;It doesn&amp;#x27;t print logs by default (neither wpexec nor wireplumber itself), enable it:&lt;br /&gt;&lt;code&gt;WIREPLUMBER_DEBUG=W,&amp;lt;my-logger-name&amp;gt;:T&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;Hi6T&quot;&gt;You are not guaranteed to have anything, which results on having lots of ObjectManagers and callback to wait for stuff to appear; i.e. do NOT assume that the node that just spawned has ports already, especially if it&amp;#x27;s something like game that takes long time to load&lt;/li&gt;
    &lt;li id=&quot;vFW7&quot;&gt;Use &lt;code&gt;Debug.dump_table(t)&lt;/code&gt; to dump properties&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;D16V&quot;&gt;Okay, how do I actually use it?&lt;/h2&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;xHOL&quot; data-align=&quot;center&quot;&gt;⚠️ Use qpwgraph app to visually see what&amp;#x27;s happening&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;rmJZ&quot;&gt;I&amp;#x27;m sorry, I can&amp;#x27;t give you a solution. Your usecase is probably just as non trivial as mine, if you are here.&lt;/p&gt;
  &lt;p id=&quot;lK7T&quot;&gt;&lt;strong&gt;If you&amp;#x27;re still reading, this is your last chance to change your mind and make it in bash via &lt;code&gt;pactl subscribe&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;K41n&quot;&gt;Okay, less words, more examples. Here&amp;#x27;s how I disconnect my steam nodes from every sink input:&lt;/p&gt;
  &lt;pre id=&quot;vcU5&quot; data-lang=&quot;lua&quot;&gt;steam_om = ObjectManager { 
  Interest {
	type = &amp;quot;node&amp;quot;,
	Constraint { &amp;quot;application.process.binary&amp;quot;, &amp;quot;matches&amp;quot;, &amp;quot;steam&amp;quot;,              type = &amp;quot;pw&amp;quot; },
	Constraint { &amp;quot;media.class&amp;quot;,                &amp;quot;matches&amp;quot;, &amp;quot;Stream/Input/Audio&amp;quot;, type = &amp;quot;pw&amp;quot; },
  },
}

-- On any steam node, ...
steam_om:connect(&amp;quot;object-added&amp;quot;, function(_, steam_node)
  -- ... We look for links with this node as one it is inputting into,
  steam_link_om = ObjectManager {
    Interest {
      type = &amp;quot;link&amp;quot;,
      Constraint { &amp;quot;link.input.node&amp;quot;, &amp;quot;equals&amp;quot;, steam_node.properties[&amp;quot;object.id&amp;quot;] },
    }
  }

  -- ... Then we look at the Audio/Sink nodes that are sources of this link,
  steam_link_om:connect(&amp;quot;object-added&amp;quot;, function(_, steam_link)
  
    steam_source_om = ObjectManager {
      Interest {
        type = &amp;quot;node&amp;quot;,
        Constraint { &amp;quot;object.id&amp;quot;,   &amp;quot;equals&amp;quot;, steam_link.properties[&amp;quot;link.output.node&amp;quot;] },
        Constraint { &amp;quot;media.class&amp;quot;, &amp;quot;matches&amp;quot;, &amp;quot;Audio/Sink*&amp;quot; },
      }
    }

    -- ... And then we destroy them.
    steam_source_om:connect(&amp;quot;object-added&amp;quot;, function(_, source_node)
    
      local _, err = pcall(function() steam_link:request_destroy() end)
      if err then log:debug(&amp;quot;Destroying error: &amp;quot; .. tostring(err)) end
    
    end)

    steam_source_om:activate()
  end)

  steam_link_om:activate()
end)&lt;/pre&gt;
  &lt;p id=&quot;ZdkR&quot;&gt;And, arguably more hard one, here&amp;#x27;s how to connect every wine64-preload node to steam:&lt;/p&gt;
  &lt;pre id=&quot;6umz&quot; data-lang=&quot;lua&quot;&gt;-- For every wine app, ...
wine_om:connect(&amp;quot;object-added&amp;quot;, function(_, wine_node)
  wine_port_om = ObjectManager {
    Interest {
      type = &amp;quot;port&amp;quot;,
      Constraint { &amp;quot;node.id&amp;quot;,        &amp;quot;equals&amp;quot;, wine_node.properties[&amp;quot;object.id&amp;quot;] },
      Constraint { &amp;quot;port.direction&amp;quot;, &amp;quot;equals&amp;quot;, &amp;quot;out&amp;quot; },
    }
  }

  -- ... We&amp;#x27;re waiting for it to have ports (which can very
  -- much be not instant, look at spotify and FFXIV), ...
  wine_port_om:connect(&amp;quot;object-added&amp;quot;, function(_, wine_port)
    -- ..., Comparing them to steam&amp;#x27;s ports, ...
    for steam_node in steam_om:iterate() do
      for steam_port in steam_node:iterate_ports(input_ports) do
      
        -- ..., Checking if those ports match channels, ...
        if steam_port.properties[&amp;quot;audio.channel&amp;quot;] == wine_port.properties[&amp;quot;audio.channel&amp;quot;] then
          -- ... And connect them.
          link_ports(steam_port, wine_port)
        end
      
      end
    end
  end)

  wine_port_om:activate()
end)
&lt;/pre&gt;
  &lt;p id=&quot;gvJC&quot;&gt;And here&amp;#x27;s a function to link two given ports (stolen from &lt;a href=&quot;https://bennett.dev/auto-link-pipewire-ports-wireplumber/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;):&lt;/p&gt;
  &lt;pre id=&quot;c1NE&quot; data-lang=&quot;lua&quot;&gt;function link_ports(input_port, output_port)
  -- Yes, sometimes I have gotten nil ports, don&amp;#x27;t remove this check
  if not input_port or not output_port then
    log:warning(&amp;quot;nil values, not linking&amp;quot;)
    return
  end

  local link_args = {
    [&amp;quot;link.input.node&amp;quot;]  = input_port.properties[&amp;quot;node.id&amp;quot;],
    [&amp;quot;link.input.port&amp;quot;]  = input_port.properties[&amp;quot;object.id&amp;quot;],
    [&amp;quot;link.output.node&amp;quot;] = output_port.properties[&amp;quot;node.id&amp;quot;],
    [&amp;quot;link.output.port&amp;quot;] = output_port.properties[&amp;quot;object.id&amp;quot;],
    [&amp;quot;object.id&amp;quot;]        = nil,
    [&amp;quot;object.linger&amp;quot;]    = true,
    [&amp;quot;node.description&amp;quot;] = &amp;quot;Link created by steam-wire&amp;quot;,
  }

  local link = Link(&amp;quot;link-factory&amp;quot;, link_args)
  link:activate(1)
end&lt;/pre&gt;
  &lt;p id=&quot;wTBX&quot;&gt;And here is link to full code in case you need it: &lt;a href=&quot;https://github.com/Widowan/steam-wire&quot; target=&quot;_blank&quot;&gt;https://github.com/Widowan/steam-wire&lt;/a&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;8uuf&quot;&gt;Put the script into &lt;code&gt;~/.local/share/wireplumber/scripts/&lt;/code&gt; (create if not exists) and make &lt;code&gt;~/.config/wireplumber/wireplumber.conf.d/90-steam-wire.conf&lt;/code&gt; that looks like this:&lt;/p&gt;
    &lt;pre id=&quot;JQMC&quot; data-lang=&quot;hcl&quot;&gt;wireplumber.components = [
  {
    name = steam-wire.lua,
    type = script/lua,
    provides = custom.steam-wire
  }
]

wireplumber.profiles = {
  main = {
    custom.steam-wire = required
  }
}&lt;/pre&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;dFB1&quot;&gt;Really useful resources!&lt;/h3&gt;
  &lt;ul id=&quot;8Zw8&quot;&gt;
    &lt;li id=&quot;csAY&quot;&gt;&lt;a href=&quot;https://bennett.dev/auto-link-pipewire-ports-wireplumber/&quot; target=&quot;_blank&quot;&gt;https://bennett.dev/auto-link-pipewire-ports-wireplumber/&lt;/a&gt; (the goat)&lt;/li&gt;
    &lt;li id=&quot;vIt2&quot;&gt;&lt;a href=&quot;https://sourcegraph.com/search?q=context:global+file:.*wireplumber.*+file:.*%5C.conf+script/lua&amp;patternType=keyword&amp;sm=0https://sourcegraph.com/search?q=context:global+file:.*wireplumber.*+file:.*%5C.conf+script/lua&amp;patternType=keyword&amp;sm=0&quot; target=&quot;_blank&quot;&gt;https://sourcegraph.com/search?q=context:global+file:.*wireplumber.*+file:.*%5C.conf+script/lua&amp;amp;patternType=keyword&amp;amp;sm=0&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;ZJKU&quot;&gt;Github code search&lt;/li&gt;
    &lt;li id=&quot;zOHW&quot;&gt;I lost more :(&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>widowan:lxc-wireguard</id><link rel="alternate" type="text/html" href="https://blog.wido.dev/lxc-wireguard?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=widowan"></link><title>Needlessly complicated: Wireguard inside LXC container as a gateway</title><published>2023-10-24T09:15:05.950Z</published><updated>2024-01-19T14:39:36.686Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/c1/5b/c15b7778-4285-4d0e-87cd-1ba8ed820920.png"></media:thumbnail><category term="it" label="IT"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/5c/3f/5c3f279a-dd5f-4b6b-80d4-3f86c4da5ec4.png&quot;&gt;It all started with a sudden itch to burn everything to the ground on my VPS server and put everything in LXC containers, including the Wireguard VPN I had. I figured it'd be a good way to learn both LXC and bits of networking, as simple UFW won't do. Well, little did I know it'd also be a major pain for a few days, so here's a short manual to ease it for other people.</summary><content type="html">
  &lt;p id=&quot;GtUh&quot;&gt;It all started with a sudden itch to burn everything to the ground on my VPS server and put everything in LXC containers, including the Wireguard VPN I had. I figured it&amp;#x27;d be a good way to learn both LXC and bits of networking, as simple UFW won&amp;#x27;t do. Well, little did I know it&amp;#x27;d also be a major pain for a few days, so here&amp;#x27;s a short manual to ease it for other people.&lt;/p&gt;
  &lt;p id=&quot;wHfP&quot;&gt;Note that all of the instructions are written for Arch Linux, but since you&amp;#x27;re doing LXC containers, you probably know what to do.&lt;/p&gt;
  &lt;h2 id=&quot;1han&quot;&gt;Installing LXC and LXD&lt;/h2&gt;
  &lt;p id=&quot;gkVK&quot;&gt;First we need to install both LXC and LXD, the first one is the virtualization API itself and the second is it&amp;#x27;s management daemon.&lt;/p&gt;
  &lt;pre id=&quot;5YvF&quot; data-lang=&quot;bash&quot;&gt;sudo pacman -S lxc lxd &lt;/pre&gt;
  &lt;p id=&quot;3vnr&quot;&gt;You may also want to consider adding yourself to lxd group to not to do &lt;code&gt;sudo&lt;/code&gt; every single time:&lt;/p&gt;
  &lt;pre id=&quot;n9q4&quot; data-lang=&quot;bash&quot;&gt;sudo usermod -a -G lxd $USER&lt;/pre&gt;
  &lt;p id=&quot;ig2T&quot;&gt;And speaking of user permissions: to run LXC containers in unprivileged mode, you have to change default mappings of your UID/GIDs:&lt;/p&gt;
  &lt;pre id=&quot;oS3t&quot; data-lang=&quot;bash&quot;&gt;sudo usermod -v 1000000-1000999999 -w 1000000-1000999999 root&lt;/pre&gt;
  &lt;p id=&quot;aLN5&quot;&gt;Alternatively you could also edit &lt;code&gt;/etc/subuid&lt;/code&gt; and &lt;code&gt;/etc/subguid&lt;/code&gt; files directly.&lt;/p&gt;
  &lt;p id=&quot;i4lh&quot;&gt;And don&amp;#x27;t forget to enable the services:&lt;/p&gt;
  &lt;pre id=&quot;c0im&quot; data-lang=&quot;bash&quot;&gt;sudo systemctl enable --now lxd lxc&lt;/pre&gt;
  &lt;p id=&quot;2pJp&quot;&gt;Now you should initialize the daemon. You probably want default answers to all of the questions.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;NGyC&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;⚠️ Heads up!&lt;/strong&gt;&lt;/p&gt;
    &lt;p id=&quot;JZit&quot;&gt;If you get weird errors such as nftables not knowing what masquerade is or any other obscure errors, try to upgrade your kernel! Apparently it&amp;#x27;s really important.&lt;/p&gt;
  &lt;/section&gt;
  &lt;pre id=&quot;ebD1&quot;&gt;lxd init&lt;/pre&gt;
  &lt;p id=&quot;5C9z&quot;&gt;That should create everything you need, including &lt;code&gt;lxdbr0&lt;/code&gt; interface that will do all the heavy lifting for us on the networking side.&lt;/p&gt;
  &lt;p id=&quot;Z9Jn&quot;&gt;After that, all that&amp;#x27;s left is creating your container (to see all images use &lt;code&gt;lxc image list images:&lt;/code&gt;, with colon):&lt;/p&gt;
  &lt;pre id=&quot;1Dla&quot; data-lang=&quot;bash&quot;&gt;lxc launch images:archlinux/current/amd64 wireguard&lt;/pre&gt;
  &lt;p id=&quot;Z19t&quot;&gt;Now you can see your container with &lt;code&gt;lxc list&lt;/code&gt; and connect to it using &lt;code&gt;lxc exec wireguard bash&lt;/code&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Boap&quot;&gt;Configuring Wireguard&lt;/h2&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;4Gsg&quot;&gt;Because LXC containers do not virtualize kernel and use host one, we would need to have wireguard kernel module loaded in host, so install &lt;code&gt;wireguard-dkms&lt;/code&gt; packge &lt;strong&gt;on the host system &lt;/strong&gt;and make sure the module is loaded.&lt;/p&gt;
    &lt;p id=&quot;Y3gY&quot;&gt;However in container we only need userspace package — so you need to install &lt;code&gt;wireguard-tools&lt;/code&gt; inside it.&lt;/p&gt;
  &lt;/section&gt;
  &lt;section style=&quot;background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;GlDg&quot;&gt;Unless you are doing this solely to learn like me, I would recommend using tools such as &lt;a href=&quot;https://github.com/gravitl/netmaker&quot; target=&quot;_blank&quot;&gt;netmaker&lt;/a&gt; or at least scripts to manage peers. Unfortunately, beautiful &lt;a href=&quot;https://github.com/angristan/wireguard-install&quot; target=&quot;_blank&quot;&gt;wireguard-install&lt;/a&gt; script would require some tinkering to not to refuse to install in LXC, and to do it &lt;em&gt;properly&lt;/em&gt; (i.e. not install kernel module).&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;vefT&quot;&gt;As per disclaimer above, install userspace tools:&lt;/p&gt;
  &lt;pre id=&quot;m0bJ&quot; data-lang=&quot;bash&quot;&gt;[root@wireguard ~]$ pacman -S wireguard-tools&lt;/pre&gt;
  &lt;p id=&quot;Bkno&quot;&gt;And configure your wireguard server profile at &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;oUWa&quot;&gt;[root@wireguard ~]$ wg genkey | tee /etc/wireguard/server-sk.key
[root@wireguard ~]$ cat /etc/wireguard/server-sk.key | wg pubkey | tee /etc/wireguard/server-pk.key
[root@wireguard ~]$ wg genkey | tee /etc/wireguard/peer1-sk.key
[root@wireguard ~]$ cat /etc/wireguard/peer1-sk.key | wg pubkey | tee /etc/wireguard/peer1-pk.key
[root@wireguard ~]$ wg genkey | tee /etc/wireguard/peer2-sk.key
[root@wireguard ~]$ cat /etc/wireguard/peer2-sk.key | wg pubkey | tee /etc/wireguard/peer2-pk.key&lt;/pre&gt;
  &lt;pre id=&quot;qN8Q&quot; data-lang=&quot;toml&quot;&gt;[Interface]
Address = 10.10.10.0/24
SaveConfig = true
ListenPort = 51820
PrivateKey = &amp;lt;server-sk goes here&amp;gt;

[Peer]
PublicKey = &amp;lt;peer1-pk goes here&amp;gt;
AllowedIPs = 10.10.10.2/32

[Peer]
PublicKey = &amp;lt;peer2-pk goes here&amp;gt;
AllowedIPs = 10.10.10.3/32&lt;/pre&gt;
  &lt;p id=&quot;ywRH&quot;&gt;Keen eye might notice that we&amp;#x27;re missing the usual masquerade commands in the interface configuration:&lt;/p&gt;
  &lt;pre id=&quot;MYFl&quot; data-lang=&quot;toml&quot;&gt;PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE&lt;/pre&gt;
  &lt;p id=&quot;HU9l&quot;&gt;Indeed - it doesn&amp;#x27;t work as is right now, but will start to if we add it. However, the one caveat of that is that &lt;strong&gt;we will not be able to connect to other containers&lt;/strong&gt; through VPN by external address: meaning if we, say, have a website hosted at myservice.wido.dev:443, we wouldn&amp;#x27;t be able to connect to it while connected to VPN, even if it&amp;#x27;s public! I have no idea why this is exactly, it may be also because I was using UDP and just missed something specific to it.&lt;/p&gt;
  &lt;p id=&quot;NXJf&quot;&gt;My findings are as follows: if you connect to &lt;code&gt;myservice.wido.dev&lt;/code&gt;, for some reason, the request goes from wg0 interface, but never response gets back; despite being masqueraded, the reply arrives designated specifically for eth0 interface and is never de-masqueraded back to wg0. I don&amp;#x27;t know whether this is a bug in LXC/LXD or a mistake of mine that I didn&amp;#x27;t notice, but I&amp;#x27;d be glad to hear anyone who knows.&lt;/p&gt;
  &lt;figure id=&quot;p1ke&quot; class=&quot;m_column&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5c/3f/5c3f279a-dd5f-4b6b-80d4-3f86c4da5ec4.png&quot; width=&quot;1240&quot; /&gt;
    &lt;figcaption&gt;Here&amp;#x27;s how it looks like&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ihP3&quot;&gt;Okay, so how do you fix it? I&amp;#x27;m gonna save you the two days I&amp;#x27;ve spent looking around and tell you the answer: routing tables! (You can see your ip using &lt;code&gt;lxc list&lt;/code&gt;)&lt;/p&gt;
  &lt;pre id=&quot;vd7y&quot; data-lang=&quot;bash&quot;&gt;ip route add 10.10.10.0/24 via &amp;lt;ip of your wg container&amp;gt; dev lxdbr0 src &amp;lt;ip of your host machine&amp;gt;
&lt;/pre&gt;
  &lt;p id=&quot;ZUyD&quot;&gt;Don&amp;#x27;t forget to make it permanent (depends on your distro), here&amp;#x27;s the service for systemd-networkd way (see the devices with &lt;code&gt;systemctl list-units&lt;/code&gt;):&lt;/p&gt;
  &lt;pre id=&quot;DnJm&quot; data-lang=&quot;toml&quot;&gt;[Unit]
Description=&amp;#x27;Add static routes to wireguard IP subnet&amp;#x27;
After=sys-devices-virtual-net-lxdbr0.device
After=lxc.service
After=lxd.service
WantedBy=sys-devices-virtual-net-lxdbr0.device

[Service]
ExecStart=/usr/bin/ip route add 10.10.10.0/24 via &amp;lt;ip of your wg container&amp;gt; dev lxdbr0 src &amp;lt;ip of your host machine&amp;gt;

[Install]
WantedBy=multi-user.target&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;PrnC&quot; data-align=&quot;center&quot;&gt;&lt;strong&gt;🚨 (Update) UDP Wild goose chase&lt;/strong&gt;&lt;/p&gt;
    &lt;hr /&gt;
    &lt;p id=&quot;OHLJ&quot;&gt;Quick update: I was banging my head against the wall for an entire week, but I finally figured out why UDP wasn&amp;#x27;t working with this setup, or rather, was working very incosistently: if you send a request to 77.77.77.77, but a reply comes from a different IP address, say, 10.50.50.1 (i.e. lxdbr0&amp;#x27;s address), wireguard will drop such packet. Given that UDP is a stateless protocol, this kind of makes sense, but I just wish it was mentioned anywhere in the internet. Anyways, &lt;strong&gt;this is the reason we have the src part in our routing&lt;/strong&gt;.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;30M9&quot;&gt;And also, while we&amp;#x27;re at the host system, don&amp;#x27;t forget one more thing: &lt;strong&gt;port forwarding&lt;/strong&gt;! Because we don&amp;#x27;t want to do it ourselves, let&amp;#x27;s make LXD do it for us:&lt;/p&gt;
  &lt;pre id=&quot;dlgh&quot;&gt;lxc config device add networking wg-proxy proxy listen=udp:0.0.0.0:51280 connect=udp:127.0.0.1:51280&lt;/pre&gt;
  &lt;h2 id=&quot;7Hbf&quot;&gt;Configuring firewall&lt;/h2&gt;
  &lt;p id=&quot;DzLX&quot;&gt;We&amp;#x27;ve done so much, and it may be working for you already depending on your distro. Congrats! But in reality, we&amp;#x27;re not done yet. One last step we have to do in to configure firewall. I will be using nftables for this, as this setup may imply you have specific networking needs as do I.&lt;/p&gt;
  &lt;p id=&quot;zEl6&quot;&gt;Here&amp;#x27;s how my &lt;code&gt;/etc/nftables/nftables.conf&lt;/code&gt; looks like:&lt;/p&gt;
  &lt;pre id=&quot;uMib&quot; data-lang=&quot;graphql&quot;&gt;#!/usr/bin/nft -f
# vim:set ts=2 tw=2 sw=2 expandtab:
table inet filter
delete table inet filter

table inet filter {
  chain input {
    # type     = [filter, route, nat]
    # hooks    = [ingress, prerouting, input, forward, output, postrouting]
    # priority = int (mnemonics: mangle = -150, dstnat = -100, filter = 0, srcnat = 100)
    # policy   = [accept, drop]
    type filter hook input priority 0
    policy drop

    # You will NOT get reply back without this
    ct state established,related accept
    ct state invalid             drop
    
    ip protocol icmp accept comment &amp;quot;Allow ICMP&amp;quot;
    iif lo           accept comment &amp;quot;Allow loopback&amp;quot;
    # If you want to configure ACL, you should add a
    # jump-chain here instead of just accept
    iifname &amp;quot;lxdbr0&amp;quot; accept comment &amp;quot;Allow LXC communication&amp;quot;

    tcp dport 3737  accept comment &amp;quot;Allow sshd&amp;quot;
    udp dport 51820 accept comment &amp;quot;Allow wireguard&amp;quot;
    tcp dport 443   accept comment &amp;quot;Allow myservice&amp;quot;

    counter comment &amp;quot;Count input traffic&amp;quot;
  }

  chain forward {
    type filter hook forward priority 0
    policy drop

    oifname &amp;quot;lxdbr0&amp;quot; accept
    iifname &amp;quot;lxdbr0&amp;quot; accept
  }
  
  chain lxc {
    type nat hook postrouting priority srcnat
    policy accept

    oif     &amp;quot;enp1s0&amp;quot; masquerade
    iifname &amp;quot;lxdbr0&amp;quot; masquerade
  }
}&lt;/pre&gt;
  &lt;p id=&quot;zs2A&quot;&gt;With this configuration, you should be done! Congratulations on taming LXC networking.&lt;/p&gt;

</content></entry><entry><id>widowan:cloud-candy</id><link rel="alternate" type="text/html" href="https://blog.wido.dev/cloud-candy?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=widowan"></link><title>Облака - не сахарная вата, какой их рекламируют, а неудобный набор велосипедов.</title><published>2023-03-03T10:17:32.477Z</published><updated>2023-03-06T00:11:21.376Z</updated><category term="it" label="IT"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/fc/c1/fcc1e6b7-e869-4072-9ed1-c194e6db8bef.png&quot;&gt;Некоторое время назад отовсюду можно было услышать радостные крики о том, насколько облачные технологии нам всем помогут и упростят разработку. Сегодня я поведаю вам, почему это нихрена не так, как я потратил неделю на написание CRUD'а в облаке и как этого избежать.</summary><content type="html">
  &lt;p id=&quot;Hstk&quot;&gt;Некоторое время назад отовсюду можно было услышать радостные крики о том, насколько облачные технологии нам всем помогут и упростят разработку. Сегодня я поведаю вам, почему это нихрена не так, как я потратил неделю на написание CRUD&amp;#x27;а в облаке и как этого избежать.&lt;/p&gt;
  &lt;h2 id=&quot;iBeM&quot;&gt;Как всё начиналось&lt;/h2&gt;
  &lt;p id=&quot;cQ56&quot;&gt;Одним днём мне захотелось размяться и сделать кое-какой проект. В чем суть проекта — не так важно. На мысли о том что хочется сделать что-нибудь наложилось ещё и то, что я не так давно праздного интереса ради смотрел бесплатные тарифы Яндекс.Облака (Yandex.Cloud, далее YC), и в целом они меня порадовали: бесплатный тир достаточно щедрый и в целом цены не то чтобы очень дорогие (хотя и всё равно дороже AWS даже с текущим курсом). Это особенно важно, учитывая, что мы живём в России 2023 года, где никакие зарубежные сервисы не работают, а среди Российских облачных провайдеров бесплатный тир есть... только у Яндекса ¯\_(ツ)_/¯.&lt;/p&gt;
  &lt;p id=&quot;pH4y&quot;&gt;На самом деле это не &lt;em&gt;самый &lt;/em&gt;первый мой опыт работы с Serverless системами — я делал сокращалку ссылок на Cloudflare Workers (&lt;a href=&quot;https://wido.dev/evil&quot; target=&quot;_blank&quot;&gt;wido.dev/evil&lt;/a&gt;, например). Но опыт там был на Rust&amp;#x27;е с использованием всего у чего только была приписка Experimental... в общем опыт интересный, но я знал, во что ввязывался (кстати бесплатные тарифы Cloudflare просто фантастически щедрые).&lt;/p&gt;
  &lt;p id=&quot;5qe7&quot;&gt;Суть в том, что я, наслушавшись хвалебных статей о величии и удобстве облаков, ожидал увидеть полноценный mature сервис с SDK и обёртками под все языки и фреймворки. Ох как же я ошибался — их даже для AWS то мало, что уж про Яндекс... Хотя нет, по Яндексу конкретно я ещё проедусь — дальше.&lt;/p&gt;
  &lt;h2 id=&quot;ok0o&quot;&gt;Выясняем разные облака на вкус&lt;/h2&gt;
  &lt;p id=&quot;yxKD&quot;&gt;Итак, вооружаемся словарём хайповых слов которые вы слышали и гуглим таблицу аналогов яндекса:&lt;/p&gt;
  &lt;ul id=&quot;mdAP&quot;&gt;
    &lt;li id=&quot;w3oF&quot;&gt;AWS Lambda - Yandex Cloud Functions: &amp;quot;Function as a Service&amp;quot; — просто удалённое выполнение функции (на самом деле кода в любом объёме и с любым количеством функций) на серверах. По существу при получении запроса рантайм просто загружает и исполняет вашу функцию. Формулировка так себе, но она важна: существует конкретный &lt;u&gt;&lt;strong&gt;рантайм&lt;/strong&gt;&lt;/u&gt; который &lt;strong&gt;&lt;u&gt;загружает&lt;/u&gt;&lt;/strong&gt; вашу функцию. Важно это потому, что накладывает ограничения на код: он должен запускаться в определённом рантайме (например только Java 11, не выше) и быть структурирован таким образом, чтобы рантайм смог ваш код загрузить (он не просто выполняет &lt;code&gt;python3 myCode.py)&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;B2CK&quot;&gt;EC2 - Yandex Compute Cloud: по существу — виртуальные машины (сервера) с возможностью автоматического масштабирования под нагрузку. &lt;/li&gt;
  &lt;/ul&gt;
  &lt;figure id=&quot;fw5z&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fc/c1/fcc1e6b7-e869-4072-9ed1-c194e6db8bef.png&quot; width=&quot;510.971454058876&quot; /&gt;
    &lt;figcaption&gt;Так и есть&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pAMG&quot;&gt;Я мог бы долго перечислять аналоги, но эти два мало того что основные, так ещё и прекрасно показывают суть serverless сервисов для тех, кто с ними не знаком: есть обычный облачный (чаще - managed) режим, а есть serverless.&lt;/p&gt;
  &lt;p id=&quot;HWtg&quot;&gt;Оплачиваются такие сервисы по системе Pay-As-You-Go — по фактическому использованию. Однако в случае с managed вариантом... фактическим использованием будет время использования виртуалки! А значит вы по существу просто арендуете ограниченный VPS. Ладно. Будем использовать Serverless - он дешевле.&lt;/p&gt;
  &lt;h2 id=&quot;HSo7&quot;&gt;Yandex Cloud Functions&lt;/h2&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;OCnp&quot;&gt;Сразу сделаю ремарку, что я по стеку — Java разработчик, и всё это решил делать для тренировки работы со Spring Boot, и оценивать буду соответственно с позиции джависта. Однако, думаю, мои мысли применимы ко всем, кроме разработчиков на С++ и Python. И может Go, судя по тому, что я видел в SDK.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;8trb&quot;&gt;Итак, открываем YCF и видим следующую картину в плане рантаймов:&lt;/p&gt;
  &lt;ul id=&quot;PW0L&quot;&gt;
    &lt;li id=&quot;AGIX&quot;&gt;NodeJS (12, 14, 16)&lt;/li&gt;
    &lt;li id=&quot;ivs6&quot;&gt;PHP (7.4, 8.0)&lt;/li&gt;
    &lt;li id=&quot;1h4T&quot;&gt;Python (3.7 - 3.11)&lt;/li&gt;
    &lt;li id=&quot;7Knq&quot;&gt;Golang (1.16 - 1.19)&lt;/li&gt;
    &lt;li id=&quot;m6Ai&quot;&gt;Java (11)&lt;/li&gt;
    &lt;li id=&quot;VYNi&quot;&gt;.NET 3.1&lt;/li&gt;
    &lt;li id=&quot;AfRJ&quot;&gt;R (4.0, 4.2)&lt;/li&gt;
    &lt;li id=&quot;6eMb&quot;&gt;Bash&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;3ulf&quot;&gt;Список мягко говоря... не впечатляет, особенно меня, как джависта: пока весь мир уже сидит на 17 джаве, в YC до сих пор 11. Да и остальные рантаймы не то чтобы сильно актуальны.&lt;/p&gt;
  &lt;p id=&quot;I2H0&quot;&gt;Ладно, не 8 и на том спасибо. Как там с этим работать...&lt;/p&gt;
  &lt;p id=&quot;aoXc&quot;&gt;&lt;strong&gt;Вариант 1:&lt;/strong&gt; Сделать реализацию стандартного интерфейса Function (или яндексовского YcFunction) и указать её в качестве точки входа. Очевидно что подойдёт это только для совсем простых функций, да и ограничения там тоже уже есть.&lt;/p&gt;
  &lt;p id=&quot;VFF0&quot;&gt;&lt;strong&gt;Вариант 2:&lt;/strong&gt; О, есть раздел про Spring Boot! Как раз мой стек! Ну-ка... мда, раздел совсем маленький, и большая его часть — описание ограничений.&lt;/p&gt;
  &lt;ul id=&quot;Yt9F&quot;&gt;
    &lt;li id=&quot;R2ps&quot;&gt;Не поддерживается Spring Boot Loader (&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/3.1.x/reference/htmlsingle/#appendix.executable-jar.alternatives&quot; target=&quot;_blank&quot;&gt;как его убрать, Appendix E.6&lt;/a&gt; или моя &lt;a href=&quot;https://gist.github.com/Widowan/3371ab44f764bd471bdac8a0600e7a46&quot; target=&quot;_blank&quot;&gt;заметка на Gist&lt;/a&gt;).&lt;/li&gt;
    &lt;li id=&quot;wFrD&quot;&gt;Функция на Spring Boot не имеет информации о том, какой её эндпоинт был вызван. Для этого надо использовать API Gateway (и соответственно писать OpenAPI спеку, если вы ещё этого не сделали).&lt;/li&gt;
    &lt;li id=&quot;d7b9&quot;&gt;Не поддерживаются некоторые метода HttpServlet* классов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;rKjZ&quot;&gt;Лааааадно, пофигу. Пишем простой Hello World на спринге, загружаем, настраиваем API Gateway, делаем наконец-то curl запрос и!.. 500 миллисекунд на ответ??? Как-то... ну вообще не впечатляет. Из плюсов — загружать проект можно исходниками, джарником или maven проектом. Удобно.&lt;/p&gt;
  &lt;h2 id=&quot;jLkd&quot;&gt;В начале было слово, и слово это было — докер. Yandex Serverless Containers&lt;/h2&gt;
  &lt;p id=&quot;g3Fh&quot;&gt;Слишком уж не впечатляет производительность и &amp;quot;удобство&amp;quot; Cloud Functions, давайте попробуем контейнеры! Тут нам никто не указ по части рантайма, да и Spring Boot 3.0 официально поддерживает GraalVM Native Image.&lt;/p&gt;
  &lt;p id=&quot;T8WZ&quot;&gt;Для того чтобы использовать контейнеры, нам надо создать реестр в YC (тоже отдельный сервис кстати) и настроить CLI утилиту. Качаем тулзу (есть в AUR), настраиваем, компилируем Native Image (не забывайте -Pnative чтобы собрать минимально возможный и оптимизированный образ, ценой 100% загрузки всех 12 ядер), тегаем как того просит документация, заливаем, делаем curl... 300 мс (и 1.5 секунды на первый холодный запуск).&lt;/p&gt;
  &lt;p id=&quot;nYgD&quot;&gt;Очевидно что контейнеры лучше по всем параметрам, но вот производительность всё равно оставляет желать лучшего. Если что, время запуска на локальной машине у меня — 50 миллисекунд.&lt;/p&gt;
  &lt;p id=&quot;REQH&quot;&gt;Ладно, черт с ним со временем ответа, не критично, пора подключать базу данных.&lt;/p&gt;
  &lt;h2 id=&quot;xALX&quot;&gt;YDB — самый нижний круг ада&lt;/h2&gt;
  &lt;p id=&quot;V4FC&quot;&gt;На этом этапе я ещё даже не подозревал, что меня ждёт...&lt;/p&gt;
  &lt;p id=&quot;aZtl&quot;&gt;Итак, какие варианты serverless баз данных есть у YC? Да никаких, только YDB. Все остальные базы данных, представленные в облаке — managed, т.е. по сути виртуалка с помесячной оплатой, причём стоят они все конских денег — самый нищенский вариант среди всех баз данных начинаются от трёх тысяч рублей в месяц (хотя Clickhouse и Redis минимально можно взять за 2500, а MongoDB минимум 5000).&lt;/p&gt;
  &lt;p id=&quot;7zTR&quot;&gt;Есть YDB, у которой существует serverless вариант с оплатой в подобающем pay as you go формате, а значит мы его и выбираем. Идём в консоль, создаём базу и сразу же сталкиваемся с тем, что таблица можем быть либо в YDB формате (реляционном), либо в документном (AWS DynamoDB-совместимая).&lt;/p&gt;
  &lt;h3 id=&quot;BCe8&quot; data-align=&quot;center&quot;&gt;Я совершил фатальную ошибку — я выбрал YDB режим.&lt;/h3&gt;
  &lt;p id=&quot;uJjD&quot;&gt;Хорошо, идём в документацию и видим следующие факты:&lt;/p&gt;
  &lt;ul id=&quot;wjFm&quot;&gt;
    &lt;li id=&quot;NJIl&quot;&gt;Язык запросов — YQL (очередной велосипед, которые Яндекс очень уж любит переизобретать).&lt;/li&gt;
    &lt;li id=&quot;fymL&quot;&gt;Существует SDK под джаву.&lt;/li&gt;
    &lt;li id=&quot;MzlZ&quot;&gt;Если найти таблицу сравнения SDK, то лучше всех поддерживается Python и C++.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Vxds&quot;&gt;Ладно, открываем ссылку на SDK для джавы, не теряя времени в ридми находим ссылку на пример использования, открываем и видим две вещи: феерическое качество кода &amp;quot;тестов&amp;quot; и безрадостную картину голого использования JDBC, причем с кастами всего что можно в свои велосипеды (Connection -&amp;gt; YdbConnection, PreparedStatement -&amp;gt; YdbPreparedStatement, ...), что убивает использование даже JdbcTemplate.&lt;/p&gt;
  &lt;p id=&quot;wjng&quot;&gt;&lt;em&gt;Ну не может же у базы данных с JDBC драйвером (пусть даже и таким) не быть библиотеки для работы со Spring Data (JPA)??&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;RigT&quot;&gt;Шерстим issues и ищем по коду в надежде найти что-то, но ничего. Абсолютно. В стадии &amp;quot;отрицания&amp;quot; отправляемся в гугл и... внезапно находим &lt;em&gt;другой&lt;/em&gt; SDK — легаси, на верхушке ридми которого написано про deprecated и дана ссылка на новый SDK, где мы только что были. Но несмотря на то что это &amp;quot;легаси&amp;quot; — этот сдк не брошен, там всё ещё есть коммиты недельной давности.&lt;/p&gt;
  &lt;p id=&quot;Ykp1&quot;&gt;И, что самое главное, тут есть разделы spring-data-jdbc и spring-data-jpa! Радостно открываем их и... видим, что последний коммит в них был 2 года назад и там даже нету джарников и pom (а значит и возможности собрать самостоятельно).&lt;/p&gt;
  &lt;p id=&quot;QiAe&quot;&gt;...&lt;/p&gt;
  &lt;p id=&quot;FKdF&quot;&gt;Ну такого ведь не может быть, да?&lt;/p&gt;
  &lt;p id=&quot;XEc2&quot;&gt;На этом моменте я потратил целый день и перерыл гугл, исходники обоих SDK, все issue, поиск по всему гитхабу (встроенными средствами и через sourcegraph) и могу вам заявить — так и есть. Возможности нормально использовать YDB со спрингом нет. И даже Datasource для спринга создать не получится — драйвера диалекта для Hibernate-то нет, у нас же велосипед, YQL.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;rcau&quot;&gt;Теоретически может быть возможно указать случайный диалект в Hibernate и использовать Native Query, однако даже так у меня не получилось установить соединение с БД, да и оно намертво отказывалось компилироваться в Native Image, так что я спустя некоторое время просто плюнул на это.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;1W9g&quot;&gt;На этом этапе я стал железобетонно уверен, что разработчики Яндекса ненавидят других программистов и людей в целом, и изобретают велосипеды только чтобы мучить всех остальных своими несовместимыми со всеми реализациями.&lt;/p&gt;
  &lt;h2 id=&quot;xIrW&quot;&gt;Помните документный режим, совместимый с DynamoDB? Давайте попробуем его!&lt;/h2&gt;
  &lt;p id=&quot;XmOc&quot;&gt;В этот момент я потерял веру в человечество (и в первую очередь в Яндекс) и решил попробовать документный режим. С ними я никогда раньше не работал (даже с MongoDB), но это меня не остановило. Я удалил старую таблицу и начал создавать новую, документную и...&lt;/p&gt;
  &lt;p id=&quot;7Saf&quot;&gt;Тут всего три типа данных (String, Number и Binary) и есть ключи партицирования и ключи сортировки. Ладно, возможно погуглить всё же придётся.&lt;/p&gt;
  &lt;p id=&quot;wkVz&quot;&gt;Итак, суть для таких же как я:&lt;/p&gt;
  &lt;ul id=&quot;rnDv&quot;&gt;
    &lt;li id=&quot;SimL&quot;&gt;Вам не нужно больше типов данных, потому что вам нужна 1-2 колонки в админке, собственно под ключи (документные БД не имеют схемы, вы просто загрузите json со всеми нужными колонками).&lt;/li&gt;
    &lt;li id=&quot;0PBI&quot;&gt;В таблице может быть только ключ партицирования, а может ключ партицирования и сортировки. Ключ партицирования — это и есть обычный Primary Key (на самом деле нет).&lt;br /&gt;Если есть ещё и ключ сортировки, то PK выступает ключом группировки (т.е. могут быть несколько одинаковых значений ключа партицирования.&lt;br /&gt;Грубо говоря — есть таблица песен, где есть исполнители и названия песен. Ключ партицирования — это имя исполнителя, а ключ сортировки — название песни.&lt;/li&gt;
    &lt;li id=&quot;tZcQ&quot;&gt;Не пытайтесь создавать таблицу в которой будет только PK типа Number — это плохая идея, идущая против best practices, да и вас в этом не поддержит SDK: он генерирует случайные UUID (Строковые), а не инкрементальные айдишники. И по-возможности всё таки используйте ключ сортировки — хорошим вариантом всегда является дата (создания, добавления в базу, чего угодно) (официальная &lt;a href=&quot;https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design&quot; target=&quot;_blank&quot;&gt;статья mongodb&lt;/a&gt; с ориентирами на то, как проектировать БД)&lt;/li&gt;
    &lt;li id=&quot;LZrJ&quot;&gt;Там нет напрямую языка запросов, все действия делаются через API. Однако дальше SDK и библиотеки для спринга этот момент нам абстрагируют.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;pht8&quot;&gt;&lt;strong&gt;Итак, поехали.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;wvZp&quot;&gt;Ставим форк форка бибилотеки для &lt;a href=&quot;https://github.com/boostchicken/spring-data-dynamodb&quot; target=&quot;_blank&quot;&gt;Spring Data&lt;/a&gt; (не JPA. Не ставьте стартер JPA, он будет просить драйверы для Hibernate!), делаем всё как в ридми кроме сущности. Если вы сделали простой ключ (только ключ партицирования), то делайте как в ридми; если композитный (партицирование + сортировка), то тут чуть запарнее: видите ли, под капотом там всё равно используются самые обычные аннотации вроде @Entity и @Id, и именно последний ставит палки в колёса: он-то может быть только один, а ключей у нас два...&lt;/p&gt;
  &lt;p id=&quot;poo1&quot;&gt;Поэтому нам надо создать абстракцию, которая будет нашим айдишником. Выглядит это примерно так (с использованием Lombok):&lt;/p&gt;
  &lt;pre id=&quot;UpQz&quot; data-lang=&quot;java&quot;&gt;// ShowId.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ShowId implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    @DynamoDBHashKey
    @DynamoDBAutoGeneratedKey
    private String showId;

    @DynamoDBRangeKey
    @DynamoDBTypeConverted(converter = ZonedDateTimeToStringConverter.class)
    private ZonedDateTime addedAt;
}


// Show.java
@Getter
@Setter
@DynamoDBTable(tableName = &amp;quot;shows&amp;quot;)
public class Show {
    @Id
    // Если тут будут геттеры то SDK
    // подавится и упадёт, даже с @Transient.
    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    @Transient
    public transient ShowId _showId = new ShowId();

    @DynamoDBHashKey
    private String showId;

    public String getShowId() {
        return _showId.getShowId();
    }

    public void setShowId(String showId) {
        _showId.setShowId(showId);
    }
    
    @DynamoDBRangeKey
    @DynamoDBTypeConverted(converter = ZonedDateTimeToStringConverter.class)
    private ZonedDateTime addedAt;

    public ZonedDateTime getAddedAt() {
        return _showId.getAddedAt();
    }

    public void setAddedAt(ZonedDateTime addedAt) {
        _showId.setAddedAt(addedAt);
    }

    @DynamoDBAttribute
    private String name;

    @DynamoDBAttribute
    private String description;
    
    @DynamoDBAttribute
    private Integer episodes;
}


// ZonedDateTimeToStringConverter.java
public class ZonedDateTimeToStringConverter implements DynamoDBTypeConverter&amp;lt;String, ZonedDateTime&amp;gt; {
    @Override
    public String convert(ZonedDateTime zonedDateTime) {
        return zonedDateTime.toString();
    }

    @Override
    public ZonedDateTime unconvert(String s) {
        return ZonedDateTime.parse(s);
    }
}
&lt;/pre&gt;
  &lt;p id=&quot;od7H&quot;&gt;Да, как можно заметить, SDK амазона до сих пор не умеет конвертировать новые классы даты в строки (а вот старые — умеет).&lt;/p&gt;
  &lt;p id=&quot;OMsk&quot;&gt;Ах да, в конфигурации DynamoDB бин создания БД должен выглядеть как-то так (endpoint так же, как указано в админке яндекса, регион тоже тот, который выбирали (i.e. &lt;code&gt;ru-central1&lt;/code&gt;))&lt;/p&gt;
  &lt;pre id=&quot;OjpD&quot; data-lang=&quot;java&quot;&gt;@Bean
public AmazonDynamoDB amazonDynamoDB() {
    return AmazonDynamoDBClientBuilder.standard()
        .withCredentials(new AWSStaticCredentialsProvider(
            new BasicAWSCredentials(dynamoDbAccessKey, dynamoDbSecretKey)
        ))
        .withEndpointConfiguration(
            new AwsClientBuilder.EndpointConfiguration(dynamoDbEndpoint, dynamoDbRegion))
        .build();
}&lt;/pre&gt;
  &lt;p id=&quot;87Yb&quot;&gt;Кстати, видите Access Key и Secret Key? В админке YC это называется &amp;quot;Статический ключ доступа&amp;quot;. А говорю я это потому, что в YC &lt;strong&gt;&lt;u&gt;ШЕСТЬ СПОСОБОВ АВТОРИЗАЦИИ!&lt;/u&gt;&lt;/strong&gt; Я сразу оставлю &lt;a href=&quot;https://cloud.yandex.ru/docs/iam/concepts/authorization/&quot; target=&quot;_blank&quot;&gt;ссылку&lt;/a&gt; на документацию, она вам понадобится. Помните про велосипеды? Вот то-то и оно...&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;M0C7&quot;&gt;На самом деле, использования DynamoDB был значительно менее болезненным, однако я всё равно в шоке, что SDK амазона тоже ничего не умеет. Казалось бы, AWS — это с огромным отрывом лидер в облачных технологиях, но нет, если бы не коммьюнити — пришлось бы писать на встроенном маппере... чуть лучше JDBC, но приятного мало.&lt;/p&gt;
  &lt;h2 id=&quot;2Sxg&quot;&gt;Вместо вывода&lt;/h2&gt;
  &lt;p id=&quot;jowi&quot;&gt;На то чтобы написать простой CRUD ушло просто непозволительно много времени, пусть и частично из-за моего упёрства и перфекционизма, но мы что, варвары, писать на JDBC без причины в 2023 году?&lt;/p&gt;
  &lt;p id=&quot;hFoS&quot;&gt;Я не хочу сказать, что Яндекс Облако — плохой сервис, как раз наоброт: это удобный сервис, и единственный, который работает в т.ч. по формату B2C, потому что все остальные serverless решения в России работают почти исключительно в формате B2B.&lt;/p&gt;
  &lt;p id=&quot;9KJM&quot;&gt;Тем не менее, желание Яндекса переизобретать велосипеды, которые до него изобретали уже десятки корпораций, которые уже устоялись и стандартизировались, накорню рубит удобство использования их продуктов, и это очень печально. Яндекс запустил YDB в облаке уже давно (а год назад его ещё и открыл в опенсорс), однако не позаботился о написании нормального SDK, как это сделали амазон — их SDK тоже не фонтан, но это всё равно не идёт ни в какое сравнение с тем, что на общее обозрение выставил яндекс.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;gVEE&quot; data-align=&quot;center&quot;&gt;Не изобретайте велосипеды, пожалуйста. Придерживайтесь стандартов индустрии.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;6rJh&quot;&gt;Стандарты позволяют чуть-чуть упростить разработку, потому что сейчас:&lt;/p&gt;
  &lt;ul id=&quot;D7Gu&quot;&gt;
    &lt;li id=&quot;Dg63&quot;&gt;Разработка под облака — это больно.&lt;/li&gt;
    &lt;li id=&quot;y6EJ&quot;&gt;Деплой под облака — это больно.&lt;/li&gt;
    &lt;li id=&quot;Jhfz&quot;&gt;Интеграция с облаками — это больно.&lt;/li&gt;
    &lt;li id=&quot;YypS&quot;&gt;Вообще любое соприкосновение с облаками — это больно.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;k0J6&quot;&gt;Давайте сделаем их чуточку удобнее, ладно?&lt;/p&gt;

</content></entry><entry><id>widowan:domain-only-infrastructure</id><link rel="alternate" type="text/html" href="https://blog.wido.dev/domain-only-infrastructure?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=widowan"></link><title>Обживаемся в интернете с одним только доменом не платя ни копейки за хостинг</title><published>2022-11-30T14:12:34.160Z</published><updated>2022-11-30T14:42:07.790Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/f5/68/f568ac2f-0430-4f78-bb08-36a6266bb747.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/86/94/86941a29-a1c9-4485-8126-375b872aa18f.png&quot;&gt;Возможно вам захотелось свой сайт. Или блог. Или просто почту на своём домене, чтобы красиво. А возможно вас пугает перспектива мигрировать все свои аккаунты на новую почту. Не суть важно — поехали делать комбайн с одним только доменом без хостинга!</summary><content type="html">
  &lt;p id=&quot;LcO8&quot;&gt;Возможно вам захотелось свой сайт. Или блог. Или просто почту на своём домене, чтобы красиво. А возможно вас пугает перспектива мигрировать все свои аккаунты на новую почту. Не суть важно — поехали делать комбайн с одним только доменом без хостинга!&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;p99p&quot;&gt;Примечание: эта заметка не имеет ультра подробных инструкций и в целом рассчитана на людей, которые знают, в чём разница между git и GitHub и как примерно работает DNS.&lt;/p&gt;
  &lt;/section&gt;
  &lt;nav&gt;
    &lt;ul&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#d6qd&quot;&gt;Обзаводимся доменом&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#YN1f&quot;&gt;Получаем домен бесплатно&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#WmI8&quot;&gt;Переносим домен на DNS Cloudflare&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#AiD7&quot;&gt;Настраиваем почту&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#kezr&quot;&gt;Настройка получения писем (inbound)&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#Y2I7&quot;&gt;Настройка отправки писем (outbound)&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#clG6&quot;&gt;Настраиваем сайт&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#sUGt&quot;&gt;Выбор темы для Jekyll&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#Y5BR&quot;&gt;Развёртывание и настройка темы&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#l8Fe&quot;&gt;Публикация сайта&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#2rB1&quot;&gt;Если вам нужен только блог на главной странице без возни с Jekyll&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#msBt&quot;&gt;Бонус: верификация домена на GitHub&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#5esy&quot;&gt;Бонус: итоговый вид DNS таблицы&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#DOmD&quot;&gt;Домашнее задание: облака и serverless&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/nav&gt;
  &lt;h2 id=&quot;d6qd&quot;&gt;Обзаводимся доменом&lt;/h2&gt;
  &lt;p id=&quot;Fbms&quot;&gt;Домен можно либо купить за 150-1500 рублей в год (смотря в какой зоне; .ru самые дешёвые), либо получить бесплатно — в зонах .tk, .ml, .ga, .cf, и .gq (конечно, со звёздочкой).&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;ayn8&quot;&gt;Если вы собираетесь покупать домен за деньги, то советую обратить внимание на менее популярных регистраторов вроде &lt;a href=&quot;http://fozzy.ru&quot; target=&quot;_blank&quot;&gt;Fozzy&lt;/a&gt;, &lt;a href=&quot;https://timeweb.com&quot; target=&quot;_blank&quot;&gt;Timeweb&lt;/a&gt;, &lt;a href=&quot;https://beget.com&quot; target=&quot;_blank&quot;&gt;Beget&lt;/a&gt; и других — у них обычно более низкие и прозрачные цены и лучше поддержка.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;YN1f&quot;&gt;Получаем домен бесплатно&lt;/h3&gt;
  &lt;p id=&quot;vQlO&quot;&gt;Бесплатные доменные имена в зонах .tk, .ml, .ga, .cf, и .gq выдаёт регистратор &lt;a href=&quot;https://freenom.com/&quot; target=&quot;_blank&quot;&gt;Freenom&lt;/a&gt;. Но конечно всем нам очевидно, что бесплатный сыр только в мышеловке, за сами следующие предупреждения:&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;VhBN&quot;&gt;Freenom, согласно пользовательскому соглашению и отзывам, может сделать продление вашего домена платным, если он станет достаточно популярным. Не критично для личных сайтов визиток, но стоит держать это в уме — регистратор очень сомнительного качества (зато бесплатно).&lt;/p&gt;
  &lt;/section&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;H0fu&quot;&gt;Бесплатные домены привлекают спамеров, поэтому настоятельно не рекомендую регистрировать такой, если вам хочется свою собственную почту: письма с таких адресов очень часто попадают в спам или напрямую блокируются администраторами, поскольку &lt;a href=&quot;https://www.spamhaus.org/statistics/tlds/&quot; target=&quot;_blank&quot;&gt;являются лидерами&lt;/a&gt; по количеству спама.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;WmI8&quot;&gt;Переносим домен на DNS Cloudflare&lt;/h2&gt;
  &lt;p id=&quot;23rl&quot;&gt;После этого стоит перенести DNS хостинг домена к Cloudflare, поскольку а) мы воспользуемся сервисом Cloudflare для настройки почты, б) он объективно один из лучших.&lt;/p&gt;
  &lt;p id=&quot;qLiD&quot;&gt;Для этого необходимо сделать следующее:&lt;/p&gt;
  &lt;ol id=&quot;hHOy&quot;&gt;
    &lt;li id=&quot;jwoG&quot;&gt;Зарегистрироваться на Cloudflare.com&lt;/li&gt;
    &lt;li id=&quot;WyxR&quot;&gt;В разделе Websites добавить свой сайт&lt;/li&gt;
    &lt;li id=&quot;ymz3&quot;&gt;После этого вам выдадут два адреса, которые нужно прописать в раздел nameservers у регистратора вашего домена вместо стандартных.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;UtPJ&quot;&gt;Обновление именных серверов вашего сайта обычно занимает до нескольких часов (хотя иногда всего пару минут).&lt;/p&gt;
    &lt;p id=&quot;ygTT&quot;&gt;Проверить текущий статус можно командой &lt;code&gt;dig&lt;/code&gt; (или похожими веб сервисами): &lt;code&gt;dig example.com NS +noall +answer&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;AdNw&quot;&gt;Пока вы ждёте переноса — можете ознакомиться сервисами, которые есть в бесплатном тарифе Cloudflare. Чем сильнее вам нравится делать разные крутые штуки в программировании и около, тем сильнее у вас загорятся глаза :)&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;AiD7&quot;&gt;Настраиваем почту&lt;/h2&gt;
  &lt;h3 id=&quot;kezr&quot;&gt;Настройка &lt;strong&gt;получения писем (inbound)&lt;/strong&gt;&lt;/h3&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;bm4B&quot;&gt;Настраивать почту мы будет пересылкой на другой ящик, поэтому вам не придётся менять уже и так удобный вам интерфейс и сервис, и переносить адрес вы сможете плавно; но также это означает, что вам нужно найти устраивающего вас провайдера почты.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;fYTl&quot;&gt;Для пересылки писем на наш адрес мы будем использовать Cloudflare:&lt;/p&gt;
  &lt;ol id=&quot;QlGo&quot;&gt;
    &lt;li id=&quot;2Aq6&quot;&gt;Открываем наш сайт в личном кабинете cloudflare&lt;/li&gt;
    &lt;li id=&quot;CBDu&quot;&gt;Выбираем раздел Email → Email Routing&lt;/li&gt;
    &lt;li id=&quot;9HZ2&quot;&gt;Cloudflare предложит нам автоматически добавить все необходимые DNS записи. Соглашаемся.&lt;/li&gt;
    &lt;li id=&quot;VWKF&quot;&gt;Выбираем тип пересылки, который нам нужен:&lt;/li&gt;
    &lt;ol id=&quot;QbfH&quot;&gt;
      &lt;li id=&quot;9vOf&quot;&gt;&lt;strong&gt;Кастомные адреса:&lt;/strong&gt; пересылка только с конкретных адресов, которые вы настроите, например &lt;code&gt;contact@example.com&lt;/code&gt; или &lt;code&gt;admin@example.com&lt;/code&gt;, остальные игнорируются.&lt;/li&gt;
      &lt;li id=&quot;y58C&quot;&gt;&lt;strong&gt;Catch-all:&lt;/strong&gt; пересылаются любые адреса, например &lt;code&gt;asdfgh@example.com&lt;/code&gt; или &lt;code&gt;github@example.com&lt;/code&gt; (рекомендую именно этот тип, вы сможете регистрироваться на любых сайтах адресом с названием сайта).&lt;/li&gt;
    &lt;/ol&gt;
    &lt;li id=&quot;tErW&quot;&gt;Выбираем адрес куда пересылается письмо.&lt;/li&gt;
    &lt;li id=&quot;H1JJ&quot;&gt;Всё, вы восхитительны!&lt;/li&gt;
  &lt;/ol&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;TqCT&quot;&gt;Хорошей альтернативой пересылке от Cloudflare может стать сервис forwardemail.net, но в нём пересылка настраивается через DNS TXT запись вида &lt;code&gt;forward-email=user@gmail.com&lt;/code&gt;, что раскрывает целевой ящик кому угодно, кто решит проверить DNS записи вашего домена: (. В остальном сервис отличный!&lt;/p&gt;
  &lt;/section&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;IuY4&quot;&gt;&lt;strong&gt;Настройка почтового клиента на входящие письма&lt;/strong&gt; (если вы пользуетесь отдельным приложением, а не вашей почтой в браузере): согласно вашему провайдеру почты. Искренне сочувствую пользователям Protonmail и Tutanota, где не поддерживается IMAP (то есть самому себе).&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;Y2I7&quot;&gt;Настройка отправки писем (outbound)&lt;/h3&gt;
  &lt;p id=&quot;n2rY&quot;&gt;С отправкой всё несколько сложнее, потому что получать репутацию спам сервиса никто не хочет, а спамеры будут использовать любые возможности, поэтому этим мало кто занимается. Согласно &lt;a href=&quot;https://www.statista.com/statistics/420400/spam-email-traffic-share-annual/&quot; target=&quot;_blank&quot;&gt;статистике&lt;/a&gt;, 40% всего трафика электронной почты это спам (а раньше было 80%).&lt;/p&gt;
  &lt;p id=&quot;D0j1&quot;&gt;С отправкой писем нам поможет сервис &lt;a href=&quot;https://www.smtp2go.com/&quot; target=&quot;_blank&quot;&gt;SMTP2GO&lt;/a&gt;. В бесплатном тарифе он предлагает 1000 писем в месяц, чего для личного пользования хватит с головой. &lt;strong&gt;Сервис хранит отправленные письма в течение пяти дней.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;5SQ8&quot;&gt;Всё достаточно тривиально:&lt;/p&gt;
  &lt;ol id=&quot;zhaP&quot;&gt;
    &lt;li id=&quot;5CjE&quot;&gt;Регистрируемся и заходим в личный кабинет&lt;/li&gt;
    &lt;li id=&quot;MRjV&quot;&gt;Добавляем адрес сайта&lt;/li&gt;
    &lt;li id=&quot;k5zt&quot;&gt;Подтверждаем добавлением указанных DNS записей в админке Cloudflare&lt;/li&gt;
    &lt;li id=&quot;UMmX&quot;&gt;Создаем нового пользователя SMTP в разделе &lt;em&gt;SMTP Users&lt;/em&gt;.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;section style=&quot;background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;DPs5&quot;&gt;При добавлении DNS записей в профиле smtp2go можно изменить имена поддоменов, например на smtp.example.com, etc.&lt;/p&gt;
  &lt;/section&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;urto&quot;&gt;Вместо использования стандартных SPF/DKIM/DMARC проверок, &lt;a href=&quot;https://www.smtp2go.com/blog/spf-record/&quot; target=&quot;_blank&quot;&gt;SMTP2GO использует&lt;/a&gt; технику &lt;a href=&quot;https://en.wikipedia.org/wiki/Variable_envelope_return_path&quot; target=&quot;_blank&quot;&gt;VERP&lt;/a&gt;, поэтому вам не нужно обновлять SPF записи и т. д., вам нужна только CNAME запись на return.smtp2go.com, которую вы уже добавили выше.&lt;/p&gt;
  &lt;/section&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;pvsu&quot;&gt;Я также пробовал зарегистрироваться в сервисе Sendgrid, однако он требует полной верификации для использования (ФИО + Телефон) и не рассчитан для личного использования — он также требует подтверждения компании через соцсети или иным способом.&lt;/p&gt;
  &lt;/section&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;5m3m&quot;&gt;&lt;strong&gt;Настройка почтового клиента на исходящие письма (для всех):&lt;/strong&gt; &lt;a href=&quot;https://www.smtp2go.com/setupguide/&quot; target=&quot;_blank&quot;&gt;тут для любого софта&lt;/a&gt; / &lt;a href=&quot;https://www.smtp2go.com/setupguide/thunderbird/&quot; target=&quot;_blank&quot;&gt;конкретно для Thunderbird&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;isGp&quot;&gt;Всё, отправляйте тестовое письмо! &lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;clG6&quot;&gt;Настраиваем сайт&lt;/h2&gt;
  &lt;p id=&quot;8x3i&quot;&gt;Если вам нужен статический сайт (визитка, блог, etc), то проще всего использовать Jekyll и GitHub Pages, что и будет описано дальше.&lt;/p&gt;
  &lt;p id=&quot;VUn0&quot;&gt;Блог, например, можно сделать с помощью всё того же Jekyll (если тема такое поддерживает) или Hugo, либо с помощью сервисов вроде Teletype (где вы и читаете эту статью).&lt;/p&gt;
  &lt;p id=&quot;AFGE&quot;&gt;Я выбрал второе из-за WYSIWYG редактора и отсутствия необходимости каждый раз добавлять статьи в качестве Markdown файлов на гитхаб и встроенных комментариев.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;vXpj&quot;&gt;Хочу обратить ваше внимание, что существует достаточно большое количество сервисов, которые позволяют встраивать динамические элементы со своих серверов, например Commento.io для комментариев или полноценных Headless CMS систем.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;sUGt&quot;&gt;Выбор темы для Jekyll&lt;/h3&gt;
  &lt;p id=&quot;9swD&quot;&gt;Для начала необходимо выбрать тему для Jekyll. Традиционно GitHub Pages поддерживал только минимальное количество тем, которые ещё и плохо настраивались.&lt;/p&gt;
  &lt;p id=&quot;k5sb&quot;&gt;Но с недавних пор там появилась поддержка развёртывания сайтов с помощью GitHub Actions, из-за сайты теперь можно делать на чём угодно, а для Jekyll выбирать какие угодно темы. Собственно подобрать их можно, например, тут:&lt;/p&gt;
  &lt;ul id=&quot;Z2mY&quot;&gt;
    &lt;li id=&quot;2kWe&quot;&gt;&lt;a href=&quot;https://jamstackthemes.dev&quot; target=&quot;_blank&quot;&gt;jamstackthemes.dev&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;xgO3&quot;&gt;&lt;a href=&quot;http://jekyll-themes.com/free/&quot; target=&quot;_blank&quot;&gt;jekyll-themes.com&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;Tsi3&quot;&gt;&lt;a href=&quot;http://jekyllthemes.org&quot; target=&quot;_blank&quot;&gt;jekyllthemes.org&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Zw3s&quot;&gt;На моём сайте — &lt;a href=&quot;https://wido.dev&quot; target=&quot;_blank&quot;&gt;wido.dev&lt;/a&gt; — стоит тема &lt;a href=&quot;https://github.com/BDHU/minimalist/&quot; target=&quot;_blank&quot;&gt;Minimalist&lt;/a&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;Y5BR&quot;&gt;Развёртывание и настройка темы&lt;/h3&gt;
  &lt;p id=&quot;xBIl&quot;&gt;С настройкой темы у всех всё индивидуально, поэтому обращайтесь к документации выбранной темы. А вот развёртывание у всех примерно одинаковое: &lt;/p&gt;
  &lt;ol id=&quot;wLmr&quot;&gt;
    &lt;li id=&quot;J7rg&quot;&gt;Ставим Ruby и Bundle (если он у вас не идёт вместе с Ruby)&lt;/li&gt;
    &lt;li id=&quot;qnT3&quot;&gt;Скачиваем нужную тему, переходим в её директорию&lt;/li&gt;
    &lt;li id=&quot;S4dq&quot;&gt;&lt;code&gt;bundle config set --local path &amp;quot;vendor/bundle&amp;quot;&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;48vg&quot;&gt;&lt;code&gt;bundle install&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;Qm6d&quot;&gt;&lt;code&gt;bundle exec jekyll serve&lt;/code&gt; — учтите, что он автоматически перезагружает контент сайта, но не &lt;code&gt;_config.yml&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;BkIm&quot;&gt;Играйтесь, настраивайте&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;l8Fe&quot;&gt;Публикация сайта&lt;/h3&gt;
  &lt;p id=&quot;80F5&quot;&gt;Для публикации сайта на GitHub Pages вам необходимо:&lt;/p&gt;
  &lt;ol id=&quot;c6vu&quot;&gt;
    &lt;li id=&quot;zSH5&quot;&gt;Создать репозиторий для сайта (если у вас есть GitHub Pro — можно приватный, иначе — публичный) и залить туда результаты вашей настройки&lt;/li&gt;
    &lt;li id=&quot;dtkc&quot;&gt;Перейти в настройки → Pages и в разделе Source выбрать GitHub Actions&lt;/li&gt;
    &lt;li id=&quot;ovfn&quot;&gt;Выбрать Jekyll (&lt;strong&gt;НЕ «GitHub Pages Jekyll»&lt;/strong&gt;) среди списка всех Workflow&lt;/li&gt;
    &lt;li id=&quot;Tiwv&quot;&gt;В разделе &lt;code&gt;Jobs -&amp;gt; build -&amp;gt; &amp;quot;Build with Jekyll&amp;quot;&lt;/code&gt; YAML файла, который нам предложит отредактировать GitHub, убрать &lt;code&gt;--baseurl &amp;quot;${{ steps.pages.outputs.base_path }}&amp;quot;&lt;/code&gt;&lt;br /&gt;То есть должно остаться только: &lt;code&gt;run: bundle exec jekyll build&lt;/code&gt;&lt;br /&gt;(этот шаг нужен чтобы разместить сайт на корне вашего домена, а не example.com/repositoryname)&lt;/li&gt;
    &lt;li id=&quot;yDHv&quot;&gt;Вернуться в настройки Pages и прописать свой домен (можно поставить галочку Enforce HTTPS)&lt;/li&gt;
    &lt;li id=&quot;ZXO4&quot;&gt;Войти в Cloudflare и прописать следующие настройки DNS (на всякий случай актуальные адреса &lt;a href=&quot;https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-an-apex-domain&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;), &lt;strong&gt;не забудьте снять галочку Proxy&lt;/strong&gt;:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;DNXF&quot;&gt;Тип записи | Имя | Адрес
A             @    185.199.108.153
A             @    185.199.109.153
A             @    185.199.110.153
A             @    185.199.111.153
AAAA          @    2606:50c0:8000::153
AAAA          @    2606:50c0:8001::153
AAAA          @    2606:50c0:8002::153
AAAA          @    2606:50c0:8003::153
CNAME        www   your-username.github.io&lt;/pre&gt;
  &lt;p id=&quot;vBOP&quot;&gt;Всё, ждите обновления DNS записей.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;2rB1&quot;&gt;Если вам нужен только блог на главной странице без возни с Jekyll&lt;/h3&gt;
  &lt;p id=&quot;WtfU&quot;&gt;В случае Teletype это делается достаточно просто: создаём блог, в его настройках указываем ваш домен и нужную DNS CNAME запись:&lt;/p&gt;
  &lt;ul id=&quot;Rl2n&quot;&gt;
    &lt;li id=&quot;cRUC&quot;&gt;Если вам нужно, чтобы ваш блог отображался на главной странице:&lt;br /&gt;&lt;code&gt;CNAME @ domains.teletype.in&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;PIfz&quot;&gt;Если по поддомену (например blog.example.com):&lt;br /&gt;&lt;code&gt;CNAME blog domains.teletype.in&lt;/code&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;j7Kp&quot;&gt;И нажимаем кнопочку подтверждения после обновления. Всё!&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;RTB9&quot;&gt;Из альтернатив есть ещё &lt;a href=&quot;https://hashnode.com/&quot; target=&quot;_blank&quot;&gt;Hashnode&lt;/a&gt;, но мне не нравится насколько он агрессивно рекламирует себя (как сервис) и как он выглядит. На эту тему даже есть статья: &lt;a href=&quot;https://coffee-web.ru/blog/free-hashnode-custom-domains-are-designed-to-do-one-thing-advertise-hashnode/&quot; target=&quot;_blank&quot;&gt;«Бесплатные» пользовательские домены Hashnode предназначены для одной цели: рекламы Hashnode&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;msBt&quot;&gt;Бонус: верификация домена на GitHub&lt;/h2&gt;
  &lt;p id=&quot;LDeE&quot;&gt;Зайдите в настройки своего профиля GitHub, раздел Pages, укажите адрес своего сайта и добавьте указанную DNS TXT запись в Cloudflare. Подождите обновления, нажмите кнопочку Verify.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;t4NR&quot;&gt;Проверить состояние DNS записи можно так:&lt;/p&gt;
    &lt;p id=&quot;6MKD&quot;&gt;&lt;code&gt;dig _github-pages-challenge-YourUsername.example.com +noall +answer TXT&lt;/code&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;5esy&quot;&gt;Бонус: итоговый вид DNS таблицы&lt;/h2&gt;
  &lt;figure id=&quot;OQr8&quot; class=&quot;m_original&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/86/94/86941a29-a1c9-4485-8126-375b872aa18f.png&quot; width=&quot;875&quot; /&gt;
    &lt;figcaption&gt;Итоговая таблица в админ-панели Cloudflare&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;DOmD&quot;&gt;Домашнее задание: облака и serverless&lt;/h2&gt;
  &lt;p id=&quot;e6B0&quot;&gt;Если вы хотите сделать что-то динамическое, то советую глянуть на Yandex Cloud и Cloudflare R2. У первого очень неплохие бесплатные лимиты на Serverless вычисления (Yandex Cloud Function), а R2 предоставляет чудовищные &lt;strong&gt;10 гигабайт&lt;/strong&gt; хранилища в месяц бесплатно! &lt;/p&gt;
  &lt;p id=&quot;0fVq&quot;&gt;Это уже совершенно другая тема, но потенциал, как видите, огромный :)&lt;/p&gt;
  &lt;p id=&quot;UwOj&quot;&gt;Если вам нужно что-то попроще, то можно использовать &lt;a href=&quot;https://pipedream.com&quot; target=&quot;_blank&quot;&gt;pipedream.com&lt;/a&gt; — это IFTTT на стероидах с вебхуками.&lt;/p&gt;
  &lt;p id=&quot;RgAR&quot;&gt;Удачи!&lt;/p&gt;

</content></entry><entry><id>widowan:Review-The-Fruit-of-Grisaia</id><link rel="alternate" type="text/html" href="https://blog.wido.dev/Review-The-Fruit-of-Grisaia?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=widowan"></link><title>The Fruit of Grisaia — обзор визуальной новеллы</title><published>2022-11-26T08:13:40.022Z</published><updated>2022-11-30T14:46:36.881Z</updated><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/c3/4e/c34e486c-d31c-42fc-aabb-7fcb3f08d038.jpeg&quot;&gt;Очень сложно подвести какую-то черту после новелл подобного масштаба. Всё таки не моэге на один вечер — согласно VNDB её длительность без малого 75 часов, и это только первая часть, а ведь есть ещё два сиквела (на самом деле нет, но об этом в следующий раз). Тем не менее, пусть и последний раз я писал развёрнутые отзывы очень давно и не в таких масштабах, я попробую. Обзор содержит небольшие спойлеры к первой паре часов игры, ибо без них тяжко.</summary><content type="html">
  &lt;p id=&quot;OmdO&quot;&gt;Очень сложно подвести какую-то черту после новелл подобного масштаба. Всё таки не моэге на один вечер — согласно &lt;a href=&quot;https://vndb.org/v5154&quot; target=&quot;_blank&quot;&gt;VNDB&lt;/a&gt; её длительность без малого 75 часов, и это только первая часть, а ведь есть ещё два сиквела (на самом деле нет, но об этом в следующий раз). Тем не менее, пусть и последний раз я писал развёрнутые отзывы очень давно и не в таких масштабах, я попробую. Обзор содержит небольшие спойлеры к первой паре часов игры, ибо без них тяжко.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;h3fh&quot;&gt;Если вы откроете страничку игры на VNDB — я настоятельно советую выключить даже minor spoilers!&lt;/p&gt;
  &lt;/section&gt;
  &lt;figure id=&quot;UZMV&quot; class=&quot;m_retina&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c3/4e/c34e486c-d31c-42fc-aabb-7fcb3f08d038.jpeg&quot; width=&quot;500&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;6Neo&quot;&gt;Синопсис&lt;/h2&gt;
  &lt;p id=&quot;dEYg&quot;&gt;Главный герой, Казами Юджи, не старше двадцати лет отроду, работает на секретную правительственную организацию, которая занимается &amp;quot;устранением&amp;quot; неугодных государству людей, однако к моменту начала новеллы он решает подать в отставку и перевести в обычные школу в поисках нормальной жизни. Однако человеку военному не то чтобы есть место среди обычных студентов, поэтому и поступает он по знакомству в специальную школу &amp;quot;для детей с особыми обстоятельствами&amp;quot;. Во всей школе всего пять студентов, и все - главные героини, конечно же.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;t5xp&quot;&gt;Тут стоит отметить, что новелла вышла в 2011 году, и пусть местами это заметно, но в общем и целом — она ничуть не устарела &lt;s&gt;(кроме разрешения 576p)&lt;/s&gt;, ведь уже на момент выхода деконструировала свои клише и из-за этого до сих пор выглядит свежо. Поэтому не дайте касту вас отпугнуть:&lt;/p&gt;
  &lt;h2 id=&quot;zFIw&quot;&gt;Каст&lt;/h2&gt;
  &lt;p id=&quot;jbez&quot;&gt;Итак, у нас есть:&lt;/p&gt;
  &lt;figure id=&quot;K8lh&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://s2.vndb.org/ch/63/76363.jpg&quot; width=&quot;250&quot; /&gt;
    &lt;figcaption&gt;Ирису Макина&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3E1m&quot;&gt;Ирису Макина — неугомонная и гиперактивная лоля, которая боится окружающих и ищет заботу и внимание в людях вокруг себя. Иногда может вести себя глуповато, хотя и понятно, что она скорее просто не хочет взрослеть.&lt;/p&gt;
  &lt;figure id=&quot;i2xO&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://s2.vndb.org/ch/64/76364.jpg&quot; width=&quot;250&quot; /&gt;
    &lt;figcaption&gt;Комине Сачи&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;TFDT&quot;&gt;Комине Сачи — вечно ходящая в униформе горничной староста класса и (почти) всегда серьёзная, выполнит любой (&lt;em&gt;вообще любой&lt;/em&gt;) приказ как данное, даже не задумываясь, из-за чего является опорой всех остальных студентов, которые уже разучились жить без её помощи.&lt;/p&gt;
  &lt;figure id=&quot;psPr&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://s2.vndb.org/ch/65/76365.jpg&quot; width=&quot;250&quot; /&gt;
    &lt;figcaption&gt;Матсушима Мичиру&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;qlh1&quot;&gt;Матсушима Мичиру — перекрашенная блондинка с хвостиками, которая очень-очень упорно пытается играть роль цундере, однако получается только роль шута. Может быть на удивление тактичной и читать атмосферу, пытаясь разрядить её своим шутовством.&lt;/p&gt;
  &lt;figure id=&quot;9Bb0&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://s2.vndb.org/ch/66/76366.jpg&quot; width=&quot;250&quot; /&gt;
    &lt;figcaption&gt;Сакаки Юмико&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CwI5&quot;&gt;Сакаки Юмико — дочь владельца академии (и по совместительству первый студент) с проблемами с социализацией. Постоянно скрывает свои эмоции берясь за канцелярский нож и кидаясь (не по-настоящему) на всех подряд, в том числе встречая так главного героя после первой попытки заговорить.&lt;/p&gt;
  &lt;figure id=&quot;vA2Y&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://s2.vndb.org/ch/67/76367.jpg&quot; width=&quot;250&quot; /&gt;
    &lt;figcaption&gt;Суо Амане&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;sM4F&quot;&gt;Суо Амане — очень высокая девушка, которая играет роль старшей сестры (или даже матери) для всех в общежитии, заботится о Макине и сразу проявляет интерес к главному герою, не стесняясь бравировать своим телом, за что давно получила много прозвищ.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2 id=&quot;KtQ1&quot;&gt;О чем игра на самом деле&lt;/h2&gt;
  &lt;p id=&quot;N9Vl&quot;&gt;Вы прочитали описание каста выше? Заметили, что оно какое-то маленькое и невзрачное? На это есть причина — вы можете забыть сразу же всё то, о чем я написал выше. Нет, вся информация там как бы легитимна, всё так, однако это очень однобокое описание персонажей, и даже если вы прочитаете чуть более полную версию на VNDB — ситуацию это не изменит. &lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;QdZm&quot;&gt;На самом деле вся игра — она про персонажей, их личности и прошлое.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;AKGL&quot;&gt;Помните в синопсисе фразу &amp;quot;школа для людей с особенными обстоятельствами&amp;quot;? Вот этим обстоятельствам на самом деле и посвящена вся игра, и пусть в ней нет претензии на философичность или истинное познание жизни, сценаристы явно пытались вложить в неё очень многое. &lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;pErJ&quot;&gt;Персонажи в игре — очень, очень комплексные и описать их одним или даже несколькими абзацами невозможно.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;snLc&quot;&gt;В самой игре есть метафора о характерах людей, по памяти — примерно такая:&lt;/p&gt;
  &lt;blockquote id=&quot;35DC&quot;&gt;Люди похожи на геометрические фигуры. Изначально их личность похожа на куб, но чем больше они сталкиваются с другими людьми, проблемами и обществом, тем больше их углы тупятся скругляются, в идеале в конце превращаясь в шар. Однако, если столкновение будет слишком сильным — это может оставить непоправимые вмятины на личности человека.&lt;/blockquote&gt;
  &lt;p id=&quot;FDdf&quot;&gt;Я не уверен, насколько эта мысль применима к реальности, однако она очень хорошая применима к самой игре:&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;7oQi&quot;&gt;У всех персонажей очень тяжёлое прошлое и огромные проблемы, и в первую очередь это касается протагониста.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;APWj&quot;&gt;Многие люди уже привыкли к тому, что протагонист в новеллах является пустышкой для ассоциации игрока с ним и своего рода &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9C%D1%8D%D1%80%D0%B8_%D0%A1%D1%8C%D1%8E&quot; target=&quot;_blank&quot;&gt;Мэри Сью&lt;/a&gt;, однако в данном случае Юджи — самый главный персонаж всей истории. Его прошлое и характер раскрывается нам по крупицам в каждом руте, и мы только к концу первого рута будем иметь относительно полное представление о том, что он за человек.&lt;/p&gt;
  &lt;h3 id=&quot;kOKc&quot;&gt;Повествование&lt;/h3&gt;
  &lt;p id=&quot;Wz0c&quot;&gt;Юджи, как было уже сказано, очень необычный для визуальных новелл протагонист. У него есть свой характер и личность, но при этом он не похож на эксцентричного Нариту Шинри из «Hello Lady», вся личность Юджи со всеми недостатками — это груз прошлого и его психологических проблем, в этой истории он учится социализироваться, как и все остальные героини. Это же, кстати, убирает вопрос &amp;quot;а почему это несколько девушек влюбились в обычного ОЯШа?&amp;quot; который может возникать в большинстве гаремников — всем героиням тут нехватает общества и внимания, а студентов парней до Юджи тут не было в принципе.&lt;/p&gt;
  &lt;p id=&quot;BKdZ&quot;&gt;Сложно назвать Юджи &amp;quot;гигачадом&amp;quot; как например упомянутого Нариту, но в сравнении с ним, персонажу Юджи, как ни странно, сильнее веришь, и симпатии он вызывает больше. Тем не менее, мы смотрим на историю почти исключительно от его лица, со всеми вытекающими. Он не твердолобый, скорее просто апатичный и холодный, и именно из-за этого он и поступил в эту академию.&lt;/p&gt;
  &lt;h3 id=&quot;Ef7P&quot;&gt;Жанр повествования&lt;/h3&gt;
  &lt;p id=&quot;bDMF&quot;&gt;Основная часть коммона — это обычная повседневная комедия (с необычной линзой), которая знакомит нас с персонажами. Причём что удивительно — комедия смешная. Химия между персонажами просто невероятная и это создаёт шутки на грани комедии абсурда, но как же это периодически разрывает... &lt;/p&gt;
  &lt;p id=&quot;Jvpn&quot;&gt;В принципе Грисая во время коммона оборачивает в геги почти любую ситуацию, даже серьёзную, вплоть до того что жажда мужского внимания у героинь высмеивается шутками об их мастурбации на протагониста.&lt;/p&gt;
  &lt;p id=&quot;MFwy&quot;&gt;Но при всём этом как только игрок выбирает конкретный рут — комедия достаточно быстро вымещается серьёзным повествованием, причём это может быть как флешбек на десять часов (да, правда, и он очень даже интересный), так и история о бегах и сражении с врагами в стиле Джона Уика.&lt;/p&gt;
  &lt;p id=&quot;ICdV&quot;&gt;Кстати, о моих словах о &amp;quot;&lt;em&gt;первом&amp;quot; руте...&lt;/em&gt;&lt;/p&gt;
  &lt;h2 id=&quot;s4A2&quot;&gt;Структура игры&lt;/h2&gt;
  &lt;p id=&quot;CzR5&quot;&gt;Не считая первых двух выборов, которые &lt;strong&gt;&lt;em&gt;почти&lt;/em&gt;&lt;/strong&gt; (pro tip: помогайте нуждающимся и не скупитесь на доброе слово) ни на что не влияют, новелла имеет лестничную структуру: это означает, что выход на конкретный рут определяется одним выбором, и в случае &amp;quot;неправильного&amp;quot; выбора, вам дадут следующий выбор уже на следующий же рут и так далее. Графически:&lt;/p&gt;
  &lt;pre id=&quot;wNek&quot;&gt;начало
   |-&amp;gt; первый рут
   |-&amp;gt; второй рут
   |-&amp;gt; третий рут
   |-&amp;gt; четвертый рут
   v
пятый рут&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;qFBN&quot;&gt;Я настоятельно рекомендую проходить руты в том порядке, в котором игра их вам предоставляет.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;B0cm&quot;&gt;Несмотря на свою монструозную длительность, я не могу сказать, что читать новеллу тяжело или что она затянута, за исключением нескольких сцен. Она написана на удивление целостно и внятно, повествование не скачет и в целом очень неохотно перескакивает с главного героя и только по необходимости. В своей сути она до последней запятой следует обычной структуре драмы из трёх актов... кроме рута Мичиру, но о нём чуть-чуть дальше.&lt;/p&gt;
  &lt;p id=&quot;yTwo&quot;&gt;В истории часто были ссылки и цитирования моментов из коммона или предыдущих рутов, что у внимательного читателя вызывает моменты радости в стиле &amp;quot;вот, я помню эту фразу, это было предвестие (foreshadowing)!&amp;quot;, что приятно и ещё больше доказывает грамотность сценаристов.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;aJBg&quot;&gt;Рут Мичиру — единственный неработающий рут в игре&lt;/h3&gt;
  &lt;p id=&quot;B8wa&quot;&gt;Это тот рут, который вызовет недоумевание примерно у всех. Не поймите превратно, сам персонаж многим очень нравится, но вот конкретно её рут — простите, ###нутый. Дело не в том, что рут плохой — он просто очень тупой по завязке и реализации и очень сильно структурно выделяется на фоне остальных. &lt;/p&gt;
  &lt;p id=&quot;jkYB&quot;&gt;Все остальные руты следуют структуре из трёх актов драматургии до последней запятой, в них можно примерно понять где ты находишься, сколько ещё осталось и что тебя ждёт; здесь - забудьте, он абсолютно хаотичный, сбивчивый сами происходящие в руте события его структурированию вообще не помогают.&lt;/p&gt;
  &lt;p id=&quot;udpf&quot;&gt;Опять же, рут не плохой — в нём есть пусть и немного дешёвые, но всё же очень сильные сцены и мысли, а развязка, если закрыть глаза на её общий тупизм, — одна из самых сильных сцен в игре. Но основная проблема рута — это противоречия самому себе и почти что паранормальная общая завязка, при том что вся остальная игра придерживается реализма, пусть и иногда такого, в который сложно поверить.&lt;/p&gt;
  &lt;p id=&quot;01Bo&quot;&gt;На все вопросы &amp;quot;почему так&amp;quot; есть достаточно простой ответ — рут доверили непрофессиональному сценаристу, который до этого в основном писал тексты песен и помогал со сценариями по мелочи — иными словами, банальная неопытность. Почему ему рут доверили — вопрос хороший, однако остающийся загадкой по сей день.&lt;/p&gt;
  &lt;h2 id=&quot;4Bxv&quot;&gt;Технические моменты&lt;/h2&gt;
  &lt;p id=&quot;9hqo&quot;&gt;Если вы зайдёте на VNDB и проспойлерите себе список персонажей, то спешу огорчить — в первой игре главными героинями будет только основная пятёрка и никого больше, да и в сиквелах ни у кого больше не будет &amp;quot;полноценного&amp;quot; рута, однако это разговор для следующего раза.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;KS3p&quot;&gt;На Linux / Steam Deck игра работает со скрипом — для запуска (через стим) необходимо выключить DRI3 и включить WINED3D9:&lt;/p&gt;
  &lt;pre id=&quot;82mr&quot;&gt;LIBGL_DRI3_DISABLE=1 PROTON_USE_WINED3D9=1 %command%&lt;/pre&gt;
  &lt;p id=&quot;67JE&quot;&gt;Для запуска не через стим (пиратки бишь) через gamescope (для повышения разрешения через FSR) нужно сделать примерно так (скриптом или каждый раз ручками - ваш выбор):&lt;/p&gt;
  &lt;pre id=&quot;iCzO&quot;&gt;LIBGL_DRI3_DISABLE=1 PROTON_USE_WINED3D9=1 WINE_FULLSCREEN_FSR_STRENGTH=0 WINE_FULLSCREEN_FSR_MODE=ultra INTEL_DEBUG=norbc gamescope -h 576 -w 1024 -H 1080 -f -U -- wine BootMenu.exe&lt;/pre&gt;
  &lt;p id=&quot;zlwQ&quot;&gt;В обоих случаях сразу советую залезть в настройки игры и выключить воспроизведение видео (всё равно не заработает) и включить фикс для отсутствующих превью файлов сохранений.&lt;/p&gt;
  &lt;p id=&quot;DkIX&quot;&gt;И ещё — игра через протон может изредка просто повиснуть при переходе сцен и просто не загрузить новый, так что сохраняйтесь почаще. И даже будучи визуальной новеллой, через протон она способно неплохо так нагрузить не самый мощный ноутбук, так что играть без зарядки рядом крайне не советую, если только не ограничивать ей фпс и потребление процессора вручную.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;kJxh&quot;&gt;Также для игры есть патч, возвращающий вырезанный контент из других версией: &lt;a href=&quot;https://vndb.org/r84358&quot; target=&quot;_blank&quot;&gt;[для Steam версии]&lt;/a&gt;, &lt;a href=&quot;https://vndb.org/r83746&quot; target=&quot;_blank&quot;&gt;[для Unrated Edition]&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;scv6&quot;&gt;Общие мысли и заключение&lt;/h2&gt;
  &lt;p id=&quot;2lWK&quot;&gt;Грисайя — это немного неоднозначная игра. Она вам либо очень понравится, либо вы её не поймёте и невзлюбите, почему-то чего-то посередине практически не случается. Тем не менее, цифры не врут: это четвёртая по популярности визуальная новелла на VNDB со средним рейтингом 8.4 и я настоятельно советую в неё поиграть.&lt;/p&gt;
  &lt;p id=&quot;MYyK&quot;&gt;Она неидеальна, в ней таки есть некоторые сюжетные дыры, химия местами страдает, но о её недостатках не очень хочется говорить (поэтому они тут не сильно и затронуты), она либо нравится, либо нет.&lt;/p&gt;
  &lt;p id=&quot;bhaD&quot;&gt;Но советую опираясь не на рейтинг, на простой факт — это единичный проект. Игры такого масштаба и качества — огромная редкость, и только лишь за это, как мне кажется, стоит хотя бы попробовать пройти её.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;f5X7&quot;&gt;Крайне советую — единичная новелла, которую стоит попробовать всем.&lt;/p&gt;
  &lt;/section&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;MbpH&quot;&gt;Спасибо, что прочитали этот малосвязный поток сознания до конца :)&lt;/p&gt;

</content></entry></feed>