Elmord's Magic Valley

Software, lingüística e rock'n'roll. Às vezes em Português, sometimes in English.

Posts com a tag: unix

My terrible nginx rules for cgit

2020-08-16 12:40 +0100. Tags: comp, unix, web, in-english

I’ve been using cgit for hosting my Git repositories since about the end of 2018. One minor thing that annoys me about cgit is that the landing page for repositories is not the about page (which shows the readme), but the summary page (which shows the last commits and repository activity). This is a useful default if you are visiting the page of a project you already know (so you can see what’s new), but not so much for someone casually browsing your repos.

There were (at least) two ways I could solve this:

I went with the second option.

Things are not so simple, however: even if I map my external URLs into cgit’s internal ones, cgit will still generate links pointing to its own version of the URLs. So the evil masterplan is to have two root locations:

In the rest of this post, I will go through the nginx rules I used. You can find them here if you would like to see them all at once.

The cgit FastCGI snippet

We will need four different rules for the /code location. All of them involve passing a translated URL to cgit, which involves setting up a bunch of FastCGI variables, most of which are identical in all rules. So let’s start by creating a file in /etc/nginx/snippets/fastcgi-cgit.conf which we can reuse in all rules:

fastcgi_pass unix:/run/fcgiwrap.socket;

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# Tell fcgiwrap about the binary we’d like to execute and cgit about
# the path we’d like to access.
fastcgi_param  SCRIPT_FILENAME    /usr/lib/cgit/cgit.cgi;
fastcgi_param  DOCUMENT_ROOT      /usr/lib/cgit;

These are standard CGI parameters; the only thing specific to cgit here is the SCRIPT_FILENAME and DOCUMENT_ROOT variables. Change those according to where you have cgit installed in your system.

The /code/ rules

Now come the interesting rules. These go in the server { ... } nginx section for your website (likely in /etc/nginx/sites-enabled/site-name, if you are using a Debian derivative). Let’s begin by the rule for the landing page: we want /code/<repo-name>/ to map to cgit’s /<repo-name>/about/:

    location ~ ^/code/([^/]+)/?$ {
        include snippets/fastcgi-cgit.conf;
        fastcgi_param  SCRIPT_NAME  /cgit;
        fastcgi_param  PATH_INFO    $1/about/;
    }

The location line matches any URL beginning with /code/, followed by one or more characters other than /, followed by an optional /. So it matches /code/foo/, but not /code/foo/bar (because foo/bar has a /, i.e., it is not “one or more characters other than /”). The foo part (i.e., the repo name) will be accessible as $1 inside the rule (because that part of the URL string is captured by the parentheses in the regex).

Inside the rule, we include the snippet we defined before, and then we set two variables: SCRIPT_NAME, which is the base URL cgit will use for its own links; and PATH_INFO, which tells cgit which page we want (i.e., the <repo-name>/about page). Note that the base URL we pass to cgit is not /code, but /cgit, so cgit will generate links to URLs like /cgit/<repo-name>/about/. This is important because later on we will define rules to redirect /cgit URLs to their corresponding /code URLs.

The second rule we want is to expose the summary page as /code/<repo-name>/summary/, which will map to cgit’s repo landing page:

    location ~ ^/code/([^/]+)/summary/$ {
        include snippets/fastcgi-cgit.conf;
        fastcgi_param  SCRIPT_NAME  /cgit;
        fastcgi_param  PATH_INFO    $1/;
    }

Again, the principle is the same: we match /code/foo/summary/, extract the foo part, and pass a modified URL to cgit. In this case, we just pass foo/ without the summary, since cgit’s repo landing page is the summary.

The third rule is a catch-all rule for all the other URLs that don’t require translation:

    location ~ ^/code/(.*) {
        include snippets/fastcgi-cgit.conf;
        fastcgi_param  SCRIPT_NAME  /cgit;
        fastcgi_param  PATH_INFO    $1;
    }

That is, /code/ followed by anything else not matched by the previous rules is passed as is (removing the /code/ part) to cgit.

The /cgit/ rules

Now we need to do the mapping in reverse: we want cgit’s links (e.g., /cgit/<repo-name>/about) to redirect to our external version of them (e.g., /code/<repo-name>/). These rules are straightforward: for each of the translation rules we created in the previous session, we add a corresponding redirect here.

    location ~ ^/cgit/([^/]+)/about/$ {
        return 302 /code/$1/;
    }

    location ~ ^/cgit/([^/]+)/?$ {
        return 302 /code/$1/summary/;
    }

    location ~ ^/cgit/(.*)$ {
        return 302 /code/$1;
    }

The cherry on the top of the kludge

This set of rules will already work if all you want is to expose cgit’s URLs in a different form. But there is one thing missing: if we go to the cgit initial page (the repository list), all the links to repositories will be of the form /cgit/<repo-name>/, which our rules will translate to /code/<repo-name>/summary/. But we don’t want that! We want the links in the repository list to lead to the repo about page (i.e., /code/<repo-name>/, not /cgit/<repo-name>/). So what do we do now?

The solution is to pass a different base URL to cgit just for the initial page. So we add a zeroth rule (it has to come before all other /code/ rules so it matches first):

    location ~ ^/code/$ {
        include snippets/fastcgi-cgit.conf;
        fastcgi_param  SCRIPT_NAME  /code;
        fastcgi_param  PATH_INFO    /;
    }

The difference between these and the other rules is that we pass PATH_INFO with the value of /code instead of /cgit, so that in the initial page, the links are of the form /code/<repo-name>/ instead of /cgit/<repo-name>/, which means they will render cgit’s /<repo-name>/about/ page instead of /<repo-name>/.

Beautiful, huh?

Caveats

One thing you have to ensure with these rules is that every repo has an about page; cgit only generates about pages for repos with a README, so your links will break if your repo doesn’t have one. One solution for this is to create a default README which cgit will use if the repo does not have a README itself. For this, I have the following settings in my /etc/cgitrc:

# Use the repo readme if available.
readme=:README.md

# Default README file. Make sure to put this file in a folder of its own,
# because all files in the folder become accessible via cgit.
readme=/home/elmord/cgit-default-readme/README.md

