Use /boot/cmdline.txt para criar o primeiro script de inicialização

11

Muitas perguntas foram feitas sobre como encontrar meu Pi na minha rede . Outros - inclusive eu - têm problemas demorados ao tentar implantar um lote de Pi's novos.

Embora a criação de imagens personalizadas possa ser uma solução para esses problemas, pergunto-me se existem outras soluções.

Com (apenas) o /bootdiretório aberto para acesso em máquinas comuns (Win / OSX), seria possível usar /boot/cmdline.txtpara canalizar texto para um script bash, executá-lo e excluí-lo posteriormente?

EDP
fonte
11
Esta questão está em discussão no Meta . Gostaria de ouvir sua opinião, se possível. Obrigado.
Thomas Weller

Respostas:

6

Criei uma versão levemente modificada do Raspberian-light que atende a essa necessidade - ele executa seu script /boot/firstboot.sh personalizado na primeira inicialização:

https://github.com/nmcclain/raspberian-firstboot

Ned McClain
fonte
Obrigado! 4 anos após o OP, finalmente há uma boa solução. Não é particularmente a ciência do foguete para construí-lo, mas você fica muitas horas toda vez que há um novo lançamento. IMHO isso deve ser adicionado ao firmware principal.
EDP
3

Para aqueles que preferem uma solução envolvendo apenas scripts inseridos na partição de inicialização do FAT32 , veja como fazer isso. [ Edit: Os arquivos estão agora disponíveis em um projeto pi-boot-script .]

Como mencionado em outras respostas, envolve os argumentos da linha de comando com os quais o kernel do Linux é iniciado. Esses argumentos estão em /boot/cmdline.txt .

Eu testei isso no Raspbian Buster (v10.1) 2019-09-26. Ele funciona em um cartão SD recém-atualizado ou na imagem de disco .img baixada , que pode ser flash para qualquer número de cartões SD.

1. Edite os argumentos do kernel

Abra o arquivo de texto /boot/cmdline.txt , remova qualquer init=parte dele e adicione-o no final da linha:

init=/bin/bash -c "mount -t proc proc /proc; mount -t sysfs sys /sys; mount /boot; source /boot/unattended"

A última palavra nesta linha é o nome de um script a ser executado pelo kernel como o primeiro processo (PID = 1) em vez de / sbin / init . A página de ajuda dos argumentos do kernel diz apenas argumentos sem .serem passados ​​para o executável init, portanto você não pode chamar o script de unattended.sh ou coisas assim.

2. Coloque o script na partição de inicialização

Salve o seguinte na partição de inicialização como / autônoma (o nome que você colocou na linha de comando):

# 1. MAKING THE SYSTEM WORK. DO NOT REMOVE
mount -t tmpfs tmp /run
mkdir -p /run/systemd
mount / -o remount,rw
sed -i 's| init=.*||' /boot/cmdline.txt

# 2. THE USEFUL PART OF THE SCRIPT
# Example:
[[ -d /boot/payload/home/pi ]] && sudo -u pi cp --preserve=timestamps -r\
 /boot/payload/home/pi /home/ && rm -rf /boot/payload/home/pi              # A
