Compare commits

..

1 commit

Author SHA1 Message Date
074d5f5f25 Change butter site location for kanglam 2026-01-23 13:24:48 +00:00
41 changed files with 275 additions and 1751 deletions

56
LICENCE
View file

@ -1,56 +0,0 @@
Copyright © 2017, Michael Stapelberg and contributors
Copyright © 2021, guardianproject
Copyright © 2025-2026, SR2 Communications Limited
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Michael Stapelberg nor the
names of contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Additionally, we include here the original licence terms as required by the terms
of the Butter Box for Raspberry Pi project, which apply only to the code derived
from that project:
MIT License
Copyright (c) 2021 guardianproject
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,34 +1,5 @@
# Butter Churn # Churn
[![License](https://img.shields.io/badge/License-BSD_3--Clause-orange.svg)](https://opensource.org/licenses/BSD-3-Clause) This is work in progress.
Tool for building Butter Box disk images. To build a butter image, run vmdb2 --rootfs-tarball=my_image.tar.gz --output my_image.img --log my_image.log
## Usage
To build a butter image, run:
```sh
vmdb2 --rootfs-tarball=my_image.tar.gz --output my_image.img --log my_image.log
```
## Advanced Documentation
* [Creating an upgrade content pack for the Butter Box](./docs/upgrade_pack.md)
* [Set up DeltaChat messaging between multiple boxes](./docs/multibox.md)
* [Extending Butter Box connectivity with WiFi HaLow and LoRa](./docs/wireless_connectivity.md)
## Copyright and Licence
&copy; 2017, Michael Stapelberg and contributors<br>
&copy; 2021, guardianproject<br>
&copy; 2025-2026, SR2 Communications Limited
See [LICENCE](./LICENCE) for details of the BSD-3 clause licence.
This project is a derivative work of:
* [Butter Box for Raspberry Pi](https://gitlab.com/likebutter/butterbox-rpi)
licensed under the MIT licence.
* [Raspberry Pi Image Specs](https://salsa.debian.org/raspi-team/image-specs)
licensed under the 3-clause BSD licence.

View file

@ -13,28 +13,28 @@
name: "{{ butter_user }}" name: "{{ butter_user }}"
state: present state: present
- name: Add butter user to sudo group - name: Get supported interface modes
ansible.builtin.user: command: iw list
name: "{{ butter_user }}" register: iw_list
groups: sudo ignore_errors: yes
append: true when: not (is_vmdb2 | bool)
- name: Allow passwordless sudo for butter user - name: Search for AP mode support
ansible.builtin.lineinfile: set_fact:
path: /etc/sudoers ap_mode_supported: "{{ 'AP' in iw_list.stdout }}"
state: present when: not (is_vmdb2 | bool)
regexp: "^{{ butter_user }}"
line: "{{ butter_user }} ALL=(ALL) NOPASSWD:ALL" - name: Show AP mode support result
validate: '/usr/sbin/visudo -cf %s' debug:
msg: >
Wi-Fi AP mode supported: {{ ap_mode_supported }}
when: not (is_vmdb2 | bool)
- name: Make sure /etc/resolv.conf is populated - name: Make sure /etc/resolv.conf is populated
ansible.builtin.lineinfile: lineinfile:
path: /etc/resolv.conf path: /etc/resolv.conf
regexp: '^nameserver 1.1.1.1' regexp: '^nameserver 1.1.1.1'
line: 'nameserver 1.1.1.1' line: 'nameserver 1.1.1.1'
state: present state: present
insertafter: EOF insertafter: EOF
create: true create: yes
owner: root
group: root
mode: '0644'

View file

@ -4,9 +4,11 @@
become: true become: true
tasks: tasks:
- name: Print Dendrite process info for debugging - name: Print Dendrite process info for debugging
become: yes
ansible.builtin.shell: | ansible.builtin.shell: |
echo "=== Dendrite PIDs ===" echo "=== Dendrite PIDs ==="
pgrep -u {{ butter_user }} -f dendrite || echo "No dendrite PIDs found" pgrep -u {{ butter_user }} -f dendrite || echo "No dendrite PIDs found"
echo echo
echo "=== Full process tree of Dendrite ===" echo "=== Full process tree of Dendrite ==="
for pid in $(pgrep -u {{ butter_user }} -f dendrite); do for pid in $(pgrep -u {{ butter_user }} -f dendrite); do
@ -14,18 +16,24 @@
pstree -p $pid || echo "pstree not available for PID $pid" pstree -p $pid || echo "pstree not available for PID $pid"
echo echo
done done
echo "=== Open files under VMDB mount ==="
lsof +D /tmp/tmpyu_8dsew || echo "No open files found"
echo "=== Current working directories of processes in mount ==="
lsof +D /tmp/tmpyu_8dsew | awk '{print $2, $NF}' | sort | uniq
register: dendrite_debug register: dendrite_debug
when: is_vmdb2 | bool when: is_vmdb2 | bool
changed_when: false
- name: Show debug output - name: Show debug output
ansible.builtin.debug: debug:
msg: "{{ dendrite_debug.stdout_lines }}" msg: "{{ dendrite_debug.stdout_lines }}"
when: is_vmdb2 | bool when: is_vmdb2 | bool
- name: Kill any running Dendrite process - name: Kill any running Dendrite process
become: yes
ansible.builtin.shell: | ansible.builtin.shell: |
set -o pipefail && pgrep -u {{ butter_user }} -f dendrite | xargs -r kill -9 pgrep -u {{ butter_user }} -f dendrite | xargs -r kill -9
register: dendrite_cleanup register: dendrite_cleanup
changed_when: dendrite_cleanup.stdout != "" changed_when: dendrite_cleanup.stdout != ""
when: is_vmdb2 | bool when: is_vmdb2 | bool
@ -36,6 +44,6 @@
when: is_vmdb2 | bool when: is_vmdb2 | bool
- name: Give processes time to exit - name: Give processes time to exit
ansible.builtin.pause: become: yes
seconds: 5 shell: sleep 5
when: is_vmdb2 | bool when: is_vmdb2 | bool

View file

View file

@ -1,33 +0,0 @@
---
- name: Install madmail/deltachat
hosts: all
become: true
tasks:
- name: Create madmail directory
ansible.builtin.file:
path: "/home/{{ butter_user }}/madmail"
state: directory
owner: "{{ butter_user }}"
group: "{{ butter_user }}"
mode: "0755"
- name: Download pre-built madmail archive
ansible.builtin.get_url:
url: "https://github.com/themadorg/madmail/releases/download/v0.12.7/madmail-linux-{{ go_arch_map[ansible_architecture] }}.tar.gz"
dest: "/tmp/madmail-linux-{{ go_arch_map[ansible_architecture] }}.tar.gz"
mode: '0644'
- name: Untar madmail
ansible.builtin.unarchive:
src: "/tmp/madmail-linux-{{ go_arch_map[ansible_architecture] }}.tar.gz"
dest: "/home/{{ butter_user }}/madmail"
remote_src: true
# extra_opts: [--strip-components=1]
- name: Ensure butter_user owns madmail directory
ansible.builtin.file:
path: "/home/{{ butter_user }}/madmail"
state: directory
recurse: true
owner: "{{ butter_user }}"
group: "{{ butter_user }}"

View file

@ -1,102 +0,0 @@
---
- name: Deploy butter portal
hosts: all
become: true
tasks:
- name: "Ensure /tmp/butter-portal is absent"
ansible.builtin.file:
path: "/home/{{ butter_user }}/butter-portal"
state: absent
- name: "Clone the portal repo"
ansible.builtin.git:
repo: "https://guardianproject.dev/butter/butter-portal"
dest: "/home/{{ butter_user }}/butter-portal"
version: main
- name: Install requirements
ansible.builtin.pip:
requirements: "/home/{{ butter_user }}/butter-portal/requirements.txt"
virtualenv: "/home/{{ butter_user }}/portal_env"
virtualenv_python: python3
- name: Seed database
ansible.builtin.shell: |
echo "Starting db initialisation!"
source /home/{{ butter_user }}/portal_env/bin/activate
flask db init
flask db migrate
flask db upgrade
flask seed-settings
args:
chdir: "/home/{{ butter_user }}/butter-portal"
executable: /bin/bash
creates: "/home/{{ butter_user }}/butter-portal/app.db"
register: database_init
- name: Template portal systemd service file
ansible.builtin.template:
src: templates/butterbox-portal.service.j2
dest: /lib/systemd/system/butterbox-portal.service
owner: root
group: root
mode: '0644'
- name: Template portal change manager service file
ansible.builtin.template:
src: templates/change-manager.service.j2
dest: /lib/systemd/system/change-manager.service
owner: root
group: root
mode: '0644'
- name: Template nginx config
ansible.builtin.template:
src: templates/nginx-config.j2
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: '0644'
- name: Enable portal by symlink
ansible.builtin.file:
src: /lib/systemd/system/butterbox-portal.service
dest: /etc/systemd/system/multi-user.target.wants/butterbox-portal.service
state: link
- name: Enable change manager by symlink
ansible.builtin.file:
src: /lib/systemd/system/change-manager.service
dest: /etc/systemd/system/multi-user.target.wants/change-manager.service
state: link
- name: Ensure butter_user owns portal directory
ansible.builtin.file:
path: "/home/{{ butter_user }}/butter-portal"
state: directory
recurse: true
owner: "{{ butter_user }}"
group: "{{ butter_user }}"
# - name: Template portal reverse proxy config for Lighttpd
# ansible.builtin.get_url:
# src: templates/50-butter-portal-reverse-proxy.conf
# dest: /etc/lighttpd/conf-available/50-butter-portal-reverse-proxy.conf
# owner: root
# group: root
# mode: '0644'
#
# - name: Ensure old symlink is removed if it exists
# ansible.builtin.file:
# path: /etc/lighttpd/conf-enabled/50-butter-portal-reverse-proxy.conf
# state: absent
# force: true
#
# - name: Enable reverse proxy config for portal in Lighttpd
# ansible.builtin.file:
# src: /etc/lighttpd/conf-available/50-butter-portal-reverse-proxy.conf
# dest: /etc/lighttpd/conf-enabled/50-butter-portal-reverse-proxy.conf
# state: link
# force: true
#
# - debug: var=database_init.stdout_lines

View file

@ -0,0 +1,70 @@
---
- name: Deploy butter site
hosts: all
become: true
tasks:
- name: Install unzip
apt:
name:
- unzip
state: present
update_cache: yes
when: not ( is_vmdb2 | bool )
- name: Ensure /etc/resolv.conf contains nameserver 1.1.1.1
copy:
dest: /etc/resolv.conf
content: "nameserver 1.1.1.1\n"
owner: root
group: root
mode: '0644'
when: is_vmdb2 | bool
- name: Ensure /tmp/butter-site is absent
file:
path: /tmp/butter-site
state: absent
- name: Ensure /tmp/site.zip is absent
file:
path: /tmp/site.zip
state: absent
- name: Download the butter-box UI zip file
get_url:
url: "https://guardianproject.dev/api/packages/butter/generic/butter-kanglam-ui/latest/kanglam-ui.tar.gz"
dest: /tmp/site.tar.gz
mode: '0644'
- name: Ensure /tmp/butter-site directory exists
file:
path: /tmp/butter-site
state: directory
mode: '0755'
- name: Unarchive site.tar.gz to /tmp/butter-site
unarchive:
src: /tmp/site.tar.gz
dest: /var/www/html
remote_src: yes
extra_opts: [--strip-components=1]
- name: Set permissions for /var/www/html/
become: true
file:
path: /var/www/html/
owner: www-data
group: www-data
mode: '0755'
recurse: yes
# - name: List files in remote directory
# ansible.builtin.find:
# paths: /var/www/html
# file_type: any
# register: dir_contents
#
# - name: Print directory contents
# ansible.builtin.debug:
# msg: "{{ dir_contents.files | map(attribute='path') | list }}"

View file

@ -4,6 +4,8 @@ go_version: "1.24.6"
go_arch_map: go_arch_map:
x86_64: "amd64" x86_64: "amd64"
aarch64: "arm64" aarch64: "arm64"
script_base_url: "https://gitlab.com/likebutter/butterbox-rpi/-/raw/main/scripts"
config_base_url: "https://gitlab.com/likebutter/butterbox-rpi/-/raw/main/configs"
vmdb2_script_base_dir: "butterbox-rpi/scripts" vmdb2_script_base_dir: "butterbox-rpi/scripts"
vmdb2_config_base_dir: "butterbox-rpi/configs" vmdb2_config_base_dir: "butterbox-rpi/configs"

View file

@ -10,4 +10,4 @@
register: firmware_update register: firmware_update
changed_when: firmware_update.rc == 0 changed_when: firmware_update.rc == 0
failed_when: firmware_update.rc != 0 failed_when: firmware_update.rc != 0
ignore_errors: true ignore_errors: yes

View file

@ -4,18 +4,18 @@
become: true become: true
tasks: tasks:
- name: Install deps - name: Install deps
ansible.builtin.apt: apt:
name: name:
- git - git
- vim - vim
- lighttpd - lighttpd
- sudo - sudo
state: present state: present
update_cache: true update_cache: yes
when: not (is_vmdb2 | bool) when: not ( is_vmdb2 | bool )
- name: Create dendrite directories - name: Create dendrite directories
ansible.builtin.file: file:
path: "/home/{{ butter_user }}/dendrite/bin" path: "/home/{{ butter_user }}/dendrite/bin"
state: directory state: directory
owner: "{{ butter_user }}" owner: "{{ butter_user }}"
@ -23,72 +23,94 @@
mode: "0755" mode: "0755"
- name: Download pre-built dendrite archive - name: Download pre-built dendrite archive
ansible.builtin.get_url: get_url:
url: "https://guardianproject.dev/api/packages/butter/generic/dendrite/latest/dendrite-{{ go_arch_map[ansible_architecture] }}.tar.gz" url: "https://guardianproject.dev/api/packages/butter/generic/dendrite/latest/dendrite-{{ go_arch_map[ansible_architecture] }}.tar.gz"
dest: /tmp dest: /tmp
mode: '0644' mode: '0644'
- name: Untar dendrite - name: Untar dendrite
ansible.builtin.unarchive: unarchive:
src: "/tmp/dendrite-{{ go_arch_map[ansible_architecture] }}.tar.gz" src: "/tmp/dendrite-{{ go_arch_map[ansible_architecture] }}.tar.gz"
dest: "/home/{{ butter_user }}/dendrite/bin" dest: "/home/{{ butter_user }}/dendrite/bin"
remote_src: true remote_src: yes
extra_opts: [--strip-components=2] extra_opts: [--strip-components=2]
- name: Ensure butter_user owns Dendrite directory - name: Ensure butter_user owns Dendrite directory
ansible.builtin.file: file:
path: "/home/{{ butter_user }}/dendrite" path: "/home/{{ butter_user }}/dendrite"
state: directory state: directory
recurse: true recurse: yes
- name: Generate Matrix signing key - name: Generate Matrix signing key
ansible.builtin.command: ./bin/generate-keys --private-key matrix_key.pem command: ./bin/generate-keys --private-key matrix_key.pem
args: args:
creates: "/home/{{ butter_user }}/dendrite/matrix_key.pem"
chdir: "/home/{{ butter_user }}/dendrite" chdir: "/home/{{ butter_user }}/dendrite"
- name: Generate self-signed TLS certificate (optional) - name: Generate self-signed TLS certificate (optional)
ansible.builtin.command: ./bin/generate-keys --tls-cert server.crt --tls-key server.key command: ./bin/generate-keys --tls-cert server.crt --tls-key server.key
args: args:
chdir: "/home/{{ butter_user }}/dendrite" chdir: "/home/{{ butter_user }}/dendrite"
creates: "/home/{{ butter_user }}/dendrite/server.key"
- name: Download Dendrite config to target - name: Download Dendrite config to target
ansible.builtin.template: get_url:
src: "templates/butterbox-dendrite.conf.j2" url: "{{ config_base_url }}/butterbox-dendrite.conf"
dest: "/home/{{ butter_user }}/dendrite/butterbox-dendrite.conf" dest: "/home/{{ butter_user }}/dendrite/butterbox-dendrite.conf"
owner: "{{ butter_user }}" owner: "{{ butter_user }}"
group: "{{ butter_user }}" group: "{{ butter_user }}"
mode: '0644' mode: '0644'
- name: Replace REPLACEME with butter_name in config
replace:
path: "/home/{{ butter_user }}/dendrite/butterbox-dendrite.conf"
regexp: 'REPLACEME'
replace: "{{ butter_name }}"
- name: Replace /home/pi with /home/butter_user in config
replace:
path: "/home/{{ butter_user }}/dendrite/butterbox-dendrite.conf"
regexp: '/pi/'
replace: "/{{ butter_user }}/"
- name: Create log directory for Dendrite - name: Create log directory for Dendrite
ansible.builtin.file: file:
path: "/var/log/dendrite" path: "/var/log/dendrite"
state: directory state: directory
owner: "{{ butter_user }}" owner: "{{ butter_user }}"
group: "{{ butter_user }}" group: "{{ butter_user }}"
mode: '0755' mode: '0755'
recurse: true recurse: yes
- name: template dendrite systemd service file - name: Download dendrite systemd service file
ansible.builtin.template: get_url:
src: templates/butterbox-dendrite.service.j2 url: "{{ config_base_url }}/butterbox-dendrite.service"
dest: /lib/systemd/system/dendrite.service dest: /lib/systemd/system/dendrite.service
owner: root owner: root
group: root group: root
mode: '0644' mode: '0644'
- name: Replace /home/pi with /home/butter_user in service file
replace:
path: /lib/systemd/system/dendrite.service
regexp: '/pi/'
replace: "/{{ butter_user }}/"
- name: Replace pi with butter_user in service file
replace:
path: /lib/systemd/system/dendrite.service
regexp: 'User=pi'
replace: "User={{ butter_user }}"
- name: Enable dendrite by symlink - name: Enable dendrite by symlink
ansible.builtin.file: file:
src: /lib/systemd/system/dendrite.service src: /lib/systemd/system/dendrite.service
dest: /etc/systemd/system/multi-user.target.wants/dendrite.service dest: /etc/systemd/system/multi-user.target.wants/dendrite.service
state: link state: link
- name: Ensure butter_user owns Dendrite directory - name: Ensure butter_user owns Dendrite directory
ansible.builtin.file: file:
path: "/home/{{ butter_user }}/dendrite" path: "/home/{{ butter_user }}/dendrite"
state: directory state: directory
recurse: true recurse: yes
owner: "{{ butter_user }}" owner: "{{ butter_user }}"
group: "{{ butter_user }}" group: "{{ butter_user }}"
mode: "0755" mode: "0755"
@ -100,10 +122,31 @@
name: dendrite name: dendrite
when: not (is_vmdb2 | bool) when: not (is_vmdb2 | bool)
- name: Download Matrix reverse proxy config for Lighttpd
get_url:
url: "{{ config_base_url }}/50-matrix-reverse-proxy.conf"
dest: /etc/lighttpd/conf-available/50-matrix-reverse-proxy.conf
owner: root
group: root
mode: '0644'
- name: Ensure old symlink is removed if it exists
file:
path: /etc/lighttpd/conf-enabled/50-matrix-reverse-proxy.conf
state: absent
force: true
- name: Enable reverse proxy config for Matrix in Lighttpd
file:
src: /etc/lighttpd/conf-available/50-matrix-reverse-proxy.conf
dest: /etc/lighttpd/conf-enabled/50-matrix-reverse-proxy.conf
state: link
force: true
- name: Start dendrite as user butter_user - name: Start dendrite as user butter_user
become: true become: yes
become_user: "{{ butter_user }}" become_user: "{{ butter_user }}"
ansible.builtin.shell: | shell: |
nohup /home/{{ butter_user }}/dendrite/bin/dendrite \ nohup /home/{{ butter_user }}/dendrite/bin/dendrite \
--config /home/{{ butter_user }}/dendrite/butterbox-dendrite.conf \ --config /home/{{ butter_user }}/dendrite/butterbox-dendrite.conf \
-really-enable-open-registration \ -really-enable-open-registration \
@ -111,10 +154,10 @@
args: args:
chdir: "/home/{{ butter_user }}" chdir: "/home/{{ butter_user }}"
when: is_vmdb2 | bool when: is_vmdb2 | bool
changed_when: false
- name: Wait for Dendrite client API to be available - name: Wait for Dendrite client API to be available
ansible.builtin.wait_for: wait_for:
host: "127.0.0.1" host: "127.0.0.1"
port: 8008 port: 8008
delay: 3 # wait a few seconds before first check delay: 3 # wait a few seconds before first check
@ -123,17 +166,16 @@
when: is_vmdb2 | bool when: is_vmdb2 | bool
- name: Copy public room script - name: Copy public room script
ansible.builtin.template: template:
src: templates/create_public_room.sh.j2 src: templates/create_public_room.sh.j2
dest: "/home/{{ butter_user }}/create_public_room.sh" dest: "/home/{{ butter_user }}/create_public_room.sh"
mode: '0755' mode: '0755'
- name: Run the create_public_room.sh script - name: Run the create_public_room.sh script
ansible.builtin.command: "/home/{{ butter_user }}/create_public_room.sh" command: "/home/{{ butter_user }}/create_public_room.sh"
register: room_creation register: room_creation
ignore_errors: false ignore_errors: false
changed_when: false
- name: Show room creation output - name: Show room creation output
ansible.builtin.debug: debug:
var: room_creation.stdout var: room_creation.stdout

View file

@ -4,21 +4,20 @@
become: true become: true
tasks: tasks:
- name: Install Node.js 22 (needed for matrix-js-sdk) - name: Install Node.js 22 (needed for matrix-js-sdk)
ansible.builtin.shell: | shell: |
set -o pipefail curl -fsSL https://deb.nodesource.com/setup_22.x | bash - curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs apt-get install -y nodejs
args: args:
executable: /bin/bash executable: /bin/bash
creates: /bin/npm
- name: Ensure previous keanu-weblite temp directory is removed - name: Ensure previous keanu-weblite temp directory is removed
ansible.builtin.file: file:
path: /tmp/keanu-weblite path: /tmp/keanu-weblite
state: absent state: absent
delegate_to: localhost delegate_to: localhost
- name: Clone keanu-weblite repository (dev branch) - name: Clone keanu-weblite repository (dev branch)
ansible.builtin.git: git:
repo: https://gitlab.com/keanuapp/keanuapp-weblite.git repo: https://gitlab.com/keanuapp/keanuapp-weblite.git
dest: /tmp/keanu-weblite dest: /tmp/keanu-weblite
version: dev version: dev
@ -26,43 +25,51 @@
delegate_to: localhost delegate_to: localhost
- name: Run npm install - name: Run npm install
ansible.builtin.command: npm install shell: npm install
args: args:
chdir: /tmp/keanu-weblite chdir: /tmp/keanu-weblite
delegate_to: localhost delegate_to: localhost
changed_when: false
- name: Download keanu-weblite config file - name: Download keanu-weblite config file
ansible.builtin.template: get_url:
src: "templates/keanu-weblite-config.json.j2" url: "{{ config_base_url }}/keanu-weblite-config.json"
dest: /tmp/keanu-weblite/src/assets/config.json dest: /tmp/keanu-weblite/src/assets/config.json
mode: '0644' mode: '0644'
delegate_to: localhost delegate_to: localhost
- name: Replace REPLACEME with butter_name in config.json
replace:
path: /tmp/keanu-weblite/src/assets/config.json
regexp: 'REPLACEME'
replace: "{{ butter_name }}"
delegate_to: localhost
- name: Run npm build with legacy OpenSSL option - name: Run npm build with legacy OpenSSL option
ansible.builtin.shell: | shell: |
export NODE_OPTIONS=--openssl-legacy-provider export NODE_OPTIONS=--openssl-legacy-provider
npm run build npm run build
args: args:
chdir: /tmp/keanu-weblite chdir: /tmp/keanu-weblite
delegate_to: localhost delegate_to: localhost
changed_when: false
- name: Copy build output to /var/www/html/chat - name: Copy build output to /var/www/html/chat
ansible.builtin.copy: become: true
src: /tmp/keanu-weblite/dist/ copy:
dest: /var/www/html/chat/ src: /tmp/keanu-weblite/dist/
mode: '0755' dest: /var/www/html/chat/
- name: Set permissions for /var/www/html/chat - name: Set permissions for /var/www/html/chat
ansible.builtin.file: become: true
file:
path: /var/www/html/chat path: /var/www/html/chat
owner: www-data owner: www-data
group: www-data group: www-data
mode: '0755' mode: '0755'
recurse: true recurse: yes
- name: Restart lighttpd service
ansible.builtin.systemd:
name: lighttpd
state: restarted
when: not (is_vmdb2 | bool)
- name: "Ensure systemd-timesyncd is disabled"
ansible.builtin.file:
path: "/etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service"
state: absent

View file

@ -9,19 +9,13 @@
raspap_adblock: 0 raspap_adblock: 0
tasks: tasks:
- name: ensure lighttpd listens on port 8080
ansible.builtin.lineinfile:
path: /etc/lighttpd/lighttpd.conf
regexp: '^server.port'
line: server.port=8080
- name: Check if RaspAP is already installed - name: Check if RaspAP is already installed
ansible.builtin.stat: ansible.builtin.stat:
path: /var/www/html/raspap path: /var/www/html/admin
register: raspap_stat register: raspap_stat
- name: Download RaspAP install script - name: Download RaspAP install script
ansible.builtin.get_url: get_url:
url: https://install.raspap.com url: https://install.raspap.com
dest: /tmp/raspap_install.sh dest: /tmp/raspap_install.sh
mode: "0755" mode: "0755"
@ -29,7 +23,7 @@
- name: Run RaspAP install script - name: Run RaspAP install script
ansible.builtin.shell: | ansible.builtin.shell: |
pwd && ls -alh / && /usr/bin/bash /tmp/raspap_install.sh --yes --path /var/www/html/raspap \ pwd && ls -alh / && /usr/bin/bash /tmp/raspap_install.sh --yes --path /var/www/html/admin \
--check 0 \ --check 0 \
--wireguard {{ raspap_wireguard }} \ --wireguard {{ raspap_wireguard }} \
--openvpn {{ raspap_openvpn }} \ --openvpn {{ raspap_openvpn }} \
@ -41,44 +35,45 @@
failed_when: raspap_install.rc != 0 failed_when: raspap_install.rc != 0
- name: Remove /var/www/html.* directories if they exist - name: Remove /var/www/html.* directories if they exist
become: true
ansible.builtin.shell: | ansible.builtin.shell: |
find /var/www/html.* -maxdepth 0 -type d -exec rm -r {} \; || : find /var/www/html.* -maxdepth 0 -type d -exec rm -r {} \; || :
changed_when: false changed_when: false
- name: Ensure /etc/hostapd directory exists - name: Ensure /etc/hostapd directory exists
ansible.builtin.file: file:
path: /etc/hostapd path: /etc/hostapd
state: directory state: directory
mode: '0755' mode: '0755'
- name: Template RaspAP network config to target - name: Template RaspAP network config to target
ansible.builtin.template: template:
src: "hostapd.conf.j2" src: "hostapd.conf.j2"
dest: "/etc/hostapd/hostapd.conf" dest: "/etc/hostapd/hostapd.conf"
mode: '0644' mode: '0644'
- name: Copy hostapd set_hostapd_iface config script - name: Copy hostapd set_hostapd_iface config script
ansible.builtin.template: template:
src: "set_hostapd_iface.py" src: "set_hostapd_iface.py"
dest: "/usr/local/bin/set_hostapd_iface.py" dest: "/usr/local/bin/set_hostapd_iface.py"
mode: '0744' mode: '0755'
- name: Template hostapd set_hostapd_iface service file - name: Copy hostapd set_hostapd_iface service file
ansible.builtin.template: template:
src: "set-hostapd-iface.service.j2" src: "set-hostapd-iface.service.j2"
dest: "/lib/systemd/system/set-hostapd-iface.service" dest: "/lib/systemd/system/set-hostapd-iface.service"
mode: '0644' mode: '0755'
- name: Template hostapd raspapd systemd service file - name: Download hostapd raspapd systemd service file
ansible.builtin.template: get_url:
src: "templates/raspapd.service.j2" url: "{{ config_base_url }}/raspapd.service"
dest: "/lib/systemd/system/raspapd.service" dest: "/lib/systemd/system/raspapd.service"
owner: root owner: root
group: root group: root
mode: '0644' mode: '0644'
- name: Enable service raspapd, avahi-daemon, and set_hostapd_iface by symlink - name: Enable service raspapd, avahi-daemon, and set_hostapd_iface by symlink
ansible.builtin.file: file:
src: "/lib/systemd/system/{{ item }}" src: "/lib/systemd/system/{{ item }}"
dest: "/etc/systemd/system/multi-user.target.wants/{{ item }}" dest: "/etc/systemd/system/multi-user.target.wants/{{ item }}"
state: link state: link
@ -87,17 +82,20 @@
- "set-hostapd-iface.service" - "set-hostapd-iface.service"
- "avahi-daemon.service" - "avahi-daemon.service"
- name: Disable service raspapd restapi service
ansible.builtin.file:
path: "/etc/systemd/system/multi-user.target.wants/{{ item }}"
state: absent
with_items:
- "restapi.service"
- name: Copy dnsmasq config - name: Copy dnsmasq config
ansible.builtin.template: template:
src: "butterbox-dnsmasq.conf.j2" src: "butterbox-dnsmasq.conf.j2"
dest: /etc/dnsmasq.d/butterbox-dnsmasq.conf dest: /etc/dnsmasq.d/butterbox-dnsmasq.conf
owner: root owner: root
group: root group: root
mode: '0644' mode: '0644'
- name: Restart service raspapd, issue daemon-reload to pick up config changes
ansible.builtin.systemd_service:
state: restarted
daemon_reload: true
name: "{{ item }}"
when: not (is_vmdb2 | bool)
with_items:
- raspapd
- set-hostapd-iface

View file

@ -4,31 +4,51 @@
become: true become: true
tasks: tasks:
- name: Copy systemd services - name: Copy systemd services
ansible.builtin.template: copy:
src: "{{ item }}" src: "{{ vmdb2_config_base_dir }}/{{ item }}"
dest: "/etc/systemd/system/{{ item }}" dest: "/etc/systemd/system/{{ item }}"
mode: '0644'
loop: loop:
- udisks2-mount@.service - udisks2-mount@.service
- serve-usb@.service - serve-usb@.service
- name: Enable services by symlink - name: Enable services by symlink
ansible.builtin.file: file:
src: "/etc/systemd/system/{{ item }}" src: "/etc/systemd/system/{{ item }}"
dest: "/etc/systemd/system/multi-user.target.wants/{{ item }}" dest: "/etc/systemd/system/multi-user.target.wants/{{ item }}"
state: link state: link
loop: loop:
- udisks2-mount@.service - udisks2-mount@.service
- serve-usb@.service - serve-usb@.service
- name: Copy web UI assets (remote to remote)
copy:
src: "/var/www/html/assets/{{ item.src }}"
dest: "/var/www/html/{{ item.dest }}"
remote_src: true
loop:
- { src: "css/butter-dir-listing.css", dest: "butter-dir-listing.css" }
- { src: "js/butter-dir-listing.js", dest: "butter-dir-listing.js" }
- name: Install Lighttpd USB config
copy:
src: "{{ vmdb2_config_base_dir }}/50-usb-butter.conf"
dest: "/etc/lighttpd/conf-available/50-usb-butter.conf"
- name: Install udev rule - name: Install udev rule
ansible.builtin.copy: copy:
src: "templates/99-usb-butter.rules" src: "{{ vmdb2_config_base_dir }}/99-usb-butter.rules"
dest: "/etc/udev/rules.d/99-usb-butter.rules" dest: "/etc/udev/rules.d/99-usb-butter.rules"
mode: '0644'
- name: Install udev trigger script - name: Install udev trigger script
ansible.builtin.template: copy:
src: templates/on-usb-drive-mounted.sh.j2 src: "{{ vmdb2_script_base_dir }}/on-usb-drive-mounted.sh"
dest: /usr/bin/on-usb-drive-mounted.sh dest: /usr/bin/on-usb-drive-mounted.sh
mode: '0744' mode: '0755'
- name: Reload udev rules
command: udevadm control --reload-rules
when: not (is_vmdb2 | bool)
- name: Reload systemd daemon
command: systemctl daemon-reload
when: not (is_vmdb2 | bool)

View file

@ -7,12 +7,10 @@
- import_playbook: install-rasp-ap.yml - import_playbook: install-rasp-ap.yml
tags: "ap" tags: "ap"
when: ap_mode_supported | bool when: ap_mode_supported | bool
- import_playbook: deploy-butter-portal.yml - import_playbook: deploy-butter-site.yml
tags: tags:
- "website" - "website"
- "usb" - "usb"
- import_playbook: delta-chat.yml
tags: "delta-chat"
- import_playbook: install-chat.yml - import_playbook: install-chat.yml
tags: "matrix" tags: "matrix"
- import_playbook: cleanup.yml - import_playbook: cleanup.yml

View file

@ -4,8 +4,8 @@
become: true become: true
tasks: tasks:
- name: Copy wpa_supplicant config - name: Copy wpa_supplicant config
ansible.builtin.template: copy:
src: "wpa_supplicant.conf" src: "{{ vmdb2_config_base_dir }}/wpa_supplicant.conf"
dest: /etc/wpa_supplicant/wpa_supplicant.conf dest: /etc/wpa_supplicant/wpa_supplicant.conf
force: true force: true
mode: '0644' mode: '0644'

View file

@ -1,11 +0,0 @@
# Using udev to mount newly attached usb drives doesn't work.
# https://unix.stackexchange.com/a/507150/223286
# So, we depend on udisks to mount the disk. *Then* we want to
# to setup the symlink.
# We can run the script immediately because it waits for the disk
# to be mounted.
# Mount newly inserted drives, creating the directory if it doesn't exist
ACTION=="add", KERNEL=="sd[a-z][1-9]", ENV{ID_BUS}=="usb", TAG+="systemd", ENV{SYSTEMD_WANTS}+="udisks2-mount@%k.service", ENV{SYSTEMD_WANTS}+="serve-usb@%k.service"
# When the disk is `unmounted, the symlink will just point to a now-empty directory.

View file

@ -1,380 +0,0 @@
# This is the Dendrite configuration file.
#
# The configuration is split up into sections - each Dendrite component has a
# configuration section, in addition to the "global" section which applies to
# all components.
#
# At a minimum, to get started, you will need to update the settings in the
# "global" section for your deployment, and you will need to check that the
# database "connection_string" line in each component section is correct.
#
# Each component with a "database" section can accept the following formats
# for "connection_string":
# SQLite: file:filename.db
# file:///path/to/filename.db
# PostgreSQL: postgresql://user:pass@hostname/database?params=...
#
# SQLite is embedded into Dendrite and therefore no further prerequisites are
# needed for the database when using SQLite mode. However, performance with
# PostgreSQL is significantly better and recommended for multi-user deployments.
# SQLite is typically around 20-30% slower than PostgreSQL when tested with a
# small number of users and likely will perform worse still with a higher volume
# of users.
#
# The "max_open_conns" and "max_idle_conns" settings configure the maximum
# number of open/idle database connections. The value 0 will use the database
# engine default, and a negative value will use unlimited connections. The
# "conn_max_lifetime" option controls the maximum length of time a database
# connection can be idle in seconds - a negative value is unlimited.
# The version of the configuration file.
version: 2
# Global Matrix configuration. This configuration applies to all components.
global:
# The domain name of this homeserver.
server_name: {{ butter_name }}.local
# The path to the signing private key file, used to sign requests and events.
# Note that this is NOT the same private key as used for TLS! To generate a
# signing key, use "./bin/generate-keys --private-key matrix_key.pem".
private_key: /home/{{ butter_user }}/dendrite/matrix_key.pem
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
# to old signing private keys that were formerly in use on this domain. These
# keys will not be used for federation request or event signing, but will be
# provided to any other homeserver that asks when trying to verify old events.
# old_private_keys:
# - private_key: old_matrix_key.pem
# expired_at: 1601024554498
# How long a remote server can cache our server signing key before requesting it
# again. Increasing this number will reduce the number of requests made by other
# servers for our key but increases the period that a compromised key will be
# considered valid by other homeservers.
key_validity_period: 168h0m0s
# The server name to delegate server-server communications to, with optional port
# e.g. localhost:443
well_known_server_name: ""
# Lists of domains that the server will trust as identity servers to verify third
# party identifiers such as phone numbers and email addresses.
trusted_third_party_id_servers:
- matrix.org
- vector.im
# Disables federation. Dendrite will not be able to make any outbound HTTP requests
# to other servers and the federation API will not be exposed.
disable_federation: false
# Configures the handling of presence events.
presence:
# Whether inbound presence events are allowed, e.g. receiving presence events from other servers
enable_inbound: false
# Whether outbound presence events are allowed, e.g. sending presence events to other servers
enable_outbound: false
# Server notices allows server admins to send messages to all users.
server_notices:
enabled: false
# The server localpart to be used when sending notices, ensure this is not yet taken
local_part: "_server"
# The displayname to be used when sending notices
display_name: "Server alerts"
# The mxid of the avatar to use
avatar_url: ""
# The roomname to be used when creating messages
room_name: "Server Alerts"
# Configuration for NATS JetStream
jetstream:
# A list of NATS Server addresses to connect to. If none are specified, an
# internal NATS server will be started automatically when running Dendrite
# in monolith mode. It is required to specify the address of at least one
# NATS Server node if running in polylith mode.
addresses:
# - localhost:4222
# Keep all NATS streams in memory, rather than persisting it to the storage
# path below. This option is present primarily for integration testing and
# should not be used on a real world Dendrite deployment.
in_memory: false
# Persistent directory to store JetStream streams in. This directory
# should be preserved across Dendrite restarts.
storage_path: /home/{{ butter_user }}/dendrite/jetstream
# The prefix to use for stream names for this homeserver - really only
# useful if running more than one Dendrite on the same NATS deployment.
topic_prefix: Dendrite
# Configuration for Prometheus metric collection.
metrics:
# Whether or not Prometheus metrics are enabled.
enabled: false
# HTTP basic authentication to protect access to monitoring.
basic_auth:
username: metrics
password: metrics
# DNS cache options. The DNS cache may reduce the load on DNS servers
# if there is no local caching resolver available for use.
dns_cache:
# Whether or not the DNS cache is enabled.
enabled: false
# Maximum number of entries to hold in the DNS cache, and
# for how long those items should be considered valid in seconds.
cache_size: 256
cache_lifetime: "5m" # 5minutes; see https://pkg.go.dev/time@master#ParseDuration for more
# Configuration for the Appservice API.
app_service_api:
internal_api:
listen: http://localhost:7777 # Only used in polylith deployments
connect: http://localhost:7777 # Only used in polylith deployments
database:
connection_string: file:///home/{{ butter_user }}/dendrite/appservice.db
max_open_conns: 10
max_idle_conns: 2
conn_max_lifetime: -1
# Disable the validation of TLS certificates of appservices. This is
# not recommended in production since it may allow appservice traffic
# to be sent to an unverified endpoint.
disable_tls_validation: false
# Appservice configuration files to load into this homeserver.
config_files: []
# Configuration for the Client API.
client_api:
internal_api:
listen: http://localhost:7771 # Only used in polylith deployments
connect: http://localhost:7771 # Only used in polylith deployments
external_api:
listen: http://[::]:8071
# Prevents new users from being able to register on this homeserver, except when
# using the registration shared secret below.
registration_disabled: false
# Prevents new guest accounts from being created. Guest registration is also
# disabled implicitly by setting 'registration_disabled' above.
guests_disabled: true
# If set, allows registration by anyone who knows the shared secret, regardless of
# whether registration is otherwise disabled.
registration_shared_secret: ""
# Whether to require reCAPTCHA for registration.
enable_registration_captcha: false
# Settings for ReCAPTCHA.
recaptcha_public_key: ""
recaptcha_private_key: ""
recaptcha_bypass_secret: ""
recaptcha_siteverify_api: ""
# TURN server information that this homeserver should send to clients.
turn:
turn_user_lifetime: ""
turn_uris: []
turn_shared_secret: ""
turn_username: ""
turn_password: ""
# Settings for rate-limited endpoints. Rate limiting will kick in after the
# threshold number of "slots" have been taken by requests from a specific
# host. Each "slot" will be released after the cooloff time in milliseconds.
rate_limiting:
enabled: true
threshold: 5
cooloff_ms: 500
# Configuration for the Federation API.
federation_api:
internal_api:
listen: http://localhost:7772 # Only used in polylith deployments
connect: http://localhost:7772 # Only used in polylith deployments
external_api:
listen: http://[::]:8072
database:
connection_string: file:///home/{{ butter_user }}/dendrite/federationapi.db
max_open_conns: 10
max_idle_conns: 2
conn_max_lifetime: -1
# How many times we will try to resend a failed transaction to a specific server. The
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc.
send_max_retries: 16
# Disable the validation of TLS certificates of remote federated homeservers. Do not
# enable this option in production as it presents a security risk!
disable_tls_validation: false
# Perspective keyservers to use as a backup when direct key fetches fail. This may
# be required to satisfy key requests for servers that are no longer online when
# joining some rooms.
key_perspectives:
- server_name: matrix.org
keys:
- key_id: ed25519:auto
public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
- key_id: ed25519:a_RXGa
public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ
# This option will control whether Dendrite will prefer to look up keys directly
# or whether it should try perspective servers first, using direct fetches as a
# last resort.
prefer_direct_fetch: false
# Configuration for the Key Server (for end-to-end encryption).
key_server:
internal_api:
listen: http://localhost:7779 # Only used in polylith deployments
connect: http://localhost:7779 # Only used in polylith deployments
database:
connection_string: file:///home/{{ butter_user }}/dendrite/keyserver.db
max_open_conns: 10
max_idle_conns: 2
conn_max_lifetime: -1
# Configuration for the Media API.
media_api:
internal_api:
listen: http://localhost:7774 # Only used in polylith deployments
connect: http://localhost:7774 # Only used in polylith deployments
external_api:
listen: http://[::]:8074
database:
connection_string: file:///home/{{ butter_user }}/dendrite/mediaapi.db
max_open_conns: 5
max_idle_conns: 2
conn_max_lifetime: -1
# Storage path for uploaded media. May be relative or absolute.
base_path: /home/{{ butter_user }}/dendrite/media_store
# The maximum allowed file size (in bytes) for media uploads to this homeserver
# (0 = unlimited). If using a reverse proxy, ensure it allows requests at
# least this large (e.g. client_max_body_size in nginx.)
# 1GB = 1 048 576 000 (1024*1024*1000)
max_file_size_bytes: 1048576000
# Whether to dynamically generate thumbnails if needed.
dynamic_thumbnails: true
# The maximum number of simultaneous thumbnail generators to run.
max_thumbnail_generators: 10
# A list of thumbnail sizes to be generated for media content.
thumbnail_sizes:
- width: 32
height: 32
method: crop
- width: 96
height: 96
method: crop
- width: 640
height: 480
method: scale
# Configuration for experimental MSC's
mscs:
# A list of enabled MSC's
# Currently valid values are:
# - msc2836 (Threading, see https://github.com/matrix-org/matrix-doc/pull/2836)
# - msc2946 (Spaces Summary, see https://github.com/matrix-org/matrix-doc/pull/2946)
mscs: []
database:
connection_string: file:///home/{{ butter_user }}/dendrite/mscs.db
max_open_conns: 5
max_idle_conns: 2
conn_max_lifetime: -1
# Configuration for the Room Server.
room_server:
internal_api:
listen: http://localhost:7770 # Only used in polylith deployments
connect: http://localhost:7770 # Only used in polylith deployments
database:
connection_string: file:///home/{{ butter_user }}/dendrite/roomserver.db
max_open_conns: 10
max_idle_conns: 2
conn_max_lifetime: -1
# Configuration for the Sync API.
sync_api:
internal_api:
listen: http://localhost:7773 # Only used in polylith deployments
connect: http://localhost:7773 # Only used in polylith deployments
external_api:
listen: http://[::]:8073
database:
connection_string: file:///home/{{ butter_user }}/dendrite/syncapi.db
max_open_conns: 10
max_idle_conns: 2
conn_max_lifetime: -1
# This option controls which HTTP header to inspect to find the real remote IP
# address of the client. This is likely required if Dendrite is running behind
# a reverse proxy server.
# real_ip_header: X-Real-IP
# Configuration for the User API.
user_api:
# The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31
# See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information.
# Setting this lower makes registration/login consume less CPU resources at the cost of security
# should the database be compromised. Setting this higher makes registration/login consume more
# CPU resources but makes it harder to brute force password hashes.
# This value can be low if performing tests or on embedded Dendrite instances (e.g WASM builds)
# bcrypt_cost: 10
internal_api:
listen: http://localhost:7781 # Only used in polylith deployments
connect: http://localhost:7781 # Only used in polylith deployments
account_database:
connection_string: file:///home/{{ butter_user }}/dendrite/userapi_accounts.db
max_open_conns: 10
max_idle_conns: 2
conn_max_lifetime: -1
# The length of time that a token issued for a relying party from
# /_matrix/client/r0/user/{userId}/openid/request_token endpoint
# is considered to be valid in milliseconds.
# The default lifetime is 3600000ms (60 minutes).
# openid_token_lifetime_ms: 3600000
# Configuration for Opentracing.
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
# how this works and how to set it up.
tracing:
enabled: false
jaeger:
serviceName: ""
disabled: false
rpc_metrics: false
tags: []
sampler: null
reporter: null
headers: null
baggage_restrictions: null
throttler: null
# Logging configuration
logging:
- type: std
level: info
- type: file
# The logging level, must be one of debug, info, warn, error, fatal, panic.
level: info
params:
path: /var/log/dendrite/
# Not part of the dendrite-sample file, but required by 0.13.7
relay_api:
database:
connection_string: file:///home/{{ butter_user }}/dendrite/relay_api.db

View file

@ -1,14 +0,0 @@
[Unit]
Description=Dendrite Service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User={{ butter_user }}
ExecStart=/home/{{ butter_user }}/dendrite/bin/dendrite --config /home/{{ butter_user }}/dendrite/butterbox-dendrite.conf -really-enable-open-registration
[Install]
WantedBy=multi-user.target

View file

@ -1,4 +1,4 @@
interface=wlan0 interface=wlan0
dhcp-range=10.3.141.50,10.3.141.255,255.255.255.0,12h dhcp-range=10.3.141.50,10.3.141.255,255.255.255.0,12h
dhcp-option=6,10.3.141.1 dhcp-option=6,10.3.141.1
address=/{{ butter_name }}.local/10.3.141.1 address=/{{ butter_name }}.lan/10.3.141.1

View file

@ -1,14 +0,0 @@
[Unit]
Description=Portal Service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User={{ butter_user }}
ExecStart=/bin/bash -c 'source /home/{{ butter_user }}/portal_env/bin/activate && cd /home/{{ butter_user }}/butter-portal && flask --app butter-portal.py run'
[Install]
WantedBy=multi-user.target

View file

@ -1,15 +0,0 @@
[Unit]
Description=Butterbox setting management service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=on-failure
RestartSec=1
User=root
ExecStart=/bin/bash -c "chmod 600 /etc/ssh/*key && python3 change_manager.py"
WorkingDirectory=/home/{{ butter_user }}/butter-portal
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target

View file

@ -7,7 +7,7 @@ beacon_int=100
ssid={{ butter_name }} ssid={{ butter_name }}
channel=1 channel=1
hw_mode=g hw_mode=g
ieee80211ac=1 ieee80211n=0
interface=wlan0 interface=wlan0
wpa=none wpa=none
wpa_pairwise=CCMP wpa_pairwise=CCMP

View file

@ -1,10 +0,0 @@
{
"appName": "Keanu on Butter Box",
"appNames": {},
"productLink": "{{ butter_name }}.local",
"defaultServer": "http://{{ butter_name }}.local",
"rtl": false,
"analytics": {
"enabled": false
}
}

View file

@ -1,23 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name {{ butter_name }}.local;
client_max_body_size 150M;
location ^~ /chat {
alias /var/www/html/chat;
}
location ^~ /raspap {
proxy_pass http://127.0.0.1:8080;
}
location ^~ /_matrix {
proxy_pass http://127.0.0.1:8008;
}
location / {
proxy_pass http://127.0.0.1:5000;
}
}

View file

@ -1,48 +0,0 @@
#!/bin/bash
# Run by udev when a USB drive is inserted
# usage: /usr/bin/on-usb-drive-mounted.sh /media/%k
# If the drive inserted contains a directory named "butter",
# symlink it to /media/usb-butter
device="$1"
# The device might not be mounted yet, so wait for it.
usb_mount_path=""
for ((i=0; i<10; i++)); do
usb_mount_path=$(findmnt -n -o TARGET --source "$device")
if [ -n "$usb_mount_path" ]; then
break
fi
sleep 1
done
# findmnt will briefly return 1, so don't set e until we're done with it.
set -e
if [ -z "$usb_mount_path" ]; then
echo "Device $device is not mounted"
exit 1
else
echo "Device $device mounted to: $usb_mount_path"
fi
butter_dir="$usb_mount_path"
served_dir="/media/usb-butter"
# make directory butter_dir world readable
sudo chmod -R a+rx "$butter_dir"
sudo chmod -R a+rx "/media/root/"
if [ -d "$butter_dir" ]; then
# Delete served_dir if it exists
if [ -L "$served_dir" ]; then
sudo rm "$served_dir"
fi
echo "Linking $butter_dir to $served_dir"
ln -sf "$butter_dir" "$served_dir"
sudo chown -R {{ butter_user }}:{{ butter_user }} $served_dir
else
echo "No butter directory $butter_dir found on $device"
exit 1
fi

View file

@ -1,15 +0,0 @@
[Unit]
Description=RaspAP Service Daemon
DefaultDependencies=no
# This line, which the default raspapd.service would use, results in a circular dependency
# And the symptom we see is that it doesn't boot. Instead, run this after network.target
# After=multi-user.target
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash /etc/raspap/hostapd/servicestart.sh --interface uap0 --seconds 3
RemainAfterExit=no
[Install]
WantedBy=multi-user.target

View file

@ -1,8 +0,0 @@
[Unit]
Description=Serve USB content
BindTo=dev-%i.device
[Service]
ExecStart=/bin/sh -c '/usr/bin/on-usb-drive-mounted.sh /dev/%i'
ExecStop=/bin/sh -c 'rm /media/usb-butter/'
RemainAfterExit=yes

View file

@ -1,8 +0,0 @@
[Unit]
Description=Mount service
BindTo=dev-%i.device
[Service]
ExecStart=/bin/sh -c '/usr/bin/udisksctl mount -b /dev/%i'
ExecStop=/bin/sh -c '/usr/bin/udisksctl unmount -b /dev/%i'
RemainAfterExit=yes

View file

@ -1,3 +0,0 @@
country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
ap_scan=1

View file

@ -1,53 +0,0 @@
## Multi-box Delta Chat setup
This document details the requirements and setup needed to allow exchanging messages between Delta Chat instances
running on different boxes connected together over LAN.
This requires a few configuration changes.
## Requirements
- A way to access the boxes via console, either by using a keyboard and monitor, or by enabling
the SSH setting through the admin portal and connecting via SSH.
- The boxes must be configured with different hostnames, e.g., butterbox.local and jambox.local.
The hostname can only be changed at the time of the initial admin setup for a box.
- The boxes must be connected together in the same LAN, either by being plugged into the same router, or
by reconfiguring one of them to be a client of the hotspot provided by the other.
- The rest of the instructions assume the router provides the boxes with IP addresses via DHCP.
## MDNS Setup
- After making sure the boxes are on the same network, check they can resolve each other's local names,
by running an MDNS lookup command (`avahi-resolve -n -4 <name.local>`) on one of the boxes for the hostname of the others. The following
example shows the address of `jambox.local` being resolved on the console of the box named `butterbox.local`:
```
root@butterbox:~# avahi-resolve -n -4 jambox.local
jambox.local 192.168.1.2
```
- If the above does not work, it could be due to a variety of reasons, the most likely being that the
router used to connect the boxes does not support forwarding MDNS queries.
In this case, run the command `ip a` to find the local `eth0` address for each box, and then add
that into the `/etc/hosts` file on the other boxes. This will allow them to find each other by hostname.
For example, if `jambox.local` had an address of `192.168.1.2`, you would need to add the following
line to `/etc/hosts` on `butterbox.local`:
```192.168.1.2 jambox.local```
- In this example, the corresponding config change would also need to be made for any other connected boxes, e.g., jambox.local's `/etc/hosts` file.
## Nginx setup
- Edit the `/etc/nginx/sites-enabled/default` file to add an entry for `/mxdeliv` that points to
localhost, port 8081, on all boxes participating in the setup:
```angular2html
location /mxdeliv {
proxy_pass http://127.0.0.1:8081;
}
```
## Testing
With the changes above, each box should be able to find the other boxes for mail delivery,
with incoming mail routed by `nginx` to the `madmail` service.
To test, create Delta Chat accounts on each box, and send test messages between them.

View file

@ -1,57 +0,0 @@
# Creating an upgrade content pack for the butterbox
Butterboxes run [Debian OS](https://www.debian.org/) and can be upgraded using `apt update` and `apt upgrade` commands.
However, since they are often expected to operate in areas without Internet connectivity,
the following instructions detail how to create and use a content pack to allow local offline upgrades.
## Requirements
- A large (min 250GB ) Hard drive or USB Stick, formatted as FAT32 or ext4
- Internet connectivity
- A computer running Debian
## Creating a Debian mirror
- Plug in and mount the hard drive into a computer running Debian. The rest of the configuration assumes this is mounted at `/mnt`.
- Edit the Debian mirror configuration. For this, depending on what kind of butterbox you have (AMD64-based, like an old laptop, or
ARM-based, like a Raspberry Pi), edit the file "/etc/apt/mirror.list" (you may wish to back it up beforehand)
to contain the following, making sure to uncomment the line corresponding to your box.
```angular2html
############# config ##################
#
set base_path /mnt # This is where the mirror will be created, ensure it corresponds to where the hard drive is mounted
#
set mirror_path $base_path/mirror
set skel_path $base_path/skel
set var_path $base_path/var
set cleanscript $var_path/clean.sh
set postmirror_script $var_path/postmirror.sh
set run_postmirror 0
set nthreads 20
set _tilde 0
#
############# end config ##############
#deb [arch=arm64] http://ftp.us.debian.org/debian stable main contrib non-free # Uncomment for ARM64, e.g. Raspberry Pi 3 or 4
#deb [arch=amd64] http://ftp.us.debian.org/debian stable main contrib non-free # Uncomment for AMD64
# Note the mirror location, ftp.us.debian.org; you can choose any other online debian mirror to download from, keep a note of its name
```
- Run the command `apt-mirror`. This will create a Debian mirror on your hard drive, that can then be used to upgrade your butterbox.
The size of the archive varies, but will utilise somewhere in the region of 150-200GB, depending on architecture.
> Careful running this command on any metered connection, as it will attempt download hundreds of GB worth of data!
> Be patient. This command will take a while to run depending on the speed of your Internet connection.
## Updating your Butterbox
- Plug in the hard drive into your butterbox, and mount it. This assumes it is mounted at `/mnt`.
- Log in via the butterbox console, as the root user.
- ensure the date is set correctly by running date, for example: `date -s "30/12/2026 10:29"`
- edit file `/etc/apt/sources.list` to contain the following line:
` deb file:/mnt/mirror/ftp.us.debian.org/debian stable main contrib non-free`
Note that the name of the directory might vary depending on the mirror you used in the previous step.
- Congrats! Now you can run `apt update` and `apt upgrade` to update the box.

View file

@ -1,129 +0,0 @@
# Extending ButterBox connectivity with WiFi HaLow and LoRa
*This documentation is entirely based upon the original blog post ["Butter Box Connectivity"](https://www.sr2.uk/posts/2026-butter-box-connectivity).*
ButterBoxes are designed to operate in areas with limited or no Internet connectivity. In some
deployments, it is useful to extend the range over which boxes can communicate with each other,
or over which client devices can reach a box, beyond what conventional WiFi allows. This document
describes two long-range wireless options that have been evaluated for use with the ButterBox,
**WiFi HaLow** (IEEE 802.11ah) and **LoRa**, and explains how to set each one up as a bridge in front of a box.
Both approaches let you keep the ButterBox itself unchanged: the long-range radio acts as a
transparent link between the box and the remote client (or another box), so the existing portal,
Delta Chat relay, and update mechanisms continue to work as normal.
## Choosing between HaLow and LoRa
| | WiFi HaLow | LoRa |
| --- | --- | --- |
| Typical range | ~400 m (modeled), usable up to ~500 m | Greater than HaLow |
| Throughput | Up to ~8.9 Mbps (Region 1/3), ~43 Mbps (Region 2); often much lower in practice | ~2680 bits per second |
| Cost per node | ~£155 (Morse Micro evaluation kit) | ~£26 (USR-LG206-P RS232 bridge) |
| Good for | Portal access, multi-box networks, software updates | Very low bandwidth messaging, store-and-forward |
In short: pick **HaLow** if you need clients to reach the ButterBox portal or to push updates
between boxes at reasonable speeds. Pick **LoRa** if you only need to move very small amounts
of data over a longer distance, and you are prepared to use protocols designed for slow,
high-latency links.
> WiFi HaLow operates in sub-1GHz ISM bands. The exact frequency allocation depends on your
> region: 863&ndash;870 MHz in ITU Regions 1 and 3, and 902&ndash;928 MHz in Region 2. Make sure
> the hardware you buy is approved for use where you intend to deploy it.
## Option 1: WiFi HaLow bridge
This setup uses a pair of Morse Micro HaLow evaluation kits to create a long-range bridge in
front of the ButterBox. Conventional WiFi clients connect to the mobile end of the bridge and
reach the box transparently over HaLow.
### Requirements
- 2 &times; Morse Micro **MM8108-EKH19** evaluation kits (available from Mouser Electronics,
approximately £155 each).
- A ButterBox running the standard image, with its portal and Delta Chat relay configured.
- A power source for the mobile end of the bridge - a USB powerbank is sufficient.
- Antennas appropriate for the band allowed in your region.
### Setup
1. Place the first HaLow kit alongside the ButterBox and connect it to the box's LAN port.
This unit acts as the **stationary bridge** at the box's location.
2. Configure the second HaLow kit as the **mobile bridge**. Power it from a powerbank so it
can be carried or positioned away from the box.
3. Both kits must be configured to use the same HaLow channel within the band approved for
your region (863&ndash;870 MHz or 902&ndash;928 MHz). Refer to the Morse Micro evaluation
kit documentation for the exact channel selection commands.
4. The mobile bridge exposes a conventional 2.4 GHz WiFi network. Client devices (phones,
laptops) join this network in the normal way; their traffic is then relayed over HaLow to
the stationary bridge and on to the ButterBox.
5. Because the bridge is transparent at the IP layer, no changes are needed on the ButterBox
itself. Clients reach the portal at the usual `butterbox.local` address.
### Notes on performance
- Coverage modelling suggests reliable operation out to roughly 400 metres, and the ButterBox
portal remained reachable in tests at around 500 metres.
- Link speed is volatile: even within 50 metres, throughput can drop to around 0.3 Mbps before
recovering. Expect the portal to feel slower than over conventional WiFi.
- HaLow operates in an ISM band and must implement "listen before send". Heavily congested
environments will reduce effective throughput further.
- For multi-box deployments (for example, several ButterBoxes across a school, refugee camp,
or evacuation centre) HaLow has enough bandwidth to also push updates between boxes.
See [upgrade_pack.md](upgrade_pack.md) for the offline upgrade workflow this complements.
## Option 2: LoRa serial bridge
LoRa offers significantly greater range than HaLow but at a fraction of the throughput. It is
not suitable for portal access, but can be used for very low bandwidth messaging or
store-and-forward style workflows between boxes.
### Requirements
- 2 &times; **USR-LG206-P** RS232-to-LoRa bridges (available from AliExpress, approximately
£26 each).
- A USB-to-RS232 adapter for each ButterBox the bridge is attached to, unless the box already
has a serial port.
- Antennas appropriate for the band approved in your region (typically 868 MHz or 915 MHz; the
433 MHz band is also available in some regions but overlaps with amateur radio allocations).
### Setup
1. Connect a USR-LG206-P to each ButterBox you want to link, using the USB-to-RS232 adapter.
2. Configure both LoRa bridges with matching frequency, spreading factor, and network ID
settings. The factory defaults will work for a point-to-point link, but the frequency must
be set to a band approved in your region.
3. The bridge presents itself as a serial device on the ButterBox (typically `/dev/ttyUSB0`).
Any application that can speak over a serial link can now exchange data between the two
boxes.
4. For practical use, layer a protocol designed for slow, high-latency links on top of the
serial connection. A reasonable starting point is **UUCP**, which is packaged in the Debian
repositories the ButterBox already uses and is well suited to store-and-forward delivery
over unreliable links.
### Notes on performance
- The advertised throughput of the USR-LG206-P is **2680 bits per second** ... bits, not
kilobits. Plan accordingly: this is enough for small text messages and metadata, but not for
interactive use of the portal.
- LoRa's physical layer is a proprietary Semtech standard. There is currently no fully open
source implementation of the radio itself, although the application protocols you run over
it can be open.
- Because the link is so slow, generic IP-over-serial approaches will be unsatisfactory.
Protocols specifically designed for constrained links (for example tinySSB, which uses
aggressive compression and small packet sizes) are a better fit if UUCP does not meet your
needs.
## Testing
Once a bridge is in place, the simplest end-to-end test is the same one used in the
[multi-box Delta Chat setup](multibox.md): create a Delta Chat account on each ButterBox and
exchange a test message. If the message is delivered, the link is working at the application
layer.
For HaLow setups, you can additionally browse to the ButterBox admin portal from a client
device connected to the mobile bridge, and confirm that pages load (slowly is fine).
For LoRa setups, do not expect the portal to be usable. Instead, verify the serial link with
a tool such as `minicom` or `screen` on each box, then test your chosen application protocol
(for example UUCP) by queueing a small file for delivery in one direction and confirming it
arrives at the other end.

View file

@ -1,191 +0,0 @@
---
# See https://wiki.debian.org/RaspberryPi3 for known issues and more details.
# image.yml based on revision: ff7fdbf (Switch from qemu-debootstrap to debootstrap., 2024-01-01)
steps:
- mkimg: "{{ output }}"
size: 3100M
- mklabel: gpt
device: "{{ output }}"
############ efi
- mkpart: primary
fs-type: 'fat32'
device: "{{ output }}"
start: 1MiB
end: 132MiB
tag: efi
- set_part_flag: "{{ output }}"
tag: efi
flag: boot
state: enabled
- set_part_flag: "{{ output }}"
tag: efi
flag: esp
state: enabled
############ bios grub
- mkpart: primary
device: "{{ output }}"
start: 132MiB
end: 133MiB
tag: bios_grub
- set_part_flag: "{{ output }}"
tag: bios_grub
flag: bios_grub
state: enabled
############ live
- mkpart: primary
device: "{{ output }}"
start: 133MiB
end: 100%
tag: tag-root
- set_part_flag: "{{ output }}"
tag: tag-root
flag: legacy_boot
state: enabled
- kpartx: "{{ output }}"
- mkfs: vfat
partition: efi
label: EFI
options: -F32
- mkfs: ext4
partition: tag-root
label: boot
- mount: tag-root
- shell: |
dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/mbr/gptmbr.bin of="{{ output }}"
root-fs: tag-root
- unpack-rootfs: tag-root
- debootstrap: trixie
require_empty_target: false
mirror: http://deb.debian.org/debian
target: tag-root
components:
- main
- non-free-firmware
- non-free
unless: rootfs_unpacked
- create-file: /etc/apt/sources.list
contents: |+
deb http://deb.debian.org/debian trixie main non-free-firmware non-free
deb http://deb.debian.org/debian trixie-updates main non-free-firmware non-free
deb http://security.debian.org/debian-security trixie-security main non-free-firmware non-free
unless: rootfs_unpacked
- apt: install
packages:
- avahi-daemon
- avahi-utils
- locales
- curl
- udisks2
- wget
- dhcpcd
- python3
- python3-packaging
- python3-virtualenv
- nginx
- lighttpd
- unzip
- sudo
- systemd-timesyncd
- ca-certificates
- dosfstools
- iw
- parted
- ssh
- wpasupplicant
- systemd
- systemd-sysv
- init-system-helpers
- syslinux
- linux-image-amd64
- git
tag: tag-root
unless: rootfs_unpacked
- cache-rootfs: tag-root
unless: rootfs_unpacked
- shell: |
echo "butterbox" > "${ROOT?}/etc/hostname"
# Allow root logins locally with no password
sed -i 's,root:[^:]*:,root::,' "${ROOT?}/etc/shadow"
install -m 644 -o root -g root image-specs/rootfs/etc/fstab "${ROOT?}/etc/fstab"
install -m 644 -o root -g root image-specs/rootfs/etc/network/interfaces.d/eth0 "${ROOT?}/etc/network/interfaces.d/eth0"
install -m 600 -o root -g root image-specs/rootfs/etc/network/interfaces.d/wlan0 "${ROOT?}/etc/network/interfaces.d/wlan0"
root-fs: tag-root
# Clean up archive cache (likely not useful) and lists (likely outdated) to
# reduce image size by several hundred megabytes.
- chroot: tag-root
shell: |
apt-get clean
rm -rf /var/lib/apt/lists
- grub: bios
tag: tag-root
console: serial
#
- grub: uefi
tag: tag-root
efi: efi
console: serial
- shell: |
rm "${ROOT?}/etc/resolv.conf"
mkdir -p "${ROOT?}/etc/systemd/system/multi-user.target.requires/"
install -m 644 -o root -g root image-specs/rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/"
ln -s "${ROOT?}/etc/systemd/system/rpi-generate-ssh-host-keys.service" "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-generate-ssh-host-keys.service"
rm -f "${ROOT?}"/etc/ssh/ssh_host_*_key*
root-fs: tag-root
# Clear /etc/machine-id and /var/lib/dbus/machine-id, as both should
# be auto-generated upon first boot. From the manpage
# (machine-id(5)):
#
# For normal operating system installations, where a custom image is
# created for a specific machine, /etc/machine-id should be
# populated during installation.
#
# Note this will also trigger ConditionFirstBoot=yes for systemd.
# On Buster, /etc/machine-id should be an emtpy file, not an absent file
# On Bullseye, /etc/machine-id should not exist in an image
#
- chroot: tag-root
shell: |
rm -f /etc/machine-id /var/lib/dbus/machine-id
echo "uninitialized" > /etc/machine-id
echo "LABEL=BOOT / ext4 rw 0 1" > /etc/fstab
- virtual-filesystems: tag-root
- ansible: tag-root
playbook: ../ansible/main.yml
config_file: ../ansible/ansible.cfg
extra_vars:
butter_language: en
butter_name: butterbox
tags: delta-chat,ap,base,usb,matrix,keanu,website
butter_user: "amd"
ap_mode_supported: "false"

View file

@ -1,178 +0,0 @@
---
# See https://wiki.debian.org/RaspberryPi3 for known issues and more details.
# image.yml based on revision: ff7fdbf (Switch from qemu-debootstrap to debootstrap., 2024-01-01)
steps:
- mkimg: "{{ output }}"
size: 3100M
- mklabel: gpt
device: "{{ output }}"
############ efi
- mkpart: primary
fs-type: 'fat32'
device: "{{ output }}"
start: 1MiB
end: 132MiB
tag: efi
- set_part_flag: "{{ output }}"
tag: efi
flag: boot
state: enabled
- set_part_flag: "{{ output }}"
tag: efi
flag: esp
state: enabled
############ bios grub
- mkpart: primary
device: "{{ output }}"
start: 132MiB
end: 133MiB
tag: bios_grub
- set_part_flag: "{{ output }}"
tag: bios_grub
flag: bios_grub
state: enabled
############ live
- mkpart: primary
device: "{{ output }}"
start: 133MiB
end: 100%
tag: tag-root
- set_part_flag: "{{ output }}"
tag: tag-root
flag: legacy_boot
state: enabled
- kpartx: "{{ output }}"
- mkfs: vfat
partition: efi
label: EFI
options: -F32
- mkfs: ext4
partition: tag-root
label: boot
- mount: tag-root
- shell: |
dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/mbr/gptmbr.bin of="{{ output }}"
root-fs: tag-root
- unpack-rootfs: tag-root
- debootstrap: trixie
require_empty_target: false
mirror: http://deb.debian.org/debian
target: tag-root
components:
- main
- non-free-firmware
- non-free
unless: rootfs_unpacked
- create-file: /etc/apt/sources.list
contents: |+
deb http://deb.debian.org/debian trixie main non-free-firmware non-free
deb http://deb.debian.org/debian trixie-updates main non-free-firmware non-free
deb http://security.debian.org/debian-security trixie-security main non-free-firmware non-free
unless: rootfs_unpacked
- apt: install
packages:
- avahi-daemon
- curl
- udisks2
- wget
- dhcpcd
- python3
- lighttpd
- unzip
- sudo
- systemd-timesyncd
- ca-certificates
- dosfstools
- iw
- parted
- ssh
- wpasupplicant
- systemd
- systemd-sysv
- init-system-helpers
- syslinux
- linux-image-amd64
tag: tag-root
unless: rootfs_unpacked
- cache-rootfs: tag-root
unless: rootfs_unpacked
- shell: |
echo "kanglam" > "${ROOT?}/etc/hostname"
# Allow root logins locally with no password
sed -i 's,root:[^:]*:,root::,' "${ROOT?}/etc/shadow"
install -m 644 -o root -g root image-specs/rootfs/etc/fstab "${ROOT?}/etc/fstab"
install -m 644 -o root -g root image-specs/rootfs/etc/network/interfaces.d/eth0 "${ROOT?}/etc/network/interfaces.d/eth0"
install -m 600 -o root -g root image-specs/rootfs/etc/network/interfaces.d/wlan0 "${ROOT?}/etc/network/interfaces.d/wlan0"
root-fs: tag-root
# Clean up archive cache (likely not useful) and lists (likely outdated) to
# reduce image size by several hundred megabytes.
- chroot: tag-root
shell: |
apt-get clean
rm -rf /var/lib/apt/lists
- grub: bios
tag: tag-root
console: serial
#
- grub: uefi
tag: tag-root
efi: efi
console: serial
- shell: |
rm "${ROOT?}/etc/resolv.conf"
root-fs: tag-root
# Clear /etc/machine-id and /var/lib/dbus/machine-id, as both should
# be auto-generated upon first boot. From the manpage
# (machine-id(5)):
#
# For normal operating system installations, where a custom image is
# created for a specific machine, /etc/machine-id should be
# populated during installation.
#
# Note this will also trigger ConditionFirstBoot=yes for systemd.
# On Buster, /etc/machine-id should be an emtpy file, not an absent file
# On Bullseye, /etc/machine-id should not exist in an image
- chroot: tag-root
shell: |
rm -f /etc/machine-id /var/lib/dbus/machine-id
echo "uninitialized" > /etc/machine-id
echo "LABEL=BOOT / ext4 rw 0 1" > /etc/fstab
- virtual-filesystems: tag-root
- ansible: tag-root
playbook: ../ansible/main.yml
config_file: ../ansible/ansible.cfg
extra_vars:
butter_language: en
butter_name: kanglam
tags: base,usb,matrix,keanu,website
butter_user: "amd"
ap_mode_supported: "false"

View file

@ -1,197 +0,0 @@
---
# See https://wiki.debian.org/RaspberryPi3 for known issues and more details.
# image.yml based on revision: ff7fdbf (Switch from qemu-debootstrap to debootstrap., 2024-01-01)
steps:
- mkimg: "{{ output }}"
size: 3100M
- mklabel: msdos
device: "{{ output }}"
- mkpart: primary
fs-type: 'fat32'
device: "{{ output }}"
start: 4MiB
end: 512MiB
tag: tag-firmware
- mkpart: primary
device: "{{ output }}"
start: 512MiB
end: 100%
tag: tag-root
- kpartx: "{{ output }}"
- mkfs: vfat
partition: tag-firmware
label: RASPIFIRM
- mkfs: ext4
partition: tag-root
label: RASPIROOT
- mount: tag-root
- mount: tag-firmware
mount-on: tag-root
dirname: '/boot/firmware'
- unpack-rootfs: tag-root
- debootstrap: trixie
require_empty_target: false
mirror: http://deb.debian.org/debian
target: tag-root
arch: arm64
components:
- main
- non-free-firmware
- non-free
unless: rootfs_unpacked
- create-file: /etc/apt/sources.list
contents: |+
deb http://deb.debian.org/debian trixie main non-free-firmware non-free
deb http://deb.debian.org/debian trixie-updates main non-free-firmware non-free
deb http://security.debian.org/debian-security trixie-security main non-free-firmware non-free
# Backports are _not_ enabled by default.
# Enable them by uncommenting the following line:
# deb http://deb.debian.org/debian trixie-backports main non-free-firmware
unless: rootfs_unpacked
- copy-file: /etc/initramfs-tools/hooks/rpi-resizerootfs
src: image-specs/rootfs/etc/initramfs-tools/hooks/rpi-resizerootfs
perm: 0755
unless: rootfs_unpacked
- copy-file: /etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs
src: image-specs/rootfs/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs
perm: 0755
unless: rootfs_unpacked
- apt: install
packages:
- avahi-daemon
- curl
- udisks2
- wget
- dhcpcd
- dnsmasq
- python3
- lighttpd
- unzip
- sudo
- systemd-timesyncd
- ca-certificates
- dosfstools
- iw
- parted
- ssh
- wpasupplicant
- systemd-timesyncd
- linux-image-arm64
- raspi-firmware
- firmware-brcm80211
- bluez-firmware
tag: tag-root
unless: rootfs_unpacked
- cache-rootfs: tag-root
unless: rootfs_unpacked
- shell: |
echo "kanglam" > "${ROOT?}/etc/hostname"
sed -i "s,root:[^:]*:,root::," "${ROOT?}/etc/shadow"
install -m 644 -o root -g root image-specs/rootfs/etc/fstab "${ROOT?}/etc/fstab"
install -m 644 -o root -g root image-specs/rootfs/etc/network/interfaces.d/eth0 "${ROOT?}/etc/network/interfaces.d/eth0"
install -m 600 -o root -g root image-specs/rootfs/etc/network/interfaces.d/wlan0 "${ROOT?}/etc/network/interfaces.d/wlan0"
install -m 755 -o root -g root image-specs/rootfs/usr/local/sbin/rpi-set-sysconf "${ROOT?}/usr/local/sbin/rpi-set-sysconf"
install -m 644 -o root -g root image-specs/rootfs/etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/"
install -m 644 -o root -g root image-specs/rootfs/boot/firmware/sysconf.txt "${ROOT?}/boot/firmware/sysconf.txt"
mkdir -p "${ROOT?}/etc/systemd/system/basic.target.requires/"
ln -s /etc/systemd/system/rpi-set-sysconf.service "${ROOT?}/etc/systemd/system/basic.target.requires/rpi-set-sysconf.service"
# Resize script is now in the initrd for first boot; no need to ship it.
rm -f "${ROOT?}/etc/initramfs-tools/hooks/rpi-resizerootfs"
rm -f "${ROOT?}/etc/initramfs-tools/scripts/local-bottom/rpi-resizerootfs"
install -m 644 -o root -g root image-specs/rootfs/etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/"
mkdir -p "${ROOT?}/etc/systemd/system/multi-user.target.requires/"
ln -s /etc/systemd/system/rpi-reconfigure-raspi-firmware.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-reconfigure-raspi-firmware.service"
install -m 644 -o root -g root image-specs/rootfs/etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/"
ln -s /etc/systemd/system/rpi-generate-ssh-host-keys.service "${ROOT?}/etc/systemd/system/multi-user.target.requires/rpi-generate-ssh-host-keys.service"
rm -f "${ROOT?}"/etc/ssh/ssh_host_*_key*
root-fs: tag-root
# Copy the relevant device tree files to the boot partition
- chroot: tag-root
shell: |
install -m 644 -o root -g root /usr/lib/linux-image-*-arm64/broadcom/bcm*rpi*.dtb /boot/firmware/
# Clean up archive cache (likely not useful) and lists (likely outdated) to
# reduce image size by several hundred megabytes.
- chroot: tag-root
shell: |
apt-get clean
rm -rf /var/lib/apt/lists
# Modify the kernel commandline we take from the firmware to boot from
# the partition labeled raspiroot instead of forcing it to mmcblk0p2.
# Also insert the serial console right before the root= parameter.
#
# These changes will be overwritten after the hardware is probed
# after dpkg reconfigures raspi-firmware (upon first boot), so make
# sure we don't lose label-based booting.
- chroot: tag-root
shell: |
sed -i 's/root=/console=ttyS1,115200 root=/' /boot/firmware/cmdline.txt
sed -i 's#root=/dev/mmcblk0p2#root=LABEL=RASPIROOT#' /boot/firmware/cmdline.txt
sed -i 's/^#ROOTPART=.*/ROOTPART=LABEL=RASPIROOT/' /etc/default/raspi*-firmware
sed -i 's/cma=64M //' /boot/firmware/cmdline.txt
# TODO(https://github.com/larswirzenius/vmdb2/issues/24): remove once vmdb
# clears /etc/resolv.conf on its own.
- shell: |
rm "${ROOT?}/etc/resolv.conf"
root-fs: tag-root
# Clear /etc/machine-id and /var/lib/dbus/machine-id, as both should
# be auto-generated upon first boot. From the manpage
# (machine-id(5)):
#
# For normal operating system installations, where a custom image is
# created for a specific machine, /etc/machine-id should be
# populated during installation.
#
# Note this will also trigger ConditionFirstBoot=yes for systemd.
# On Buster, /etc/machine-id should be an emtpy file, not an absent file
# On Bullseye, /etc/machine-id should not exist in an image
- chroot: tag-root
shell: |
rm -f /etc/machine-id /var/lib/dbus/machine-id
echo "uninitialized" > /etc/machine-id
# Create /etc/raspi-image-id to know, from what commit the image was built
- chroot: tag-root
shell: |
echo "image based on revision: ff7fdbf (Switch from qemu-debootstrap to debootstrap., 2024-01-01) and build on 2025-10-27 20:22 (UTC)" > "/etc/raspi-image-id"
- virtual-filesystems: tag-root
- ansible: tag-root
playbook: ../ansible/main.yml
config_file: ../ansible/ansible.cfg
extra_vars:
butter_language: bo
butter_name: kanglam

View file

@ -75,20 +75,13 @@ steps:
- apt: install - apt: install
packages: packages:
- avahi-daemon - avahi-daemon
- avahi-utils
- locales
- fake-hwclock
- curl - curl
- udisks2 - udisks2
- wget - wget
- dhcpcd - dhcpcd
- dnsmasq - dnsmasq
- python3 - python3
- python3-packaging
- python3-virtualenv
- nginx
- lighttpd - lighttpd
- git
- unzip - unzip
- sudo - sudo
- systemd-timesyncd - systemd-timesyncd

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
GIT_BRANCH=$(git branch --show-current 2>/dev/null)
GIT_TAG=$(git tag 2>/dev/null | tail -n1)
BUILD_DATE=$(date +"%d%m%y")
SUFFIX="${GIT_BRANCH}_${GIT_TAG}_${BUILD_DATE}"
time vmdb2 --rootfs-tarball=64_$SUFFIX.tar.gz --output 64_butter_$SUFFIX.img --log 64_butter_$SUFFIX.log amd64_trixie.yaml
tar cvfz 64_butter_$SUFFIX.img.tar.gz 64_butter_$SUFFIX.img
#curl -H "Authorization: token" $CHURN_SECRET -X PUT --upload-file raspi4_butter_$SUFFIX.img.tar.gz https://guardianproject.dev/api/packages/butter/generic/churn/latest/raspi4_butter_$SUFFIX.img.tar.gz
#rm *img *tar.gz

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
GIT_BRANCH=$(git branch --show-current 2>/dev/null)
GIT_TAG=$(git tag 2>/dev/null | head -n1)
BUILD_DATE=$(date +"%d%m%y")
SUFFIX="${GIT_BRANCH}_${GIT_TAG}_${BUILD_DATE}"
time vmdb2 --rootfs-tarball=64_$SUFFIX.tar.gz --output 64_butter_$SUFFIX.img --log 64_butter_$SUFFIX.log amd64_trixie_kanglam.yaml
tar cvfz 64_butter_$SUFFIX.img.tar.gz 64_butter_$SUFFIX.img
#curl -H "Authorization: token" $CHURN_SECRET -X PUT --upload-file raspi4_butter_$SUFFIX.img.tar.gz https://guardianproject.dev/api/packages/butter/generic/churn/latest/raspi4_butter_$SUFFIX.img.tar.gz
#rm *img *tar.gz

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
GIT_BRANCH=$(git branch --show-current 2>/dev/null)
GIT_TAG=$(git tag 2>/dev/null | tail -n1)
BUILD_DATE=$(date +"%d%m%y")
SUFFIX="${GIT_BRANCH}_${GIT_TAG}_${BUILD_DATE}"
time vmdb2 --rootfs-tarball=raspi4_$SUFFIX.tar.gz --output raspi4_butter_$SUFFIX.img --log raspi4_butter_$SUFFIX.log raspi_4_trixie.yaml
tar cvfz raspi4_butter_$SUFFIX.img.tar.gz raspi4_butter_$SUFFIX.img
#curl -H "Authorization: token" $CHURN_SECRET -X PUT --upload-file raspi4_butter_$SUFFIX.img.tar.gz https://guardianproject.dev/api/packages/butter/generic/churn/latest/raspi4_butter_$SUFFIX.img.tar.gz
#rm *img *tar.gz

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
GIT_BRANCH=$(git branch --show-current 2>/dev/null)
GIT_TAG=$(git tag 2>/dev/null | tail -n1)
BUILD_DATE=$(date +"%d%m%y")
SUFFIX="${GIT_BRANCH}_${GIT_TAG}_${BUILD_DATE}"
time vmdb2 --rootfs-tarball=raspi4_$SUFFIX.tar.gz --output raspi4_butter_$SUFFIX.img --log raspi4_butter_$SUFFIX.log kanglam_pi4.yaml
tar cvfz raspi4_butter_$SUFFIX.img.tar.gz raspi4_butter_$SUFFIX.img
#curl -H "Authorization: token" $CHURN_SECRET -X PUT --upload-file raspi4_butter_$SUFFIX.img.tar.gz https://guardianproject.dev/api/packages/butter/generic/churn/latest/raspi4_butter_$SUFFIX.img.tar.gz
#rm *img *tar.gz