EOF

That’s all I have for today, folks. If you have comments, feel free to, well, leave a comment.

Comentários / Comments

Switching to the i3 window manager

2020-07-19 16:00 +0100. Tags: comp, unix, wm, emacs, in-english

After almost 3 years using EXWM as my window manager, I decided to give i3 a try. And after two weeks using it, I have to say I'm definitely sticking with it.

I had actually tried i3 years ago, but I had never used a tiling window manager before, and for some reason it didn't click for me at the time. This time, after a long time using EXWM (which I picked up more easily at the time since all the commands were the regular Emacs window/buffer commands I already knew), i3 was quite easy to pick up. So here I am.

Why not EXWM?

EXWM has a lot going for it, mainly from the fact of running in Emacs, and therefore benefitting from the general powers that all things built on Emacs have: it's eminently hackable and customizable (and you can generally see the results of your hacks without even restarting it), and can be integrated in your Emacs workflow in various ways (I gave some examples in my previous EXWM post).

However, it also has some drawbacks. EXWM does not really do much in the way of managing windows: essentially, EXWM just turns all your windows into Emacs buffers, and the window management tasks proper (splitting, deciding which Emacs window will display a new X window, etc.) is the built-in window management Emacs uses for its own windows. Which can of course be customized to death, but is not particularly great for large numbers of windows, in my opinion.

Another problem with EXWM is that if Emacs hangs for any reason (e.g., waiting for TRAMP to open a remote SSH file, or syntax highlighting choking on an overly long line in a JSON file or a Python shell), your whole graphical session freezes, because EXWM does not get an opportunity to react to X events while Emacs is hung doing other stuff. This will happen more or less often depending on the kinds of tasks you do with Emacs. (Also, if you have to kill Emacs for any reason, you kill your entire graphical session, though this can be avoided by starting Emacs like this in your .xsession:

until emacs; do :; done

an idea I wish I had had earlier.)

Finally, EXWM is glitchy. Those glitches don't manifest too often, and it's hard to separate the glitches that come naturally with it from the ones caused by my own hacks, but the fact is that I got tired of the glitchiness and hanging, and also I was lured by i3's tabs support, so I decided to switch.

First steps into i3

The first time you start i3, it presents you with a dialog asking whether you want to use Alt or Win (the 'Windows' key, a.k.a. Super) as the modifier key for i3 shortcuts. I recommend choosing Super here, since it will avoid conflicts with shortcuts from applications. i3 will then generate a config file at ~/.config/i3/config.

The generated config file contains all the default keybindings; there are no extra keybindings other than those listed in this file. This is good because you can peruse the config file to have a general idea of what keybindings exist and how their corresponding commands are expressed. That being said, the i3 User's Guide is quite good as well, and you should at least skim over it to get an idea of i3's abilities.

One peculiar thing about the standard keybindings is the use of Super+j/k/l/; to move to the window to the left, down, up, and right, respectively. That's shifted one key to the right of the traditional h/j/k/l movement commands used by Vim and some other programs. The documentation justifies this as being easier to type without moving away from the home row (and also, Super+h is used to set horizontal window splitting), but I ended up changing this to Super+h/j/k/l simply for the convenience of having bindings similar to what other applications use (and then moving horizontal splitting to Super+b, right beside Super+v for vertical splitting).

Unlike EXWM or some other window managers, the i3 config file is not a full-fledged programming language, so it's not as flexible as those other WMs. However, i3 has a trick up its sleeve: the i3-msg program, which allows sending commands to a running i3. Thanks to i3-msg, you can do tasks that require more of a programming language (e.g., conditional execution) by writing small shell scripts. For example, I have a script called jump-to-terminal.sh, which is just:

#!/bin/bash
i3-msg '[class="terminal"] move container to workspace current, focus' | grep -q true || x-terminal-emulator

i.e., try to find an open terminal window and move it to the current workspace; if the operation does not succeed (because there is no open terminal window), open a new terminal. I can then bind this script to a shortcut in the i3 config file. (I've actually changed script later to not move the window to the current workspace, but it shows how you can string multiple i3 commands together applying to the same window.)

Containers, tabs, and more

i3 uses the concept of containers to organize windows: windows themselves are containers (containing the actual X11 window), but the whole workspace is itself a container that can be split into multiple subcontainers that can be arbitrarily nested. Containers can either use a split layout (windows are tiled horizontally or vertically within the container), or a tabbed layout (i3 shows a tab bar at the top of the container, and each contained window is a tab), or a stacked layout (which is the same as the tabbed layout but the tab titles are placed in separate lines rather than side-by-side). You can switch the layout of the current container with the shortcuts Super+w (tabbed), Super+s (stacked), and Super+e (split, toggling between horizontal and vertical tiling).

(Note that what i3 calls "horizontal split container" is a split container with horizontal tiling orientation, i.e., windows are laid out side-by-side. This can be confusing if you expect "horizontal split" to mean that the splitting line will be horizontal. This is the same terminology that Emacs uses for window splitting, but the opposite of Vim.)

Containers can be arbitrarily nested, and you can have different layouts in each subcontainer. For example, you could have your workspace divided into two horizontally-tiled containers, and have a tabbed layout in one of the subcontainers. Note that because of this, it's important to know which container you have selected when you use the layout-changing commands. The colors of the borders tell you that, but it takes a while to get used to paying attention to it. i3 comes with a pre-defined binding Super+a to select the parent of the current container, but not one to select a child; I have found it useful to bind Super+z to focus child for this purpose.

Unnesting containers

The commands Super+v and Super+h (Super+b in my modified keymap) select a tiling orientation for new windows opened in the current container. (Again, the border colors tell you which mode is active.) It implicitly turns the current container into a nested container, so that new windows will become siblings of the current window. It is very easy to create nested containers by accident in this way, especially when you are just starting with i3. Those show up like i3: H[emacs] in the window title (i.e., a horizontally-tiled container containing just an emacs window), and you can even get into multiple levels of nested containers with a single window inside. In these situations, it is useful to have a command to move the current window back to its parent container. Surprisingly, i3 does not have a built-in command for that, but it is possible to concoct one from existing commands (based on this StackExchange answer):

# Move container to parent
bindsym $mod+Shift+a mark subwindow; focus parent; focus parent; mark parent; [con_mark="subwindow"] focus; move window to mark parent; [con_mark="subwindow"] focus; unmark

What this does is to use i3 marks (which are like Vim marks, allowing you to assign labels to windows) to mark the current window and its parent's parent, and then moving the window to inside its parent's parent (i.e., it becomes a sibling of its current parent).

The status bar

In EXWM, I had recently implemented a hack to display desktop notifications in the Emacs echo area. I hate desktop notifications appearing on the top of what I'm doing (especially when I'm coding), and I had most of them disabled for this reason until recently, but Slack notifications are useful to see at work. With this hack, I could finally have non-obtrusive desktop notifications. I was only going to switch to i3 if I could find a way to have similar functionality in it.

