From 084e9ab432f34dd42572773b396e1470b14becbb Mon Sep 17 00:00:00 2001 From: Tyler Stuyfzand Date: Mon, 26 May 2025 23:02:42 -0400 Subject: [PATCH] first commit --- .gitignore | 2 + ansible.cfg | 9 ++ inventory.yaml | 3 + playbook.yaml | 5 + roles/seaweed/defaults/main.yaml | 60 ++++++++++ roles/seaweed/tasks/certificates.yaml | 37 ++++++ roles/seaweed/tasks/download.yaml | 28 +++++ roles/seaweed/tasks/generate_cert.yaml | 41 +++++++ roles/seaweed/tasks/main.yaml | 115 ++++++++++++++++++ roles/seaweed/templates/filer.toml.j2 | 25 ++++ roles/seaweed/templates/s3.json.j2 | 1 + roles/seaweed/templates/seaweed.service.j2 | 12 ++ roles/seaweed/templates/security.toml.j2 | 132 +++++++++++++++++++++ roles/seaweed/vars/main.yaml | 41 +++++++ 14 files changed, 511 insertions(+) create mode 100644 .gitignore create mode 100644 ansible.cfg create mode 100644 inventory.yaml create mode 100644 playbook.yaml create mode 100644 roles/seaweed/defaults/main.yaml create mode 100644 roles/seaweed/tasks/certificates.yaml create mode 100644 roles/seaweed/tasks/download.yaml create mode 100644 roles/seaweed/tasks/generate_cert.yaml create mode 100644 roles/seaweed/tasks/main.yaml create mode 100644 roles/seaweed/templates/filer.toml.j2 create mode 100644 roles/seaweed/templates/s3.json.j2 create mode 100644 roles/seaweed/templates/seaweed.service.j2 create mode 100644 roles/seaweed/templates/security.toml.j2 create mode 100644 roles/seaweed/vars/main.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..caa32e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +*.iml \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..c8696ef --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,9 @@ +[defaults] +# Use this private key for all SSH connections +private_key_file = ~/.ssh/id_rsa + +# Set the default inventory file +inventory = ./inventory.yaml + +# Set the default remote user for SSH +remote_user = root \ No newline at end of file diff --git a/inventory.yaml b/inventory.yaml new file mode 100644 index 0000000..5ca8cd1 --- /dev/null +++ b/inventory.yaml @@ -0,0 +1,3 @@ +seaweed: + hosts: + diff --git a/playbook.yaml b/playbook.yaml new file mode 100644 index 0000000..1a20213 --- /dev/null +++ b/playbook.yaml @@ -0,0 +1,5 @@ +- name: Install SeaweedFS + hosts: seaweed + become: yes + roles: + - seaweed \ No newline at end of file diff --git a/roles/seaweed/defaults/main.yaml b/roles/seaweed/defaults/main.yaml new file mode 100644 index 0000000..3d37619 --- /dev/null +++ b/roles/seaweed/defaults/main.yaml @@ -0,0 +1,60 @@ +seaweed_config_dir: /etc/seaweedfs +seaweed_cert_dir: "{{ seaweed_config_dir }}/certs" +seaweed_data_dir: /var/lib/seaweedfs +seaweed_log_dir: /var/log/seaweedfs +seaweed_user: seaweed +seaweed_group: seaweed +download_dir: "/tmp" +seaweed_variant: full # set to large_disk if using large disk variant + +seaweed_binary_path: /usr/local/sbin/weed + +# Security (Certificates) +seaweed_ca_key: "{{ seaweed_cert_dir }}/ca.key" +seaweed_ca_cert: "{{ seaweed_cert_dir }}/ca.crt" + +# SeaweedFS Master +seaweed_master_bind: 0.0.0.0 +seaweed_master_host: 127.0.0.1 +seaweed_master_port: 9333 + +# SeaweedFS Volume +seaweed_volume_port: 8080 +seaweed_volume_max: 50 + +# SeaweedFS Filer +seaweed_filer_port: 8888 + +# SeaweedFS S3 +seaweed_s3_port: 8333 +seaweed_s3_anonymous: false + +seaweed_cert_domains: + - master01 + - volume01 + - filer01 + - client01 + +seaweedfs_s3_config: + identities: + - name: admin + credentials: + - accessKey: "{{ s3_access_key }}" + secretKey: "{{ s3_secret_key }}" + actions: + - Admin + - Read + - List + - Tagging + - Write + # identities: + # - name: default + # credentials: + # - accessKey: "example" + # secretKey: "example" + # actions: + # - Admin + # - Read + # - List + # - Tagging + # - Write \ No newline at end of file diff --git a/roles/seaweed/tasks/certificates.yaml b/roles/seaweed/tasks/certificates.yaml new file mode 100644 index 0000000..00b722c --- /dev/null +++ b/roles/seaweed/tasks/certificates.yaml @@ -0,0 +1,37 @@ +- name: Ensure certs directory exists + file: + path: "{{ seaweed_config_dir }}/certs" + state: directory + mode: '0700' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + +# Use OpenSSL here because the crypto library isn't working properly +- name: Generate CA private key as seaweedfs user + command: openssl genrsa -out {{ seaweed_ca_key }} 4096 + args: + creates: "{{ seaweed_ca_key }}" + +- name: Generate self-signed CA certificate as seaweedfs user + command: > + openssl req -x509 -new -nodes -key {{ seaweed_ca_key }} + -sha256 -days 3650 -out {{ seaweed_ca_cert }} + -subj "/CN=SeaweedFS CA" + args: + creates: "{{ seaweed_ca_cert }}" + +- name: Change ownership of CA files + file: + path: "{{ item }}" + owner: "{{ seaweed_user }}" + group: "{{ seaweed_user }}" + mode: '0644' + loop: + - "{{ seaweed_ca_key }}" + - "{{ seaweed_ca_cert }}" + +- name: Generate server private keys and certificates for domains + include_tasks: generate_cert.yaml + loop: "{{ seaweed_cert_domains }}" + loop_control: + loop_var: domain diff --git a/roles/seaweed/tasks/download.yaml b/roles/seaweed/tasks/download.yaml new file mode 100644 index 0000000..0d84606 --- /dev/null +++ b/roles/seaweed/tasks/download.yaml @@ -0,0 +1,28 @@ +- name: Determine SeaweedFS asset name based on architecture and disk flag + set_fact: + seaweedfs_asset_name: "{{ asset_name_matrix[ansible_architecture][seaweed_variant] }}" + +- name: Ensure download directory exists + file: + path: "{{ download_dir }}" + state: directory + mode: '0755' + +- name: Download SeaweedFS latest release artifact + get_url: + url: "https://github.com/seaweedfs/seaweedfs/releases/latest/download/{{ seaweedfs_asset_name }}" + dest: "{{ download_dir }}/{{ seaweedfs_asset_name }}" + mode: '0644' + +- name: Extract SeaweedFS archive + unarchive: + src: "{{ download_dir }}/{{ seaweedfs_asset_name }}" + dest: "{{ download_dir }}" + remote_src: yes + +- name: Move 'weed' binary to /usr/local/bin + copy: + src: "{{ download_dir }}/weed" + dest: "{{ seaweed_binary_path }}" + mode: '0755' + remote_src: yes \ No newline at end of file diff --git a/roles/seaweed/tasks/generate_cert.yaml b/roles/seaweed/tasks/generate_cert.yaml new file mode 100644 index 0000000..7d734cd --- /dev/null +++ b/roles/seaweed/tasks/generate_cert.yaml @@ -0,0 +1,41 @@ +- name: Generate private key for {{ domain }} + command: > + openssl genrsa -out {{ seaweed_cert_dir }}/{{ domain }}.key 2048 + args: + creates: "{{ seaweed_cert_dir }}/{{ domain }}.key" + register: gen_key_result + +- name: Generate CSR for {{ domain }} + command: > + openssl req -new -key {{ seaweed_cert_dir }}/{{ domain }}.key + -out {{ seaweed_cert_dir }}/{{ domain }}.csr + -subj "/CN={{ domain }}" + args: + creates: "{{ seaweed_cert_dir }}/{{ domain }}.csr" + register: gen_csr_result + +- name: Generate certificate signed by CA for {{ domain }} + command: > + openssl x509 -req + -in {{ seaweed_cert_dir }}/{{ domain }}.csr + -CA {{ seaweed_ca_cert }} + -CAkey {{ seaweed_ca_key }} + -CAcreateserial + -out {{ seaweed_cert_dir }}/{{ domain }}.crt + -days 3650 + -sha256 + args: + creates: "{{ seaweed_cert_dir }}/{{ domain }}.crt" + register: gen_crt_result + +- name: Set certificate permissions + file: + path: "{{ item }}" + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + mode: '0644' + recurse: true + loop: + - "{{ seaweed_cert_dir }}/{{ domain }}.crt" + - "{{ seaweed_cert_dir }}/{{ domain }}.csr" + - "{{ seaweed_cert_dir }}/{{ domain }}.key" \ No newline at end of file diff --git a/roles/seaweed/tasks/main.yaml b/roles/seaweed/tasks/main.yaml new file mode 100644 index 0000000..82a7414 --- /dev/null +++ b/roles/seaweed/tasks/main.yaml @@ -0,0 +1,115 @@ +- name: Download and Install SeaweedFS + include_tasks: download.yaml + +- name: Create system user for SeaweedFS + user: + name: "{{ seaweed_user }}" + shell: /usr/sbin/nologin + system: yes + create_home: no + +- name: Create SeaweedFS configuration directory + file: + path: "{{ seaweed_config_dir }}" + state: directory + mode: '0755' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + +- name: Create SeaweedFS data directory + file: + path: "{{ seaweed_data_dir }}" + state: directory + mode: '0755' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + +- name: Create SeaweedFS log directory + file: + path: "{{ seaweed_log_dir }}" + state: directory + mode: '0755' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + +- name: Create SeaweedFS data directories + file: + path: "{{ seaweed_data_dir }}/{{ item.name }}" + state: directory + mode: '0755' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + loop: "{{ seaweed_services }}" + +- name: Create certificates + include_tasks: certificates.yaml + +- name: Generate seaweed JWT signing secrets + set_fact: + seaweed_jwt_signing: "{{ lookup('password', '/dev/null length=64 chars=ascii_letters,digits') }}" + seaweed_jwt_filer_signing: "{{ lookup('password', '/dev/null length=64 chars=ascii_letters,digits') }}" + +- name: Configure security configuration + template: + src: security.toml.j2 + dest: "{{ seaweed_config_dir }}/security.toml" + mode: '0644' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + vars: + seaweed_master_cert: "{{ seaweed_cert_dir }}/master01.crt" + seaweed_master_key: "{{ seaweed_cert_dir }}/master01.key" + seaweed_volume_cert: "{{ seaweed_cert_dir }}/volume01.crt" + seaweed_volume_key: "{{ seaweed_cert_dir }}/volume01.key" + seaweed_filer_cert: "{{ seaweed_cert_dir }}/filer01.crt" + seaweed_filer_key: "{{ seaweed_cert_dir }}/filer01.key" + seaweed_client_cert: "{{ seaweed_cert_dir }}/client01.crt" + seaweed_client_key: "{{ seaweed_cert_dir }}/client01.key" + +- name: Configure filer + template: + src: filer.toml.j2 + dest: "{{ seaweed_config_dir }}/filer.toml" + mode: '0644' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + +- name: Generate S3 access key + set_fact: + s3_access_key: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters,digits') }}" + +- name: Generate S3 secret key + set_fact: + s3_secret_key: "{{ lookup('password', '/dev/null length=40 chars=ascii_letters,digits') }}" + +- name: Configure s3 + template: + src: s3.json.j2 + dest: "{{ seaweed_config_dir }}/s3.json" + mode: '0644' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + +- name: Install SeaweedFS services + template: + src: seaweed.service.j2 + dest: "/etc/systemd/system/seaweed-{{ item.name }}.service" + mode: '0644' + owner: "{{ seaweed_user }}" + group: "{{ seaweed_group }}" + loop: "{{ seaweed_services }}" + vars: + seaweed_service: "{{ item.name }}" + seaweed_command: "{{ item.name }}" + seaweed_args: "{{ item.args }}" + +- name: Reload systemd daemon + systemd: + daemon_reload: yes + +- name: Enable and start SeaweedFS services + systemd: + name: "seaweed-{{ item.name }}" + enabled: yes + state: restarted + loop: "{{ seaweed_services }}" \ No newline at end of file diff --git a/roles/seaweed/templates/filer.toml.j2 b/roles/seaweed/templates/filer.toml.j2 new file mode 100644 index 0000000..8892c3b --- /dev/null +++ b/roles/seaweed/templates/filer.toml.j2 @@ -0,0 +1,25 @@ +# A sample TOML config file for SeaweedFS filer store +# Used with "weed filer" or "weed server -filer" +# Put this file to one of the location, with descending priority +# ./filer.toml +# $HOME/.seaweedfs/filer.toml +# /etc/seaweedfs/filer.toml + +#################################################### +# Customizable filer server options +#################################################### +[filer.options] +# with http DELETE, by default the filer would check whether a folder is empty. +# recursive_delete will delete all sub folders and files, similar to "rm -Rf" +recursive_delete = false +#max_file_name_length = 255 + +#################################################### +# The following are filer store options +#################################################### + +[leveldb2] +# local on disk, mostly for simple single-machine setup, fairly scalable +# faster than previous leveldb, recommended. +enabled = true +dir = "{{ seaweed_data_dir }}/filer/ldb2" # directory to store level db files diff --git a/roles/seaweed/templates/s3.json.j2 b/roles/seaweed/templates/s3.json.j2 new file mode 100644 index 0000000..c143e9c --- /dev/null +++ b/roles/seaweed/templates/s3.json.j2 @@ -0,0 +1 @@ +{{ seaweedfs_s3_config | default({}) | to_nice_json }} \ No newline at end of file diff --git a/roles/seaweed/templates/seaweed.service.j2 b/roles/seaweed/templates/seaweed.service.j2 new file mode 100644 index 0000000..ada0336 --- /dev/null +++ b/roles/seaweed/templates/seaweed.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=SeaweedFS - {{ seaweed_service }} Server +After=network.target + +[Service] +ExecStart={{ seaweed_binary_path }} -logdir={{ seaweed_log_dir }} {{ seaweed_command }} {% for arg in seaweed_args %}{{ arg }} {% endfor +%} +User={{ seaweed_user }} +Restart=on-failure +LimitNOFILE=1048576 + +[Install] +WantedBy=multi-user.target diff --git a/roles/seaweed/templates/security.toml.j2 b/roles/seaweed/templates/security.toml.j2 new file mode 100644 index 0000000..c11c329 --- /dev/null +++ b/roles/seaweed/templates/security.toml.j2 @@ -0,0 +1,132 @@ +# Put this file to one of the location, with descending priority +# ./security.toml +# $HOME/.seaweedfs/security.toml +# /etc/seaweedfs/security.toml +# this file is read by master, volume server, and filer + +# comma separated origins allowed to make requests to the filer and s3 gateway. +# enter in this format: https://domain.com, or http://localhost:port +[cors.allowed_origins] +values = "*" + +# this jwt signing key is read by master and volume server, and it is used for write operations: +# - the Master server generates the JWT, which can be used to write a certain file on a volume server +# - the Volume server validates the JWT on writing +# the jwt defaults to expire after 10 seconds. +[jwt.signing] +key = "{{ seaweed_jwt_signing }}" +expires_after_seconds = 10 # seconds + +# by default, if the signing key above is set, the Volume UI over HTTP is disabled. +# by setting ui.access to true, you can re-enable the Volume UI. Despite +# some information leakage (as the UI is not authenticated), this should not +# pose a security risk. +[access] +ui = false + +# by default the filer UI is enabled. This can be a security risk if the filer is exposed to the public +# and the JWT for reads is not set. If you don't want the public to have access to the objects in your +# storage, and you haven't set the JWT for reads it is wise to disable access to directory metadata. +# This disables access to the Filer UI, and will no longer return directory metadata in GET requests. +[filer.expose_directory_metadata] +enabled = true + +# this jwt signing key is read by master and volume server, and it is used for read operations: +# - the Master server generates the JWT, which can be used to read a certain file on a volume server +# - the Volume server validates the JWT on reading +# NOTE: jwt for read is only supported with master+volume setup. Filer does not support this mode. +[jwt.signing.read] +key = "" +expires_after_seconds = 10 # seconds + + +# If this JWT key is configured, Filer only accepts writes over HTTP if they are signed with this JWT: +# - f.e. the S3 API Shim generates the JWT +# - the Filer server validates the JWT on writing +# the jwt defaults to expire after 10 seconds. +[jwt.filer_signing] +key = "{{ seaweed_jwt_filer_signing }}" +expires_after_seconds = 10 # seconds + +# If this JWT key is configured, Filer only accepts reads over HTTP if they are signed with this JWT: +# - f.e. the S3 API Shim generates the JWT +# - the Filer server validates the JWT on writing +# the jwt defaults to expire after 10 seconds. +[jwt.filer_signing.read] +key = "" +expires_after_seconds = 10 # seconds + +# all grpc tls authentications are mutual +# the values for the following ca, cert, and key are paths to the PERM files. +# the host name is not checked, so the PERM files can be shared. +[grpc] +ca = "{{ seaweed_ca_cert }}" +# Set wildcard domain for enable TLS authentication by common names +allowed_wildcard_domain = "" # .mycompany.com + +[grpc.volume] +cert = "{{ seaweed_volume_cert }}" +key = "{{ seaweed_volume_key }}" +allowed_commonNames = "" # comma-separated SSL certificate common names + +[grpc.master] +cert = "{{ seaweed_master_cert }}" +key = "{{ seaweed_master_key }}" +allowed_commonNames = "" # comma-separated SSL certificate common names + +[grpc.filer] +cert = "{{ seaweed_filer_cert }}" +key = "{{ seaweed_filer_key }}" +allowed_commonNames = "" # comma-separated SSL certificate common names + +[grpc.s3] +cert = "{{ seaweed_client_cert }}" +key = "{{ seaweed_client_key }}" +allowed_commonNames = "" # comma-separated SSL certificate common names + +[grpc.msg_broker] +cert = "" +key = "" +allowed_commonNames = "" # comma-separated SSL certificate common names + +[grpc.msg_agent] +cert = "" +key = "" +allowed_commonNames = "" # comma-separated SSL certificate common names + +# use this for any place needs a grpc client +# i.e., "weed backup|benchmark|filer.copy|filer.replicate|mount|s3|upload" +[grpc.client] +cert = "{{ seaweed_client_cert }}" +key = "{{ seaweed_client_key }}" + +# https client for master|volume|filer|etc connection +# It is necessary that the parameters [https.volume]|[https.master]|[https.filer] are set +[https.client] +enabled = false +cert = "" +key = "" +ca = "" + +# volume server https options +[https.volume] +cert = "" +key = "" +ca = "" + +# master server https options +[https.master] +cert = "" +key = "" +ca = "" + +# filer server https options +[https.filer] +cert = "" +key = "" +ca = "" +# disable_tls_verify_client_cert = true|false (default: false) + +# white list. It's checking request ip address. +[guard] +white_list = "" diff --git a/roles/seaweed/vars/main.yaml b/roles/seaweed/vars/main.yaml new file mode 100644 index 0000000..2f58955 --- /dev/null +++ b/roles/seaweed/vars/main.yaml @@ -0,0 +1,41 @@ +# Map architecture + large_disk flag to artifact name +asset_name_matrix: + x86_64: + full: "linux_amd64_full.tar.gz" + large_disk: "linux_amd64_large_disk.tar.gz" + aarch64: + full: "linux_arm64_full.tar.gz" + large_disk: "linux_arm64_large_disk.tar.gz" + armv7l: + full: "linux_arm_full.tar.gz" + large_disk: "linux_arm_large_disk.tar.gz" + +seaweed_services: + - name: master + args: + - "-mdir={{ seaweed_data_dir}}/master" + - "-ip.bind={{ seaweed_master_bind }}" + - "-ip={{ seaweed_master_host }}" + - "-port={{ seaweed_master_port }}" + - name: volume + args: + - "-dir={{ seaweed_data_dir }}/volume" + - "-max={{ seaweed_volume_max }}" + - "-mserver={{ seaweed_master_host }}:{{ seaweed_master_port }}" + - "-ip.bind={{ seaweed_master_bind }}" + - "-ip={{ seaweed_master_host }}" + - "-port={{ seaweed_volume_port }}" + - name: filer + args: + - "-master={{ seaweed_master_host }}:{{ seaweed_master_port }}" + - "-ip.bind={{ seaweed_master_bind }}" + - "-ip={{ seaweed_master_host }}" + - "-port={{ seaweed_filer_port }}" + - "-localSocket={{ seaweed_data_dir }}/filer/seaweed-filer-{{ seaweed_filer_port }}.sock" + - name: s3 + args: + - "-filer={{ seaweed_master_host }}:{{ seaweed_filer_port }}" + - "-ip.bind={{ seaweed_master_bind }}" + - "-port={{ seaweed_s3_port }}" + - "-localSocket={{ seaweed_data_dir }}/s3/seaweed-s3-{{ seaweed_s3_port }}.sock" + - "-config /etc/seaweedfs/s3.json" \ No newline at end of file