[[ -d /boot/payload ]] && cp --preserve=timestamps -r /boot/payload/* /\
 && rm -rf /boot/payload                                                   # B
ln -s /lib/systemd/system/one-time-script.service\
 /etc/systemd/system/multi-user.target.wants/                              # C

# 3. CLEANING UP AND REBOOTING
sync
umount /boot
mount / -o remount,ro
sync
echo 1 > /proc/sys/kernel/sysrq
echo b > /proc/sysrq-trigger
sleep 5

Esse script faz algumas preparações necessárias (capítulo 1), depois tudo o que você deseja fazer (2) e depois limpa e reinicia (3). Substitua o material abaixo de 2 pelos comandos que você deseja executar.

Para algumas tarefas de configuração, você provavelmente precisará de uma inicialização normal para ativar a rede e outros serviços; portanto, o exemplo nesta versão (explicado abaixo) prepara apenas um script adequado para ser executado quando o Pi for reinicializado.

3. Coloque quaisquer outros arquivos que seu script precise na partição de inicialização

...obviamente.

Exemplo

Juntamente com o meu script, coloquei uma pasta de carga / na partição de inicialização, que contém os arquivos que eu quero mover para a partição Linux. No script autônomo acima,

  • a linha A move os arquivos para o diretório do usuário pi. Por exemplo, payload / home / pi / .bashrc é movido para o sistema de arquivos raiz como /home/pi/.bashrc ;
  • a linha B move arquivos de propriedade raiz para a partição Linux, incluindo payload / usr / local / bin / one-time-script.sh, que se torna /usr/local/bin/one-time-script.sh e semelhante para carga útil / lib / systemd / system / one-time-script.service ;
  • A linha C cria um link simbólico para o último arquivo, para que meu script de configuração one-time-script.sh seja executado na próxima inicialização.

Esse script faz várias personalizações que eu gosto: ele cria e formata outra partição FAT32 e a adiciona ao / etc / fstab para que o usuário pi possa escrever nele (para logs de aplicativos etc.); redimensiona a partição ext4 e o sistema de arquivos para o restante do cartão SD; altera o código do idioma, fuso horário, nome do host (com base no número de série da CPU), país do WiFi; define rede WiFi e senha; ativa o SSH; corrige um problema de configurações de idioma para sessões SSH; configura a inicialização em um console sem login automático; grava alguns dados sobre o sistema em um arquivo na partição de inicialização; e, é claro, remove esse link simbólico para que não seja executado novamente na inicialização.

A maioria dos usuários acha isso desnecessário e prefere usar PiBakery , pi-init2 ou uma imagem ext4 personalizada, que são ótimas soluções. Eu prefiro isso porque posso entendê-lo completamente e não preciso executar outro software. E também funciona: com o arquivo .img em que eu inseri meus scripts, todos piscam um cartão SD + o colocam em um Pi +, permitindo que ele funcione para se configurar, leva 6 minutos.

Fonte Encontrei a idéia de um script como init=argumento do kernel e os mountcomandos necessários para fazê-lo funcionar no script init_resize.sh que é executado por padrão para redimensionar a partição Linux.

Jim Danner
fonte
2

Você PODE fazer com que o código seja executado mexendo com a linha de comando do kernel. O método mais óbvio é substituir init por outra coisa. A aplicação mais comum disso é iniciar um shell muito cedo no processo de inicialização, geralmente porque você precisa corrigir alguma coisa ou porque todo o resto está muito danificado, por exemplo:

init=/bin/bash

Lembre-se de que, neste ponto do processo de inicialização, os sistemas de arquivos ainda estão todos montados somente leitura. Além disso, há um monte de coisas que simplesmente não funcionam corretamente. Como você não tem um init real em execução, o desligamento e a reinicialização não funcionarão. É necessário remontar manualmente o sistema de arquivos raiz somente leitura e chamar reboot -fa reinicialização, por exemplo.

Não tenho idéia se você pode passar argumentos para o bash dessa maneira. Eu nunca tentei. Em teoria, se você pode passar -cpara o bash, pode dizer ao processo do bash para fazer qualquer coisa. Mas pode se transformar em um argumento bastante longo, e não sei se o kernel permitiria essas coisas.

Segunda coisa que você pode fazer. Você pode copiar um ramfs inicial (initramfs) para o sistema de arquivos e configurar o gerenciador de inicialização para usá-lo config.txt. Existem várias maneiras de inserir scripts em um initramfs para fazer coisas especiais. Você precisará preparar um initramfs especial para esse fim (consulte initramfs-tools (8)), portanto, não tenho certeza se esta é uma solução melhor do que uma imagem personalizada.

Você pode incluir o script em / boot (eu ri de sua sugestão sobre máquinas "regulares", mas seria o pouco que você pode acessar a partir dessas máquinas) e tente iniciá-lo usando a linha init do kernel, mas os arquivos dos sistemas de arquivos não são é executável, a menos que você o faça para todo o sistema de arquivos.

Se fosse eu, eu faria uma imagem personalizada que usa dhcp para configurar a rede e que contém um script personalizado que é executado na inicialização. Este script verifica um arquivo específico que atua como um sinalizador. Se o arquivo existir, não faça nada. Caso contrário, configure as coisas e crie o arquivo de sinalizador.

Seu script de configuração pode até extrair a coisa real de um servidor http. Isso significa que você não precisa criar uma nova imagem se precisar ajustar algo.

Essa deve ser a solução menos estressante.

Uma possibilidade final, mas você terá que fazer isso em uma máquina "não-regular" :-) Você pode montar o sistema de arquivos ext4 em um dispositivo de loop e copiar arquivos para ele sem escrever primeiro no sdcard. Para uma imagem Jessie Raspbian padrão, seria algo como isto:

sudo losetup /dev/loop0 /tmp/gw.img -o 62914560
sudo mount /dev/loop0 /mnt
sudo cp /my/superduper/script.sh /mnt
sudo umount /dev/loop0
sudo fsck -f /dev/loop0 # This is optional
sudo losetup -d /dev/loop0

Eu gosto de fazer um fsck forçado nos meus sistemas de arquivos antes de fazer imagens. Define a contagem de montagem como zero na primeira inicialização :-)

EDIT : Depois de muitos meses e mais experiência. Você quer olhar para o u-boot. Substitua o carregador de inicialização por u-boot. Isso pode ser feito a partir de uma "máquina comum". Depois de inicializar o u-boot, você pode inicializar em rede uma distribuição a partir da qual pode facilmente exibir o cartão sd ou, em teoria, o cartão diretamente, embora eu não tenha ideia de quão difícil isso seria.

Essencialmente, o u-boot traz o boot de rede para o Raspberry Pi, algo que ele não suporta por si só.

izak
fonte
Ok, o sistema de arquivos é somente leitura nessa fase. Que tal init=script & init? O script seria executado em segundo plano enquanto o init inicia normalmente. O script precisaria de alguma verificação de condição no início e, por exemplo, continuará quando o init terminar seu trabalho.
Thomas Weller
11
Usar & para colocar em segundo plano algo é algo do tipo shell. A menos que você diga ao kernel para executar um comando específico em um shell (por exemplo: bash -c "some command & another command") que não funcione, e eu já acho que é uma má idéia. Mas estendi minha resposta e adicionei a opção u-boot, algo que descobri recentemente.
Izak 31/08/16
11
Acabei de tentar ;-) Não, ele realmente não funciona #
Thomas Weller
1

Eu não recomendaria tocar em nada na área de inicialização (exceto config.txt), a menos que você tenha uma compreensão detalhada do que essas coisas estão fazendo. cmdline.txtnão foi projetado para executar coisas quando o RPi é iniciado. É usado para passar parâmetros para o kernel do Linux na inicialização.

Eu sugeriria fazer isso tudo através do SSH. Um script na área de trabalho pode enviar por push um bash / python / java / c / qualquer programa para o RPi, executá-lo e excluí-lo quando terminar. Adicione o encadeamento ao script na área de trabalho e você poderá enviá-lo para quantos dispositivos quiser, tudo ao mesmo tempo.

Jacobm001
fonte
11
É assim que fazemos agora, mas estou procurando uma solução mais fácil.
EDP
11
Leia: execute a instalação iniciada pelo cliente em vez de iniciada pelo servidor
EDP
@ EDP: não há como conseguir o que você deseja, simplesmente editando a posição de inicialização. Você pode escrever um script que extraia o arquivo de um servidor na primeira inicialização do RPi e depois fazer com que esse programa remova o script de inicialização. Isso exigiria o uso de uma imagem personalizada.
Jacobm001
11
"Um script na sua área de trabalho" - Qual script na minha área de trabalho? Na primeira inicialização, não há script na área de trabalho. "Fazendo isso tudo através do SSH" - na primeira inicialização, o Pi pode não ter as configurações corretas de Ethernet ou WLAN.
Thomas Weller
Você nem precisa de encadeamento, confira o projeto de malha. IIRC sua única rewuirement é SSH
Steve Robillard
1

Pode-se argumentar que, se você estiver bem com a modificação da imagem para executar automaticamente um script na primeira inicialização, você pode simplesmente modificar a imagem da maneira que o script faria e, em seguida, salvar o cartão SD em um arquivo de imagem e usá-lo para piscar. Cartões SD que você vai usar com os novos RPis. Por exemplo, se você deseja que todos os seus RPis tenham uma entrada específica /etc/fstab, você pode simplesmente se modificar /etc/fstabem vez de escrever um script que faça a modificação.

Se você precisar absolutamente de ações com script (por exemplo, se cada imagem deve ser modificada de uma maneira diferente), você pode mover /etc/rc.localpara /etc/rc.bake colocar um script no /etc/rc.localqual se substitui /etc/rc.bakno último comando. Esse script pode executar as primeiras ações de inicialização, ou pode chamar um script específico da /bootpartição, se você preferir.

É possível fazer uma execução automática tocando apenas na /bootpartição, fornecendo uma imagem especial de ramdisk de inicialização para o kernel, conforme descrito aqui . Essa imagem conteria os scripts para modificar a partição raiz e depois excluir automaticamente config.txt. Não tenho certeza se vale a pena o problema.

Dmitry Grigoryev
fonte
-2

Você pode querer olhar para o meu projeto Nard, que tem uma solução para o seu problema:

1) Cada cartão SD pode receber uma ID exclusiva com um PC Windows regular, conforme descrito aqui:
http://www.arbetsmyra.dyndns.org/nard/#devsettingsid

2) Ligue todos os seus Pis

3) Desative o firewall do PC, se possível

4) Abra uma janela do prompt do DOS e execute ping no endereço de transmissão da sub-rede

5) Liste a tabela ARP com o comando do Windows "arp -a". Na lista, você encontrará os endereços MAC e IP de todos os Raspberry Pi nas proximidades.

6) Conecte-se a cada dispositivo com telnet (normalmente também disponível no Windows). A frase de boas-vindas exibirá o ID atribuído na etapa 1.

Ronny Nilsson
fonte
Talvez minha descrição inicial não tenha sido clara o suficiente. Não estou particularmente procurando uma maneira de identificar os Pi. Eu já tenho isso coberto. O que estou procurando é executar um ou mais comandos bash alterando o arquivo /boot/cmdline.txt. Tudo isso sem a necessidade de efetuar login via ssh - mesmo uma vez.
EDP
Você provavelmente não poderá 'executar ping no endereço de difusão de sub-rede', geralmente são impedidos ataques de Smurf.
CrackerJack9