i3 does not exactly have an echo line, but it does have a desktop bar which shows your workspaces to the left, tray icons to the right, and the output of a status command in the middle. The status command can be any command you want, and the status line shows the last line of output the command has printed so far, so the command can keep updating it. i3bar actually supports two output formats: a plain-text one in which every line is displayed as-is in the status line, and a JSON-based format which allows specifying colors, separators and other features in the output.

This means that you can write a script to listen for D-Bus desktop notifications and print them as they come, together with whatever else you want in the status line (such as a clock and battery status), and blanking them after a while, or when a 'close notification' message is received. I have done just that, and it works like a charm. (It requires python3-pydbus to be installed.) The only problem with this is that the content of the status line is aligned to the right (because it is meant to be used for a clock and stuff like that), and there is no way to make it aligned to the left, so I actually pad the message to be shown with spaces to a length that happens to fit my monitor. It is sub-optimal, but it works well enough.

Conclusion

I'm pretty happy with the switch to i3. Although I've lost the deep integration with Emacs, it has actually been an improvement even for my Emacs usage, since i3 tabs supplement Emacs's lack of tabs better than any tabbing package I have seen for Emacs. (Having tabs for all programs, including things like Evince, is really nice.) If you are interested in tiling window managers and are willing to spend a few days getting used to it, I definitely recommend it.

Comentários / Comments

From Thunderbird to Liferea as a feed reader

2019-09-20 18:04 -0300. Tags: comp, unix, mundane, in-english

I've recently switched from Thunderbird to Liferea as my RSS feed reader. Thunderbird was randomly failing to update feeds at times*, and I thought it might be a good idea to use separate programs for e-mail and RSS for a change, so I went for Liferea. (I considered Elfeed too, but Elfeed does not support folders, only tags. In principle, tags can do everything folders can and more; the problem is that Elfeed cannot show a pane with all tags and the number of unread articles with each tag, the way Thunderbird or Liferea (or your average mail client) can do with folders.)

Liferea is pretty good, although I miss some shortcuts from Thunderbird, and sometimes shortcuts don't work (because focus is on some random widget). Here are some tips and tricks.

Importing feeds from Thunderbird to Liferea

Thunderbird can export the feed list in OPML format (right click on the feed folder, click Subscribe…, then Export). You can then import that on Liferea (Subscriptions > Import Feed List). No surprises here.

The tray icon

Liferea comes with a number of plugins (Tools > Plugins). By default, it comes with the Tray Icon (GNOME Classic) plugin enabled, which, unsurprisingly, creates a tray icon for Liferea. The problem with this for me is that whenever the window is 'minimized', Liferea hides the window entirely; you can only bring it back by clicking on the tray icon. I believe the idea is so that the window does not appear in the taskbar and the tray, but this interacts badly with EXWM, where switching workspaces or replacing Liferea with another buffer in the same Emacs 'window' counts as minimizing it, and after that it disappears from the EXWM buffer list. The solution I used is to disable the tray icon plugin.

Playing media

Liferea has a Media Player plugin to play media attachments/enclosures (such as in podcast feeds). To use it on Debian, you must have the gir1.2-gstreamer-1.0 package installed (it is a 'Recommends' dependency, not a mandatory one).

Alternatively, you can set Liferea to run an arbitrary command to open a media enclosure; the command will receive the enclosure URL as an argument. You can use VLC for that. The good thing about it is that VLC will start playing the stream immediately; you don't have to wait for it to download completely before playing it. The bad thing is that once it finishes playing the stream, the stream is gone; if you play it again, it will start downloading again. Maybe there is a way to configure this in VLC, but the solution I ended up using was to write a small script to start the download, wait a bit, and start VLC on the partially downloaded file. This way, the file will be fully downloaded and can be replayed (and moved elsewhere if you want to preserve it), but you don't have to wait for the download to finish.

#!/bin/bash
# download-and-play-media.sh

# Save file in a temporary place.
file="/tmp/$(date "+%Y%m%d-%H%M%S").media"
# Start download in a terminal so we can see the progress.
x-terminal-emulator -e wget "$1" -O "$file" &
# Wait for the file to be non-empty (i.e, for the download to start).
until [[ -s "$file" ]]; do
    sleep 1
done
# Wait a bit for the file to fill.
sleep 2
# Play it.
vlc "$file"

Miscellaneous tips

Caveats

So far I had two UI-related problems with Liferea:

Conclusion

Overall, I'm pretty satisfied with Liferea. There are a few problems, but so far I like it better than Thunderbird for feed reading.

Update (2020-03-23): After a few months using Liferea, I have to say that Thunderbird is better to use from the keyboard. Liferea is way too sensitive to which invisible thing has focus at a given moment. Were it not for Thunderbird not handling well hundreds of feeds, I think I would switch back.

Update (2020-07-10): I ended up switching to Elfeed.

_____

* I suspect the problem was that Thunderbird was trying to DNS-resolve the domains for a huge number (perhaps all) of feeds at the same time, and some of the requests were being dropped by the network. I did not do a very deep investigation, though.

Comentários / Comments

Truques com SSH

2017-01-24 19:40 -0200. Tags: comp, unix, network, em-portugues

