Streamline Alpine package sources

This commit is contained in:
Joachim Schlöffel
2026-06-07 21:37:27 +02:00
parent ca2db8e206
commit 6ef5cae2b2
28 changed files with 435 additions and 64 deletions

104
README.md
View File

@@ -1,14 +1,16 @@
# SeaweedFS Alpine package base
# SeaweedFS Alpine Package
This repository is set up to build a local SeaweedFS Alpine package with `mise` while keeping all Alpine tooling inside Docker.
Local Alpine packaging for SeaweedFS `4.31`. The workflow runs Alpine tooling
inside Docker and writes signed packages under `packages/local/<arch>/`.
## Tasks
## Commands
```sh
mise run apk:build
mise run apk:build-all
mise run apk:build-x86_64
mise run apk:build-aarch64
mise run apk:update-generated
mise run apk:checksum
mise run apk:lint
mise run apk:packages
@@ -18,57 +20,72 @@ mise run apk:shell
mise run apk:clean
```
`mise` also declares the Docker CLI as a project tool dependency, so the package workflow has a single command surface.
## Tool stub
The SeaweedFS release binary is also available as a mise HTTP tool stub:
```sh
./bin/weed version
```
The stub is pinned to SeaweedFS `4.31` and includes Linux x64 and arm64 release URLs with SHA-256 checksums.
The default build targets `x86_64`. Multi-arch builds currently target `x86_64` and Alpine's `aarch64`; override this with `ALPINE_ARCHES`, for example:
`apk:build` targets `x86_64` by default. Multi-arch builds target `x86_64` and
`aarch64`; override with `ALPINE_ARCHES` when needed:
```sh
ALPINE_ARCHES="x86_64 aarch64" mise run apk:build-all
```
Generated packages and repository indexes are written under `packages/`, typically:
The release binary is also exposed as a mise HTTP tool stub:
```text
packages/local/x86_64/
packages/local/aarch64/
```sh
./bin/weed version
```
Because this package repackages upstream release binaries, all targets are built from the native Docker builder platform by default (`ALPINE_BUILD_PLATFORM=linux/amd64`). No QEMU/binfmt setup is required.
## Package Layout
The signing key and distfiles cache are kept in `.cache/abuild/` and `.cache/apk-distfiles/` so repeated builds use the same local repository key.
```text
packaging/alpine/local/seaweedfs/APKBUILD
packaging/alpine/local/seaweedfs/*.toml
packaging/alpine/local/seaweedfs/example-*.toml
packaging/alpine/local/seaweedfs/weed.bash-completion
packaging/alpine/local/seaweedfs/seaweedfs.*.confd
packaging/alpine/local/seaweedfs/seaweedfs.initd
packages/local/<arch>/
```
## Local repository
The package repackages upstream release tarballs, so builds use
`ALPINE_BUILD_PLATFORM=linux/amd64` by default. Signing keys and distfiles are
cached in `.cache/abuild/` and `.cache/apk-distfiles/`.
To test the package in an Alpine container:
## Test And Publish
```sh
mise run apk:smoke
```
Before publishing or handing the local repository to another system, run:
```sh
mise run apk:publish-check
```
That runs APKBUILD linting, rebuilds all configured architectures, lists the generated package metadata, and performs the install smoke test.
`apk:publish-check` runs APKBUILD linting, rebuilds configured architectures,
lists package metadata, and installs the built packages in an Alpine container.
To install OpenRC service scripts as well:
Install from the local repo with the base package plus the role-specific OpenRC
subpackage you need:
```sh
apk add seaweedfs seaweedfs-master-openrc
```
Optional generated material is split out:
```sh
apk add seaweedfs-doc
apk add bash-completion seaweedfs-bash-completion
```
The doc split installs upstream scaffolds under
`/usr/share/doc/seaweedfs/examples/`. The bash completion split installs
`/usr/share/bash-completion/completions/weed` and is also selected by
`install_if` when `seaweedfs` and `bash-completion` are installed together.
This repository publishes `seaweedfs`, matching Alpine aports. Remove Alpine's
old generic OpenRC package before installing a role split:
```sh
apk del seaweedfs-openrc
apk add seaweedfs seaweedfs-master-openrc
```
The OpenRC subpackages are:
```text
@@ -82,15 +99,32 @@ seaweedfs-admin-openrc -> seaweedfs.admin
seaweedfs-worker-openrc -> seaweedfs.worker
```
Each service has matching defaults in `/etc/conf.d/seaweedfs.*`. Remote targets use distinct variables such as `SEAWEEDFS_MASTER`, `SEAWEEDFS_FILER`, and `SEAWEEDFS_ADMIN`; `SEAWEEDFS_OPTS` remains available for additional flags. Enable only the roles needed for a node, for example:
Each service has defaults in `/etc/conf.d/seaweedfs.*`. Enable only the roles
needed on the node:
```sh
rc-update add seaweedfs.master default
rc-service seaweedfs.master start
```
If the repository key is not copied into `packages/local/x86_64/`, copy the public key from `.cache/abuild/*.rsa.pub` into `/etc/apk/keys/` in the test system before running `apk update`.
If the repo key is missing on a target system, copy `.cache/abuild/*.rsa.pub`
into `/etc/apk/keys/` before `apk update`.
## Package inputs
## Alpine Package Dos And Don'ts
The package lives in `packaging/alpine/local/seaweedfs/APKBUILD`. It repackages SeaweedFS `4.31` release binaries from GitHub and installs the `weed` binary. Generated scaffold configs are installed under `/etc/seaweedfs/`.
Do:
- Keep local `source` files next to the `APKBUILD`.
- Put full upstream scaffolds in `/usr/share/doc/seaweedfs/examples/`, not active `/etc`.
- Run `mise run apk:update-generated` when updating generated examples or completions.
- Run `mise run apk:checksum` after changing any package source file.
- Increment `pkgrel` for package-only changes; reset it when `pkgver` changes.
- Keep installed defaults short and production-neutral.
- Run `mise run apk:publish-check` before handing off a repository.
Don't:
- Edit generated `src/`, `pkg/`, or `packages/` output.
- Hand-edit checksum lines when `abuild checksum` can do it.
- Bundle long upstream examples as active `/etc` defaults.
- Install all OpenRC roles on a node by default.

View File

@@ -23,6 +23,10 @@ run = "scripts/apk/build.sh build aarch64"
description = "Refresh APKBUILD checksums in Docker"
run = "scripts/apk/build.sh checksum"
[tasks."apk:update-generated"]
description = "Refresh packaged upstream examples and shell completions"
run = "scripts/apk/update-generated-sources.sh"
[tasks."apk:lint"]
description = "Run Alpine APKBUILD lint in Docker"
run = "scripts/apk/build.sh lint"

View File

@@ -1,15 +1,18 @@
# Maintainer: Local Builder <local@example.invalid>
pkgname=seaweedfs
pkgver=4.31
pkgrel=0
pkgrel=2
pkgdesc="Distributed storage system for object storage, file systems, and Iceberg tables"
url="https://github.com/seaweedfs/seaweedfs"
arch="x86_64 aarch64"
license="Apache-2.0"
depends="ca-certificates"
provides="!$pkgname-openrc"
install="$pkgname.pre-install"
subpackages="
$pkgname-admin-openrc:_openrc_admin:noarch
$pkgname-bash-completion:_bashcomp:noarch
$pkgname-doc
$pkgname-filer-openrc:_openrc_filer:noarch
$pkgname-master-openrc:_openrc_master:noarch
$pkgname-s3-openrc:_openrc_s3:noarch
@@ -22,43 +25,57 @@ options="!check !strip"
case "$CARCH" in
x86_64)
_archive_source="$pkgname-$pkgver-linux-amd64.tar.gz::https://github.com/seaweedfs/seaweedfs/releases/download/$pkgver/linux_amd64.tar.gz"
_archive_sha512="60c758d6d565d0cbc533e4e5677186a4700d48be12ebbd2bd1c6b4bcf38f687d0ab1f66e0953c8a27c663549cdbae73e5fd7eda74bb422c20e10e0d0850b5ead seaweedfs-4.31-linux-amd64.tar.gz"
_archive="$pkgname-$pkgver-linux-amd64.tar.gz"
;;
aarch64)
_archive_source="$pkgname-$pkgver-linux-arm64.tar.gz::https://github.com/seaweedfs/seaweedfs/releases/download/$pkgver/linux_arm64.tar.gz"
_archive_sha512="93c7539fe15a0aa6192d26ac2360003961f0643e302d910179d02341b10a7243df776294fb1b0134ec2a2a9596b1abc3ce9ab701f9bc3d78f2b965ea464a4b18 seaweedfs-4.31-linux-arm64.tar.gz"
_archive="$pkgname-$pkgver-linux-arm64.tar.gz"
;;
esac
source="
$_archive_source
config/credential.toml
config/filer.toml
config/master.toml
config/notification.toml
config/replication.toml
config/security.toml
config/shell.toml
openrc/seaweedfs.admin.confd
openrc/seaweedfs.filer.confd
openrc/seaweedfs.initd
openrc/seaweedfs.master.confd
openrc/seaweedfs.s3.confd
openrc/seaweedfs.sftp.confd
openrc/seaweedfs.volume.confd
openrc/seaweedfs.webdav.confd
openrc/seaweedfs.worker.confd
$pkgname-$pkgver-linux-amd64.tar.gz::https://github.com/seaweedfs/seaweedfs/releases/download/$pkgver/linux_amd64.tar.gz
$pkgname-$pkgver-linux-arm64.tar.gz::https://github.com/seaweedfs/seaweedfs/releases/download/$pkgver/linux_arm64.tar.gz
credential.toml
example-credential.toml
example-filer.toml
example-master.toml
example-notification.toml
example-replication.toml
example-security.toml
example-shell.toml
filer.toml
master.toml
notification.toml
replication.toml
security.toml
shell.toml
seaweedfs.admin.confd
seaweedfs.filer.confd
seaweedfs.initd
seaweedfs.master.confd
seaweedfs.s3.confd
seaweedfs.sftp.confd
seaweedfs.volume.confd
seaweedfs.webdav.confd
seaweedfs.worker.confd
weed.bash-completion
"
builddir="$srcdir"
prepare() {
default_prepare
tar -xzf "$srcdir/$_archive" -C "$builddir"
}
package() {
install -Dm755 "$srcdir"/weed "$pkgdir"/usr/bin/weed
local _config
for _config in credential filer master notification replication security shell; do
install -Dm644 "$srcdir"/$_config.toml \
"$pkgdir"/etc/seaweedfs/$_config.toml
install -Dm644 "$srcdir"/example-$_config.toml \
"$pkgdir"/usr/share/doc/$pkgname/examples/$_config.toml
done
}
@@ -82,14 +99,31 @@ _openrc_volume() { _openrc_service volume; }
_openrc_webdav() { _openrc_service webdav; }
_openrc_worker() { _openrc_service worker; }
_bashcomp() {
pkgdesc="Bash completions for $pkgname"
depends=""
install_if="$pkgname=$pkgver-r$pkgrel bash-completion"
install -Dm644 "$srcdir"/weed.bash-completion \
"$subpkgdir"/usr/share/bash-completion/completions/weed
}
sha512sums="
$_archive_sha512
257ed55050782379ed5b70437f6316ca2d8862817e17f6af48e599f000277453a0dbbb5cfa16697ba3e82acc7597e7e3e0505a57a4d601c5d743a46df195832d credential.toml
73f980cdfd3b453f8b279ed5823bb9ccad6780520abe64a3b957f2780de2d31dd863f1abb22e1dbf5f261073040026f702df7ab8a1f6cd8f85774ab49d188e72 filer.toml
cfb31d44311169a23215b1ad5cabf577d085388f935b47890281c8160bd02c85ff8ff16f58ef1075e40f6085b69aedfc3c3d1ced9ca228e5d25d0b99fa5f3fc6 master.toml
4e3468a848c1593b291f4b08e1214c9ddc54363d32f73da3981ac7c132fc2dd642f3c9d3ea4a6c4dd6b84a81ec50d3ca67caf3367fa62759b9f54d081bfeb19a notification.toml
72fdc133ad640c56cf3eee2421c53ac908497192cb68122b80e0deff057de68caa83e11fee01be617c9fd0d7663611cca051ab91b043e3f549111dff77dede1c replication.toml
e5cc5d93d1e8eb95961a8150b70e2bef105994a659153eb183f6d70f78c017b4696c4882c6ac46301fcc34bcbafd74f25430484cadd8b48d38decd47dfaa1e56 security.toml
60c758d6d565d0cbc533e4e5677186a4700d48be12ebbd2bd1c6b4bcf38f687d0ab1f66e0953c8a27c663549cdbae73e5fd7eda74bb422c20e10e0d0850b5ead seaweedfs-4.31-linux-amd64.tar.gz
93c7539fe15a0aa6192d26ac2360003961f0643e302d910179d02341b10a7243df776294fb1b0134ec2a2a9596b1abc3ce9ab701f9bc3d78f2b965ea464a4b18 seaweedfs-4.31-linux-arm64.tar.gz
b6ce09d6d83dd70e2797a185171eea3587436fb024a99b4998f714b713244d6f768ef59f67c9b7251650d4f19d414eea231eab92099763d85cd92e41cb2bdc3f credential.toml
7105de7053654b8c0502521ce58585915fc831c3152801e1db02b1c5214dc3c3ed31f4fc18000665d8338c7a10ffbf0d248368bef2ef3a6d37d46cd856398366 example-credential.toml
6213e9cff227d66a7a04237d15e2f77904f1bcdf5709fd985c1e12fe4aca0d0327db6997d567303eff47dabec3650db2a6d874b03adda41b239e04ee0c3557ee example-filer.toml
e5753bd0bcc60dc84428c40ba556c08acae61dec083a5b76640125f637532e7c5759a72cdfd95114222a7a0901a916ec3c4d12f306853cfca0fe9a0b1857893e example-master.toml
2c200c28172a5d0b1c80ab28e849287e7af8d103355638fd3183b8614d3e2b1b3482132522663f7c6d844e10e13b8c6e23fb5fba0dec2cc83de45c2f17b33477 example-notification.toml
4b507540d57799e42ec0ee84073c6fa32c577a2f4a98bf66ee7302355080c63c65d70709115185debdee3c957e9294e79a8e461347d7148af7fdd368dc548af6 example-replication.toml
a0f307bc73b1aa03653c42c22a1fc704640cbd86957a417f947fe33356e03734c33782777cb1d6cb17360fc6b3930ffbe1386f89123e9dc3c21468210418aae2 example-security.toml
70069f97edf261a2cd0669dd60adad80533609a25d7429254e0c3322d7e80337f5ff69a5581bc39e6a15442098d14d610ce5f0cb14f02c461c8d3dd440b22c56 example-shell.toml
be5771a5e1bf626c53f9dffbfa2a400d9b8f9626d55a7b61dcd853753561989427ba872a2638a5e6343f44dea6790678483bd29101d6495dbbfcd3029947b9a7 filer.toml
2cd6c73a5e23e0395b6f099dcc3acf9280cbb3e13f54408571e91741c4e6b0dad85f6d4e5a92f23ad541bad57ce295c28bb8450fe11da35acbd4da25848f61e5 master.toml
0a1b3d0fd1671744f96e7642fb9aee9b80debd0d9a82f586beb36fe32f9ad440332acd2a8b0ee0b210b912cb281d88ca56fd15e3a2ece61be9dcf96afc00a6f8 notification.toml
5a607ce6cdd11ffbe528b9a85e0012b48accefa6ccfeda9d6aa70d92ccf7f219e75c93cd2f1c36f3eb16204b3652a45dcd52ffcb64bceef91d081702b19cef3c replication.toml
d21981923b5964cf8365185a4f7c28a14e33ba6f60ad63e797e315544a6c22efdaa72814292824c07ef14554738769e0fd4c670fe797cf77b0226b2595a8b43b security.toml
7a91ce9da79b92e5ef42d4915f56a010bceaaa6c96dcef1f7b1821ee208d381aaa88b9cf495248276132e66d3215927f589b7fabc4eae2c2cb195645f904fac7 shell.toml
82e2793bf483ffb5b0c8fbe38e6c9df75afe01f824f8414cafd91e4bed5c79c13dae1ca659070401d4968d16ed4b26455b0c6208f777705527bceb2e5f286988 seaweedfs.admin.confd
5ad952c37c62a770327bd70f7349fa677406606f0b9bf03fdc8f6c6804701f1e77710ab20d6153f9e0cfb549ec161d68a3316abab9c59837bbe3f91ee99e5ce0 seaweedfs.filer.confd
@@ -100,4 +134,5 @@ e5cc5d93d1e8eb95961a8150b70e2bef105994a659153eb183f6d70f78c017b4696c4882c6ac4630
262cc5132a70a43f9f154d24da16fe2f34f736c0bfdafd56cd341b7f34b48b82aa3a38a93d64206360c391e1167e2b665a3ade5c7eddfe4b322253521629f4e0 seaweedfs.volume.confd
84a9caa8f5203a31f2f96ef812dca8e4f7597f8f06ed3f55db38daff167448b0332564a421dcb356405d761c7a9e815656208d431e84b8106951554ab7ef2142 seaweedfs.webdav.confd
b586dfbdbcf17591366a7a62c4da6f2b13e1bd5dfb5b066beea114d01057eeb3d81172814655d812c48e02336277b3d70fa9f9550923cfe616e9dd8162661f51 seaweedfs.worker.confd
2815330810545d17cffd993d93502c934c24af5aeff8f7096bcf0f020f895f1da50b74c356c028b67de552cbd8e4ccfa176636da2642feae99542035e4272171 weed.bash-completion
"

View File

@@ -0,0 +1,19 @@
[credential.filer_etc]
enabled = true
[credential.postgres]
enabled = false
hostname = "localhost"
port = 5432
username = "seaweedfs"
password = ""
database = "seaweedfs"
schema = "public"
sslmode = "disable"
table_prefix = "sw_"
connection_max_idle = 10
connection_max_open = 100
connection_max_lifetime_seconds = 3600
[credential.memory]
enabled = false

View File

@@ -45,3 +45,4 @@ enabled = false
# export WEED_CREDENTIAL_POSTGRES_PASSWORD=secret
# export WEED_CREDENTIAL_POSTGRES_HOSTNAME=db.example.com
# export WEED_CREDENTIAL_FILER_ETC_ENABLED=true

View File

@@ -451,3 +451,4 @@ timeout = "5s"
maxReconnects = 1000

View File

@@ -61,3 +61,4 @@ disable = false # disables volume growth if true
# try to replicate to all available volumes. You should only use this option
# if you are doing your own replication or periodic sync of volumes.
treat_replication_as_minimums = false

View File

@@ -80,3 +80,4 @@ workers = 5 # optional: concurrent worker
buffer_size = 10000 # optional: event buffer size (default: 10000, range: 100-1000000)
# event_types = ["create", "update", "delete", "rename"] # optional: filter by event types (default: all)
# path_prefixes = ["/important", "/data"] # optional: filter by path prefixes (default: all)

View File

@@ -72,3 +72,4 @@ b2_region = ""
bucket = "mybucket" # an existing bucket
directory = "/" # destination directory
is_incremental = false

View File

@@ -202,3 +202,4 @@ key = ""
# white list. It's checking request ip address.
[guard]
white_list = ""

View File

@@ -0,0 +1,9 @@
[cluster]
default = "c1"
[cluster.c1]
master = "localhost:9333" # comma-separated master servers
[cluster.c2]
master = ""

View File

@@ -0,0 +1,46 @@
[filer.options]
recursive_delete = false
[leveldb2]
enabled = true
dir = "/var/lib/seaweedfs/filer/meta"
[sqlite]
enabled = false
dbFile = "/var/lib/seaweedfs/filer/filer.db"
[mysql]
enabled = false
dsn = "root@tcp(localhost:3306)/seaweedfs?collation=utf8mb4_bin"
enable_tls = false
hostname = "localhost"
port = 3306
username = "root"
password = ""
database = ""
connection_max_idle = 10
connection_max_open = 50
connection_max_lifetime_seconds = 300
interpolateParams = false
enableUpsert = true
[postgres]
enabled = false
hostname = "localhost"
port = 5432
username = "postgres"
password = ""
database = "postgres"
schema = ""
sslmode = "disable"
connection_max_idle = 10
connection_max_open = 50
connection_max_lifetime_seconds = 300
pgbouncer_compatible = false
enableUpsert = true
[redis2]
enabled = false
address = "localhost:6379"
password = ""
database = 0

View File

@@ -0,0 +1,27 @@
[master.maintenance]
scripts = ""
sleep_minutes = 17
[master.sequencer]
type = "raft"
sequencer_snowflake_id = 0
[storage.backend.s3.default]
enabled = false
aws_access_key_id = ""
aws_secret_access_key = ""
region = "us-east-2"
bucket = ""
endpoint = ""
storage_class = "STANDARD_IA"
[master.volume_growth]
copy_1 = 7
copy_2 = 6
copy_3 = 3
copy_other = 1
threshold = 0.9
disable = false
[master.replication]
treat_replication_as_minimums = false

View File

@@ -0,0 +1,42 @@
[notification.log]
enabled = false
[notification.kafka]
enabled = false
hosts = ["localhost:9092"]
topic = "seaweedfs_filer"
offsetFile = "/var/lib/seaweedfs/filer/last.offset"
offsetSaveIntervalSeconds = 10
sasl_enabled = false
sasl_mechanism = "PLAIN"
sasl_username = ""
sasl_password = ""
tls_enabled = false
tls_ca_cert = ""
tls_client_cert = ""
tls_client_key = ""
tls_insecure_skip_verify = false
[notification.aws_sqs]
enabled = false
aws_access_key_id = ""
aws_secret_access_key = ""
region = "us-east-2"
sqs_queue_name = ""
[notification.google_pub_sub]
enabled = false
google_application_credentials = ""
project_id = ""
topic = ""
[notification.webhook]
enabled = false
endpoint = ""
bearer_token = ""
timeout_seconds = 10
max_retries = 3
backoff_seconds = 3
max_backoff_seconds = 30
workers = 5
buffer_size = 10000

View File

@@ -0,0 +1,29 @@
[source.filer]
enabled = false
grpcAddress = "localhost:18888"
directory = "/buckets"
excludeDirectories = "/buckets/tmp"
[sink.local]
enabled = false
directory = "/var/lib/seaweedfs/backup"
is_incremental = false
[sink.filer]
enabled = false
grpcAddress = "localhost:18888"
directory = "/backup"
replication = ""
collection = ""
ttlSec = 0
is_incremental = false
[sink.s3]
enabled = false
aws_access_key_id = ""
aws_secret_access_key = ""
region = "us-east-2"
bucket = ""
directory = "/"
endpoint = ""
is_incremental = false

View File

@@ -0,0 +1,104 @@
[cors.allowed_origins]
values = "*"
[jwt.signing]
key = ""
expires_after_seconds = 10
[access]
ui = false
[filer.expose_directory_metadata]
enabled = true
[jwt.signing.read]
key = ""
expires_after_seconds = 10
[jwt.filer_signing]
key = ""
expires_after_seconds = 10
[jwt.filer_signing.read]
key = ""
expires_after_seconds = 10
[grpc]
ca = ""
allowed_wildcard_domain = ""
[grpc.volume]
cert = ""
key = ""
allowed_commonNames = ""
[grpc.master]
cert = ""
key = ""
allowed_commonNames = ""
[grpc.filer]
cert = ""
key = ""
allowed_commonNames = ""
[grpc.s3]
cert = ""
key = ""
allowed_commonNames = ""
[grpc.admin]
cert = ""
key = ""
allowed_commonNames = ""
[grpc.worker]
cert = ""
key = ""
allowed_commonNames = ""
[grpc.client]
cert = ""
key = ""
[https.client]
enabled = false
cert = ""
key = ""
ca = ""
insecure_skip_verify = false
[https.volume]
cert = ""
key = ""
ca = ""
[https.master]
cert = ""
key = ""
ca = ""
[https.filer]
cert = ""
key = ""
ca = ""
[https.admin]
cert = ""
key = ""
ca = ""
[admin]
user = ""
password = ""
[admin.readonly]
user = ""
password = ""
[s3.sse]
kek = ""
key = ""
[guard]
white_list = ""

View File

@@ -0,0 +1 @@
complete -C /usr/bin/weed weed

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
package_dir="${repo_root}/packaging/alpine/local/seaweedfs"
configs=(credential filer master notification replication security shell)
for config in "${configs[@]}"; do
"${repo_root}/bin/weed" scaffold -config "${config}" 2>/dev/null \
> "${package_dir}/example-${config}.toml"
done
"${repo_root}/bin/weed" autocomplete bash 2>/dev/null \
| sed -E 's#complete -C "?[^"]*/weed"? weed#complete -C /usr/bin/weed weed#' \
> "${package_dir}/weed.bash-completion"