Pous, nos últimos tempos eu aprendi algumas coisinhas novas sobre o SSH. Neste post relato algumas delas.

Port forwarding

O SSH possui duas opções, -L e -R, que permitem encaminhar conexões de uma porta local para um host remoto e vice-versa.

Porta local para um host remoto

Imagine que você está na sua máquina local, chamada midgard, e há uma máquina remota, chamada asgard, que é acessível por SSH. Você quer acessar um serviço na pora 8000 da máquina asgard a partir da máquina midgard, mas você quer tunelar o acesso por SSH (seja porque você quer que o acesso seja criptografado, ou porque a porta 8000 simplesmente não é acessivel remotamente). Você pode usar o comando:

midgard$ ssh -L 9000:127.0.0.1:8000 fulano@asgard

O resultado disso é que conexões TCP feitas para sua porta local 9000 serão tuneladas através da conexão com fulano@asgard para o endereço 127.0.0.1, porta 8000 na outra ponta. Por exemplo, se asgard tem um servidor web ouvindo na porta 8000, agora você vai poder abrir um browser em midgard, apontar para http://localhost:9000, e a conexão vai cair na porta 8000 de asgard, tudo tunelado por uma conexão SSH.

Note que o 127.0.0.1 é o endereço de destino do ponto de vista do servidor. Você poderia usar outro endereço para acessar outras máquinas na rede do servidor. Por exemplo, se a máquina vanaheim é acessível a partir de asgard, você poderia rodar:

midgard$ ssh -L 9000:vanaheim:8000 fulano@asgard

e agora todos os acessos à porta TCP 9000 da sua máquina local cairão na porta 8000 de vanaheim, tunelados através da conexão SSH com asgard.

Opcionalmente, você pode especificar um "bind address" antes da porta local, para especificar que apenas a porta 9000 de uma interface de rede específica deve ficar ouvindo por conexões. Por exemplo, você pode usar:

midgard$ ssh -L localhost:9000:vanaheim:8000 fulano@asgard

para dizer que a porta deve escutar apenas conexões da própria máquina. (Por padrão, que interfaces serão usadas é decidido pela opção GatewayPorts do cliente SSH, que defaulta para ouvir apenas na interface local de qualquer forma.) Alternativamente, pode-se passar um bind address vazio (i.e., :9000:vanaheim:8000, sem nada antes do primeiro :), para ouvir em todas as interfaces. Dessa maneira, outras máquinas na sua rede local que acessem a porta 9000 de midgard também terão o acesso tunelado para a porta 8000 de asgard. (* também funciona ao invés da string vazia, mas aí você tem que escapar o * para o shell não tentar expandir.)

Porta remota para a máquina local

Também é possível fazer o contrário: instruir o servidor SSH remoto a redirecionar alguma de suas portas para uma máquina e porta acessível a partir da sua máquina local. Para isso, utiliza-se a opção -R. Por exemplo:

midgard$ ssh -R 8000:localhost:22 fulano@asgard

Isso faz com que a porta 8000 em asgard seja tunelada para a porta 22 da máquina local. Agora, se alguém na máquina asgard acessar a porta 8000 (por exemplo, com ssh -p 8000 beltrano@localhost), a conexão vai cair na sua porta 22 local (e a pessoa terá acesso ao seu servidor SSH local). Você pode usar isso se você está atrás de um firewall ou NAT e a máquina remota é acessível pela Internet, mas a sua máquina local não, e você quer dar acesso a algum serviço da sua máquina local à máquina remota. (Já abordamos isso por aqui antes, mas menciono de novo for completeness.)

Proxy SOCKS

O SSH é capaz de funcionar como um proxy SOCKS. Para isso, utiliza-se a opção -D ("dynamic forwarding"):

midgard$ ssh -D localhost:8000 fulano@asgard

Isso faz com que o SSH ouça como um servidor SOCKS na porta 8000 da máquina local. Conexões recebidas nessa porta serão tuneladas para a máquina asgard, que funcionará como um proxy. Você pode então apontar o proxy SOCKS do seu browser ou outra aplicação para localhost, porta 8000.

Outras opções úteis

-C habilita compressão da conexão. E útil principalmente com conexões lentas (numa rede local, a compressão não compensa muito).

Por padrão, se você usa um dos comandos de redirecionamento de portas acima, o SSH faz o redirecionamento e abre uma sessão de shell comum. Se você quer apenas fazer o redirecionamento, pode usar as opções -N (não executa comando remoto) e -f (vai para background (forks) depois de pedir a senha). As opções podem ser combinadas em um único argumento (e.g., -CNf).

Escapes e comandos especiais

Em uma sessão SSH, a seqüência ENTER ~ é reconhecida como um prefixo de escape para acessar uma série de comandos especiais. Se você digitar ENTER ~ ?, verá uma lista de todos os comandos disponíveis:

Supported escape sequences:
 ~.   - terminate connection (and any multiplexed sessions)
 ~B   - send a BREAK to the remote system
 ~C   - open a command line
 ~R   - request rekey
 ~V/v - decrease/increase verbosity (LogLevel)
 ~^Z  - suspend ssh
 ~#   - list forwarded connections
 ~&   - background ssh (when waiting for connections to terminate)
 ~?   - this message
 ~~   - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

O comando ENTER ~ C abre um prompt onde é possível fazer e cancelar redirecionamentos de porta, com uma sintaxe análoga à das opções vistas anteriormente:

ENTER ~ C
ssh> ?
Commands:
      -L[bind_address:]port:host:hostport    Request local forward
      -R[bind_address:]port:host:hostport    Request remote forward
      -D[bind_address:]port                  Request dynamic forward
      -KL[bind_address:]port                 Cancel local forward
      -KR[bind_address:]port                 Cancel remote forward
      -KD[bind_address:]port                 Cancel dynamic forward

Pasmem.

Observações

O uso das opções de redirecionamento pode ser controlado/desabilitado na configuração do servidor. Consulte a man page sshd_config(5) para mais informações.

2 comentários / comments

Making new Pidgin conversation windows not appear on top of other windows in Xfce

2016-02-14 17:05 -0200. Tags: comp, unix, wm, in-english

Recently I started using Xfce/Xfwm instead of IceWM. One of the problems I had after migrating is that Xfwm places new Pidgin conversation windows on the top of existing windows, obstructing whatever I am doing. At least it doesn't give focus to the new window, but ideally the window should be placed in background.

The solution I found was, well, modifying the source code. Fortunately, there is only a single line to be changed.

  1. Download Xfwm 4.12's source code, unpack it (tar -xvf xfwm4-4.12.0.tar.bz2) in a place of your choosing, and enter the xfwm4-4.12.0 directory.

  2. In the file src/focus.c, replace line 232, which says:

                clientRaise (c, None);

    with:

                clientLower (c, None);
  3. Run ./configure. If it complains about missing libraries, you will have to install the corresponding development (header) packages from your distribution. On Debian, Ubuntu and similar, these packages have names ending in -dev. For instance, if ./configure complains that libxfce4ui-1 is missing, you will have to install the package libxfce4ui-1-dev.

    Update: You can install all(?) dependencies with:

    sudo apt-get install intltool libx11-dev libgtk2.0-dev libxfce4util-dev libxfce4ui-1-dev libwnck-dev 
    
  4. After ./configure succeeds, run make, then make install.

  5. If everything succeeded, test the newly compiled window manager by running xfwm4 --replace. (You may have to specify the full path, e.g., /usr/local/bin/xfwm4 --replace, to make sure you are using the newly compiled Xfwm, not the one that came with your distro.) If the new window manager works, you are done! (If anything goes wrong, you can run make uninstall to undo the installation, then run /usr/bin/xfwm4 --replace to re-run the original Xfwm.)

One day, maybe, I may try to turn this into a configuration option in Xfwm and submit a patch. One day...

3 comentários / comments

Trocando de janela pelo nome

2015-06-23 02:50 -0300. Tags: comp, unix, em-portugues

Por uma série de acidentes enquanto experimentava window managers hoje,

  1. Eu me dei conta de que seria bem conveniente poder trocar de janela digitando uma parte do título ao invés de procurar na barra de tarefas ou dar vinte Alt+Tabs até encontrar a janela. (Eu me dei conta disso quando, depois de não conseguir achar a janela que eu queria no i3, a minha primeira reação foi querer dar C-x b (o comando switch-buffer do Emacs) e digitar o nome da janela.)
  2. Eu descobri ferramentas apropriadas para tornar isso possível.

[Screenshot do programa 'dmenu']

Você vai precisar de:

Com isso, podemos escrever um pequeno script para apresentar um menu e selecionar a janela escolhida.

O script

#!/bin/bash

option="$(
    wmctrl -xl |
        #         \1       \2       \3       \4       \5
        #         win-id   desktop  class    hostname title
        sed -r 's|([^ ]* +)([^ ]* +)([^ ]* +)([^ ]* +)(.*)|\1 \3 \5|' |
        sort -k 3 |
        dmenu -i -l 10
    )" || exit 1

wmctrl -ia "${option%% *}"

wmctrl -l lista as janelas existentes. A opção -x inclui a classe da janela na listagem. O sed não é estritamente necessário, mas deixa a lista menos poluída removendo campos desnecessários; você pode alterar essa linha para escolher os campos. O ID da janela é meio irrelevante para o usuário, mas precisamos dele para poder passá-lo ao wmctrl para ativar a janela.

sort -k 3 ordena o menu pelo título da janela. Você pode comentar essa linha fora se não quiser ordenar a lista, ou mudar os parâmetros para obter uma ordem diferente (e.g., sort -k 2 para ordenar pela classe).

Quanto ao dmenu, a opção -i faz com que ele ignore maiúsculas vs. minúsculas ao filtrar as opções pelo texto digitado pelo usuário. -l 10 indica que queremos uma opção por linha, e que no máximo 10 linhas devem ser mostradas de cada vez. Por padrão, o dmenu usa apenas uma linha e mostra as opções lado a lado. (Uma coisa meio ruim do dmenu é que ele não dá nenhuma indicação de que é possível scrollar o menu; ele só mostra as primeiras N opções e as demais ficam escondidas.)

wmctrl -a JANELA ativa a primeira janela cujo título contenha a string specificada. Como queremos que a seleção seja inambígua, utilizamos a opção -i, que permite especificar o ID da janela ao invés do título. Para extrair o ID da seleção, removemos tudo depois do primeiro espaço na string ("${option%% *}").

Instalação

Salve o script no local de sua preferência, dê permissão de execução a ele (chmod +x nome-do-script), e associe-o a alguma tecla de atalho no seu ambiente gráfico favorito. Por exemplo, no IceWM isso pode ser feito adicionando no ~/.icewm/keys uma linha como:

key "Super+Tab" /caminho/do/script

substituindo Super+Tab pelo atalho de sua preferência (Super é a tecla "janelinha").

Para mais informações e possibilidades, consulte a manpage dos programas.

2 comentários / comments

My very brief affair with Btrfs

2014-09-14 01:38 -0300. Tags: comp, unix, mundane, ramble, em-portugues

Meia dúzia de dias atrás eu migrei meu / para Btrfs. Hoje eu reformatei a partição como ext4 e recuperei meu backup do / da semana passada.

O causo foi assim. Para usar o Btrfs, eu atualizei meu kernel para o 3.16, já que diversas melhorias foram realizadas no suporte a Btrfs nessa versão. Porém, o driver da minha placa de rede wireless (o broadcom-sta) andava não se comportando muito bem, o iwconfig hoje resolveu não listar nenhuma rede, e eu resolvi bootar com o meu kernel 3.14 anterior para ver se a situação melhorava. (Na verdade, com a atualização do kernel 3.2 para 3.14, que eu fiz para poder usar o Btrfs, eu tive que substituir o broadcom-sta da stable pelo da testing, e desde então ele já andava com uns comportamentos desagradáveis (tais como emitir um trace sempre que a wi-fi era iniciada), mas aparentemente a wi-fi estava funcionando corretamente mesmo assim.) Até aí, tudo transcorreu normalmente. Kernel 3.14 bootado, wi-fi funcionando, todos comemora.

Eis que eu fui abrir o aptitude (já não lembro mais por que motivo), e o módulo do Btrfs capota, emitindo algum erro sobre quotas/qgroups. Reiniciei a máquina com o kernel 3.14, fui abrir o aptitude de novo, mesmo erro. Agora não lembro mais a seqüência exata das ações, mas em algum momento eu desativei o suporte a quotas (btrfs quota disable /), abri o aptitude de novo, e dessa vez ele abriu. Porém, turns out que, no piripaque do filesystem, meu /var/lib/dpkg/status virou um arquivo vazio, e o aptitude abriu me mostrando nenhum pacote instalado e me oferecendo para baixar 3GB de pacotes (i.e., todos os pacotes que eu tinha na máquina). Nesse momento eu me disse "well, fuck", reformatei o / como ext4 e recuperei o backup que eu tinha feito quando fui migrar para Btrfs (que por sorte eu ainda não tinha apagado).

Moral da história: Talvez se eu tivesse me mantido usando o kernel 3.16 eu não tivesse tido esse problema. Porém, depois dessa experiência, e dado que na atual conjuntura eu deveria estar me preocupando com o mestrado e não com a saúde do meu filesystem, eu prefiro esperar mais uns meses para ver se o Btrfs fica mais estável e experimentá-lo de novo. Enquanto isso, eu voltei para o kernel 3.2 da stable, que pode não ser new and shiny, mas é sólido como uma rocha, forte como um touro e pesado como uma porpeta.

3 comentários / comments

Partição de sistema Btrfs no Debian

2014-09-12 03:03 -0300. Tags: comp, unix, mundane, em-portugues

Btrfs é um sistema de arquivos relativamente "recente" (o desenvolvimento começou em 2008) com um bocado de features interessantes. Neste post, falarei uma porção de coisas sobre como usar uma partição Btrfs como sistema de arquivos raiz no Debian. O Btrfs ainda é considerado experimental (embora seja bastante estável em kernels recentes), e eu não confiaria meus arquivos pessoais a ele no momento (meu /home é uma partição ext4), mas como sistema de arquivos raiz de uma máquina de uso pessoal (que se eu perder é só reinstalar), acredito que os benefícios compensam os riscos.

[Update: Aparentemente ele não é tão estável assim.]

E que benefícios são esses?

A principal razão pela qual eu migrei meu / para Btrfs foi para usar sua feature de snapshots, que permite criar uma "duplicata" do estado do sistema de arquivos em um dado momento. A criação de um snapshot não duplica os arquivos; ao invés disso, os arquivos são compartilhados entre as duas "versões" do sistema de arquivos, e só são copiados à medida em que são modificados, o que torna a criação do snapshot praticamente instantânea e não consome espaço desnecessariamente. Com isso você pode, por exemplo, tirar um snapshot do /, atualizar/bagunçar o sistema e, se alguma coisa der errado, voltar ao estado anterior são e salvo.

Requisitos mínimos

Kernel 3.14 ou superior. O suporte a Btrfs do kernel atual (3.2) do Debian stable é bastante precário (eu consegui derrubar ele com um for ((i=0; ; i++)); do mkdir $i; done em um filesystem recém criado). As opções são:

btrfs-tools 3.14 ou superior. Disponível tanto no repositório da backports quanto no da unstable.

Adaptando o initramfs

Para poder bootar a partir de um raiz Btrfs, você precisará adicionar suporte ao filesystem ao seu initramfs. Para isso, adicione as seguintes linhas ao /etc/initramfs-tools/modules:

crc32c
btrfs

e execute update-initramfs -u -k all.

A linha crc32c é particularmente relevante: Geralmente, o update-initramfs é esperto o suficiente para incluir junto com um módulo todas as suas dependências no initramfs. Porém, por um bug no módulo btrfs, ele não indica explicitamente sua dependência pelo módulo crc32c. Se o módulo crc32c não for incluído manualmente na lista, a carga do módulo btrfs falhará no boot, com uma mensagem do tipo can't load module btrfs (kernel/fs/btrfs/btrfs.ko): unknown symbol in module, or unknown parameter. (Isso me tomou uma boa hora de sofrimento.)

Migrando para Btrfs

Para transformar seu raiz em Btrfs, você precisará bootar com um outro sistema e seguir uma de duas rotas:

Antes de reiniciar

Antes de rebootar o sistema no filesystem recém criado, lembre-se de altarar o etc/fstab do sistema. Você poderá ter que trocar:

Se o seu /boot fica dentro na mesma partição que o raiz, você pode ter que fazer alguma mudança no seu gerenciador de boot para garantir que ele consiga ler o Btrfs. O GRUB 2 aparentemente consegue ler Btrfs sem problemas. (Eu ainda uso o bom e velho GRUB 0.97, mas meu /boot é uma partição ext2 separada.)

Subvolumes e snapshots

O Btrfs organiza o filesystem em subvolumes. Inicialmente, o filesystem contém apenas um "subvolume raiz", mas outros subvolumes podem ser criados com o comando btrfs subvolume create /caminho/novo-nome, onde /caminho é um diretório dentro do filesystem de interesse. btrfs subvolume list /caminho lista todos os subvolumes (e respectivos IDs) contidos em um filesystem.

O comando btrfs subvolume snapshot /caminho-subvol-origem /caminho-subvol-destino duplica um subvolume, i.e., cria um "snapshot". Depois da duplicação, os dois subvolumes são independentes: alterações em um não se refletem no outro.

Um filesystem Btrfs possui um subvolume padrão, i.e., o subvolume que é usado como raiz do filesystem se nenhum outro for especificado. Inicialmente, o "subvolume raiz" é o padrão, mas você pode usar o comando btrfs subvolume set-default ID /caminho (onde ID é o ID mostrado por btrfs subvolume list) para escolher outro. Com isso você pode setar o subvolume padrão para um snapshot com um estado anterior do sistema, por exemplo, reiniciar a máquina, e magicamente seu sistema volta a ser o que era no momento do snapshot.

Você pode montar um subvolume diferente do padrão passando a opção -o subvolid=ID para o comando mount. -o subvolid=0 monta o subvolume raiz original. Você pode montar a mesma partição mais de uma vez.

Como eu mencionei antes, ao invés de copiar o backup do sistema diretamente para o raiz da nova partição, pode ser mais conveniente criar um subvolume primeiro, especialmente se você pretende usar a feature de snapshots com freqüência. Por exemplo, ao criar o filesystem:

# mount /dev/xxx /mnt/sistema
# cp -avx /mnt/sistema /algum/lugar/backup
# umount /mnt/sistema
# mkfs.btrfs -f /dev/xxx
# mount /dev/xxx /mnt/sistema
# btrfs subvolume create /mnt/sistema/current
# btrfs subvolume list /mnt/sistema
ID 264 gen 142 top level 5 path current
# btrfs subvolume set-default 264 /mnt/sistema
# cp -avx /algum/lugar/backup/* /mnt/sistema/current
# (realize as adaptações pré-boot adequadas)

Com o subvolume padrão setado, você pode reiniciar e bootar o sistema novo normalmente. Quando você quiser fazer um snapshot, basta montar o raiz original em algum lugar (e.g., /mnt/root) e fazer as operações de interesse:

# mount -o subvolid=0 /dev/xxx /mnt/root
# btrfs subvolume snapshot /mnt/root/current /mnt/root/snapshot-20120912
# umount /mnt/root

Feito isso, você pode bagunçar com seu sistema à vontade e, se quiser voltar atrás, pode executar:

btrfs subvolume set-default ID-do-snapshot

e reiniciar a partir do snapshot. Ou, se preferir não alterar o snapshot, você pode duplicá-lo primeiro e reiniciar pela cópia:

# mount -o subvolid=0 /dev/xxx /mnt/root
# btrfs subvolume snapshot /mnt/root/snapshot-20120912 /mnt/root/current2
# btrfs subvolume list /mnt/root
ID 264 gen 142 top level 5 path current
ID 266 gen 142 top level 5 path snapshot-20120912
ID 268 gen 142 top level 5 path current2
# btrfs subvolume set-default 268 /mnt/root
# reboot

Lembre-se de depois apagar o current antigo (com btrfs subvolume delete /mnt/root/current), caso não queira que ele fique ocupando espaço para sempre.

A vantagem de criar um subvolume primeiro antes de copiar o sistema é deixar o subvolume raiz original contendo apenas subvolumes, evitando misturar subvolumes e arquivos de sistema no mesmo nível. Isso permite manipular todos os subvolumes/snapshots da mesma maneira (qualquer versão do sistema pode ser apagada facilmente, por exemplo; com o sistema no raiz original isso não seria possível).

Observações sortidas

O utilitário btrfs permite abreviar comandos, desde que as abreviações não sejam ambíguas. Por exemplo, btrfs subvolume list / pode ser escrito como btrfs sub l /.

Espaço usado e livre em Btrfs são conceitos um tanto quanto curiosos, devido ao mecanismo de copy-on-write, suporte a compressão e outras peculiaridades do Btrfs. O comando btrfs filesystem df /caminho pode dar um resultado mais preciso do que o df -h.

Descobrir o espaço utilizado por cada subvolume é uma questão mais complicada (até porque subvolumes podem compartilhar dados). Se o mecanismo de quotas for habilitado no subvolume raiz original (com o comando btrfs quota enable /mnt/root), é possível usar o comando btrfs qgroup show /, que lista a quantidade de dados apontada por cada subvolume e a quantidade de dados não compartilhada com nenhum outro subvolume (e que portanto seria liberada se o subvolume fosse apagado). Para mais informações, dê uma olhada neste artigo.

É possível criar um snapshot somente-leitura passando a opção -r para o comando btrfs subvolume snapshot. A vantagem é que é mais difícil de destruir um backup do sistema assim. A desvantagem é que não é mais possível bootar pelo snapshot como se fosse um sistema comum.

2 comentários / comments

O que são capabilities e o que elas têm de tão mágico

2014-04-19 08:39 -0300. Tags: comp, prog, unix, security, em-portugues

Eu já falei de capabilities por aqui algumas vezes antes. Neste post tentarei explicar o que elas são e por que eu acho que elas são a panacéia universal (ok, não, mas por que eu acho que elas são um avanço em comparação com as permissões convencionais do Unix).

(Antes de mais nada, gostaria de ressaltar que as capabilities a que eu me refiro aqui não têm nada que ver com o que o Linux chama de capabilities, que são basicamente uma maneira de separar o tradicional balaio de poderes do root em unidades que podem ser atribuídas individualmente a processos (e.g., com isso é possível dar a um processo o poder de alterar o relógio do sistema sem conceder todos os outros poderes de root junto).)

Ok, que diabos são capabilities?

Uma capability é um objeto ou "token" que representa a habilidade de um processo de acessar um certo recurso, tal como um arquivo ou uma conexão de rede. Capabilities possuem três propriedades importantes:

Turns out que file descriptors no Unix possuem essas três propriedades. Ao abrir um arquivo no Unix, o processo recebe um número inteiro que é um índice na tabela de file descriptors do processo, que é acessível apenas pelo kernel. File descriptors abertos podem ser passados adiante para os filhos de um processo ou transferidos via sockets. Uma vez aberto o arquivo, as credenciais do processo são irrelevantes para o seu acesso: um processo pode, por exemplo, começar executando como root, abrir um recurso privilegiado (e.g., ouvir em uma porta menor que 1024), e depois trocar de credenciais para um usuário menos poderoso sem perder o acesso ao recurso privilegiado, pois a posse do file descriptor da conexão é suficiente para garantir-lhe acesso ao recurso. (Um file descriptor não é uma capability pura porque conserva outros dados além dos necessários ao acesso do recurso, tais como a posição do cursor no arquivo, o que dificulta seu uso compartilhado por outros processos depois de transmitido, mas em essência trata-se de uma capability.)

A mágica de um modelo de segurança baseado em capabilities, entretanto, é que todo acesso a recursos é feito por meio de capabilities, e um processo tem acesso apenas aos recursos representados pelas capabilities que lhe são entregues. No Unix, por outro lado, um processo recebe acesso implícito e mais ou menos inevitável a diversos recursos, tais como o filesystem e a habilidade de criar conexões de rede. É possível cercar o acesso a esses recursos, e.g., usando chroot para entregar um filesystem alternativo ao processo (mas não é possível não entregar filesystem nenhum ao processo) ou regras de firewall para bloquear o acesso do processo à rede (geralmente indiretamente, e.g., rodando o processo com outro usuário e bloqueando o usuário no iptables), mas há uma série de dificuldades e inconvenientes envolvidos:

A raiz do problema é que o modelo de segurança do Unix foi criado no contexto dos sistemas multi-usuário dos anos 1970, em que a preocupação primária era proteger os usuários uns dos outros e o sistema dos usuários. Hoje em dia as preocupações são outras: no caso de computadores pessoais, a maioria das máquinas roda com um único usuário, e queremos proteger o usuário de programas potencialmente mal-comportados (seja por conterem vulnerabilidades, seja por descuido do programador, seja porque o programa é intencionalmente malicioso) que o próprio usuário executa. No caso de servidores, queremos minimizar o potencial de desastre caso um serviço seja comprometido. Capabilities se encaixam melhor (acredito) com essas preocupações do que o modelo de segurança tradicional do Unix, pois permitem um controle maior de o que um processo é capaz de acessar. Ao invés de passarmos aos programas o acesso ao filesystem inteiro e os nomes de arquivos que queremos que o programa manipule, passamos capabilities aos arquivos de interesse, sem entregar o acesso a todo o resto do filesystem junto. Ao invés de chamar todos os programas com o poder de abrir conexões de rede, podemos passar esse poder apenas aos processos que realmente tenham que ter esse acesso.

E o browser?

A essas alturas você talvez esteja se perguntando: "Ok, meu filho, e como isso resolve o problema do browser? Eu não vou ter que entregar uma capability para acessar todos os meus arquivos para o caso de eu querer fazer upload de um deles? Hã? Hã?"

A solução é uma das coisas mais legais que se consegue fazer com capabilities. Lembre-se de que capabilities podem ser transmitidas entre processos. Isso significa que nós podemos ter um daemon (chamemo-lo fileopend) capaz de fornecer capabilities. Ao iniciarmos o browser, passamos a ele uma capability que é um canal de comunicação com o fileopend. Quando o usuário vai fazer upload de alguma coisa, ao invés de o browser abrir a janelinha de "Abrir arquivo", ele manda uma requisição de abertura de arquivo ao fileopend. O fileopend, então, mostra a janelinha de "Abrir arquivo" ao usuário. O usuário escolhe o arquivo, e então o fileopend o abre e envia a capability correspondente àquele arquivo específico para o browser. O browser, assim, só tem acesso a arquivos que o usuário tenha selecionado explicitamente na janela de "Abrir arquivo".

Genial, hã?

And we can do it right now!

Atualmente existe um projeto chamado Capsicum: practical capabilities for UNIX, que teve bastante progresso recentemente. Trata-se de uma implementação de capabilities no FreeBSD, que está sendo adaptada para o Linux. O projeto inclusive produziu uma versão do Chromium baseada em capabilities, usando uma idéia análoga à do fileopend (que eles chamam de "user angels") para abrir arquivos do usuário.

Mas teoricamente, seria possível implementar capabilities em user-space no Unix com uma pequena dose de faconice. No cenário mais simples, seria possível rodar cada processo com um usuário/grupo diferente (gerar um UID/GID para cada processo novo), em um chroot, com acesso à rede bloqueado no firewall, etc., apenas com um canal de comunicação com um daemon que intermediaria o acesso dos processos a todos os recursos, tais como arquivos, conexões de rede, etc. Esse daemon faria o papel do kernel em um sistema com suporte nativo a capabilities. O problema com essa abordagem é performance: todo acesso a recursos teria que passar pelo canal de comunicação entre os processos comuns e o daemon. Porém, uma vez que file descriptors podem ser transmitidos por sockets no Unix, seria possível usar o daemon apenas para criar e transmitir file descriptors (capabilities) para os processos. Uma vez de posse do file descriptor, o processo pode utilizar o recurso "nativamente". A perda de performance seria apenas na abertura de recursos, e talvez não fosse tão significativa. Anyway, graças ao Capsicum, estamos em vias de ter capabilities nativas no Linux (hopefully no kernel mainline) sem ter que apelar a gambiarras.

Unix is dead. Long live Unix.

Comentários / Comments

Convertendo archives do LISTSERV para mbox

2013-09-04 01:45 -0300. Tags: comp, unix, prog, perl, em-portugues

Escrevi um pequeno script em Perl para converter um archive de mailing list do LISTSERV para o formato mbox, que pode ser importado em diversos clientes de e-mail. Possa ele ser-vos útil.

3 comentários / comments

Main menu

Posts recentes

Comentários recentes

Tags

em-portugues (213) comp (138) prog (68) in-english (51) life (47) unix (35) pldesign (35) lang (32) random (28) about (27) mind (25) lisp (23) mundane (22) fenius (20) web (18) ramble (17) img (13) rant (12) hel (12) privacy (10) scheme (10) freedom (8) bash (7) copyright (7) music (7) academia (7) lash (7) esperanto (7) home (6) mestrado (6) shell (6) conlang (5) emacs (5) misc (5) latex (4) editor (4) book (4) php (4) worldly (4) politics (4) android (4) etymology (4) wrong (3) security (3) tour-de-scheme (3) kbd (3) c (3) film (3) network (3) cook (2) poem (2) physics (2) wm (2) treta (2) philosophy (2) comic (2) lows (2) llvm (2) perl (1) en-esperanto (1) audio (1) german (1) kindle (1) old-chinese (1) pointless (1) translation (1)

Elsewhere

Quod vide


Copyright © 2010-2020 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.