# Life! > "Your time is limited, so don’t waste it living someone else’s life!" Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages ### Newsletter URL: https://ostreff.info/newsletter/ Last updated: 2021-01-27T16:43:29.000Z _No content available._ ### Authors URL: https://ostreff.info/authors/ Last updated: 2026-01-06T21:48:41.000Z _No content available._ ### Tags URL: https://ostreff.info/tags/ Last updated: 2026-01-06T21:48:37.000Z _No content available._ ### Community guidelines URL: https://ostreff.info/community-guidelines/ Last updated: 2026-01-06T21:48:22.000Z ## One of the best things about sharing work online is being able to engage with the community that forms around it. That's you! The comments section on this site is a space for you to chat with other members,share knowledge and experiences, and share what content you enjoyed the most. To help keep the community safe, constructive and fun, here are a few best practices to keep in mind. Thanks for your help & support. ❤️ ### **Respect each other.** This should be a safe and inviting community for everyone, so it's expected that all members be civil and respectful in their interactions. Like any online community, we're a group of people with a range of experiences and beliefs, many of which may differ from your own. Consider this a gift — interesting and challenging perspectives can open doors you didn't even know existed. ### **Share your real name and expertise.** When you publish your first comment on this site, you'll be prompted to complete a member profile, with your name and expertise. You can also update this any time. Using your real name and sharing your expertise or lived experience helps give context to valuable discussions. Sharing these extra details alongside your comments shows the perspective from which you are joining the conversation. ![](https://ghost.org/resources/content/images/2022/08/add-expertise.png) ### **Stay on topic, and share your sources.** A new post might prompt a dozen different types of responses, from a reader sharing a personal story to a newcomer asking for clarification. In every instance, do your best to stay on topic, support your replies with additional context or sources where appropriate, and focus on discussing the ideas presented. - Link to trustworthy articles, reports, and sources to support your arguments. - Only share links to your own content when it directly adds to the conversation. Please don't spam. Nobody likes spam. Nobody. If you've contributed to the conversation and someone else replies to your thread, you'll be notified by email. You can choose to switch these emails off in your member account settings, under email preferences. ![](https://ghost.org/resources/content/images/2022/08/email-preferences.png) ### **Keep it constructive and kind.** Constructive contributions are the best kind. If you have knowledge that places the topic of the post in a new light, or if you have feedback about the subject matter — it's best shared in a way that helps everyone feel like this is a space where new ideas are respected and valued. With every comment you make, you add to the dialogue. Many people may read your words and use them to shape their ideas and feelings about a topic, event, or person. Employ that influence wisely. Move the collective conversation forward, and invite others to follow your lead. ### **Flag inappropriate interactions.** As a member, you can help maintain a safe and inviting space by reporting any behaviour that goes against these guidelines. If you see someone being rude, negative, hateful, divisive, or combative in the comments section, flag it. All you need to do is click the three dots next to the inappropriate comment, and then click **Report comment**. These reports are taken seriously, and there's a zero tolerance policy for abusive behaviour. ![](https://ghost.org/resources/content/images/2022/08/report-comment.png) ### **Be the you online that you are in-person.** Most people are kind and well-intentioned humans. Although it may seem like the opposite is true on the internet at times, the reality is that people tend to follow the majority. You can help keep the tone of this space friendly and fun if you lead the way with insightful, humane language. Let's keep this a space that you want to spend time in. That’s it! If you’ve scrolled this far, thank you for being a valued member, and see you in the comments! ****On this page** 1. [Community guidelines](https://ghost.org/resources/community-guidelines-template/?ref=ostreff.info#community-guidelines) ### Jordan Ostrev - CV URL: https://ostreff.info/jordan-ostrev-cv/ Last updated: 2026-08-20T14:18:13.000Z # JORDAN OSTREV Head of Mobile Core Network & Packet Services 📍 Sofia, Bulgaria 📞 (+359) 878500555 ✉️ jostreff@gmail.com 🔗 linkedin.com/in/jostreff **Executive Summary:** Accomplished Telecommunications Executive and Mobile Core Infrastructure Architect with 25+ years of experience leading core network planning, design, and deployment. Proven track record in orchestrating multi-vendor EPC/5G migrations, CUPS implementations, and high-availability Telco Cloud architectures (VMware/OpenStack). Strong leader in cross-functional team management, vendor negotiations, and high-level strategy. | Professional Experience 07/2010 – Present Head of Development & Planning - Packet Services BTC Ltd. (Vivacom) \| Sofia, Bulgaria **Strategic Network Leadership:** Directing team responsible for engineering, capacity planning, and architectural development of Mobile Core Network Packet Services. **5G & Next-Gen Core Integration:** Architected and executed planning/implementation of 4G/5G services and Control-User-Plane-Separation (CUPS) architecture. **Vendor Migration & Swaps:** Managed seamless swaps and transitions between major vendors (Ericsson, Huawei, Nokia, Cisco) with zero service disruption. **Telco Private Cloud:** Designed and implemented distributed VMware Telco Private Cloud and Ericsson Cloud Execution Environment (CEE/OpenStack). **Core Infrastructure:** Overseeing MPBN (MPLS & Segment Routing Core), PCRF, SGSN-MME, GGSN/SGW/PGW, CGNAT, and IPX/GRX connectivity. **Governance & Compliance:** Implemented EU roaming regulations, bill-shock prevention systems, and conducted C-level executive technical proposals. 03/2009 – 06/2010 Team Leader GSM Core Network - Packet Services BTC Ltd. (Vivacom) | Sofia, Bulgaria Led operations and configuration of GSM Core packet infrastructure (SGSN/GGSN, MPLS, VRRP, IPSEC). Deployed high-availability monitoring tools and established Level-2 technical support procedures. 01/2005 – 02/2009 Senior Value Added Services (VAS) Engineer BTC Mobile Ltd. (vivatel) | Sofia, Bulgaria Managed HA platforms for WAP Gateway, MMSC, PoC, MDSP real-time billing, and custom streaming solutions. Engineered custom SS7<->SIP/RTP video gateway solutions and high-availability database clusters. 12/1996 – 01/2005 Senior System Administrator & Developer Roles RTC Ltd. / Mobikom / Lirex Ltd. **Mobikom (2001-2005):** Architected AAA, RADIUS, Tacacs+, e-mail/SMS gateways, and dynamic routing HA setups. **Lirex (1999):** Core engineer for Bulgarian National Identity Cards infrastructure project. | Core Competencies Mobile Core & 4G/5G EPC, PCRF, SGSN-MME, SGW/PGW, VoWiFi, CUPS, 5G Core Architecture, IPX/GRX Network & Routing MPBN, MPLS, Segment Routing, BGP, OSPF, CGNAT, Firewalls (Palo Alto, CheckPoint, Cisco ASA) Telco Cloud & Virtualization VMware NFVI, Ericsson CEE, OpenStack, KVM, vSAN, High Availability Clusters Vendors & Platforms Ericsson, Huawei, Nokia, Cisco/Starent, Alcatel-Lucent, Sun Solaris, Linux Education PhD Candidate (ICT) Technical University of Sofia Thesis: 4G Network Services for Industry M.Sc. Computer Science Technical University of Sofia Electronics & Automation Engineer Key Certifications **Ericsson:** 5G Core Concepts, Protocols & Procedures, VoLTE E2E **ITIL v3:** Service Design & Foundations **Cisco:** IOS XR Advanced Deploy & Troubleshoot, CCNA **PMP:** Exam Preparation Course **Huawei:** SGSN/GGSN O&M, UPCC Policy Config Languages Bulgarian Native English C1 (Proficient) Russian C2 (Master) | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ### Jordan Ostrev CV (en) URL: https://ostreff.info/jordan-ostrev-cv-en/ Last updated: 2026-08-21T11:25:20.000Z # Jordan Ostrev Senior Network / Telecom Architect — Mobile Core, IP/MPLS & 4G/5G Infrastructure Sofia, Bulgaria +359 878 500 555 jostreff@gmail.com linkedin.com/in/jostreff ## Profile Senior telecom architect with 25+ years of hands-on and leadership experience designing, planning and operating mobile core and IP/MPLS backbone networks. Since 2010, leads architecture and capacity planning for the entire Mobile Core Network Packet Services domain at BTC/Vivacom — EPC (PCRF, SGSN-MME, GGSN/SGW/PGW), CUPS control/user-plane separation, and the MPBN backbone (MPLS, Segment Routing, IPv4/IPv6, CGNAT, IPX/GRX). Proven track record of designing multi-vendor architectures, leading vendor-swap migrations without service disruption, building distributed telco-grade private cloud, and driving 4G/5G rollout end-to-end. Combines deep protocol- and platform-level expertise (Cisco IOS-XR, BGP/OSPF/IS-IS, Nokia SR OS) with FreeBSD/Linux systems engineering, virtualization and open-source tooling — able to work equally on high-level network architecture and low-level implementation. ## Core Competencies Network & Telecom Architecture: EPC/mobile core design (PCRF, SGSN-MME, GGSN/SGW/PGW), CUPS, 4G/5G architecture, VoWiFi, IPX/GRX, CGNAT, EU roaming compliance, multi-vendor architecture & migration strategy Routing & IP/MPLS: Cisco IOS-XR (ASR9k), BGP, OSPF, IS-IS/TI-LFA, MPLS/Segment Routing, VRF design, BFD, Nokia SR OS Systems & Platforms: FreeBSD (ports maintainer & administration), Linux, Solaris; ZFS; HA design (CARP/VRRP, HSRP, clustering) Virtualization & Cloud: KVM, VMware NFVI, OpenStack (Ericsson CEE), distributed telco private cloud architecture Engineering & Automation: Open-source tooling and in-house HA solutions, scripting (Perl, shell), Git/SVN/CVS-based configuration management Leadership & Process: Team leadership, capacity planning, technical documentation & MOPs, vendor evaluation & PoC trials, executive presentations ## Professional Experience Head of Development and Planning, Packet Services Department — Mobile Core Network | BTC Ltd. / Vivacom Jul 2010 – Present Sofia, Bulgaria - **Owns the end-to-end architecture** of the Mobile Core Network Packet Services domain — EPC elements (PCRF, SGSN-MME, GGSN/SGW/PGW) and the MPBN IP/MPLS backbone (Segment Routing, IPv4/IPv6, CGNAT, IPX/GRX, voice interconnect) — covering capacity planning, network-wide reconfiguration, and infrastructure roadmap. - Designed and led rollout of **CUPS architecture** (control/user-plane separation) and **4G/5G service architecture** for subscribers, plus solutions for EU roaming and bill-shock-prevention regulations. - Architected and deployed a **distributed telco-grade private cloud** (VMware-based) as a substitute NFVI layer for vendors without a certified cloud solution of their own. - Led **multiple vendor-swap migrations** (core network elements, routers) with zero-disruption cutovers; ran vendor evaluations, tenders and PoC trials for new network elements. - Built in-house HA mobile network functions and open-source tooling; introduced Git/SVN/CVS-based configuration tracking across the domain; authored architecture docs, guides and MOPs used by the operations team. - Acts as final escalation tier before vendor support; presents architecture and roadmap decisions to executive management. Team Leader, GSM Core Network | BTC Ltd. Mar 2009 – Jun 2010 Sofia, Bulgaria - Led the team responsible for installation, configuration, monitoring and maintenance of the GSM core network (SGSN/GGSN, MPLS/AToM, VRRP/HSRP, IPSec tunnels); introduced additional network monitoring systems. Senior Value Added Services Engineer (WAP, MMS, PoC) | BTC Mobile Ltd. Jan 2005 – Feb 2009 Sofia, Bulgaria - Owned WAP Gateway, MMS Center, PoC platform, real-time billing (MDSP) and streaming/video gateway solutions (SS7↔SIP/RTP); maintained HA clusters and Oracle databases. Senior System Administrator, Internet Department | RTC Ltd. / Mobikom Mar 2001 – Jan 2005 Sofia, Bulgaria - Built and operated AAA, mail, SMS/MMS gateway services and public/private web, file and database servers; developed an in-house billing system integrated with AAA (Radius/Tacacs+). **Earlier Career (1993 – 2001):** Application Programmer and System Administrator (RTC Ltd./Mobikom, Lirex Ltd., Ringier Bulgaria) — built electronic payment and billing systems, systems administration and network support, contributed to Bulgaria's national ID card project for the Ministry of Interior. ## Education PhD Candidate, Engineering Sciences (4G Network Services for Industry) | Technical University of Sofia, Faculty of Automatics Current Sofia, Bulgaria M.Sc., Computer Systems and Control — Electronics and Automation Engineering | Technical University of Sofia 1990 – 1995 Sofia, Bulgaria ## Certifications & Training (Selected) - **CCNA** – Cisco Systems - **Advanced Services:** Deploy, Maintain and Troubleshoot IOS XR (Cisco CRS / XR 12000) – Cisco Systems - **5G Core Concepts;** 5G Core Protocols and Procedures – Ericsson AB - **VoLTE E2E Use Cases;** IMS Signaling; Authentication Federation Gateway – Ericsson AB - **SGSN/GGSN/UPCC Operation & Maintenance** – Huawei Technologies - **ITIL v3 Foundations & Service Design** – New Horizons Computer Learning Centers - **CompTIA Security+** – New Horizons Computer Learning Centers ## Languages & Additional Info **Languages:** Bulgarian (Native), English (C1 - fluent), Russian (C2 listening/reading, C1 speaking/writing) **Driving licence:** Category B ## Posts ### How to Work Around ghost update pnpm Failures on FreeBSD URL: https://ostreff.info/how-to-work-around-ghost-update-pnpm-failures-on-freebsd/ Last updated: 2026-10-01T17:52:38.000Z UPDATE: latest versions of pnpm and ghost are now working perfectly on FreeBSD. If you have tried updating Ghost recently using `ghost update`, you likely ran into this brick wall: ```text Downloading the pnpm binary for freebsd-x64... Could not download the pnpm binary: Sorry! pnpm does not provide a pre-built binary for freebsd. ``` Because Ghost-CLI relies heavily on `Corepack` and `pnpm` to fetch dependencies, updates halt immediately on FreeBSD. Newer versions of `pnpm` insist on fetching pre-compiled native binaries that do not exist for BSD architectures. Furthermore, forcing a standard `npm install` triggers dependency resolution crashes (`ERESOLVE`) due to peer conflicts between `knex` and `bookshelf`. Here is how to solve this issue permanently using two lightweight wrapper scripts. **Step 1: Intercept Corepack** First, back up your original `corepack` binary and replace it with a shell wrapper. This prevents Corepack from attempting to fetch non-existent FreeBSD binaries when invoked by Ghost-CLI. ```bash COREPACK_BIN=$(which corepack) mv "$COREPACK_BIN" "${COREPACK_BIN}.real" cat << 'EOF' > "$COREPACK_BIN" #!/bin/sh if [ "$1" = "pnpm" ]; then echo "Bypassing pnpm for FreeBSD: running npm install..." exec npm install --omit=dev --ignore-scripts --legacy-peer-deps fi exec /usr/local/bin/corepack.real "$@" EOF chmod +x "$COREPACK_BIN" ``` **Step 2: Mock pnpm and Reroute to npm** Ghost-CLI checks `pnpm -v` directly before initiating an update. To satisfy Ghost-CLI's checks while avoiding binary downloads, preserve your original `pnpm` executable and create a shim in `/usr/local/bin/pnpm`. ```bash mv /usr/local/bin/pnpm /usr/local/bin/pnpm.orig cat << 'EOF' > /usr/local/bin/pnpm #!/bin/sh if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then echo "12.3.4" exit 0 fi echo "Bypassing pnpm for FreeBSD: running npm install..." exec npm install --omit=dev --ignore-scripts --legacy-peer-deps EOF chmod +x /usr/local/bin/pnpm ``` **How It Works** - **Version Spoofing:** When Ghost-CLI runs `pnpm -v`, the wrapper returns a valid version string (`12.3.4`), passing pre-flight system checks. - **Rerouted Installation:** When Ghost executes `pnpm install`, the script redirects the command to `npm`. - **Dependency Handling:** The `--legacy-peer-deps` flag bypasses strict peer resolution errors between Ghost's internal dependencies without breaking runtime functionality. **Updating Ghost** With both shims active, execute `ghost update` under your unprivileged `ghost` user: ```bash su - ghost ghost update ``` Ghost-CLI will complete dependency fetching via `npm`, execute database migrations, and bring your instance online without requiring source modifications or custom CLI forks. ### How to Fix the Circular Dependency Between GLib and GObject-Introspection on FreeBSD URL: https://ostreff.info/how-to-fix-the-circular-dependency-between-glib-and-gobject-introspection-on-freebsd/ Last updated: 2026-08-07T12:27:05.000Z Welcome to the classic chicken-and-egg (circular dependency) problem. The Root Cause - gobject-introspection requires a current version of glib to build. - glib (in its default configuration) requires gobject-introspection to generate introspection data. When both ports fall out of sync, neither can compile first. The Solution: Using the bootstrap Flavor Fortunately, the FreeBSD `devel/glib20` port includes a bootstrap flavor. This flavor builds a minimal version of GLib without depending on `gobject-introspection`, allowing you to break the loop cleanly. Here is the step-by-step procedure to resolve it: Step 1: Build GLib in Bootstrap Mode First, switch to the `glib20` directory and reinstall it using the `bootstrap` flavor. This installs the core GLib libraries without requiring `gobject-introspection`: ```bash cd /usr/ports/devel/glib20 make FLAVOR=bootstrap clean reinstall clean ``` Step 2: Rebuild GObject-Introspection Now that your system has the updated core GLib binaries, you can successfully rebuild `gobject-introspection`: ```bash cd /usr/ports/devel/gobject-introspection make clean reinstall clean ``` Step 3: Restore GLib to the Default Flavor Finally, rebuild `glib20` using its `default` flavor so you get the full package with complete introspection support: ```bash cd /usr/ports/devel/glib20 make FLAVOR=default clean reinstall clean ``` TL;DR / Quick Commands ```bash # 1. Break the cycle with bootstrap GLib cd /usr/ports/devel/glib20 && make FLAVOR=bootstrap clean reinstall clean # 2. Upgrade gobject-introspection cd /usr/ports/devel/gobject-introspection && make clean reinstall clean # 3. Rebuild full GLib cd /usr/ports/devel/glib20 && make FLAVOR=default clean reinstall clean ``` That's it! Your ports system is back in sync without having to force-delete packages or break system dependencies. ### Designing a Bulletproof Backup & Restore System for PowerDNS with LMDB Views on FreeBSD URL: https://ostreff.info/designing-a-bulletproof-backup-restore-system-for-powerdns-with-lmdb-views-on-freebsd/ Last updated: 2026-07-06T21:44:32.000Z In my [previous post](https://ostreff.info/solving-lmdb-version-incompatibility-in-powerdns-a-post-mortem/), I detailed how a silent dependency upgrade from LMDB 0.9.x to LMDB 1.0 can completely break a PowerDNS Authoritative Server installation due to on-disk format incompatibility. While upgrading binaries is simple, the real challenge lies in protecting your data. If you are leveraging PowerDNS Views (for example, to separate internal telecom core network zones like `gprs` or `3gppnetwork` from public ones), standard database file backups won't cut it during major backend migrations. To ensure absolute peace of mind, we need a solution that exports everything into human-readable, database-agnostic BIND Zone Files, while explicitly preserving the metadata mapping of which zone belongs to which DNS View. Here is how to build a production-ready backup and recovery system that strictly adheres to the standard FreeBSD filesystem hierarchy. **FreeBSD Filesystem Architecture** Following the conventions laid out in the FreeBSD `hier(7)` manual, our system layout will be structured as follows: - **Automation Scripts:** Placed in `/usr/local/bin/` so they are available system-wide. - **Backup Storage:** Placed under `/var/backups/powerdns/` to keep the volatile data separate from application binaries. Let's initialize the storage directory structure: ```bash mkdir -p /var/backups/powerdns/zones_dump ``` **1\. The Backup Engine (**`/usr/local/bin/pdns-backup.sh`**)** This script extracts every single zone present on the system. Because PowerDNS doesn't have a direct "show view for this zone" command, the script dynamically cross-references each zone against all existing views. If a zone is part of a view, it's flagged accordingly; if not, it's categorized as a `global` zone. Run this command in your shell to deploy the backup script: ```bash cat > /usr/local/bin/pdns-backup.sh << 'EOF' #!/bin/sh # ============================================================================== # PowerDNS Full Automated Backup Script (Networks, Views & Global Zones) # Optimized for FreeBSD Filesystem Hierarchy # ============================================================================== BACKUP_DIR="/var/backups/powerdns" ZONES_DIR="$BACKUP_DIR/zones_dump" META_FILE="$BACKUP_DIR/views_structure.txt" NET_FILE="$BACKUP_DIR/networks_structure.txt" # Ensure backup directories exist mkdir -p "$ZONES_DIR" # Clean up previous dumps safely rm -f "$ZONES_DIR"/*.zone rm -f "$META_FILE" "$NET_FILE" echo "=== Starting FULL PowerDNS Backup [$(date)] ===" # 1. Export Network-to-View mappings (ACLs) echo " -> Exporting network definitions..." pdnsutil network list > "$NET_FILE" # 2. Export Zones ALL_ZONES=$(pdnsutil zone list-all) for zone in $ALL_ZONES; do ASSIGNED_VIEW="global" for view in $(pdnsutil view list-all); do pdnsutil view list "$view" | grep -Fqx "$zone" if [ $? -eq 0 ]; then ASSIGNED_VIEW="$view" break fi done echo " -> Exporting zone: $zone ($ASSIGNED_VIEW)" SAFE_ZONE_NAME=$(echo "$zone" | tr '.' '_') FILE_NAME="${ASSIGNED_VIEW}_${SAFE_ZONE_NAME}.zone" echo "${ASSIGNED_VIEW}:${zone}" >> "$META_FILE" pdnsutil zone list "$zone" > "$ZONES_DIR/$FILE_NAME" done echo "=== Backup completed successfully! ===" EOF chmod +x /usr/local/bin/pdns-backup.sh ``` **2\. The Restore Engine (**`/usr/local/bin/pdns-restore.sh`**)** The recovery script does the heavy lifting in reverse. It reads the metadata file line by line, provisions the bare zones if they don't exist, maps the view-specific zones back to their respective PowerDNS Views (while keeping global zones untouched), populates the records, and forces a DNSSEC rectification. Run this command to deploy the recovery script: ```bash cat > /usr/local/bin/pdns-restore.sh << 'EOF' #!/bin/sh # ============================================================================== # PowerDNS Full Automated Restore Script (Networks, Views & Global Zones) # Optimized for FreeBSD Filesystem Hierarchy # ============================================================================== BACKUP_DIR="/var/backups/powerdns" ZONES_DIR="$BACKUP_DIR/zones_dump" META_FILE="$BACKUP_DIR/views_structure.txt" NET_FILE="$BACKUP_DIR/networks_structure.txt" if [ ! -f "$META_FILE" ]; then echo "Error: Metadata structure file $META_FILE does not exist!" exit 1 fi echo "=== Starting Full Restore [$(date)] ===" # 1. Restore Network-to-View mappings if [ -f "$NET_FILE" ]; then echo "Restoring Network View Definitions..." while read -r net view; do [ -z "$net" ] || [ -z "$view" ] && continue echo " [+] Mapping network $net to View $view..." pdnsutil network set "$net" "$view" done < "$NET_FILE" fi # 2. Restore Zones while IFS=":" read -r view zone do [ -z "$view" ] && continue SAFE_ZONE_NAME=$(echo "$zone" | tr '.' '_') ZONE_FILE="$ZONES_DIR/${view}_${SAFE_ZONE_NAME}.zone" if [ ! -f "$ZONE_FILE" ]; then echo "Warning: Zone file for $zone ($ZONE_FILE) is missing. Skipping." continue fi echo "Restoring $zone ($view)..." pdnsutil zone show "$zone" >/dev/null 2>&1 if [ $? -ne 0 ]; then echo " [+] Creating zone baseline $zone..." pdnsutil zone create "$zone" fi if [ "$view" != "global" ]; then echo " [+] Mapping $zone to View: $view..." pdnsutil view add-zone "$view" "$zone" 2>/dev/null fi echo " [+] Loading resource records..." pdnsutil zone load "$zone" "$ZONE_FILE" done < "$META_FILE" echo "=== Rectifying all zones for DNSSEC compliance... ===" pdnsutil zone rectify-all quiet echo "=== Restore completed successfully! ===" EOF chmod +x /usr/local/bin/pdns-restore.sh ``` **Verifying the Setup** Running `pdns-backup.sh` creates a predictable, clean snapshot. Your output folder `/var/backups/powerdns/zones_dump/` will look like this: ```bash global_72_137_82_in-addr_arpa.zone global_epc_mnc003_mcc284_3gppnetwork_org.zone global_mnc003_mcc284_gprs.zone internal_epc_mnc003_mcc284_3gppnetwork_org__internal.zone internal_mnc003_mcc284_gprs__internal.zone ``` And your `views_structure.txt` maps the precise relations: ```bash global:mnc003.mcc284.gprs internal:mnc003.mcc284.gprs..internal ``` ```markdown Additionally, the new backup engine will produce a `networks_structure.txt` file, preserving the subnets mapped to their respective views:```text10.127.0.0/16 internal ``` **The Core Link: Backing Up Network Mappings (ACLs)** This highlights a critical architectural shift in modern PowerDNS releases. In traditional DNS servers like BIND, the Access Control Lists (ACLs) that dictate which subnets land on which view are defined within flat configuration files. However, PowerDNS with the LMDB backend treats network routing rules as first-class citizens inside the database itself. Using `pdnsutil`, these mappings are queried and modified via the network command group: - `pdnsutil network list` — Lists all subnets linked to their respective views. - `pdnsutil network set NET VIEW` — Maps a subnet to a specific view inside the database. During my actual migration on the telecom testbed, I hit the exact pain point of omitting the file `networks_structure.txt`. After wiping the old LMDB binaries and restoring the zone data, internal core network infrastructure components suddenly lost access to their internal views. The mapping telling PowerDNS that the `10.XXX.0.0/16` subnet belonged to the `internal` view had been completely wiped out along with the old database file. **Conclusion** By isolating the data layer into plain text BIND files, you eliminate any dependency on the underlying database engine's file format. Whether you are recovering from a corrupt LMDB file, migrating storage backends, or doing a major software version hop, running `pdns-restore.sh` will rehydrate your entire DNS infrastructure—views and all—in just a few seconds. ### Solving LMDB Version Incompatibility in PowerDNS: A Post-Mortem URL: https://ostreff.info/solving-lmdb-version-incompatibility-in-powerdns-a-post-mortem/ Last updated: 2026-10-05T12:17:56.000Z Recently, I encountered a critical issue after a routine system upgrade: my PowerDNS instance was running on a newer version of `liblmdb` (1.0.0), while the existing database files were created with an older 0.9.x version. Here is how I navigated the transition, recovered the data, and cleaned up the environment. **The Challenge** When upgrading the base system, the `liblmdb` library transitioned to version 1.0.0\. Because PowerDNS uses an LMDB backend, the discrepancy between the on-disk file format and the new library version created an immediate risk of data corruption. Simply restarting the service was not a safe option; we needed to ensure the data was compatible with the new ABI. **Step 1: Data Extraction** The biggest obstacle was the LMDB file structure. PowerDNS uses a sharded storage approach (multiple `.lmdb` files), and standard tools like `mdb_dump` expect a directory rather than a file path, resulting in the dreaded `mdb_env_open failed, error 20: Not a directory`. To solve this, I performed a "staging" operation: 1. Created a temporary directory structure mimicking an LMDB environment. 2. Copied each individual shard (`pdns.lmdb`, `pdns.lmdb-1`, etc.) into this temporary space, renaming them to `data.mdb`. 3. Executed `mdb_dump` on the temporary folder to extract the data into a safe, text-based format. **Step 2: Verification and Cleanup** Once the data was safely backed up, I cleaned the environment. Over the years, the directory had accumulated legacy SQLite artifacts from a previous migration. Since the PowerDNS configuration (`pdns.conf`) was explicitly set to use the `lmdb` backend (`launch=lmdb`), I moved the unused SQLite files to an archive directory, significantly decluttering the system. **Step 3: Re-integration** With the service stopped, I ensured that all file permissions were correctly set to the `bind` user. Upon restarting the service, PowerDNS detected the existing shard files. Even though my configuration specified `lmdb-shards=1`, the engine intelligently detected the 64 shards on disk and adapted accordingly, successfully loading the backend without data loss. **Practical Implementation: The Migration Script** To automate the data extraction without hardcoding paths or shard counts, we can extract the database location directly from `pdns.conf`. The following `sh` script demonstrates how to safely dump all shards regardless of their number. **The Final Migration Tool** This `sh` script handles the entire lifecycle of the migration process: 1. **Tool Discovery:** Automatically finds and extracts legacy `mdb_dump` versions from your package backups (`/usr/ports/packages/`). 2. **Data Integrity:** Performs a "Stage-Dump-Load" operation, ensuring the new database format is created natively by the current system tools. 3. **Safety First:** Includes multiple sanity checks (root user verification, path safety, and non-destructive cleanup options). 4. **Environment Awareness:** Dynamically identifies PowerDNS ownership and permissions to ensure the service resumes seamlessly after migration. ```bash #!/bin/sh set -e # Ensure script is run as root if [ "$(id -u)" -ne 0 ]; then echo "Error: This script must be run as root." exit 1 fi # Configuration CONFIG="/usr/local/etc/pdns/pdns.conf" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/tmp/pdns_lmdb_backup_${TIMESTAMP}" STAGING_DIR="/tmp/pdns_migrate_staging_${TIMESTAMP}" NEW_DB_DIR="/tmp/pdns_migrate_newdb_${TIMESTAMP}" LEGACY_DIR="/tmp/legacy_tools" PKG_DIR="/usr/ports/packages/portmaster-backup" SYSTEM_DUMP="/usr/local/bin/mdb_dump" SYSTEM_LOAD="/usr/local/bin/mdb_load" # Verify config exists if [ ! -f "$CONFIG" ]; then echo "Error: Configuration file $CONFIG not found!" exit 1 fi # Extract DB path from pdns.conf DB_PATH=$(grep "^lmdb-filename" "$CONFIG" | cut -d'=' -f2 | tr -d ' ') if [ -z "$DB_PATH" ]; then echo "Error: Could not determine lmdb-filename from $CONFIG" exit 1 fi DB_DIR=$(dirname "$DB_PATH") # 1. Detect available legacy packages echo "Searching for lmdb packages in $PKG_DIR..." PKGS=$(ls "$PKG_DIR"/lmdb-*.pkg 2>/dev/null || true) if [ -z "$PKGS" ]; then echo "No legacy packages found. Using system default mdb_dump." MDB_DUMP_BIN="$SYSTEM_DUMP" else echo "Found the following packages:" i=1 set -- $PKGS for p in "$@"; do echo "$i) $(basename "$p")" i=$((i + 1)) done printf "Select a package to extract mdb_dump from (or press Enter to use system default): " read -r choice if [ -n "$choice" ] && [ "$choice" -ge 1 ] && [ "$choice" -le "$#" ]; then SELECTED_PKG=$(eval echo \${$choice}) echo "Extracting mdb_dump from $(basename "$SELECTED_PKG")..." mkdir -p "$LEGACY_DIR" tar -xf "$SELECTED_PKG" -C "$LEGACY_DIR" usr/local/bin/mdb_dump MDB_DUMP_BIN="$LEGACY_DIR/usr/local/bin/mdb_dump" else MDB_DUMP_BIN="$SYSTEM_DUMP" fi fi # Verify binaries exist and are executable if [ ! -x "$MDB_DUMP_BIN" ]; then echo "Error: $MDB_DUMP_BIN is not executable." exit 1 fi if [ ! -x "$SYSTEM_LOAD" ]; then echo "Error: $SYSTEM_LOAD not found." exit 1 fi echo "Using dump binary: $($MDB_DUMP_BIN -V 2>&1 | head -n 1)" # 2. Stop PowerDNS service to prevent active writes during copy echo "Stopping PowerDNS service..." service pdns stop || true # Проверка дали процесът наистина е спрял if pgrep -q pdns_server; then echo "Error: PowerDNS is still running! Please stop it manually before proceeding." exit 1 fi mkdir -p "$BACKUP_DIR" "$STAGING_DIR" "$NEW_DB_DIR" echo "Dumping database files from $DB_DIR..." # Process the main file and all shards for file in "$DB_DIR"/pdns.lmdb*; do [ -e "$file" ] || continue case "$file" in *-lock) continue ;; esac filename=$(basename "$file") DUMP_FILE="$BACKUP_DIR/${filename}.dump" rm -f "$STAGING_DIR/data.mdb" cp "$file" "$STAGING_DIR/data.mdb" echo "Dumping $filename -> $(basename "$DUMP_FILE")..." "$MDB_DUMP_BIN" -a "$STAGING_DIR" > "$DUMP_FILE" done # 3. Load dumps into staging DB location using explicit sub-database support (-s) echo "Recreating database structure in temporary location ($NEW_DB_DIR)..." for dump in "$BACKUP_DIR"/*.dump; do [ -e "$dump" ] || continue target=$(basename "$dump" .dump) # Extract sub-database names from header SUB_DBS=$(grep "^database=" "$dump" | cut -d'=' -f2 || true) if [ -n "$SUB_DBS" ]; then echo "Loading $target with sub-databases:" for db in $SUB_DBS; do echo " -> Sub-database: $db" "$SYSTEM_LOAD" -f "$dump" -n -s "$db" "$NEW_DB_DIR/$target" 2>/dev/null done else echo "Loading $target (standalone)..." "$SYSTEM_LOAD" -f "$dump" -n "$NEW_DB_DIR/$target" fi done # Safety check: Verify that new database file was generated if [ ! -f "$NEW_DB_DIR/pdns.lmdb" ]; then echo "Error: Database creation failed! Original files in $DB_DIR were NOT modified." service pdns start exit 1 fi # 4. Safe Archive & Replace OLD_SAVE_DIR="$DB_DIR/old_lmdb_backup_${TIMESTAMP}" echo "Archiving original database files to $OLD_SAVE_DIR..." mkdir -p "$OLD_SAVE_DIR" mv "$DB_DIR"/pdns.lmdb* "$OLD_SAVE_DIR/" echo "Deploying new LMDB 1.0 database files..." cp "$NEW_DB_DIR"/pdns.lmdb* "$DB_DIR/" # 5. Apply permissions PDNS_USER=$(grep "^setuid" "$CONFIG" | cut -d'=' -f2 | tr -d ' ') PDNS_GROUP=$(grep "^setgid" "$CONFIG" | cut -d'=' -f2 | tr -d ' ') [ -z "$PDNS_USER" ] && PDNS_USER=$(ls -ld "$DB_DIR" | awk '{print $3}') [ -z "$PDNS_GROUP" ] && PDNS_GROUP=$(ls -ld "$DB_DIR" | awk '{print $4}') echo "Applying permissions ($PDNS_USER:$PDNS_GROUP) on $DB_DIR..." chown -R "$PDNS_USER:$PDNS_GROUP" "$DB_DIR" # Cleanup staging area rm -rf "$STAGING_DIR" "$NEW_DB_DIR" "$LEGACY_DIR" # 6. Service start & verification echo "Starting PowerDNS service..." service pdns start echo "Migration finished successfully." echo "Original files kept safely in: $OLD_SAVE_DIR" echo "Recent log entries:" tail -20 /var/log/messages | grep pdns || true ``` **Lessons Learned** - **Don't ignore the ldd output:** Checking which shared libraries your binaries are actually linking against is the first step in diagnosing versioning conflicts. - **Staging is your best friend:** When dealing with strict tool requirements (like `mdb_dump` expecting directories), manual staging is the safest way to perform a low-level migration. - **Clean house:** If you have migrated from SQLite to LMDB in the past, don't leave the old files sitting in your production directory. They only increase the complexity of backups and debugging. **Final Thoughts** Upgrading database engines doesn't always have to be a "break-and-fix" cycle. By preparing a solid backup, understanding the underlying storage structure, and verifying the service logs post-restart, you can perform complex migrations while keeping your DNS infrastructure rock-solid. The FreeBSD mentioned this in `/usr/ports/UPDATING` article: ```sh 20260702: AFFECTS: users of databases/lmdb AUTHOR: delphij@FreeBSD.org LMDB 1.0 introduced an incompatible on-disk file format change. Versions 0.9.x and 1.0.x databases are mutually incompatible. Before upgrading, export all existing databases using the old v0.9 mdb_dump utility, then import them with the new v1.0 mdb_load after upgrading. There is no support for opening v0.9 database files directly with LMDB 1.0. Example migration procedure: # mdb_dump -a /path/to/db > /tmp/mydb.dump # pkg upgrade databases/lmdb # mdb_load -f /tmp/mydb.dump /path/to/newdb ``` Tags: #PowerDNS #LMDB #SystemAdministration #FreeBSD #DatabaseMigration #DevOps ### How to monitor your AdGuardHome via netdata URL: https://ostreff.info/how-to-monitor-your-adguardhome-via-netdata/ Last updated: 2026-07-06T21:57:57.000Z This guide covers compiling the AdGuardHome Prometheus exporter from source, configuring it natively using the FreeBSD `rc.subr` framework under a non-privileged user (`bind or unbound or adguardhome`), and feeding the telemetry into Netdata. ![](https://ostreff.info/content/images/2026/05/image-1.png) **1\. Build the Exporter from Source** Since the exporter is written in Go, we install the Go compiler, clone the repository, and build a clean binary optimized for your system: ```bash # Install Go and Git pkg install go git # Clone and build cd /usr/local/src git clone https://github.com/henrywhitaker3/adguard-exporter.git cd adguard-exporter go build -o adguard-exporter main.go # Move to system path install -m 755 adguard-exporter /usr/local/bin/adguard-exporter ``` **2\. Create the FreeBSD rc.d Service Script** To run the exporter safely as a background daemon under the `bind` user context, create the startup script at `/usr/local/etc/rc.d/adguard_exporter`: Find on which `ip:port` the adguard dashboard interface is working: ```bash grep 'address:' /usr/local/etc/adguardhome/AdGuardHome.yaml address: 0.0.0.0:3000 ``` ```bash #!/bin/sh # PROVIDE: adguard_exporter # REQUIRE: LOGIN adguardhome # KEYWORD: shutdown . /etc/rc.subr name="adguard_exporter" rcvar="adguard_exporter_enable" load_rc_config $name : ${adguard_exporter_enable:="NO"} : ${adguard_exporter_user:="bind"} pidfile="/tmp/${name}.pid" procname="/usr/local/bin/adguard-exporter" # Natively pass Environment Variables to the 'bind' user context # Note: The exporter source code expects plural variable names adguard_exporter_env="ADGUARD_SERVERS=http://127.0.0.1:3000 \ ADGUARD_USERNAMES=your_username \ ADGUARD_PASSWORDS=your_password \ INTERVAL=15s \ BIND_ADDR=127.0.0.1:9311" command="/usr/sbin/daemon" command_args="-f -p ${pidfile} -o /tmp/${name}.log ${procname}" run_rc_command "$1" ``` **Note:** Change `http://127.0.0.1:3000` to match your local AdGuard Home web panel port, and replace the placeholder credentials with your actual AGH details. Also change the `BIND_ADDR=` value with something relevant to your system. **3\. Enable and Start the Service** Configure the system to run the service as the `bind` user, then start it up: ```bash chmod +x /usr/local/etc/rc.d/adguard_exporter sysrc adguard_exporter_enable="YES" sysrc adguard_exporter_user="bind" service adguard_exporter start ``` Verify that the exporter is successfully listening locally on port `9311`: ```bash sockstat -4l | grep 9311 ``` **4\. Configure Netdata Integration** Netdata utilizes its native Prometheus collector (`go.d.plugin`) to scrape metrics from the newly created endpoint. Edit the Netdata configuration file: ```bash cd /usr/local/etc/netdata/ ./edit-config go.d/prometheus.conf ``` Add your custom scrape job to the file: ```yaml jobs: - name: adguardhome url: 'http://127.0.0.1:9311/metrics' ``` **5\. Group Processes & Restart** To ensure Netdata cleanly groups the resource usage (CPU/RAM) of the processes under an "adguard" category instead of dropping them into generic buckets, map them in `apps_groups.conf`: ```bash cd /usr/local/etc/netdata/ ./edit-config apps_groups.conf ``` Add the following line near other DNS software definitions: ```text adguard: adguardhome adguard-exporter ``` Finally, restart Netdata to apply all configuration changes: ```bash service netdata restart ``` Once restarted, a dedicated adguardhome section with high-resolution charts for queries, blocks, processing times, and top lists will automatically populate on your Netdata dashboard. ![](https://ostreff.info/content/images/2026/05/image.png) ### Recommendation for www/adguardhome FreeBSD port URL: https://ostreff.info/recommendation-for-www-adguardhome-freebsd-port/ Last updated: 2026-04-19T20:18:27.000Z Installing the port is easy and fast, no matter whether you install it via [portmaster](https://man.freebsd.org/cgi/man.cgi?portmaster&ref=ostreff.info) or with [pkg](https://man.freebsd.org/cgi/man.cgi?query=pkg&apropos=0&sektion=0&manpath=FreeBSD+15.0-RELEASE+and+Ports.quarterly&format=html&ref=ostreff.info). Keep in mind that this is more a filter, not a full-fledged [recursive dns](https://www.akamai.com/glossary/what-is-recursive-dns?ref=ostreff.info). This means that if you have an existing DNS recursive server set up, you will need to reconfigure it as well, depending on whether you want your new [AdGuard](https://adguard.com/en/adguard-home/overview.html?ref=ostreff.info) filter to listen directly on port 53/853 (for [DoT](https://en.wikipedia.org/wiki/DNS%5Fover%5FTLS?ref=ostreff.info)). This way, all clients on your network can easily and without reconfiguration start using it. Using your http load-balancer ( I recommend [caddy](https://caddyserver.com/?ref=ostreff.info) as such ) you can easily export `adguardhome` also as [DoH](https://en.wikipedia.org/wiki/DNS%5Fover%5FHTTPS?ref=ostreff.info) server. I personally recommend using dns/dnsdist - this will allow you to easily redirect the flow of dns requests in your system. Even if you are worried about whether your new adguard dns will stop, you can easily use [dnsdist](https://www.dnsdist.org/index.html?ref=ostreff.info) to make it so that in case of failure of www/adguardhome, the traffic will automatically switch to be served by the main recursive dns. ![](https://ostreff.info/content/images/2026/04/image.png) ### NetData latest version for FreeBSD URL: https://ostreff.info/netdata-latest-version-for-freebsd/ Last updated: 2026-04-09T13:56:29.000Z I have not met the requirements to be included in the `official ports-tree`. For example: \- it uses internet access so that `cmake` and `cargo` can download current versions of sources from public internet sources; \- it doesn't fullfill `portlint -A` requirements. I clearly realize that this is a **security risk** and therefore there is no way this port can be approved in its current form. Its use is only intended to help you become more familiar with the new functionalities in netdata latest developments. Here is my QnD manual to quickly use it: ```bash cd /usr/home git clone https://github.ostreff.info/jostreff/netdata-devel.git cd /usr/ports/net-mgmt ln -s ../../home/netdata-devel netdata-devel portmaster net-mgmt/netdata-devel ``` ### 16 ( 0x10 ) URL: https://ostreff.info/16-0x10/ Last updated: 2026-03-29T10:26:41.000Z ![](https://ostreff.info/content/images/2026/03/----------_--------------_2_cleaned.png) ![](https://ostreff.info/content/images/2026/03/-------------------------.png) ### How can you use Beszel on FreeBSD? URL: https://ostreff.info/how-can-you-use-beszel-on-freebsd/ Last updated: 2026-03-30T14:15:44.000Z \*\*\* The post was updated to reflect with v0.18.5 on 28 March 2026. \*\*\* Tested the methodology with v0.18.6 on 30 March 2026. I hope you can run [Beszel](https://beszel.dev/?ref=ostreff.info) not only under freebsd 15, because of its ability to depend on almost no dynamic libraries. The problems I encountered when trying to compile the project using [https://github.com/henrygd/beszel](https://github.com/henrygd/beszel?ref=ostreff.info) were due to the peculiarities of [nodejs](https://nodejs.org/en?ref=ostreff.info) and its specific libraries used into project. Unpack the archive ( attached to bottom ), and move the files you will use to the correct location on your file system. Here are the files you can expect to find in the archive. ```bash a -rwxr-xr-x 1 root wheel 8675603 8 март 11:29 usr/local/bin/beszel-agent a -rwxr-xr-x 1 root wheel 32629011 8 март 12:18 usr/local/bin/beszel-hub a -rwxr-xr-x 1 root wheel 1822 7 март 23:09 usr/local/etc/rc.d/beszel_agent a -rwxr-xr-x 1 root wheel 1482 7 март 23:31 usr/local/etc/rc.d/beszel_hub a -rw-r--r-- 1 root wheel 74 7 март 23:10 usr/local/etc/newsyslog.conf.d/beszel_agent.conf a -rw-r--r-- 1 root wheel 72 10 март 10:50 usr/local/etc/newsyslog.conf.d/beszel_hub.conf ``` If you are going to use both, then you will need to add first `beszel-hub` to your `rc.d` like this: ```bash pw useradd beszel -d /nonexistent -s /usr/sbin/nologin -c "Beszel" sysrc beszel_hub_enable=YES service beszel_hub start ``` by default, `bezel-hub` starts at `127.0.0.1:8090`. It's a good idea to use some sort of reverse proxy, I use [caddy](https://caddyserver.com/?ref=ostreff.info) and my minimal configuration looks like this: ```bash beszel. { encode gzip zstd @dashboard { path /_/* } @api { path /api/* } reverse_proxy 127.0.0.1:8090 log { output file /var/log/caddy/beszel.log { roll_size 10mb roll_keep 10 roll_keep_for 720h } format console } } ``` - It is not mandatory to generate a separate log file of course. access the web interface and after creating the superadmin account you will see a "+" button at the top where you can add the agents you want to visualize. The important thing here is to be careful when creating a new agent, to create/copy certain things into your configuration files located in `/usr/local/etc/` ![](https://ostreff.info/content/images/2026/03/image.png) ![](https://ostreff.info/content/images/2026/03/image-1.png) The contents of the public key field should be placed in `/usr/local/etc/beszel_agent_key.pub` , and the contents of the token field in the file `/usr/local/etc/beszel_agent.secret`. Please fulfil correct `host/ip` for sure and `name` can be anything descriptive for you. For security reasons, it's a good idea for your files to have the following permissions: ```bash -r-------- 1 beszel wheel 81 7 март 23:04 /usr/local/etc/beszel_agent_key.pub -r-------- 1 beszel wheel 37 7 март 23:04 /usr/local/etc/beszel_agent.secret ``` Lets start `beszel-agent` then, it depends did you have already `beszel` uid created. Don't forget to configure the url of your `beszel-hub`: ```bash pw useradd beszel -d /nonexistent -s /usr/sbin/nologin -c "Beszel" sysrc beszel_agent_url="https://beszel." sysrc beszel_agent_enable=YES service beszel_agent start ``` \*be careful because into `/usr/local/etc/rc.d/beszel_agent` it's hardcoded by default to use my own `beszel-hub` url. Finally here is the my binary: [beszel-0.18.4+-freebsd-amd64.tarbeszel-0.18.4+-freebsd-amd64.tar.gz15 MBdownload-circle](https://ostreff.info/content/files/2026/03/beszel-0.18.4--freebsd-amd64.tar.gz "Download") - This program can only be run on AMD64 processors with v4 microarchitecture support. I also add how you can build your own binary packages for your architecture and processor type. You must have go and git installed. You will need also prebuilded by me web interface ( nodejs ). It's here: [site-distsite-dist.tgz1 MBdownload-circle](https://ostreff.info/content/files/2026/03/site-dist.tgz "Download") ```bash # how to made your own prebuild file cd internal/site npm install npm run build cd ../.. tar zcvvf site-dist.tgz internal/site/dist/* ``` Execute commands in folder where you have space to build the binary: ```bash fetch https://ostreff.info/content/files/2026/03/site-dist.tgz git clone https://github.com/henrygd/beszel.git cd beszel/internal/site ; tar zxvf ../../../site-dist.tgz cd ../.. go build -ldflags "-w -s" -o build/beszel-hub ./internal/cmd/hub go build -ldflags "-w -s" -o build/beszel-agent ./internal/cmd/agent install -m 755 build/beszel-agent /usr/local/bin/beszel-agent install -m 755 build/beszel-hub /usr/local/bin/beszel-hub ``` ### Using Caddy as free SSL certificates manager URL: https://ostreff.info/using-caddy-as-free-certificates-manager/ Last updated: 2026-02-26T09:10:45.000Z ```bash #!/bin/sh CADDY_BASE="/var/db/caddy/data/caddy/certificates/acme-v02.api.letsencrypt.org-directory" POSTFIX_DIR="/usr/local/etc/postfix" STATE_FILE="/var/run/last_ssl_update" # list of domains DOMAINS="mail.ostreff.info mail.classic-bg.net mail.albena-bg.be mail.realesr.ostreff.info" # 1. Find newest certificate (Epoch) NEWEST_CHANGE=$(find $CADDY_BASE -name "*.crt" -exec stat -f "%m" {} + | sort -rn | head -1) # last execution [ -f "$STATE_FILE" ] && LAST_CHANGE=$(cat "$STATE_FILE") || LAST_CHANGE=0 # 2. is there change? if [ "$NEWEST_CHANGE" -gt "$LAST_CHANGE" ]; then echo "$(date): Found new certificates. Refreshing services..." : > "$POSTFIX_DIR/mail.crt" : > "$POSTFIX_DIR/mail.key" # Protect the key (only for root ) chmod 600 "$POSTFIX_DIR/mail.key" for DOMAIN in $DOMAINS; do CRT="$CADDY_BASE/$DOMAIN/$DOMAIN.crt" KEY="$CADDY_BASE/$DOMAIN/$DOMAIN.key" if [ -f "$CRT" ] && [ -f "$KEY" ]; then cat "$CRT" >> "$POSTFIX_DIR/mail.crt" cat "$KEY" >> "$POSTFIX_DIR/mail.key" echo " [+] Added certificate for: $DOMAIN" else echo " [!] Error: Missing files for $DOMAIN" fi done # 3. for Postfix /usr/local/sbin/postmap -F hash:"$POSTFIX_DIR/vmail_ssl.map" /usr/sbin/service postfix restart echo " [+] Postfix restarted." # 4. Restart/Reload of Dovecot, certificates are already in place because of https://ostreff.info/use-sni-in-dovecot-and-postfix/ if [ -f "/usr/local/etc/rc.d/dovecot" ]; then /usr/sbin/service dovecot restart echo " [+] Dovecot restarted." fi # Refresh state echo "$NEWEST_CHANGE" > "$STATE_FILE" echo "Ready." else echo "No changes in certificates." fi ``` ```bash 0 * * * * /root/mail-ssl-generate.sh > /dev/null 2>&1 ``` ### Change php version on FreeBSD URL: https://ostreff.info/change-php-version-on-freebsd/ Last updated: 2026-01-12T12:00:24.000Z Here is my script which helps me to change to different [PHP](https://www.php.net/releases/8.5/en.php?ref=ostreff.info#features) version on [FreeBSD](https://www.freebsd.org/?ref=ostreff.info): ```bash #!/usr/local/bin/bash # 1. Автоматично откриване на текущата версия # Търсим пакет, чийто Origin е lang/phpXX OLD_V_FULL=$(pkg query -e '%o ~ lang/php*' '%v' | head -n 1) OLD_V_SHORT=$(pkg query -e '%o ~ lang/php*' '%n' | sed 's/php//' | head -n 1) if [ -z "$OLD_V_SHORT" ]; then echo "Грешка: Не беше открита инсталирана PHP версия през pkg." exit 1 fi # 2. Проверка за подаден аргумент за нова версия if [ "$#" -ne 1 ]; then echo "Текуща версия: $OLD_V_SHORT ($OLD_V_FULL)" echo "Употреба: $0 <нова_версия>" echo "Пример: $0 85" exit 1 fi NEW_V=$1 # 3. Валидация на версиите (само 82, 83, 84, 85) PATTERN="^(82|83|84|85)$" if [[ ! $OLD_V_SHORT =~ $PATTERN ]] || [[ ! $NEW_V =~ $PATTERN ]]; then echo "Грешка: Версиите трябва да бъдат една от следните: 82, 83, 84, 85" echo "Открита текуща версия: $OLD_V_SHORT" exit 1 fi if [ "$OLD_V_SHORT" == "$NEW_V" ]; then echo "Текущата версия вече е $NEW_V. Няма нужда от ъпгрейд." exit 0 fi LIST_DEL="" LIST_INS="" MAIN_PHP="lang/php$NEW_V" # 4. Генериране на списъци за замяна for FILE in $(pkg info | grep "php$OLD_V_SHORT" | cut -f1 -d' ') ; do PKGNAME=$(pkg info "$FILE" | grep 'Origin ' | cut -f2 -d':' | xargs) ; NEWPKGNAME="${PKGNAME//php$OLD_V_SHORT/php$NEW_V}" ; LIST_DEL="$LIST_DEL $FILE"; if [ "$NEWPKGNAME" != "$MAIN_PHP" ]; then LIST_INS="$LIST_INS $NEWPKGNAME"; fi done echo "### ОТКРИТА ВЕРСИЯ: $OLD_V_SHORT -> ЦЕЛЕВА ВЕРСИЯ: $NEW_V ###" echo "pkg delete -f $LIST_DEL" echo "portmaster $MAIN_PHP && portmaster $LIST_INS" ``` ### oCIS on FreeBSD URL: https://ostreff.info/ocis-on-freebsd/ Last updated: 2026-02-17T18:48:50.000Z ```bash # grep ocis /etc/rc.conf ocis_user="ocis" ocis_enable="YES" ocis_env="HOME=/home/ocis OCIS_URL=https://owncloud.ostreff.info PROXY_HTTP_ADDR=0.0.0.0:9200 PROXY_ENABLE_BASIC_AUTH=true OCIS_INSECURE=true OCIS_LOG_COLOR=true OCIS_LOG_PRETTY=true DEMO_USERS=false OCIS_LDAP_INSECURE=true" ``` ```bash # cat /usr/local/etc/rc.d/ocis #!/bin/sh # PROVIDE: ocis # REQUIRE: DAEMON NETWORKING # KEYWORD: shutdown . /etc/rc.subr name="ocis" rcvar="ocis_enable" load_rc_config ${name} : ${ocis_enable:="NO"} : ${ocis_user:="ocis"} : ${ocis_group:="ocis"} : ${ocis_dir:="/home/ocis"} pidfile="/var/run/${name}/${name}.pid" command="/usr/sbin/daemon" command_args="-r -f -P ${pidfile} -p /var/run/${name}/child.pid -o /var/log/${name}/${name}.log -T ${name} ${ocis_dir}/ocis server" ocis_user=${ocis_user} ocis_group=${ocis_group} ocis_chdir=${ocis_dir} start_precmd="ocis_precmd" ocis_precmd() { # Тези директории трябва да съществуват и да са достъпни install -d -m 0750 -o ${ocis_user} -g ${ocis_group} /var/run/${name} install -d -m 0750 -o ${ocis_user} -g ${ocis_group} /var/log/${name} } run_rc_command "$1" ``` ### Evolution of EuroZone URL: https://ostreff.info/evolution-of-eurozone/ Last updated: 2026-01-06T21:04:07.000Z ![](https://ostreff.info/content/images/2026/01/G9h70lnWcAANedc.jpg) ### Happy New Year! URL: https://ostreff.info/happy-new-year-2/ Last updated: 2026-01-08T14:52:03.000Z 0:00 /0:59 1× ![](https://ostreff.info/content/images/2026/01/happy-new-year-2026.jpg.webp) ### OwnCloud Infinite Scale with Go 1.25.x URL: https://ostreff.info/owncloud-infinite-scale-with-go-1-25-x/ Last updated: 2025-12-05T09:10:11.000Z ```bash git clone https://github.com/owncloud/ocis.git ocis push ocis go get -u github.com/gookit/goutil/sysutil go mod tidy go mod vendor gmake generate gmake -C ocis clean gmake -C ocis build strip ocis/bin/ocis ``` Now you have shiny new build of [OCIS](https://owncloud.com/infinite-scale/?ref=ostreff.info) inside file `ocis/bin/ocis` . For me it looks like following patch file I have, and the result is working site [https://owncloud.ostreff.info](https://owncloud.ostreff.info/?ref=ostreff.info) : ```bash diff --git a/go.mod b/go.mod index 7ccb9a06a99..3d83a1d83f5 100644 --- a/go.mod +++ b/go.mod @@ -221,7 +221,7 @@ require ( github.com/google/go-tpm v0.9.6 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/renameio/v2 v2.0.0 // indirect - github.com/gookit/goutil v0.7.1 // indirect + github.com/gookit/goutil v0.7.2 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/schema v1.4.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect diff --git a/go.sum b/go.sum index 18d7a3f4831..dbe99ef2f36 100644 --- a/go.sum +++ b/go.sum @@ -486,8 +486,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gookit/config/v2 v2.2.7 h1:P58/uENzkDp7r7Hp8YSZxOhZ/F5a5Y/AzyhDUkQYa9A= github.com/gookit/config/v2 v2.2.7/go.mod h1:QST99HmkZXXD/HkZmOm1OXpgdAnc6Rl9syGl+u62Pi8= -github.com/gookit/goutil v0.7.1 h1:AaFJPN9mrdeYBv8HOybri26EHGCC34WJVT7jUStGJsI= -github.com/gookit/goutil v0.7.1/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU= +github.com/gookit/goutil v0.7.2 h1:NSiqWWY+BT0MwIlKDeSVPfQmr9xTkkAqwDjhplobdgo= +github.com/gookit/goutil v0.7.2/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU= github.com/gookit/ini/v2 v2.3.2 h1:W6tzOGE6zOLQelH2xhcH8BIBZPtnEpJgQ+J6SsAKBSw= github.com/gookit/ini/v2 v2.3.2/go.mod h1:StKSqY5niArRwYBS8Z71+iWUt5ow47qt359sS9YQLYY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= diff --git a/protogen/gen/ocis/messages/settings/v0/settings.pb.go b/protogen/gen/ocis/messages/settings/v0/settings.pb.go index cf104c870ba..563436da5a5 100644 --- a/protogen/gen/ocis/messages/settings/v0/settings.pb.go +++ b/protogen/gen/ocis/messages/settings/v0/settings.pb.go @@ -627,13 +627,13 @@ type Bundle struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // @gotags: yaml:"id" - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"` // @gotags: yaml:"name" - Type Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty" yaml:"type"` // @gotags: yaml:"type" - Extension string `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty" yaml:"extension"` // @gotags: yaml:"extension" - DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"` // @gotags: yaml:"display_name" - Settings []*Setting `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty" yaml:"settings"` // @gotags: yaml:"settings" - Resource *Resource `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // @gotags: yaml:"id" + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // @gotags: yaml:"name" + Type Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty"` // @gotags: yaml:"type" + Extension string `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty"` // @gotags: yaml:"extension" + DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // @gotags: yaml:"display_name" + Settings []*Setting `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty"` // @gotags: yaml:"settings" + Resource *Resource `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource" } func (x *Bundle) Reset() { @@ -722,10 +722,10 @@ type Setting struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // @gotags: yaml:"id" - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"` // @gotags: yaml:"name" - DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"` // @gotags: yaml:"display_name" - Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` // @gotags: yaml:"description" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // @gotags: yaml:"id" + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // @gotags: yaml:"name" + DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // @gotags: yaml:"display_name" + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` // @gotags: yaml:"description" // Types that are assignable to Value: // // *Setting_IntValue @@ -736,7 +736,7 @@ type Setting struct { // *Setting_PermissionValue // *Setting_MultiChoiceCollectionValue Value isSetting_Value `protobuf_oneof:"value"` - Resource *Resource `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource" + Resource *Resource `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource" } func (x *Setting) Reset() { @@ -867,31 +867,31 @@ type isSetting_Value interface { } type Setting_IntValue struct { - IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value" + IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value" } type Setting_StringValue struct { - StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value" + StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value" } type Setting_BoolValue struct { - BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value" + BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value" } type Setting_SingleChoiceValue struct { - SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof" yaml:"single_choice_value"` // @gotags: yaml:"single_choice_value" + SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof"` // @gotags: yaml:"single_choice_value" } type Setting_MultiChoiceValue struct { - MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof" yaml:"multi_choice_value"` // @gotags: yaml:"multi_choice_value" + MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof"` // @gotags: yaml:"multi_choice_value" } type Setting_PermissionValue struct { - PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof" yaml:"permission_value"` // @gotags: yaml:"permission_value" + PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof"` // @gotags: yaml:"permission_value" } type Setting_MultiChoiceCollectionValue struct { - MultiChoiceCollectionValue *MultiChoiceCollection `protobuf:"bytes,12,opt,name=multi_choice_collection_value,json=multiChoiceCollectionValue,proto3,oneof" yaml:"multi_choice_collection_value"` // @gotags: yaml:"multi_choice_collection_value" + MultiChoiceCollectionValue *MultiChoiceCollection `protobuf:"bytes,12,opt,name=multi_choice_collection_value,json=multiChoiceCollectionValue,proto3,oneof"` // @gotags: yaml:"multi_choice_collection_value" } func (*Setting_IntValue) isSetting_Value() {} @@ -913,11 +913,11 @@ type Int struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Default int64 `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default" - Min int64 `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty" yaml:"min"` // @gotags: yaml:"min" - Max int64 `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty" yaml:"max"` // @gotags: yaml:"max" - Step int64 `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty" yaml:"step"` // @gotags: yaml:"step" - Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"` // @gotags: yaml:"placeholder" + Default int64 `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default" + Min int64 `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty"` // @gotags: yaml:"min" + Max int64 `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty"` // @gotags: yaml:"max" + Step int64 `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty"` // @gotags: yaml:"step" + Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"` // @gotags: yaml:"placeholder" } func (x *Int) Reset() { @@ -992,11 +992,11 @@ type String struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Default string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default" - Required bool `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty" yaml:"required"` // @gotags: yaml:"required" - MinLength int32 `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty" yaml:"min_length"` // @gotags: yaml:"min_length" - MaxLength int32 `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty" yaml:"max_length"` // @gotags: yaml:"max_length" - Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"` // @gotags: yaml:"placeholder" + Default string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default" + Required bool `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty"` // @gotags: yaml:"required" + MinLength int32 `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty"` // @gotags: yaml:"min_length" + MaxLength int32 `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty"` // @gotags: yaml:"max_length" + Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"` // @gotags: yaml:"placeholder" } func (x *String) Reset() { @@ -1071,8 +1071,8 @@ type Bool struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Default bool `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default" - Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty" yaml:"label"` // @gotags: yaml:"label" + Default bool `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default" + Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` // @gotags: yaml:"label" } func (x *Bool) Reset() { @@ -1126,7 +1126,7 @@ type SingleChoiceList struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options" + Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options" } func (x *SingleChoiceList) Reset() { @@ -1173,7 +1173,7 @@ type MultiChoiceList struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options" + Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options" } func (x *MultiChoiceList) Reset() { @@ -1220,9 +1220,9 @@ type ListOption struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Value *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"` // @gotags: yaml:"value" - Default bool `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default" - DisplayValue string `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty" yaml:"display_value"` // @gotags: yaml:"display_value" + Value *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` // @gotags: yaml:"value" + Default bool `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default" + DisplayValue string `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` // @gotags: yaml:"display_value" } func (x *ListOption) Reset() { @@ -1283,7 +1283,7 @@ type MultiChoiceCollection struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Options []*MultiChoiceCollectionOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options" + Options []*MultiChoiceCollectionOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options" } func (x *MultiChoiceCollection) Reset() { @@ -1330,10 +1330,10 @@ type MultiChoiceCollectionOption struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Value *MultiChoiceCollectionOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"` // @gotags: yaml:"value" - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty" yaml:"key"` // @gotags: yaml:"key" - Attribute string `protobuf:"bytes,3,opt,name=attribute,proto3" json:"attribute,omitempty" yaml:"attribute"` // @gotags: yaml:"attribute" - DisplayValue string `protobuf:"bytes,4,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty" yaml:"display_value"` // @gotags: yaml:"display_value" + Value *MultiChoiceCollectionOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` // @gotags: yaml:"value" + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // @gotags: yaml:"key" + Attribute string `protobuf:"bytes,3,opt,name=attribute,proto3" json:"attribute,omitempty"` // @gotags: yaml:"attribute" + DisplayValue string `protobuf:"bytes,4,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` // @gotags: yaml:"display_value" } func (x *MultiChoiceCollectionOption) Reset() { @@ -1474,15 +1474,15 @@ type isMultiChoiceCollectionOptionValue_Option interface { } type MultiChoiceCollectionOptionValue_IntValue struct { - IntValue *Int `protobuf:"bytes,1,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value" + IntValue *Int `protobuf:"bytes,1,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value" } type MultiChoiceCollectionOptionValue_StringValue struct { - StringValue *String `protobuf:"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value" + StringValue *String `protobuf:"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value" } type MultiChoiceCollectionOptionValue_BoolValue struct { - BoolValue *Bool `protobuf:"bytes,3,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value" + BoolValue *Bool `protobuf:"bytes,3,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value" } func (*MultiChoiceCollectionOptionValue_IntValue) isMultiChoiceCollectionOptionValue_Option() {} @@ -1496,8 +1496,8 @@ type Permission struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Operation Permission_Operation `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty" yaml:"operation"` // @gotags: yaml:"operation" - Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty" yaml:"constraint"` // @gotags: yaml:"constraint" + Operation Permission_Operation `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty"` // @gotags: yaml:"operation" + Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty"` // @gotags: yaml:"constraint" } func (x *Permission) Reset() { @@ -1552,12 +1552,12 @@ type Value struct { unknownFields protoimpl.UnknownFields // id is the id of the Value. It is generated on saving it. - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // @gotags: yaml:"id" - BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty" yaml:"bundle_id"` // @gotags: yaml:"bundle_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // @gotags: yaml:"id" + BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` // @gotags: yaml:"bundle_id" // setting_id is the id of the setting from within its bundle. - SettingId string `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty" yaml:"setting_id"` // @gotags: yaml:"setting_id" - AccountUuid string `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty" yaml:"account_uuid"` // @gotags: yaml:"account_uuid" - Resource *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource" + SettingId string `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty"` // @gotags: yaml:"setting_id" + AccountUuid string `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty"` // @gotags: yaml:"account_uuid" + Resource *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource" // Types that are assignable to Value: // // *Value_BoolValue @@ -1682,23 +1682,23 @@ type isValue_Value interface { } type Value_BoolValue struct { - BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value" + BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value" } type Value_IntValue struct { - IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value" + IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value" } type Value_StringValue struct { - StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value" + StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value" } type Value_ListValue struct { - ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof" yaml:"list_value"` // @gotags: yaml:"list_value" + ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof"` // @gotags: yaml:"list_value" } type Value_CollectionValue struct { - CollectionValue *CollectionValue `protobuf:"bytes,10,opt,name=collection_value,json=collectionValue,proto3,oneof" yaml:"collection_value"` // @gotags: yaml:"collection_value" + CollectionValue *CollectionValue `protobuf:"bytes,10,opt,name=collection_value,json=collectionValue,proto3,oneof"` // @gotags: yaml:"collection_value" } func (*Value_BoolValue) isValue_Value() {} @@ -1716,7 +1716,7 @@ type ListValue struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" yaml:"values"` // @gotags: yaml:"values" + Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` // @gotags: yaml:"values" } func (x *ListValue) Reset() { @@ -1836,15 +1836,15 @@ type isListOptionValue_Option interface { } type ListOptionValue_StringValue struct { - StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value" + StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value" } type ListOptionValue_IntValue struct { - IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value" + IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value" } type ListOptionValue_BoolValue struct { - BoolValue bool `protobuf:"varint,3,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value" + BoolValue bool `protobuf:"varint,3,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value" } func (*ListOptionValue_StringValue) isListOptionValue_Option() {} @@ -1858,7 +1858,7 @@ type CollectionValue struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Values []*CollectionOption `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" yaml:"values"` // @gotags: yaml:"values" + Values []*CollectionOption `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` // @gotags: yaml:"values" } func (x *CollectionValue) Reset() { @@ -1906,7 +1906,7 @@ type CollectionOption struct { unknownFields protoimpl.UnknownFields // required - Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty" yaml:"key"` // @gotags: yaml:"key" + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // @gotags: yaml:"key" // Types that are assignable to Option: // // *CollectionOption_IntValue @@ -1987,15 +1987,15 @@ type isCollectionOption_Option interface { } type CollectionOption_IntValue struct { - IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value" + IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value" } type CollectionOption_StringValue struct { - StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value" + StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value" } type CollectionOption_BoolValue struct { - BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value" + BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value" } func (*CollectionOption_IntValue) isCollectionOption_Option() {} diff --git a/vendor/github.com/gookit/goutil/.gitignore b/vendor/github.com/gookit/goutil/.gitignore index 6eec81adfef..ec0f864bcf5 100644 --- a/vendor/github.com/gookit/goutil/.gitignore +++ b/vendor/github.com/gookit/goutil/.gitignore @@ -18,6 +18,7 @@ *.out *.cov .DS_Store +.xenv.toml *~ testdata/ diff --git a/vendor/github.com/gookit/goutil/README.md b/vendor/github.com/gookit/goutil/README.md index 60ae33df5a2..0d03f6e20ca 100644 --- a/vendor/github.com/gookit/goutil/README.md +++ b/vendor/github.com/gookit/goutil/README.md @@ -1,4 +1,4 @@ -# Go Util +# GoUtil ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/goutil?style=flat-square) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/goutil)](https://github.com/gookit/goutil) @@ -7,7 +7,7 @@ [![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](https://pkg.go.dev/github.com/gookit/goutil) -💪 Useful utils(**800+**) package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more. +💪 Useful utils(**900+**) package for the Go: int, string, array/slice, map, struct, reflect, error, time, format, CLI, ENV, filesystem, system, testing and more. > **[中文说明](README.zh-CN.md)** @@ -52,8 +52,8 @@ - [`encodes`](encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc. - [`finder`](x/finder) Provides a simple and convenient file/dir lookup function, supports filtering, excluding, matching, ignoring, etc. - [`netutil`](netutil) Network util functions. eg: Ip, IpV4, IpV6, Mac, Port, Hostname, etc. -- [textutil](strutil/textutil) Provide some extensions text handle util functions. eg: text replace, etc. -- [textscan](strutil/textscan) Implemented a parser that quickly scans and analyzes text content. It can be used to parse INI, Properties and other formats +- [`textutil`](strutil/textutil) Provide some extensions text handle util functions. eg: text replace, etc. +- [`textscan`](strutil/textscan) Implemented a parser that quickly scans and analyzes text content. It can be used to parse INI, Properties and other formats - [`cmdr`](sysutil/cmdr) Provide for quick build and run a cmd, batch run multi cmd tasks - [`clipboard`](x/clipboard) Provide a simple clipboard read and write operations. - [`process`](sysutil/process) Provide some process handle util functions. @@ -63,7 +63,7 @@ ## Go Doc Please see [Go doc](https://pkg.go.dev/github.com/gookit/goutil). -Wiki docs on [DeepWiki - gookit/goutil](https://deepwiki.com/gookit/goutil) +Wiki docs on [ZRead.ai - gookit/goutil](https://zread.ai/gookit/goutil) ## Install @@ -109,6 +109,8 @@ dump.Print(somevar, somevar2, ...) > Package `github.com/gookit/goutil/arrutil` +
Click to see functions 👈 + ```go // source at arrutil/arrutil.go func GetRandomOne[T any](arr []T) T @@ -152,6 +154,7 @@ func IntsToString[T comdef.Integer](ints []T) string func ToInt64s(arr any) (ret []int64, err error) func MustToInt64s(arr any) []int64 func SliceToInt64s(arr []any) []int64 +func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V func AnyToSlice(sl any) (ls []any, err error) func AnyToStrings(arr any) []string func MustToStrings(arr any) []string @@ -171,7 +174,8 @@ func FormatIndent(arr any, indent string) string func Reverse[T any](ls []T) func Remove[T comdef.Compared](ls []T, val T) []T func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T -func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V +func Map[T, V any](list []T, mapFn MapFn[T, V]) []V +func Map1[T, R any](list []T, fn func(t T) R) []R func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V func Unique[T comdef.NumberOrString](list []T) []T func IndexOf[T comdef.NumberOrString](val T, list []T) int @@ -189,6 +193,8 @@ func StringsFilter(ss []string, filter ...comdef.StringMatchFunc) []string func StringsMap(ss []string, mapFn func(s string) string) []string func TrimStrings(ss []string, cutSet ...string) []string ``` + + #### ArrUtil Usage **check value**: @@ -211,10 +217,13 @@ ss, err := arrutil.ToStrings(\[\]int{1, 2}) // ss: \[\]string{"1", "2"} ``` + ### Bytes Utils > Package `github.com/gookit/goutil/byteutil` +
Click to see functions 👈 + ```go // source at byteutil/buffer.go func NewBuffer() *Buffer @@ -244,18 +253,22 @@ func NewStdEncoder(encFn BytesEncodeFunc, decFn BytesDecodeFunc) *StdEncoder // source at byteutil/pool.go func NewChanPool(chSize int, width int, capWidth int) *ChanPool ``` + + ### Cflag > Package `github.com/gookit/goutil/cflag` +`cflag` \- Wraps and extends go `flag.FlagSet` to build simple command line applications + + + Click to see functions 👈 + ```go -// source at cflag/app.go -func NewApp(fns ...func(app *App)) *App -func NewCmd(name, desc string, runFunc ...func(c *Cmd) error) *Cmd // source at cflag/cflag.go -func SetDebug(open bool) func New(fns ...func(c *CFlags)) *CFlags +func NewWith(name, version, desc string, fns ...func(c *CFlags)) *CFlags func NewEmpty(fns ...func(c *CFlags)) *CFlags func WithDesc(desc string) func(c *CFlags) func WithVersion(version string) func(c *CFlags) @@ -269,6 +282,8 @@ func Value // source at cflag/optarg.go func NewArg(name, desc string, required bool) *FlagArg // source at cflag/util.go +func SetDebug(open bool) +func DebugMsg(format string, args ...any) func IsGoodName(name string) bool func IsZeroValue(opt *flag.Flag, value string) (bool, bool) func AddPrefix(name string) string @@ -280,15 +295,20 @@ func IsFlagHelpErr(err error) bool func WrapColorForCode(s string) string func ReplaceShorts(args []string, shortsMap map[string]string) []string ``` + + #### `cflag` Usage `cflag` usage please see ### CLI Utils > Package `github.com/gookit/goutil/cliutil` + Click to see functions 👈 + ```go // source at cliutil/cliutil.go func SplitMulti(ss []string, sep string) []string @@ -325,6 +345,8 @@ func InputIsYes(ans string) bool func ByteIsYes(ans byte) bool func ReadPassword(question ...string) string ``` + + #### CLI Util Usage @@ -381,10 +403,13 @@ Build line: ./myapp -a val0 -m "this is message" arg0 > More, please see [./cliutil/README](cliutil/README.md) ### Var Dumper > Package `github.com/gookit/goutil/dump` + Click to see functions 👈 + ```go // source at dump/dump.go func Std() *Dumper @@ -411,7 +436,10 @@ func WithoutPosition() OptionFunc func WithoutOutput(out io.Writer) OptionFunc func WithoutColor() OptionFunc func WithoutType() OptionFunc +func WithoutLen() OptionFunc ``` + + #### Examples example code: @@ -456,10 +484,13 @@ Preview: ![](dump/_examples/preview-nested-struct.png) ### ENV/Environment > Package `github.com/gookit/goutil/envutil` + Click to see functions 👈 + ```go // source at envutil/envutil.go func VarReplace(s string) string @@ -503,6 +534,8 @@ func UnsetEnvs(keys ...string) func LoadText(text string) func LoadString(line string) bool ``` + + #### ENV Util Usage **helper functions:** @@ -522,13 +555,18 @@ envutil.ParseValue("${ENV\_NAME | defValue}") ``` + ### Errorx > Package `github.com/gookit/goutil/errorx` -Package errorx provide a enhanced error implements, allow with call stack and wrap another error. +`errorx` provides an enhanced error reporting implementation that contains call stack information and can wrap the previous level of error. + +> Additional call stack information is included when printing errors, making it easy to log and find problems. +
Click to see functions 👈 + ```go // source at errorx/assert.go func IsTrue(result bool, fmtAndArgs ...any) error @@ -578,6 +616,8 @@ func Is(err, target error) bool func To(err error, target any) bool func As(err error, target any) bool ``` + + #### Errorx Usage @@ -655,16 +695,23 @@ runtime.goexit() ``` + ### File System > Package `github.com/gookit/goutil/fsutil` +Package `fsutil` Filesystem util functions: quick check, create, read and write file. eg: file and dir check, operate + + +
Click to see functions 👈 + ```go // source at fsutil/check.go func PathExists(path string) bool func IsDir(path string) bool func FileExists(path string) bool func IsFile(path string) bool +func IsSymlink(path string) bool func IsAbsPath(aPath string) bool func IsEmptyDir(dirPath string) bool func IsImageFile(path string) bool @@ -680,6 +727,10 @@ func FirstExistsDir(paths ...string) string func FirstExistsFile(paths ...string) string func MatchPaths(paths []string, matcher PathMatchFunc) []string func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) string +func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string +func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string +func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn) +func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int) func SearchNameUp(dirPath, name string) string func SearchNameUpx(dirPath, name string) (string, bool) func WalkDir(dir string, fn fs.WalkDirFunc) error @@ -713,9 +764,12 @@ func FileExt(fPath string) string func Extname(fPath string) string func Suffix(fPath string) string func Expand(pathStr string) string +func ExpandHome(pathStr string) string func ExpandPath(pathStr string) string func ResolvePath(pathStr string) string func SplitPath(pathStr string) (dir, name string) +func UserHomeDir() string +func HomeDir() string // source at fsutil/info_nonwin.go func Realpath(pathStr string) string // source at fsutil/mime.go @@ -723,9 +777,11 @@ func DetectMime(path string) string func MimeType(path string) (mime string) func ReaderMimeType(r io.Reader) (mime string) // source at fsutil/operate.go -func Mkdir(dirPath string, perm os.FileMode) error -func MkDirs(perm os.FileMode, dirPaths ...string) error -func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error +func Mkdir(dirPath string, perm fs.FileMode) error +func MkdirQuick(dirPath string) error +func EnsureDir(path string) error +func MkDirs(perm fs.FileMode, dirPaths ...string) error +func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error func MkParentDir(fpath string) error func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption func OpenOptOrNew(opt *OpenOption) *OpenOption @@ -779,7 +835,10 @@ func WriteOSFile(f *os.File, data any) (n int, err error) func CopyFile(srcPath, dstPath string) error func MustCopyFile(srcPath, dstPath string) func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error +func CreateSymlink(target, linkPath string) error ``` + + #### FsUtil Usage @@ -811,6 +870,7 @@ func main() { ``` + ### JSON Utils > Package `github.com/gookit/goutil/jsonutil` @@ -841,10 +901,13 @@ func IsObject(s string) bool func StripComments(src string) string ``` \-### Map + +### Maputil > Package `github.com/gookit/goutil/maputil` + Click to see functions 👈 + ```go // source at maputil/check.go func HasKey(mp, key any) (ok bool) @@ -890,6 +953,7 @@ func Merge1level(mps ...map[string]any) map[string]any func DeepMerge(src, dst map[string]any, deep int) map[string]any func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string func MergeStrMap(src, dst map[string]string) map[string]string +func AppendSMap(dst, src map[string]string) map[string]string func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string func MergeMultiSMap(mps ...map[string]string) map[string]string func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string @@ -900,16 +964,23 @@ func MakeByKeys(keys []string, val any) (mp map[string]any) func SetByPath(mp *map[string]any, path string, val any) error func SetByKeys(mp *map[string]any, keys []string, val any) (err error) ``` + + ### Math/Number > Package `github.com/gookit/goutil/mathutil` +Package `mathutil` provide math(int, number) util functions. eg: convert, math calc, random + + Click to see functions 👈 + ```go // source at mathutil/calc.go func Abs[T comdef.Int](val T) T // source at mathutil/check.go func IsNumeric(c byte) bool +func IsInteger(val any) bool func Compare(first, second any, op string) bool func CompInt[T comdef.Xint](first, second T, op string) (ok bool) func CompInt64(first, second int64, op string) bool @@ -928,11 +999,7 @@ func SwapMaxInt(x, y int) (int, int) func MaxI64(x, y int64) int64 func SwapMaxI64(x, y int64) (int64, int64) func MaxFloat(x, y float64) float64 -// source at mathutil/convert.go -func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T] -func WithNilAsFail[T any](opt *ConvOption[T]) -func WithHandlePtr[T any](opt *ConvOption[T]) -func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] +// source at mathutil/conv2int.go func Int(in any) (int, error) func SafeInt(in any) int func QuietInt(in any) int @@ -943,8 +1010,6 @@ func IntOr(in any, defVal int) int func IntOrErr(in any) (int, error) func ToInt(in any) (int, error) func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error) -func StrInt(s string) int -func StrIntOr(s string, defVal int) int func Int64(in any) (int64, error) func SafeInt64(in any) int64 func QuietInt64(in any) int64 @@ -972,6 +1037,19 @@ func Uint64Or(in any, defVal uint64) uint64 func Uint64OrErr(in any) (uint64, error) func ToUint64(in any) (uint64, error) func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error) +func StrInt(s string) int +func StrIntOr(s string, defVal int) int +func TryStrInt(s string) (int, error) +func TryStrInt64(s string) (int64, error) +func TryStrUint64(s string) (uint64, error) +// source at mathutil/convert.go +func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T] +func WithNilAsFail[T any](opt *ConvOption[T]) +func WithHandlePtr[T any](opt *ConvOption[T]) +func WithStrictMode[T any](opt *ConvOption[T]) +func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] +func StrictInt(val any) (int64, bool) +func StrictUint(val any) (uint64, bool) func QuietFloat(in any) float64 func SafeFloat(in any) float64 func FloatOrPanic(in any) float64 @@ -995,6 +1073,7 @@ func TryToString(val any, defaultAsErr bool) (string, error) func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error) // source at mathutil/format.go func DataSize(size uint64) string +func FormatBytes(bytes int) string func HowLongAgo(sec int64) string // source at mathutil/mathutil.go func OrElse[T comdef.Number](val, defVal T) T @@ -1015,11 +1094,17 @@ func RandInt(min, max int) int func RandIntWithSeed(min, max int, seed int64) int func RandomIntWithSeed(min, max int, seed int64) int ``` + + ### Reflects > Package `github.com/gookit/goutil/reflects` +Package `reflects` Provide extends reflection util functions. eg: check, convert, value set, etc. + + Click to see functions 👈 + ```go // source at reflects/check.go func IsTimeType(t reflect.Type) bool @@ -1085,11 +1170,17 @@ func SetRValue(rv, val reflect.Value) func Wrap(rv reflect.Value) Value func ValueOf(v any) Value ``` + \-### Structs + +### Struct Utils > Package `github.com/gookit/goutil/structs` +Package `structs` Provide some extends util functions for struct. eg: tag parse, struct init, value set/get + + Click to see functions 👈 + ```go // source at structs/alias.go func NewAliases(checker func(alias string)) *Aliases @@ -1140,11 +1231,15 @@ func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error ``` + \-### Strings + +### String Utils > Package `github.com/gookit/goutil/strutil` + Click to see functions 👈 + ```go // source at strutil/bytes.go func NewBuffer(initSize ...int) *Buffer @@ -1152,16 +1247,21 @@ func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool // source at strutil/check.go func IsNumChar(c byte) bool func IsInt(s string) bool +func IsUint(s string) bool func IsFloat(s string) bool func IsNumeric(s string) bool +func IsPositiveNum(s string) bool func IsAlphabet(char uint8) bool func IsAlphaNum(c uint8) bool +func IsUpper(s string) bool +func IsLower(s string) bool func StrPos(s, sub string) int func BytePos(s string, bt byte) int func IEqual(s1, s2 string) bool func NoCaseEq(s, t string) bool func IContains(s, sub string) bool func ContainsByte(s string, c byte) bool +func ContainsByteOne(s string, bs []byte) bool func ContainsOne(s string, subs []string) bool func HasOneSub(s string, subs []string) bool func IContainsOne(s string, subs []string) bool @@ -1186,6 +1286,7 @@ func HasEmpty(ss ...string) bool func IsAllEmpty(ss ...string) bool func IsVersion(s string) bool func IsVarName(s string) bool +func IsEnvName(s string) bool func Compare(s1, s2, op string) bool func VersionCompare(v1, v2, op string) bool func SimpleMatch(s string, keywords []string) bool @@ -1197,7 +1298,9 @@ func MatchNodePath(pattern, s string, sep string) bool // source at strutil/convbase.go func Base10Conv(src string, to int) string func BaseConv(src string, from, to int) string +func BaseConvInt(src uint64, to int) string func BaseConvByTpl(src string, fromBase, toBase string) string +func BaseConvIntByTpl(dec uint64, toBase string) string // source at strutil/convert.go func Quote(s string) string func Unquote(s string) string @@ -1257,7 +1360,6 @@ func ToArray(s string, sep ...string) []string func Strings(s string, sep ...string) []string func ToStrings(s string, sep ...string) []string func ToSlice(s string, sep ...string) []string -func ToOSArgs(s string) []string func ToDuration(s string) (time.Duration, error) // source at strutil/encode.go func EscapeJS(s string) string @@ -1299,6 +1401,10 @@ func Camel(s string, sep ...string) string func CamelCase(s string, sep ...string) string func Indent(s, prefix string) string func IndentBytes(b, prefix []byte) []byte +func Replaces(str string, pairs map[string]string) string +func ReplaceVars(s string, vars map[string]string) string +func NewReplacer(pairs map[string]string) *strings.Replacer +func WrapTag(s, tag string) string // source at strutil/gensn.go func MicroTimeID() string func MicroTimeHexID() string @@ -1307,7 +1413,8 @@ func MTimeBase36() string func MTimeBaseID(toBase int) string func DatetimeNo(prefix string) string func DateSN(prefix string) string -func DateSNV2(prefix string, extBase ...int) string +func DateSNv2(prefix string, extBase ...int) string +func DateSNv3(prefix string, dateLen int, extBase ...int) string // source at strutil/hash.go func Md5(src any) string func MD5(src any) string @@ -1335,6 +1442,7 @@ func RepeatRune(char rune, times int) []rune func RepeatBytes(char byte, times int) []byte func RepeatChars[T byte | rune](char T, times int) []T // source at strutil/parse.go +func NumVersion(s string) string func MustToTime(s string, layouts ...string) time.Time func ToTime(s string, layouts ...string) (t time.Time, err error) func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) @@ -1398,31 +1506,17 @@ func OrElse(s, orVal string) string func OrElseNilSafe(s *string, orVal string) string func OrHandle(s string, fn comdef.StringHandleFunc) string func Valid(ss ...string) string -func Replaces(str string, pairs map[string]string) string -func NewReplacer(pairs map[string]string) *strings.Replacer -func WrapTag(s, tag string) string func SubstrCount(s, substr string, params ...uint64) (int, error) ``` + ## \-### Syncs ## \-> Package `github.com/gookit/goutil/syncs` \-`go -// source at syncs/chan.go -func Go(f func() error) error -// source at syncs/group.go -func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context) -func NewErrGroup(limit ...int) *ErrGroup -// source at syncs/signal.go -func WaitCloseSignals(onClose func(sig os.Signal), sigCh ...chan os.Signal) -func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) -` ### System Utils > Package `github.com/gookit/goutil/sysutil` + Click to see functions 👈 + ```go // source at sysutil/exec.go func NewCmd(bin string, args ...string) *cmdr.Cmd @@ -1473,6 +1567,13 @@ func OpenURL(URL string) error // source at sysutil/sysutil_nonwin.go func Kill(pid int, signal syscall.Signal) error func ProcessExists(pid int) bool +// source at sysutil/sysutil_unix.go +func IsWin() bool +func IsWindows() bool +func IsMac() bool +func IsDarwin() bool +func IsLinux() bool +func OpenURL(URL string) error // source at sysutil/user.go func MustFindUser(uname string) *user.User func LoginUser() *user.User @@ -1491,24 +1592,29 @@ func ChangeUserByName(newUname string) error func ChangeUserUidGid(newUID int, newGid int) error func ChangeUserUIDGid(newUID int, newGid int) (err error) ``` + + ### Testing Utils > Package `github.com/gookit/goutil/testutil` + Click to see functions 👈 + ```go // source at testutil/buffer.go func NewBuffer() *byteutil.Buffer +func NewSafeBuffer() *SafeBuffer // source at testutil/envmock.go func MockEnvValue(key, val string, fn func(nv string)) func MockEnvValues(kvMap map[string]string, fn func()) func MockOsEnvByText(envText string, fn func()) -func MockOsEnv(mp map[string]string, fn func()) func SetOsEnvs(mp map[string]string) string func RemoveTmpEnvs(tmpKey string) func ClearOSEnv() func RevertOSEnv() func RunOnCleanEnv(runFn func()) +func MockOsEnv(mp map[string]string, fn func()) func MockCleanOsEnv(mp map[string]string, fn func()) // source at testutil/httpmock.go func NewHTTPRequest(method, path string, data *MD) *http.Request @@ -1533,12 +1639,17 @@ func RestoreTimeLocal() func NewTestWriter() *TestWriter func NewDirEnt(fPath string, isDir ...bool) *fakeobj.DirEntry ``` + + ### Timex > Package `github.com/gookit/goutil/timex` \-Provides an enhanced time.Time implementation, and add more commonly used functional methods. +Provides an enhanced `time.Time` implementation, and add more commonly used functional methods. + + Click to see functions 👈 + ```go // source at timex/check.go func IsDuration(s string) bool @@ -1546,12 +1657,14 @@ func InRange(dst, start, end time.Time) bool // source at timex/conv.go func Elapsed(start, end time.Time) string func ElapsedNow(start time.Time) string +func FormatDuration(d time.Duration) string func FromNow(t time.Time) string func FromNowWith(u time.Time, tms []TimeMessage) string func HowLongAgo(diffSec int64) string func HowLongAgo2(diffSec int64, tms []TimeMessage) string func ToTime(s string, layouts ...string) (time.Time, error) func ToDur(s string) (time.Duration, error) +func ParseDuration(s string) (time.Duration, error) func ToDuration(s string) (time.Duration, error) func TryToTime(s string, bt time.Time) (time.Time, error) func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error) @@ -1600,6 +1713,8 @@ func FormatUnix(sec int64, layout ...string) string func FormatUnixBy(sec int64, layout string) string func FormatUnixByTpl(sec int64, template ...string) string ``` + + #### Timex Usage **Create timex instance** @@ -1719,6 +1834,7 @@ date := FormatUnixByTpl(ts, "Y-m-d H:I:S") // Get: 2022-04-20 19:40:34 ``` + ## Code Check & Testing ```bash diff --git a/vendor/github.com/gookit/goutil/README.zh-CN.md b/vendor/github.com/gookit/goutil/README.zh-CN.md index ee6d10db0e0..17d654cd612 100644 --- a/vendor/github.com/gookit/goutil/README.zh-CN.md +++ b/vendor/github.com/gookit/goutil/README.zh-CN.md @@ -1,4 +1,4 @@ -# Go Util +# GoUtil ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/goutil?style=flat-square) [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/goutil)](https://github.com/gookit/goutil) @@ -7,7 +7,7 @@ [![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master) [![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](https://pkg.go.dev/github.com/gookit/goutil) -`goutil` Go 常用功能的扩展工具库(**800+**)。包含:数字,byte, 字符串,slice/数组,Map,结构体,反射,文本,文件,错误,时间日期,测试,特殊处理,格式化,常用信息获取等等。 +`goutil` Go 常用功能的扩展工具库(**900+**)。包含:数字,byte, 字符串,slice/数组,Map,结构体,反射,文本,文件,错误,时间日期,测试,特殊处理,格式化,常用信息获取等等。 > **[EN README](README.md)** @@ -53,17 +53,17 @@ - [`cmdline`](cliutil/cmdline) 提供 cmdline 解析,args 构建到 cmdline - [`encodes`](x/encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc. - [`finder`](x/finder) 提供简单方便的file/dir查找功能,支持过滤、排除、匹配、忽略等。 -- [textscan](strutil/textscan) 实现了一个快速扫描和分析文本内容的解析器. 可用于解析 INI, Properties 等格式内容 -- [textutil](strutil/textutil) 提供一些常用的扩展文本处理功能函数。 -- [cmdr](sysutil/cmdr) 提供快速构建和运行一个cmd,批量运行多个cmd任务 -- [process](sysutil/process) 提供一些进程操作相关的实用功能。 +- [`textscan`](strutil/textscan) 实现了一个快速扫描和分析文本内容的解析器. 可用于解析 INI, Properties 等格式内容 +- [`textutil`](strutil/textutil) 提供一些常用的扩展文本处理功能函数。 +- [`cmdr`](sysutil/cmdr) 提供快速构建和运行一个cmd,批量运行多个cmd任务 +- [`process`](sysutil/process) 提供一些进程操作相关的实用功能。 - [`fmtutil`](x/fmtutil) 格式化数据工具函数 eg:数据size - [`goinfo`](x/goinfo) 提供一些与Go info, runtime 相关的工具函数。 ## GoDoc - [Godoc for github](https://pkg.go.dev/github.com/gookit/goutil) -- Wiki docs on [DeepWiki - gookit/goutil](https://deepwiki.com/gookit/goutil) +- Wiki docs on [ZRead.ai - gookit/goutil](https://zread.ai/gookit/goutil) ## 获取 @@ -109,6 +109,8 @@ dump.Print(somevar, somevar2, ...) > Package `github.com/gookit/goutil/arrutil` +
Click to see functions 👈 + ```go // source at arrutil/arrutil.go func GetRandomOne[T any](arr []T) T @@ -152,6 +154,7 @@ func IntsToString[T comdef.Integer](ints []T) string func ToInt64s(arr any) (ret []int64, err error) func MustToInt64s(arr any) []int64 func SliceToInt64s(arr []any) []int64 +func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V func AnyToSlice(sl any) (ls []any, err error) func AnyToStrings(arr any) []string func MustToStrings(arr any) []string @@ -171,7 +174,8 @@ func FormatIndent(arr any, indent string) string func Reverse[T any](ls []T) func Remove[T comdef.Compared](ls []T, val T) []T func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T -func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V +func Map[T, V any](list []T, mapFn MapFn[T, V]) []V +func Map1[T, R any](list []T, fn func(t T) R) []R func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V func Unique[T comdef.NumberOrString](list []T) []T func IndexOf[T comdef.NumberOrString](val T, list []T) int @@ -189,6 +193,8 @@ func StringsFilter(ss []string, filter ...comdef.StringMatchFunc) []string func StringsMap(ss []string, mapFn func(s string) string) []string func TrimStrings(ss []string, cutSet ...string) []string ``` + + #### ArrUtil Usage **check value**: @@ -211,10 +217,13 @@ ss, err := arrutil.ToStrings(\[\]int{1, 2}) // ss: \[\]string{"1", "2"} ``` + ### Bytes Utils > Package `github.com/gookit/goutil/byteutil` +
Click to see functions 👈 + ```go // source at byteutil/buffer.go func NewBuffer() *Buffer @@ -244,18 +253,22 @@ func NewStdEncoder(encFn BytesEncodeFunc, decFn BytesDecodeFunc) *StdEncoder // source at byteutil/pool.go func NewChanPool(chSize int, width int, capWidth int) *ChanPool ``` + + ### Cflag > Package `github.com/gookit/goutil/cflag` +`cflag` \- Wraps and extends go `flag.FlagSet` to build simple command line applications + + + Click to see functions 👈 + ```go -// source at cflag/app.go -func NewApp(fns ...func(app *App)) *App -func NewCmd(name, desc string, runFunc ...func(c *Cmd) error) *Cmd // source at cflag/cflag.go -func SetDebug(open bool) func New(fns ...func(c *CFlags)) *CFlags +func NewWith(name, version, desc string, fns ...func(c *CFlags)) *CFlags func NewEmpty(fns ...func(c *CFlags)) *CFlags func WithDesc(desc string) func(c *CFlags) func WithVersion(version string) func(c *CFlags) @@ -269,6 +282,8 @@ func Value // source at cflag/optarg.go func NewArg(name, desc string, required bool) *FlagArg // source at cflag/util.go +func SetDebug(open bool) +func DebugMsg(format string, args ...any) func IsGoodName(name string) bool func IsZeroValue(opt *flag.Flag, value string) (bool, bool) func AddPrefix(name string) string @@ -280,15 +295,20 @@ func IsFlagHelpErr(err error) bool func WrapColorForCode(s string) string func ReplaceShorts(args []string, shortsMap map[string]string) []string ``` + + #### `cflag` Usage \-`cflag` 使用说明请看 +`cflag` usage please see + ### CLI Utils > Package `github.com/gookit/goutil/cliutil` + Click to see functions 👈 + ```go // source at cliutil/cliutil.go func SplitMulti(ss []string, sep string) []string @@ -325,6 +345,8 @@ func InputIsYes(ans string) bool func ByteIsYes(ans byte) bool func ReadPassword(question ...string) string ``` + + #### CLI Util Usage @@ -381,10 +403,13 @@ Build line: ./myapp -a val0 -m "this is message" arg0 > More, please see [./cliutil/README](cliutil/README.md) ### Var Dumper > Package `github.com/gookit/goutil/dump` + Click to see functions 👈 + ```go // source at dump/dump.go func Std() *Dumper @@ -411,7 +436,10 @@ func WithoutPosition() OptionFunc func WithoutOutput(out io.Writer) OptionFunc func WithoutColor() OptionFunc func WithoutType() OptionFunc +func WithoutLen() OptionFunc ``` + + #### Examples example code: @@ -456,10 +484,13 @@ Preview: ![](dump/_examples/preview-nested-struct.png) ### ENV/Environment > Package `github.com/gookit/goutil/envutil` + Click to see functions 👈 + ```go // source at envutil/envutil.go func VarReplace(s string) string @@ -503,6 +534,8 @@ func UnsetEnvs(keys ...string) func LoadText(text string) func LoadString(line string) bool ``` + + #### ENV Util Usage **helper functions:** @@ -522,15 +555,18 @@ envutil.ParseValue("${ENV\_NAME | defValue}") ``` + ### Errorx > Package `github.com/gookit/goutil/errorx` -`errorx` 提供了增强的错误报告实现,包含调用堆栈信息并且可以包装上一级错误。 +`errorx` provides an enhanced error reporting implementation that contains call stack information and can wrap the previous level of error. -> 在打印 error 时会额外附带调用栈信息, 方便记录日志和查找问题。 +> Additional call stack information is included when printing errors, making it easy to log and find problems. +
Click to see functions 👈 + ```go // source at errorx/assert.go func IsTrue(result bool, fmtAndArgs ...any) error @@ -580,12 +616,14 @@ func Is(err, target error) bool func To(err error, target any) bool func As(err error, target any) bool ``` + + \-#### Errorx 使用示例 +#### Errorx Usage \-**创建错误带有调用栈信息** +**Create error with call stack info** \-- 使用 `errorx.New` 替代 `errors.New` +- use the `errorx.New` instead `errors.New` ```go func doSomething() error { @@ -596,7 +634,7 @@ func doSomething() error { } ``` \-- 使用 `errorx.Newf` 或者 `errorx.Errorf` 替代 `fmt.Errorf` +- use the `errorx.Newf` or `errorx.Errorf` instead `fmt.Errorf` ```go func doSomething() error { @@ -607,9 +645,9 @@ func doSomething() error { } ``` \-**包装上一级错误** +**Wrap the previous error** \-之前这样使用: +used like this before: ```go if err := SomeFunc(); err != nil { @@ -617,7 +655,7 @@ func doSomething() error { } ``` \-可以替换成: +can be replaced with: ```go if err := SomeFunc(); err != nil { @@ -625,9 +663,9 @@ func doSomething() error { } ``` \-**使用效果示例** +**Print the errorx.New() error** \-更多关于 `errorx` 的使用请看 [./errorx/README](errorx/README.md) +Examples for use `errorx` package, more please see [./errorx/README](errorx/README.md) ```go err := errorx.New("the error message") @@ -657,16 +695,23 @@ runtime.goexit() ``` ### File System > Package `github.com/gookit/goutil/fsutil` +Package `fsutil` Filesystem util functions: quick check, create, read and write file. eg: file and dir check, operate + + + Click to see functions 👈 + ```go // source at fsutil/check.go func PathExists(path string) bool func IsDir(path string) bool func FileExists(path string) bool func IsFile(path string) bool +func IsSymlink(path string) bool func IsAbsPath(aPath string) bool func IsEmptyDir(dirPath string) bool func IsImageFile(path string) bool @@ -682,6 +727,10 @@ func FirstExistsDir(paths ...string) string func FirstExistsFile(paths ...string) string func MatchPaths(paths []string, matcher PathMatchFunc) []string func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) string +func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string +func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string +func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn) +func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int) func SearchNameUp(dirPath, name string) string func SearchNameUpx(dirPath, name string) (string, bool) func WalkDir(dir string, fn fs.WalkDirFunc) error @@ -715,9 +764,12 @@ func FileExt(fPath string) string func Extname(fPath string) string func Suffix(fPath string) string func Expand(pathStr string) string +func ExpandHome(pathStr string) string func ExpandPath(pathStr string) string func ResolvePath(pathStr string) string func SplitPath(pathStr string) (dir, name string) +func UserHomeDir() string +func HomeDir() string // source at fsutil/info_nonwin.go func Realpath(pathStr string) string // source at fsutil/mime.go @@ -725,9 +777,11 @@ func DetectMime(path string) string func MimeType(path string) (mime string) func ReaderMimeType(r io.Reader) (mime string) // source at fsutil/operate.go -func Mkdir(dirPath string, perm os.FileMode) error -func MkDirs(perm os.FileMode, dirPaths ...string) error -func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error +func Mkdir(dirPath string, perm fs.FileMode) error +func MkdirQuick(dirPath string) error +func EnsureDir(path string) error +func MkDirs(perm fs.FileMode, dirPaths ...string) error +func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error func MkParentDir(fpath string) error func NewOpenOption(optFns ...OpenOptionFunc) *OpenOption func OpenOptOrNew(opt *OpenOption) *OpenOption @@ -781,7 +835,10 @@ func WriteOSFile(f *os.File, data any) (n int, err error) func CopyFile(srcPath, dstPath string) error func MustCopyFile(srcPath, dstPath string) func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error +func CreateSymlink(target, linkPath string) error ``` + + #### FsUtil Usage @@ -813,6 +870,7 @@ func main() { ``` + ### JSON Utils > Package `github.com/gookit/goutil/jsonutil` @@ -843,10 +901,13 @@ func IsObject(s string) bool func StripComments(src string) string ``` \-### Map + +### Maputil > Package `github.com/gookit/goutil/maputil` + Click to see functions 👈 + ```go // source at maputil/check.go func HasKey(mp, key any) (ok bool) @@ -892,6 +953,7 @@ func Merge1level(mps ...map[string]any) map[string]any func DeepMerge(src, dst map[string]any, deep int) map[string]any func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string func MergeStrMap(src, dst map[string]string) map[string]string +func AppendSMap(dst, src map[string]string) map[string]string func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string func MergeMultiSMap(mps ...map[string]string) map[string]string func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string @@ -902,16 +964,23 @@ func MakeByKeys(keys []string, val any) (mp map[string]any) func SetByPath(mp *map[string]any, path string, val any) error func SetByKeys(mp *map[string]any, keys []string, val any) (err error) ``` + + ### Math/Number > Package `github.com/gookit/goutil/mathutil` +Package `mathutil` provide math(int, number) util functions. eg: convert, math calc, random + + Click to see functions 👈 + ```go // source at mathutil/calc.go func Abs[T comdef.Int](val T) T // source at mathutil/check.go func IsNumeric(c byte) bool +func IsInteger(val any) bool func Compare(first, second any, op string) bool func CompInt[T comdef.Xint](first, second T, op string) (ok bool) func CompInt64(first, second int64, op string) bool @@ -930,11 +999,7 @@ func SwapMaxInt(x, y int) (int, int) func MaxI64(x, y int64) int64 func SwapMaxI64(x, y int64) (int64, int64) func MaxFloat(x, y float64) float64 -// source at mathutil/convert.go -func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T] -func WithNilAsFail[T any](opt *ConvOption[T]) -func WithHandlePtr[T any](opt *ConvOption[T]) -func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] +// source at mathutil/conv2int.go func Int(in any) (int, error) func SafeInt(in any) int func QuietInt(in any) int @@ -945,8 +1010,6 @@ func IntOr(in any, defVal int) int func IntOrErr(in any) (int, error) func ToInt(in any) (int, error) func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error) -func StrInt(s string) int -func StrIntOr(s string, defVal int) int func Int64(in any) (int64, error) func SafeInt64(in any) int64 func QuietInt64(in any) int64 @@ -974,6 +1037,19 @@ func Uint64Or(in any, defVal uint64) uint64 func Uint64OrErr(in any) (uint64, error) func ToUint64(in any) (uint64, error) func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error) +func StrInt(s string) int +func StrIntOr(s string, defVal int) int +func TryStrInt(s string) (int, error) +func TryStrInt64(s string) (int64, error) +func TryStrUint64(s string) (uint64, error) +// source at mathutil/convert.go +func NewConvOption[T any](optFns ...ConvOptionFn[T]) *ConvOption[T] +func WithNilAsFail[T any](opt *ConvOption[T]) +func WithHandlePtr[T any](opt *ConvOption[T]) +func WithStrictMode[T any](opt *ConvOption[T]) +func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] +func StrictInt(val any) (int64, bool) +func StrictUint(val any) (uint64, bool) func QuietFloat(in any) float64 func SafeFloat(in any) float64 func FloatOrPanic(in any) float64 @@ -997,6 +1073,7 @@ func TryToString(val any, defaultAsErr bool) (string, error) func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error) // source at mathutil/format.go func DataSize(size uint64) string +func FormatBytes(bytes int) string func HowLongAgo(sec int64) string // source at mathutil/mathutil.go func OrElse[T comdef.Number](val, defVal T) T @@ -1017,11 +1094,17 @@ func RandInt(min, max int) int func RandIntWithSeed(min, max int, seed int64) int func RandomIntWithSeed(min, max int, seed int64) int ``` + + ### Reflects > Package `github.com/gookit/goutil/reflects` +Package `reflects` Provide extends reflection util functions. eg: check, convert, value set, etc. + + Click to see functions 👈 + ```go // source at reflects/check.go func IsTimeType(t reflect.Type) bool @@ -1087,11 +1170,17 @@ func SetRValue(rv, val reflect.Value) func Wrap(rv reflect.Value) Value func ValueOf(v any) Value ``` + \-### Structs + +### Struct Utils > Package `github.com/gookit/goutil/structs` +Package `structs` Provide some extends util functions for struct. eg: tag parse, struct init, value set/get + + Click to see functions 👈 + ```go // source at structs/alias.go func NewAliases(checker func(alias string)) *Aliases @@ -1142,11 +1231,15 @@ func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error ``` + \-### Strings + +### String Utils > Package `github.com/gookit/goutil/strutil` + Click to see functions 👈 + ```go // source at strutil/bytes.go func NewBuffer(initSize ...int) *Buffer @@ -1154,16 +1247,21 @@ func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool // source at strutil/check.go func IsNumChar(c byte) bool func IsInt(s string) bool +func IsUint(s string) bool func IsFloat(s string) bool func IsNumeric(s string) bool +func IsPositiveNum(s string) bool func IsAlphabet(char uint8) bool func IsAlphaNum(c uint8) bool +func IsUpper(s string) bool +func IsLower(s string) bool func StrPos(s, sub string) int func BytePos(s string, bt byte) int func IEqual(s1, s2 string) bool func NoCaseEq(s, t string) bool func IContains(s, sub string) bool func ContainsByte(s string, c byte) bool +func ContainsByteOne(s string, bs []byte) bool func ContainsOne(s string, subs []string) bool func HasOneSub(s string, subs []string) bool func IContainsOne(s string, subs []string) bool @@ -1188,6 +1286,7 @@ func HasEmpty(ss ...string) bool func IsAllEmpty(ss ...string) bool func IsVersion(s string) bool func IsVarName(s string) bool +func IsEnvName(s string) bool func Compare(s1, s2, op string) bool func VersionCompare(v1, v2, op string) bool func SimpleMatch(s string, keywords []string) bool @@ -1199,7 +1298,9 @@ func MatchNodePath(pattern, s string, sep string) bool // source at strutil/convbase.go func Base10Conv(src string, to int) string func BaseConv(src string, from, to int) string +func BaseConvInt(src uint64, to int) string func BaseConvByTpl(src string, fromBase, toBase string) string +func BaseConvIntByTpl(dec uint64, toBase string) string // source at strutil/convert.go func Quote(s string) string func Unquote(s string) string @@ -1259,7 +1360,6 @@ func ToArray(s string, sep ...string) []string func Strings(s string, sep ...string) []string func ToStrings(s string, sep ...string) []string func ToSlice(s string, sep ...string) []string -func ToOSArgs(s string) []string func ToDuration(s string) (time.Duration, error) // source at strutil/encode.go func EscapeJS(s string) string @@ -1301,6 +1401,10 @@ func Camel(s string, sep ...string) string func CamelCase(s string, sep ...string) string func Indent(s, prefix string) string func IndentBytes(b, prefix []byte) []byte +func Replaces(str string, pairs map[string]string) string +func ReplaceVars(s string, vars map[string]string) string +func NewReplacer(pairs map[string]string) *strings.Replacer +func WrapTag(s, tag string) string // source at strutil/gensn.go func MicroTimeID() string func MicroTimeHexID() string @@ -1309,7 +1413,8 @@ func MTimeBase36() string func MTimeBaseID(toBase int) string func DatetimeNo(prefix string) string func DateSN(prefix string) string -func DateSNV2(prefix string, extBase ...int) string +func DateSNv2(prefix string, extBase ...int) string +func DateSNv3(prefix string, dateLen int, extBase ...int) string // source at strutil/hash.go func Md5(src any) string func MD5(src any) string @@ -1337,6 +1442,7 @@ func RepeatRune(char rune, times int) []rune func RepeatBytes(char byte, times int) []byte func RepeatChars[T byte | rune](char T, times int) []T // source at strutil/parse.go +func NumVersion(s string) string func MustToTime(s string, layouts ...string) time.Time func ToTime(s string, layouts ...string) (t time.Time, err error) func ParseSizeRange(expr string, opt *ParseSizeOpt) (min, max uint64, err error) @@ -1400,31 +1506,17 @@ func OrElse(s, orVal string) string func OrElseNilSafe(s *string, orVal string) string func OrHandle(s string, fn comdef.StringHandleFunc) string func Valid(ss ...string) string -func Replaces(str string, pairs map[string]string) string -func NewReplacer(pairs map[string]string) *strings.Replacer -func WrapTag(s, tag string) string func SubstrCount(s, substr string, params ...uint64) (int, error) ``` + ## \-### Syncs ## \-> Package `github.com/gookit/goutil/syncs` \-`go -// source at syncs/chan.go -func Go(f func() error) error -// source at syncs/group.go -func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context) -func NewErrGroup(limit ...int) *ErrGroup -// source at syncs/signal.go -func WaitCloseSignals(onClose func(sig os.Signal), sigCh ...chan os.Signal) -func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error)) -` ### System Utils > Package `github.com/gookit/goutil/sysutil` + Click to see functions 👈 + ```go // source at sysutil/exec.go func NewCmd(bin string, args ...string) *cmdr.Cmd @@ -1475,6 +1567,13 @@ func OpenURL(URL string) error // source at sysutil/sysutil_nonwin.go func Kill(pid int, signal syscall.Signal) error func ProcessExists(pid int) bool +// source at sysutil/sysutil_unix.go +func IsWin() bool +func IsWindows() bool +func IsMac() bool +func IsDarwin() bool +func IsLinux() bool +func OpenURL(URL string) error // source at sysutil/user.go func MustFindUser(uname string) *user.User func LoginUser() *user.User @@ -1493,24 +1592,29 @@ func ChangeUserByName(newUname string) error func ChangeUserUidGid(newUID int, newGid int) error func ChangeUserUIDGid(newUID int, newGid int) (err error) ``` + + ### Testing Utils > Package `github.com/gookit/goutil/testutil` + Click to see functions 👈 + ```go // source at testutil/buffer.go func NewBuffer() *byteutil.Buffer +func NewSafeBuffer() *SafeBuffer // source at testutil/envmock.go func MockEnvValue(key, val string, fn func(nv string)) func MockEnvValues(kvMap map[string]string, fn func()) func MockOsEnvByText(envText string, fn func()) -func MockOsEnv(mp map[string]string, fn func()) func SetOsEnvs(mp map[string]string) string func RemoveTmpEnvs(tmpKey string) func ClearOSEnv() func RevertOSEnv() func RunOnCleanEnv(runFn func()) +func MockOsEnv(mp map[string]string, fn func()) func MockCleanOsEnv(mp map[string]string, fn func()) // source at testutil/httpmock.go func NewHTTPRequest(method, path string, data *MD) *http.Request @@ -1535,12 +1639,17 @@ func RestoreTimeLocal() func NewTestWriter() *TestWriter func NewDirEnt(fPath string, isDir ...bool) *fakeobj.DirEntry ``` + + ### Timex > Package `github.com/gookit/goutil/timex` \-Provides an enhanced time.Time implementation, and add more commonly used functional methods. +Provides an enhanced `time.Time` implementation, and add more commonly used functional methods. + + Click to see functions 👈 + ```go // source at timex/check.go func IsDuration(s string) bool @@ -1548,12 +1657,14 @@ func InRange(dst, start, end time.Time) bool // source at timex/conv.go func Elapsed(start, end time.Time) string func ElapsedNow(start time.Time) string +func FormatDuration(d time.Duration) string func FromNow(t time.Time) string func FromNowWith(u time.Time, tms []TimeMessage) string func HowLongAgo(diffSec int64) string func HowLongAgo2(diffSec int64, tms []TimeMessage) string func ToTime(s string, layouts ...string) (time.Time, error) func ToDur(s string) (time.Duration, error) +func ParseDuration(s string) (time.Duration, error) func ToDuration(s string) (time.Duration, error) func TryToTime(s string, bt time.Time) (time.Time, error) func ParseRange(expr string, opt *ParseRangeOpt) (start, end time.Time, err error) @@ -1602,6 +1713,8 @@ func FormatUnix(sec int64, layout ...string) string func FormatUnixBy(sec int64, layout string) string func FormatUnixByTpl(sec int64, template ...string) string ``` + + #### Timex Usage **Create timex instance** @@ -1721,6 +1834,7 @@ date := FormatUnixByTpl(ts, "Y-m-d H:I:S") // Get: 2022-04-20 19:40:34 ``` + ## Code Check & Testing ```bash diff --git a/vendor/github.com/gookit/goutil/arrutil/convert.go b/vendor/github.com/gookit/goutil/arrutil/convert.go index 9b7fe30f09b..a505e14fe19 100644 --- a/vendor/github.com/gookit/goutil/arrutil/convert.go +++ b/vendor/github.com/gookit/goutil/arrutil/convert.go @@ -125,6 +125,27 @@ func SliceToInt64s(arr []any) []int64 { return i64s } +// ToMap convert a list to new map. +// +// Example: +// type User struct { +// Name string +// Age int +// } +// users := []User{{"Tom", 18}, {"Jack", 20}} +// mp := arrutil.ToMap(users, func(u User) (string, int) { +// return u.Name, u.Age +// }) +// // mp = map[string]int{"Tom":18, "Jack":20} +func ToMap[T any, K comdef.ScalarType, V any](list []T, mapFn func(T) (K, V)) map[K]V { + mp := make(map[K]V, len(list)) + for _, item := range list { + k, v := mapFn(item) + mp[k] = v + } + return mp +} + /************************************************************* * convert func for any-slice *************************************************************/ diff --git a/vendor/github.com/gookit/goutil/arrutil/process.go b/vendor/github.com/gookit/goutil/arrutil/process.go index dc72c9df65e..0bbb8532fa8 100644 --- a/vendor/github.com/gookit/goutil/arrutil/process.go +++ b/vendor/github.com/gookit/goutil/arrutil/process.go @@ -38,6 +38,9 @@ func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T { fn = filter[0] } else { fn = func(el T) bool { + // if el == nil { // Filter nil value + // return false + // } return !reflect.ValueOf(el).IsZero() } } @@ -57,7 +60,7 @@ type MapFn[T any, V any] func(input T) (target V, find bool) // Map a list to new list // // eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...] -func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V { +func Map[T, V any](list []T, mapFn MapFn[T, V]) []V { flatArr := make([]V, 0, len(list)) for _, obj := range list { @@ -68,6 +71,16 @@ func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V { return flatArr } +// Map1 a list to new list, alias of Map func +func Map1[T, R any](list []T, fn func(t T) R) []R { + ret := make([]R, len(list)) + + for i := range list { + ret[i] = fn(list[i]) + } + return ret +} + // Column alias of Map func func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V { return Map(list, mapFn) diff --git a/vendor/github.com/gookit/goutil/byteutil/buffer.go b/vendor/github.com/gookit/goutil/byteutil/buffer.go index 6a833627cd3..f8690cede57 100644 --- a/vendor/github.com/gookit/goutil/byteutil/buffer.go +++ b/vendor/github.com/gookit/goutil/byteutil/buffer.go @@ -16,9 +16,7 @@ type Buffer struct { } // NewBuffer instance -func NewBuffer() *Buffer { - return &Buffer{} -} +func NewBuffer() *Buffer { return &Buffer{} } // PrintByte to buffer, ignore error. alias of WriteByte() func (b *Buffer) PrintByte(c byte) { @@ -26,14 +24,10 @@ func (b *Buffer) PrintByte(c byte) { } // WriteStr1 quiet write one string to buffer -func (b *Buffer) WriteStr1(s string) { - b.writeStringNl(s, false) -} +func (b *Buffer) WriteStr1(s string) { b.writeStringNl(s, false) } // WriteStr1Nl quiet write one string and end with newline -func (b *Buffer) WriteStr1Nl(s string) { - b.writeStringNl(s, true) -} +func (b *Buffer) WriteStr1Nl(s string) { b.writeStringNl(s, true) } // writeStringNl quiet write one string and end with newline func (b *Buffer) writeStringNl(s string, nl bool) { @@ -93,6 +87,9 @@ func (b *Buffer) writeAnysWithNl(vs []any, nl bool) { } } +// Writef write message to buffer, ignore error. alias of Printf() +func (b *Buffer) Writef(tpl string, vs ...any) { _, _ = fmt.Fprintf(b, tpl, vs...) } + // Printf quick write message to buffer, ignore error. func (b *Buffer) Printf(tpl string, vs ...any) { _, _ = fmt.Fprintf(b, tpl, vs...) } @@ -112,16 +109,10 @@ func (b *Buffer) ResetAndGet() string { } // Close buffer -func (b *Buffer) Close() error { - return b.CloseErr -} +func (b *Buffer) Close() error { return b.CloseErr } // Flush buffer -func (b *Buffer) Flush() error { - return b.FlushErr -} +func (b *Buffer) Flush() error { return b.FlushErr } // Sync anf flush buffer -func (b *Buffer) Sync() error { - return b.SyncErr -} +func (b *Buffer) Sync() error { return b.SyncErr } diff --git a/vendor/github.com/gookit/goutil/byteutil/check.go b/vendor/github.com/gookit/goutil/byteutil/check.go index f4d2e5ba607..cab9c7a8693 100644 --- a/vendor/github.com/gookit/goutil/byteutil/check.go +++ b/vendor/github.com/gookit/goutil/byteutil/check.go @@ -7,4 +7,3 @@ func IsNumChar(c byte) bool { return c >= '0' && c <= '9' } func IsAlphaChar(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } - diff --git a/vendor/github.com/gookit/goutil/conv.go b/vendor/github.com/gookit/goutil/conv.go index f9e8047a0b2..20eff91cba1 100644 --- a/vendor/github.com/gookit/goutil/conv.go +++ b/vendor/github.com/gookit/goutil/conv.go @@ -122,7 +122,7 @@ func ConvOrDefault(val any, kind reflect.Kind, defVal any) any { // // Examples: // -// val, err := ToKind("123", reflect.Int) // 123 +// val, err := ToKind("123", reflect.Int) // 123 func ToKind(val any, kind reflect.Kind, fbFunc func(val any) (any, error)) (newVal any, err error) { switch kind { case reflect.Int: diff --git a/vendor/github.com/gookit/goutil/envutil/info.go b/vendor/github.com/gookit/goutil/envutil/info.go index b18cb448553..9ff250b648c 100644 --- a/vendor/github.com/gookit/goutil/envutil/info.go +++ b/vendor/github.com/gookit/goutil/envutil/info.go @@ -36,7 +36,6 @@ func IsMSys() bool { return sysutil.IsMSys() } - // IsTerminal isatty check // // Usage: diff --git a/vendor/github.com/gookit/goutil/envutil/set.go b/vendor/github.com/gookit/goutil/envutil/set.go index b317155f542..8c7fa6b570a 100644 --- a/vendor/github.com/gookit/goutil/envutil/set.go +++ b/vendor/github.com/gookit/goutil/envutil/set.go @@ -34,7 +34,8 @@ func UnsetEnvs(keys ...string) { // LoadText parse multiline text to ENV. Can use to load .env file contents. // // Usage: -// envutil.LoadText(fsutil.ReadFile(".env")) +// +// envutil.LoadText(fsutil.ReadFile(".env")) func LoadText(text string) { envMp := SplitText2map(text) for key, value := range envMp { diff --git a/vendor/github.com/gookit/goutil/fsutil/check.go b/vendor/github.com/gookit/goutil/fsutil/check.go index a015d40b02b..337a10e8085 100644 --- a/vendor/github.com/gookit/goutil/fsutil/check.go +++ b/vendor/github.com/gookit/goutil/fsutil/check.go @@ -61,6 +61,8 @@ func FileExists(path string) bool { } // IsFile reports whether the named file or directory exists. +// +// - NOTE: not support symlink file func IsFile(path string) bool { if path == "" || len(path) > 468 { return false @@ -72,6 +74,15 @@ func IsFile(path string) bool { return false } +// IsSymlink reports whether the named file is a symlink. +func IsSymlink(path string) bool { + fi, err := os.Lstat(path) + if err != nil { + return false + } + return fi.Mode()&os.ModeSymlink != 0 +} + // IsAbsPath is abs path. func IsAbsPath(aPath string) bool { if len(aPath) > 0 { diff --git a/vendor/github.com/gookit/goutil/fsutil/find.go b/vendor/github.com/gookit/goutil/fsutil/find.go index 4dadc430722..5eea9f78083 100644 --- a/vendor/github.com/gookit/goutil/fsutil/find.go +++ b/vendor/github.com/gookit/goutil/fsutil/find.go @@ -23,7 +23,7 @@ func FilePathInDirs(fPath string, dirs ...string) string { } for _, dirPath := range dirs { - fPath := JoinSubPaths(dirPath, fPath) + fPath = JoinSubPaths(dirPath, fPath) if FileExists(fPath) { return fPath } @@ -67,6 +67,103 @@ func MatchFirst(paths []string, matcher PathMatchFunc, defaultPath string) strin return defaultPath } +// FindParentOption options +type FindParentOption struct { + MaxLevel int // default: 10 + // NeedDir true: find dirs; false(default): find files + NeedDir bool + OnlyOne bool // only find one, default: true + // Collector func + Collector func(fullPath string) + // MatchFunc custom matcher func. return false to stop find. + MatchFunc func(currentDir string) bool +} + +// FindParentOptFn find parent option func +type FindParentOptFn func(opt *FindParentOption) + +// FindAllInParentDirs looks for all match file(default)/dir in the current directory and parent directories +func FindAllInParentDirs(dirPath, name string, optFns ...FindParentOptFn) []string { + var foundPaths []string + optFns = append(optFns, func(opt *FindParentOption) { + opt.OnlyOne = false + }) + + FindNameInParentDirs(dirPath, name, func(fullPath string) { + foundPaths = append(foundPaths, fullPath) + }, optFns...) + return foundPaths +} + +// FindOneInParentDirs looks for a file(default)/dir in the current directory and parent directories +func FindOneInParentDirs(dirPath, name string, optFns ...FindParentOptFn) string { + var foundPath string + FindNameInParentDirs(dirPath, name, func(fullPath string) { + foundPath = fullPath + }, optFns...) + return foundPath +} + +// FindNameInParentDirs looks for file(default)/dir in the current directory and parent directories +func FindNameInParentDirs(dirPath, name string, collectFn func(fullPath string), optFns ...FindParentOptFn) { + opts := &FindParentOption{ + MaxLevel: 10, + OnlyOne: true, + Collector: collectFn, + } + for _, fn := range optFns { + fn(opts) + } + + FindInParentDirs(dirPath, func(currentDir string) bool { + filePath := filepath.Join(currentDir, name) + if fi, err := os.Stat(filePath); err == nil { + found := false + if fi.IsDir() { + found = opts.NeedDir + } else { + found = !opts.NeedDir + } + + if found { + opts.Collector(filePath) + return !opts.OnlyOne + } + } + return true + }, opts.MaxLevel) +} + +// FindInParentDirs looks for file/dir in the current directory and parent directories +// - MatchFunc custom matcher func. return false to stop find. +func FindInParentDirs(dirPath string, matchFunc func(dir string) bool, maxLevel int) { + currentLv := 1 + currentDir := ToAbsPath(dirPath) + + for { + // Check if the file exists in the current directory + if !matchFunc(currentDir) { + return + } + + // check find level + if maxLevel > 0 && currentLv > maxLevel { + break + } + + // Get parent directory + parentDir := filepath.Dir(currentDir) + if parentDir == currentDir { + // Reached the root, file not found + return + } + + // Move to parent directory + currentLv++ + currentDir = parentDir + } +} + // SearchNameUp find file/dir name in dirPath or parent dirs, // return the name of directory path // @@ -158,14 +255,10 @@ type ( ) // OnlyFindDir on find -func OnlyFindDir(_ string, ent fs.DirEntry) bool { - return ent.IsDir() -} +func OnlyFindDir(_ string, ent fs.DirEntry) bool { return ent.IsDir() } // OnlyFindFile on find -func OnlyFindFile(_ string, ent fs.DirEntry) bool { - return !ent.IsDir() -} +func OnlyFindFile(_ string, ent fs.DirEntry) bool { return !ent.IsDir() } // ExcludeNames on find func ExcludeNames(names ...string) FilterFunc { @@ -182,9 +275,7 @@ func IncludeSuffix(ss ...string) FilterFunc { } // ExcludeDotFile on find -func ExcludeDotFile(_ string, ent fs.DirEntry) bool { - return ent.Name()[0] != '.' -} +func ExcludeDotFile(_ string, ent fs.DirEntry) bool { return ent.Name()[0] != '.' } // ExcludeSuffix on find func ExcludeSuffix(ss ...string) FilterFunc { @@ -205,7 +296,7 @@ func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool { // FindInDir code refer the go pkg: path/filepath.glob() // -// - TIP: will be not found in sub-dir. +// - TIP: default will be not found in sub-dir. // // filters: return false will skip the file. func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) { diff --git a/vendor/github.com/gookit/goutil/fsutil/info.go b/vendor/github.com/gookit/goutil/fsutil/info.go index c843bc792ab..94b45cc7981 100644 --- a/vendor/github.com/gookit/goutil/fsutil/info.go +++ b/vendor/github.com/gookit/goutil/fsutil/info.go @@ -9,12 +9,15 @@ import ( ) // DirPath get dir path from filepath, without a last name. +// eg: "/foo/bar/baz.js" => "/foo/bar" func DirPath(fPath string) string { return filepath.Dir(fPath) } // Dir get dir path from filepath, without a last name. +// eg: "/foo/bar/baz.js" => "/foo/bar" func Dir(fPath string) string { return filepath.Dir(fPath) } -// PathName get file/dir name from a full path +// PathName get file/dir name from a full path. +// eg: "/foo/bar/baz.js" => "baz.js" func PathName(fPath string) string { return filepath.Base(fPath) } // PathNoExt get path from full path, without ext. @@ -30,7 +33,9 @@ func PathNoExt(fPath string) string { // Name get file/dir name from full path. // -// eg: path/to/main.go => "main.go" +// eg: +// "path/to/main.go" => "main.go" +// "/foo/bar/baz" => "baz" func Name(fPath string) string { if fPath == "" { return "" @@ -74,16 +79,15 @@ func Extname(fPath string) string { func Suffix(fPath string) string { return filepath.Ext(fPath) } // Expand will parse first `~` to user home dir path. -func Expand(pathStr string) string { - return comfunc.ExpandHome(pathStr) -} +func Expand(pathStr string) string { return comfunc.ExpandHome(pathStr) } + +// ExpandHome will parse first `~` to user home dir path. +func ExpandHome(pathStr string) string { return comfunc.ExpandHome(pathStr) } // ExpandPath will parse `~` to user home dir path. -func ExpandPath(pathStr string) string { - return comfunc.ExpandHome(pathStr) -} +func ExpandPath(pathStr string) string { return comfunc.ExpandHome(pathStr) } -// ResolvePath will parse `~` and env var in path +// ResolvePath will parse `~` and ENV var in path func ResolvePath(pathStr string) string { pathStr = comfunc.ExpandHome(pathStr) // return comfunc.ParseEnvVar() @@ -91,6 +95,18 @@ func ResolvePath(pathStr string) string { } // SplitPath splits path immediately following the final Separator, separating it into a directory and file name component -func SplitPath(pathStr string) (dir, name string) { - return filepath.Split(pathStr) +func SplitPath(pathStr string) (dir, name string) { return filepath.Split(pathStr) } + +// homeDir cache +var _homeDir string + +// UserHomeDir is alias of os.UserHomeDir, but ignore error.(by os.UserHomeDir) +func UserHomeDir() string { + if _homeDir == "" { + _homeDir, _ = os.UserHomeDir() + } + return _homeDir } + +// HomeDir get user home dir path. +func HomeDir() string { return UserHomeDir() } diff --git a/vendor/github.com/gookit/goutil/fsutil/operate.go b/vendor/github.com/gookit/goutil/fsutil/operate.go index 1d68e4c5377..59bd4d98589 100644 --- a/vendor/github.com/gookit/goutil/fsutil/operate.go +++ b/vendor/github.com/gookit/goutil/fsutil/operate.go @@ -13,12 +13,21 @@ import ( ) // Mkdir alias of os.MkdirAll() -func Mkdir(dirPath string, perm os.FileMode) error { - return os.MkdirAll(dirPath, perm) +func Mkdir(dirPath string, perm fs.FileMode) error { return os.MkdirAll(dirPath, perm) } + +// MkdirQuick with default permission 0755. +func MkdirQuick(dirPath string) error { return EnsureDir(dirPath) } + +// EnsureDir creates a directory if it doesn't exist +func EnsureDir(path string) error { + if !DirExist(path) { + return os.MkdirAll(path, 0755) + } + return nil } // MkDirs batch makes multi dirs at once -func MkDirs(perm os.FileMode, dirPaths ...string) error { +func MkDirs(perm fs.FileMode, dirPaths ...string) error { for _, dirPath := range dirPaths { if err := os.MkdirAll(dirPath, perm); err != nil { return err @@ -28,7 +37,7 @@ func MkDirs(perm os.FileMode, dirPaths ...string) error { } // MkSubDirs batch makes multi sub-dirs at once -func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error { +func MkSubDirs(perm fs.FileMode, parentDir string, subDirs ...string) error { for _, dirName := range subDirs { dirPath := parentDir + "/" + dirName if err := os.MkdirAll(dirPath, perm); err != nil { diff --git a/vendor/github.com/gookit/goutil/fsutil/opwrite.go b/vendor/github.com/gookit/goutil/fsutil/opwrite.go index 305d8ce8b13..0431ca9718c 100644 --- a/vendor/github.com/gookit/goutil/fsutil/opwrite.go +++ b/vendor/github.com/gookit/goutil/fsutil/opwrite.go @@ -1,6 +1,7 @@ package fsutil import ( + "fmt" "io" "os" @@ -185,3 +186,16 @@ func UpdateContents(filePath string, handleFn func(bs []byte) []byte) error { } return err } + +// CreateSymlink creates a symbolic link +func CreateSymlink(target, linkPath string) error { + // Check if the link already exists + if IsFile(linkPath) { + // Remove existing link/file + if err := os.Remove(linkPath); err != nil { + return fmt.Errorf("failed to remove existing symlink: %w", err) + } + } + + return os.Symlink(target, linkPath) +} diff --git a/vendor/github.com/gookit/goutil/goutil.go b/vendor/github.com/gookit/goutil/goutil.go index c0e92e6a574..33713b85b61 100644 --- a/vendor/github.com/gookit/goutil/goutil.go +++ b/vendor/github.com/gookit/goutil/goutil.go @@ -206,4 +206,3 @@ func FuncName(f any) string { func PkgName(funcName string) string { return goinfo.PkgName(funcName) } - diff --git a/vendor/github.com/gookit/goutil/internal/checkfn/check.go b/vendor/github.com/gookit/goutil/internal/checkfn/check.go index e85f2315431..81eaee37ce3 100644 --- a/vendor/github.com/gookit/goutil/internal/checkfn/check.go +++ b/vendor/github.com/gookit/goutil/internal/checkfn/check.go @@ -118,11 +118,31 @@ func StringsContains(ss []string, s string) bool { return false } -// check is number: int or float -var numReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`) +var ( + // check is number: int or float + numReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`) + // is positive number: int or float + pNumReg = regexp.MustCompile(`^\d*\.?\d+$`) +) // IsNumeric returns true if the given string is a numeric, otherwise false. -func IsNumeric(s string) bool { return numReg.MatchString(s) } +func IsNumeric(s string) bool { + if s == "" { + return false + } + return numReg.MatchString(s) +} + +// IsPositiveNum check input string is positive number +func IsPositiveNum(s string) bool { + if s == "" { + return false + } + if s[0] == '-' { + return false + } + return pNumReg.MatchString(s) +} // IsHttpURL check input is http/https url func IsHttpURL(s string) bool { diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/convert.go b/vendor/github.com/gookit/goutil/internal/comfunc/convert.go index 3d4f3b278ef..9e12be8377c 100644 --- a/vendor/github.com/gookit/goutil/internal/comfunc/convert.go +++ b/vendor/github.com/gookit/goutil/internal/comfunc/convert.go @@ -43,6 +43,10 @@ func StrToBool(s string) (bool, error) { } // FormatWithArgs format message with args +// +// - only one element, format to string +// - first is format: fmt.Sprintf(firstElem, fmtAndArgs[1:]...) +// - all is args: return fmt.Sprint(fmtAndArgs...) func FormatWithArgs(fmtAndArgs []any) string { ln := len(fmtAndArgs) if ln == 0 { @@ -50,16 +54,15 @@ func FormatWithArgs(fmtAndArgs []any) string { } first := fmtAndArgs[0] - if ln == 1 { - if msgAsStr, ok := first.(string); ok { - return msgAsStr + if str, ok := first.(string); ok { + return str } return fmt.Sprintf("%+v", first) } // is template string. - if tplStr, ok := first.(string); ok { + if tplStr, ok := first.(string); ok && strings.IndexByte(tplStr, '%') >= 0 { return fmt.Sprintf(tplStr, fmtAndArgs[1:]...) } return fmt.Sprint(fmtAndArgs...) @@ -86,11 +89,6 @@ var StrBySprintFn = func(v any) (string, error) { return fmt.Sprint(v), nil } -// WithHandlePtr set ConvOption.HandlePtr option -func WithHandlePtr(opt *ConvOption) { - opt.HandlePtr = true -} - // WithUserConvFn set ConvOption.UserConvFn option func WithUserConvFn(fn comdef.ToStringFunc) ConvOptionFn { return func(opt *ConvOption) { @@ -116,11 +114,6 @@ func (opt *ConvOption) WithOption(optFns ...ConvOptionFn) { // ToStringWith try to convert value to string. can with some option func, more see ConvOption. func ToStringWith(in any, optFns ...ConvOptionFn) (str string, err error) { - opt := NewConvOption(optFns...) - if !opt.NilAsFail && in == nil { - return "", nil - } - switch value := in.(type) { case int: str = strconv.Itoa(value) @@ -161,6 +154,18 @@ func ToStringWith(in any, optFns ...ConvOptionFn) (str string, err error) { case error: str = value.Error() default: + if len(optFns) == 0 && in == nil { + return "", nil + } + + opt := NewConvOption(optFns...) + if in == nil { + if opt.NilAsFail { + err = comdef.ErrConvType + } + return + } + if opt.HandlePtr { if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer { rv = rv.Elem() diff --git a/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go b/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go index 4f43d3ad132..d3f6224be25 100644 --- a/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go +++ b/vendor/github.com/gookit/goutil/internal/comfunc/sysfunc.go @@ -87,19 +87,24 @@ func CurrentShell(onlyName bool, fallbackShell ...string) (binPath string) { // 检查父进程名称 parentProcess := os.Getenv("GOPROCESS") if parentProcess != "" { - return parentProcess + binPath = parentProcess + } else { + binPath = os.Getenv("SHELL") // 适用于 Unix-like 系统 + if len(binPath) == 0 { + // TODO check on Windows git bash + binPath, err = ShellExec("echo $SHELL") + if err != nil { + binPath = fbShell + } + } + binPath = strings.TrimSpace(binPath) } - binPath = os.Getenv("SHELL") // 适用于 Unix-like 系统 - if len(binPath) == 0 { - // TODO check on Windows - binPath, err = ShellExec("echo $SHELL") - if err != nil { - return fbShell - } + // fix: 去除 .exe 后缀 + if pos := strings.IndexByte(binPath, '.'); pos > 0 { + binPath = binPath[:pos] } - binPath = strings.TrimSpace(binPath) // cache result curShellCache = binPath } else { diff --git a/vendor/github.com/gookit/goutil/jsonutil/encoding.go b/vendor/github.com/gookit/goutil/jsonutil/encoding.go index b921a421523..791033f7535 100644 --- a/vendor/github.com/gookit/goutil/jsonutil/encoding.go +++ b/vendor/github.com/gookit/goutil/jsonutil/encoding.go @@ -72,4 +72,4 @@ func DecodeFile(file string, ptr any) error { } return json.Unmarshal(bs, ptr) -} \ No newline at end of file +} diff --git a/vendor/github.com/gookit/goutil/maputil/alias.go b/vendor/github.com/gookit/goutil/maputil/alias.go index b8109591177..9f739d22a7e 100644 --- a/vendor/github.com/gookit/goutil/maputil/alias.go +++ b/vendor/github.com/gookit/goutil/maputil/alias.go @@ -1,11 +1,15 @@ package maputil -import "fmt" +import ( + "fmt" + "sort" +) // Aliases implemented a simple string alias map. +// - key: alias, value: real name type Aliases map[string]string -// AddAlias to the Aliases +// AddAlias to the Aliases map func (as Aliases) AddAlias(alias, real string) { if rn, ok := as[alias]; ok { panic(fmt.Sprintf("The alias '%s' is already used by '%s'", alias, rn)) @@ -13,21 +17,21 @@ func (as Aliases) AddAlias(alias, real string) { as[alias] = real } -// AddAliases to the Aliases +// AddAliases to the Aliases map func (as Aliases) AddAliases(real string, aliases []string) { for _, a := range aliases { as.AddAlias(a, real) } } -// AddAliasMap to the Aliases +// AddAliasMap to the Aliases map func (as Aliases) AddAliasMap(alias2real map[string]string) { for a, r := range alias2real { as.AddAlias(a, r) } } -// HasAlias in the Aliases +// HasAlias in the Aliases map func (as Aliases) HasAlias(alias string) bool { if _, ok := as[alias]; ok { return true @@ -42,3 +46,24 @@ func (as Aliases) ResolveAlias(alias string) string { } return alias } + +// AliasesNames returns all sorted alias names. +func (as Aliases) AliasesNames() []string { + ns := make([]string, 0, len(as)) + for alias := range as { + ns = append(ns, alias) + } + sort.Strings(ns) + return ns +} + +// GroupAliases groups aliases by real name. +// +// returns: {real name -> []aliases, ...} +func (as Aliases) GroupAliases() map[string][]string { + gaMap := make(map[string][]string) + for alias, name := range as { + gaMap[name] = append(gaMap[name], alias) + } + return gaMap +} diff --git a/vendor/github.com/gookit/goutil/maputil/convert.go b/vendor/github.com/gookit/goutil/maputil/convert.go index fd0a17d4ac5..215ba9d4b8a 100644 --- a/vendor/github.com/gookit/goutil/maputil/convert.go +++ b/vendor/github.com/gookit/goutil/maputil/convert.go @@ -86,7 +86,7 @@ func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V { } // SliceToSMap convert string k-v pairs slice to map[string]string -// - eg: []string{k1,v1,k2,v2} -> map[string]string{k1:v1, k2:v2} +// - eg: []string{k1,v1,k2,v2} -> map[string]string{k1:v1, k2:v2} func SliceToSMap(kvPairs ...string) map[string]string { ln := len(kvPairs) // check kvPairs length must be even diff --git a/vendor/github.com/gookit/goutil/maputil/data.go b/vendor/github.com/gookit/goutil/maputil/data.go index 71e227ad997..5eb89cfbc78 100644 --- a/vendor/github.com/gookit/goutil/maputil/data.go +++ b/vendor/github.com/gookit/goutil/maputil/data.go @@ -9,12 +9,12 @@ import ( "github.com/gookit/goutil/strutil" ) -// Data an map data type -type Data map[string]any - // Map alias of Data type Map = Data +// Data alias of map[string]any +type Data map[string]any + // Has value on the data map func (d Data) Has(key string) bool { _, ok := d.GetByPath(key) diff --git a/vendor/github.com/gookit/goutil/maputil/maputil.go b/vendor/github.com/gookit/goutil/maputil/maputil.go index 8acd64d409f..6aaf2584da5 100644 --- a/vendor/github.com/gookit/goutil/maputil/maputil.go +++ b/vendor/github.com/gookit/goutil/maputil/maputil.go @@ -63,6 +63,11 @@ func MergeStrMap(src, dst map[string]string) map[string]string { return MergeStringMap(src, dst, false) } +// AppendSMap append string map data to dst map. +func AppendSMap(dst, src map[string]string) map[string]string { + return MergeStringMap(src, dst, false) +} + // MergeStringMap simple merge two string map. merge src to dst map func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string { if len(src) == 0 { diff --git a/vendor/github.com/gookit/goutil/maputil/smap.go b/vendor/github.com/gookit/goutil/maputil/smap.go index 6fc767a64ce..63dc2d0fea0 100644 --- a/vendor/github.com/gookit/goutil/maputil/smap.go +++ b/vendor/github.com/gookit/goutil/maputil/smap.go @@ -5,6 +5,8 @@ import ( "github.com/gookit/goutil/strutil" ) +// SM is alias of map[string]string +type SM = StrMap // SMap and StrMap is alias of map[string]string type SMap = StrMap type StrMap map[string]string diff --git a/vendor/github.com/gookit/goutil/mathutil/check.go b/vendor/github.com/gookit/goutil/mathutil/check.go index ed1d11957c9..2eda9ebe6dd 100644 --- a/vendor/github.com/gookit/goutil/mathutil/check.go +++ b/vendor/github.com/gookit/goutil/mathutil/check.go @@ -3,8 +3,16 @@ package mathutil import "github.com/gookit/goutil/comdef" // IsNumeric returns true if the given character is a numeric, otherwise false. -func IsNumeric(c byte) bool { - return c >= '0' && c <= '9' +func IsNumeric(c byte) bool { return c >= '0' && c <= '9' } + +// IsInteger strict check the given value is an integer(intX,uintX), otherwise false. +func IsInteger(val any) bool { + switch val.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr: + return true + default: + return false + } } // Compare any intX,floatX value by given op. returns `first op(=,!=,<,<=,>,>=) second` diff --git a/vendor/github.com/gookit/goutil/mathutil/convert.go b/vendor/github.com/gookit/goutil/mathutil/convert.go index cbedf90f6bd..edcb49fcc2e 100644 --- a/vendor/github.com/gookit/goutil/mathutil/convert.go +++ b/vendor/github.com/gookit/goutil/mathutil/convert.go @@ -1,8 +1,6 @@ package mathutil import ( - "fmt" - "math" "reflect" "strconv" "strings" @@ -13,24 +11,6 @@ import ( "github.com/gookit/goutil/internal/comfunc" ) -// ToIntFunc convert value to int -type ToIntFunc func(any) (int, error) - -// ToInt64Func convert value to int64 -type ToInt64Func func(any) (int64, error) - -// ToUintFunc convert value to uint -type ToUintFunc func(any) (uint, error) - -// ToUint64Func convert value to uint -type ToUint64Func func(any) (uint64, error) - -// ToFloatFunc convert value to float -type ToFloatFunc func(any) (float64, error) - -// ToTypeFunc convert value to defined type -type ToTypeFunc[T any] func(any) (T, error) - // ConvOption convert options type ConvOption[T any] struct { // if ture: value is nil, will return convert error; @@ -40,6 +20,11 @@ type ConvOption[T any] struct { // - if true: will use real type try convert. default is false // - NOTE: current T type's ptr is default support. HandlePtr bool + // StrictMode for convert value. default is false + // + // TRUE: + // - to int: string, float will return error + StrictMode bool // set custom fallback convert func for not supported type. UserConvFn ToTypeFunc[T] } @@ -68,14 +53,13 @@ type ConvOptionFn[T any] func(opt *ConvOption[T]) // Example: // // ToIntWithFunc(val, mathutil.WithNilAsFail[int]) -func WithNilAsFail[T any](opt *ConvOption[T]) { - opt.NilAsFail = true -} +func WithNilAsFail[T any](opt *ConvOption[T]) { opt.NilAsFail = true } // WithHandlePtr set ConvOption.HandlePtr option -func WithHandlePtr[T any](opt *ConvOption[T]) { - opt.HandlePtr = true -} +func WithHandlePtr[T any](opt *ConvOption[T]) { opt.HandlePtr = true } + +// WithStrictMode set ConvOption.StrictMode option +func WithStrictMode[T any](opt *ConvOption[T]) { opt.StrictMode = true } // WithUserConvFn set ConvOption.UserConvFn option func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] { @@ -85,488 +69,71 @@ func WithUserConvFn[T any](fn ToTypeFunc[T]) ConvOptionFn[T] { } /************************************************************* - * convert value to int + * region Strict to int/uint *************************************************************/ -// Int convert value to int -func Int(in any) (int, error) { return ToInt(in) } - -// SafeInt convert value to int, will ignore error -func SafeInt(in any) int { - val, _ := ToInt(in) - return val -} - -// QuietInt convert value to int, will ignore error -func QuietInt(in any) int { return SafeInt(in) } - -// IntOrPanic convert value to int, will panic on error -func IntOrPanic(in any) int { - val, err := ToInt(in) - if err != nil { - panic(err) - } - return val -} - -// MustInt convert value to int, will panic on error -func MustInt(in any) int { return IntOrPanic(in) } - -// IntOrDefault convert value to int, return defaultVal on failed -func IntOrDefault(in any, defVal int) int { return IntOr(in, defVal) } - -// IntOr convert value to int, return defaultVal on failed -func IntOr(in any, defVal int) int { - val, err := ToIntWith(in) - if err != nil { - return defVal - } - return val -} - -// IntOrErr convert value to int, return error on failed -func IntOrErr(in any) (int, error) { return ToIntWith(in) } - -// ToInt convert value to int, return error on failed -func ToInt(in any) (int, error) { return ToIntWith(in) } - -// ToIntWith convert value to int, can with some option func. -// -// Example: -// -// ToIntWithFunc(val, mathutil.WithNilAsFail, mathutil.WithUserConvFn(func(in any) (int, error) { -// }) -func ToIntWith(in any, optFns ...ConvOptionFn[int]) (iVal int, err error) { - opt := NewConvOption[int](optFns...) - if !opt.NilAsFail && in == nil { - return 0, nil - } - - switch tVal := in.(type) { +// StrictInt check the given value is an integer(intX,uintX), return the int64 value and true if success +func StrictInt(val any) (int64, bool) { + switch tVal := val.(type) { case int: - iVal = tVal - case *int: // default support int ptr type - iVal = *tVal + return int64(tVal), true case int8: - iVal = int(tVal) + return int64(tVal), true case int16: - iVal = int(tVal) + return int64(tVal), true case int32: - iVal = int(tVal) + return int64(tVal), true case int64: - if tVal > math.MaxInt32 { - err = fmt.Errorf("value overflow int32. input: %v", tVal) - } else { - iVal = int(tVal) - } + return tVal, true case uint: - if tVal > math.MaxInt32 { - err = fmt.Errorf("value overflow int32. input: %v", tVal) - } else { - iVal = int(tVal) - } + return int64(tVal), true case uint8: - iVal = int(tVal) + return int64(tVal), true case uint16: - iVal = int(tVal) + return int64(tVal), true case uint32: - if tVal > math.MaxInt32 { - err = fmt.Errorf("value overflow int32. input: %v", tVal) - } else { - iVal = int(tVal) - } + return int64(tVal), true case uint64: - if tVal > math.MaxInt32 { - err = fmt.Errorf("value overflow int32. input: %v", tVal) - } else { - iVal = int(tVal) - } - case float32: - iVal = int(tVal) - case float64: - iVal = int(tVal) - case time.Duration: - if tVal > math.MaxInt32 { - err = fmt.Errorf("value overflow int32. input: %v", tVal) - } else { - iVal = int(tVal) - } - case string: - sVal := strings.TrimSpace(tVal) - iVal, err = strconv.Atoi(sVal) - // handle the case where the string might be a float - if err != nil && checkfn.IsNumeric(sVal) { - var floatVal float64 - if floatVal, err = strconv.ParseFloat(sVal, 64); err == nil { - iVal = int(math.Round(floatVal)) - err = nil - } - } - case comdef.Int64able: // eg: json.Number - var i64 int64 - if i64, err = tVal.Int64(); err == nil { - if i64 > math.MaxInt32 { - err = fmt.Errorf("value overflow int32. input: %v", tVal) - } else { - iVal = int(i64) - } - } + return int64(tVal), true + case uintptr: + return int64(tVal), true default: - if opt.HandlePtr { - if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer { - rv = rv.Elem() - if checkfn.IsSimpleKind(rv.Kind()) { - return ToIntWith(rv.Interface(), optFns...) - } - } - } - - if opt.UserConvFn != nil { - return opt.UserConvFn(in) - } - err = comdef.ErrConvType - } - return -} - -// StrInt convert. -func StrInt(s string) int { - iVal, _ := strconv.Atoi(strings.TrimSpace(s)) - return iVal -} - -// StrIntOr convert string to int, return default val on failed -func StrIntOr(s string, defVal int) int { - iVal, err := strconv.Atoi(strings.TrimSpace(s)) - if err != nil { - return defVal - } - return iVal -} - -/************************************************************* - * convert value to int64 - *************************************************************/ - -// Int64 convert value to int64, return error on failed -func Int64(in any) (int64, error) { return ToInt64(in) } - -// SafeInt64 convert value to int64, will ignore error -func SafeInt64(in any) int64 { - i64, _ := ToInt64With(in) - return i64 -} - -// QuietInt64 convert value to int64, will ignore error -func QuietInt64(in any) int64 { return SafeInt64(in) } - -// MustInt64 convert value to int64, will panic on error -func MustInt64(in any) int64 { - i64, err := ToInt64With(in) - if err != nil { - panic(err) - } - return i64 -} - -// Int64OrDefault convert value to int64, return default val on failed -func Int64OrDefault(in any, defVal int64) int64 { return Int64Or(in, defVal) } - -// Int64Or convert value to int64, return default val on failed -func Int64Or(in any, defVal int64) int64 { - i64, err := ToInt64With(in) - if err != nil { - return defVal + return 0, false } - return i64 } -// ToInt64 convert value to int64, return error on failed -func ToInt64(in any) (int64, error) { return ToInt64With(in) } - -// Int64OrErr convert value to int64, return error on failed -func Int64OrErr(in any) (int64, error) { return ToInt64With(in) } - -// ToInt64With try to convert value to int64. can with some option func, more see ConvOption. -func ToInt64With(in any, optFns ...ConvOptionFn[int64]) (i64 int64, err error) { - opt := NewConvOption(optFns...) - if !opt.NilAsFail && in == nil { - return 0, nil - } - - switch tVal := in.(type) { - case string: - sVal := strings.TrimSpace(tVal) - i64, err = strconv.ParseInt(sVal, 10, 0) - // handle the case where the string might be a float - if err != nil && checkfn.IsNumeric(sVal) { - var floatVal float64 - if floatVal, err = strconv.ParseFloat(sVal, 64); err == nil { - i64 = int64(math.Round(floatVal)) - err = nil - } - } +// StrictUint strict check value is integer(intX,uintX) and convert to uint64. +func StrictUint(val any) (uint64, bool) { + switch tVal := val.(type) { case int: - i64 = int64(tVal) + return uint64(tVal), true case int8: - i64 = int64(tVal) + return uint64(tVal), true case int16: - i64 = int64(tVal) + return uint64(tVal), true case int32: - i64 = int64(tVal) + return uint64(tVal), true case int64: - i64 = tVal - case *int64: // default support int64 ptr type - i64 = *tVal + return uint64(tVal), true case uint: - i64 = int64(tVal) + return uint64(tVal), true case uint8: - i64 = int64(tVal) + return uint64(tVal), true case uint16: - i64 = int64(tVal) + return uint64(tVal), true case uint32: - i64 = int64(tVal) + return uint64(tVal), true case uint64: - i64 = int64(tVal) - case float32: - i64 = int64(tVal) - case float64: - i64 = int64(tVal) - case time.Duration: - i64 = int64(tVal) - case comdef.Int64able: // eg: json.Number - i64, err = tVal.Int64() + return tVal, true + case uintptr: + return uint64(tVal), true default: - if opt.HandlePtr { - if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer { - rv = rv.Elem() - if checkfn.IsSimpleKind(rv.Kind()) { - return ToInt64With(rv.Interface(), optFns...) - } - } - } - - if opt.UserConvFn != nil { - i64, err = opt.UserConvFn(in) - } else { - err = comdef.ErrConvType - } + return 0, false } - return -} - -/************************************************************* - * convert value to uint - *************************************************************/ - -// Uint convert any to uint, return error on failed -func Uint(in any) (uint, error) { return ToUint(in) } - -// SafeUint convert any to uint, will ignore error -func SafeUint(in any) uint { - val, _ := ToUint(in) - return val -} - -// QuietUint convert any to uint, will ignore error -func QuietUint(in any) uint { return SafeUint(in) } - -// MustUint convert any to uint, will panic on error -func MustUint(in any) uint { - val, err := ToUintWith(in) - if err != nil { - panic(err) - } - return val -} - -// UintOrDefault convert any to uint, return default val on failed -func UintOrDefault(in any, defVal uint) uint { return UintOr(in, defVal) } - -// UintOr convert any to uint, return default val on failed -func UintOr(in any, defVal uint) uint { - val, err := ToUintWith(in) - if err != nil { - return defVal - } - return val -} - -// UintOrErr convert value to uint, return error on failed -func UintOrErr(in any) (uint, error) { return ToUintWith(in) } - -// ToUint convert value to uint, return error on failed -func ToUint(in any) (u64 uint, err error) { return ToUintWith(in) } - -// ToUintWith try to convert value to uint. can with some option func, more see ConvOption. -func ToUintWith(in any, optFns ...ConvOptionFn[uint]) (uVal uint, err error) { - opt := NewConvOption(optFns...) - if !opt.NilAsFail && in == nil { - return 0, nil - } - - switch tVal := in.(type) { - case int: - uVal = uint(tVal) - case int8: - uVal = uint(tVal) - case int16: - uVal = uint(tVal) - case int32: - uVal = uint(tVal) - case int64: - uVal = uint(tVal) - case uint: - uVal = tVal - case *uint: // default support uint ptr type - uVal = *tVal - case uint8: - uVal = uint(tVal) - case uint16: - uVal = uint(tVal) - case uint32: - uVal = uint(tVal) - case uint64: - uVal = uint(tVal) - case float32: - uVal = uint(tVal) - case float64: - uVal = uint(tVal) - case time.Duration: - uVal = uint(tVal) - case comdef.Int64able: // eg: json.Number - var i64 int64 - i64, err = tVal.Int64() - uVal = uint(i64) - case string: - var u64 uint64 - u64, err = strconv.ParseUint(strings.TrimSpace(tVal), 10, 0) - uVal = uint(u64) - default: - if opt.HandlePtr { - if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer { - rv = rv.Elem() - if checkfn.IsSimpleKind(rv.Kind()) { - return ToUintWith(rv.Interface(), optFns...) - } - } - } - - if opt.UserConvFn != nil { - uVal, err = opt.UserConvFn(in) - } else { - err = comdef.ErrConvType - } - } - return -} - -/************************************************************* - * convert value to uint64 - *************************************************************/ - -// Uint64 convert any to uint64, return error on failed -func Uint64(in any) (uint64, error) { return ToUint64(in) } - -// QuietUint64 convert any to uint64, will ignore error -func QuietUint64(in any) uint64 { return SafeUint64(in) } - -// SafeUint64 convert any to uint64, will ignore error -func SafeUint64(in any) uint64 { - val, _ := ToUint64(in) - return val -} - -// MustUint64 convert any to uint64, will panic on error -func MustUint64(in any) uint64 { - val, err := ToUint64With(in) - if err != nil { - panic(err) - } - return val -} - -// Uint64OrDefault convert any to uint64, return default val on failed -func Uint64OrDefault(in any, defVal uint64) uint64 { return Uint64Or(in, defVal) } - -// Uint64Or convert any to uint64, return default val on failed -func Uint64Or(in any, defVal uint64) uint64 { - val, err := ToUint64With(in) - if err != nil { - return defVal - } - return val -} - -// Uint64OrErr convert value to uint64, return error on failed -func Uint64OrErr(in any) (uint64, error) { return ToUint64With(in) } - -// ToUint64 convert value to uint64, return error on failed -func ToUint64(in any) (uint64, error) { return ToUint64With(in) } - -// ToUint64With try to convert value to uint64. can with some option func, more see ConvOption. -func ToUint64With(in any, optFns ...ConvOptionFn[uint64]) (u64 uint64, err error) { - opt := NewConvOption(optFns...) - if !opt.NilAsFail && in == nil { - return 0, nil - } - - switch tVal := in.(type) { - case int: - u64 = uint64(tVal) - case int8: - u64 = uint64(tVal) - case int16: - u64 = uint64(tVal) - case int32: - u64 = uint64(tVal) - case int64: - u64 = uint64(tVal) - case uint: - u64 = uint64(tVal) - case uint8: - u64 = uint64(tVal) - case uint16: - u64 = uint64(tVal) - case uint32: - u64 = uint64(tVal) - case uint64: - u64 = tVal - case *uint64: // default support uint64 ptr type - u64 = *tVal - case float32: - u64 = uint64(tVal) - case float64: - u64 = uint64(tVal) - case time.Duration: - u64 = uint64(tVal) - case comdef.Int64able: // eg: json.Number - var i64 int64 - i64, err = tVal.Int64() - u64 = uint64(i64) - case string: - u64, err = strconv.ParseUint(strings.TrimSpace(tVal), 10, 0) - default: - if opt.HandlePtr { - if rv := reflect.ValueOf(in); rv.Kind() == reflect.Pointer { - rv = rv.Elem() - if checkfn.IsSimpleKind(rv.Kind()) { - return ToUint64With(rv.Interface(), optFns...) - } - } - } - - if opt.UserConvFn != nil { - u64, err = opt.UserConvFn(in) - } else { - err = comdef.ErrConvType - } - } - return } /************************************************************* - * convert value to float64 + * region convert to float64 *************************************************************/ // QuietFloat convert value to float64, will ignore error. alias of SafeFloat @@ -671,7 +238,7 @@ func ToFloatWith(in any, optFns ...ConvOptionFn[float64]) (f64 float64, err erro } /************************************************************* - * convert intX/floatX to string + * region intX/floatX to string *************************************************************/ // MustString convert intX/floatX value to string, will panic on error diff --git a/vendor/github.com/gookit/goutil/mathutil/format.go b/vendor/github.com/gookit/goutil/mathutil/format.go index deb29fd2a50..e310eb09116 100644 --- a/vendor/github.com/gookit/goutil/mathutil/format.go +++ b/vendor/github.com/gookit/goutil/mathutil/format.go @@ -22,6 +22,21 @@ func DataSize(size uint64) string { } } +// FormatBytes Format the byte size to be a readable string. eg: 1024 => 1 KB +func FormatBytes(bytes int) string { + const unit = 1024 + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} + var timeFormats = [][]int{ {0}, {1}, diff --git a/vendor/github.com/gookit/goutil/structs/convert.go b/vendor/github.com/gookit/goutil/structs/convert.go index f8029865dad..8c55e943164 100644 --- a/vendor/github.com/gookit/goutil/structs/convert.go +++ b/vendor/github.com/gookit/goutil/structs/convert.go @@ -66,11 +66,11 @@ func ToString(st any, optFns ...MapOptFunc) string { const defaultFieldTag = "json" // CustomUserFunc for map convert -// - fName: raw field name in struct +// - fName: raw field name in struct // // Returns: -// - ok: return true to collect field, otherwise excluded. -// - newVal: `newVal != nil` return new value to collect, otherwise collect original value. +// - ok: return true to collect field, otherwise excluded. +// - newVal: `newVal != nil` return new value to collect, otherwise collect original value. type CustomUserFunc func(fName string, fv reflect.Value) (ok bool, newVal any) // MapOptions for convert struct to map diff --git a/vendor/github.com/gookit/goutil/strutil/check.go b/vendor/github.com/gookit/goutil/strutil/check.go index 04660c307d9..a990f6f0575 100644 --- a/vendor/github.com/gookit/goutil/strutil/check.go +++ b/vendor/github.com/gookit/goutil/strutil/check.go @@ -3,6 +3,7 @@ package strutil import ( "path" "regexp" + "strconv" "strings" "unicode" "unicode/utf8" @@ -17,18 +18,43 @@ var IsHttpURL = checkfn.IsHttpURL // IsNumChar returns true if the given character is a numeric, otherwise false. func IsNumChar(c byte) bool { return c >= '0' && c <= '9' } -var intReg = regexp.MustCompile(`^\d+$`) -var floatReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`) +var ( + uintReg = regexp.MustCompile(`^\d+$`) + intReg = regexp.MustCompile(`^[-+]?\d+$`) + + floatReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`) +) // IsInt check the string is an integer number -func IsInt(s string) bool { return intReg.MatchString(s) } +func IsInt(s string) bool { + if s == "" { + return false + } + return intReg.MatchString(s) +} + +// IsUint check the string is an unsigned integer number +func IsUint(s string) bool { + if s == "" { + return false + } + return uintReg.MatchString(s) +} // IsFloat check the string is a float number -func IsFloat(s string) bool { return floatReg.MatchString(s) } +func IsFloat(s string) bool { + if s == "" { + return false + } + return floatReg.MatchString(s) +} // IsNumeric returns true if the given string is a numeric(int/float), otherwise false. func IsNumeric(s string) bool { return checkfn.IsNumeric(s) } +// IsPositiveNum check the string is a positive number +func IsPositiveNum(s string) bool { return checkfn.IsPositiveNum(s) } + // IsAlphabet char func IsAlphabet(char uint8) bool { // A 65 -> Z 90 @@ -48,6 +74,28 @@ func IsAlphaNum(c uint8) bool { return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' } +// IsUpper returns true if the given string is an uppercase, otherwise false. +func IsUpper(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= 'A' && s[i] <= 'Z' { + continue + } + return false + } + return true +} + +// IsLower returns true if the given string is a lowercase, otherwise false. +func IsLower(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= 'a' && s[i] <= 'z' { + continue + } + return false + } + return true +} + // StrPos alias of the strings.Index func StrPos(s, sub string) int { return strings.Index(s, sub) } @@ -68,6 +116,16 @@ func IContains(s, sub string) bool { // ContainsByte in given string. func ContainsByte(s string, c byte) bool { return strings.IndexByte(s, c) >= 0 } +// ContainsByteOne in given string. +func ContainsByteOne(s string, bs []byte) bool { + for _, b := range bs { + if strings.IndexByte(s, b) >= 0 { + return true + } + } + return false +} + // InArray alias of HasOneSub() var InArray = HasOneSub @@ -95,10 +153,10 @@ func IContainsOne(s string, subs []string) bool { return false } -// ContainsAll substr(s) in the given string. alias of HasAllSubs() +// ContainsAll given string should contain all substrings. alias of HasAllSubs() func ContainsAll(s string, subs []string) bool { return HasAllSubs(s, subs) } -// HasAllSubs all substr in the given string. +// HasAllSubs given string should contain all substrings func HasAllSubs(s string, subs []string) bool { for _, sub := range subs { if !strings.Contains(s, sub) { @@ -227,6 +285,8 @@ var ( verRegex = regexp.MustCompile(`^[0-9][\d.]+(-\w+)?$`) // regex for check variable name varRegex = regexp.MustCompile(`^[a-zA-Z][\w-]*$`) + // regex for check env var name + envRegex = regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`) // IsVariableName alias for IsVarName IsVariableName = IsVarName ) @@ -237,25 +297,90 @@ func IsVersion(s string) bool { return verRegex.MatchString(s) } // IsVarName is valid variable name. func IsVarName(s string) bool { return varRegex.MatchString(s) } -// Compare for two strings. -func Compare(s1, s2, op string) bool { return VersionCompare(s1, s2, op) } +// IsEnvName is valid ENV var name. eg: APP_NAME +func IsEnvName(s string) bool { return envRegex.MatchString(s) } -// VersionCompare for two version strings. -func VersionCompare(v1, v2, op string) bool { +// Compare for two strings. +func Compare(s1, s2, op string) bool { switch op { case ">", "gt": - return v1 > v2 + return s1 > s2 case "<", "lt": - return v1 < v2 + return s1 < s2 case ">=", "gte": - return v1 >= v2 + return s1 >= s2 case "<=", "lte": - return v1 <= v2 + return s1 <= s2 case "!=", "ne", "neq": - return v1 != v2 + return s1 != s2 default: // eq - return v1 == v2 + return s1 == s2 + } +} + +// VersionCompare for two version strings. eg: 1.2.0 > 1.1.0 +func VersionCompare(v1, v2, op string) bool { + parts1 := parseVersion(v1) + parts2 := parseVersion(v2) + + result := compareVersions(parts1, parts2) + switch op { + case ">", "gt": + return result > 0 + case "<", "lt": + return result < 0 + case "=", "==", "eq": + return result == 0 + case "!=", "ne", "neq": + return result != 0 + case ">=", "gte": + return result >= 0 + case "<=", "lte": + return result <= 0 + default: + return false + } +} + +// parseVersion 将版本号字符串解析为整数数组 +func parseVersion(version string) []int { + parts := strings.Split(version, ".") + result := make([]int, len(parts)) + + for i, part := range parts { + num, _ := strconv.Atoi(part) + result[i] = num + } + return result +} + +// compareVersions 比较两个版本号数组 +// 返回: -1 表示 v1 < v2, 0 表示 v1 = v2, 1 表示 v1 > v2 +func compareVersions(v1, v2 []int) int { + maxLen := len(v1) + if len(v2) > maxLen { + maxLen = len(v2) } + + for i := 0; i < maxLen; i++ { + num1 := 0 + if i < len(v1) { + num1 = v1[i] + } + + num2 := 0 + if i < len(v2) { + num2 = v2[i] + } + + if num1 > num2 { + return 1 + } else if num1 < num2 { + return -1 + } + } + + return 0 } // SimpleMatch all substring in the give text string. diff --git a/vendor/github.com/gookit/goutil/strutil/convbase.go b/vendor/github.com/gookit/goutil/strutil/convbase.go index a405778a647..c9f57edd425 100644 --- a/vendor/github.com/gookit/goutil/strutil/convbase.go +++ b/vendor/github.com/gookit/goutil/strutil/convbase.go @@ -16,6 +16,7 @@ const ( Base16Chars = "0123456789abcdef" Base32Chars = "0123456789abcdefghjkmnpqrstvwxyz" Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz" + Base48Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL" Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" ) @@ -33,14 +34,26 @@ func Base10Conv(src string, to int) string { return BaseConv(src, 10, to) } // BaseConv("7b", 16, 10) // Output: "123" func BaseConv(src string, from, to int) string { if from > 64 || from < 2 { - return "" + from = 10 } if to > 64 || to < 2 { - return "" + to = 16 } return BaseConvByTpl(src, Base64Chars[:from], Base64Chars[:to]) } +// BaseConvInt convert base int to new base string. +// +// Usage: +// +// BaseConv(123, 16) // Output: "7b" +func BaseConvInt(src uint64, to int) string { + if to > 64 || to < 2 { + to = 16 + } + return BaseConvIntByTpl(src, Base64Chars[:to]) +} + // BaseConvByTpl convert base string by template. // // Usage: @@ -58,7 +71,7 @@ func BaseConvByTpl(src string, fromBase, toBase string) string { var err error dec, err = strconv.ParseUint(src, 10, 0) if err != nil { - basefn.Panicf("input is not a valid decimal number: %s", src) + basefn.Panicf("input is not a valid decimal number: %s(%v)", src, err) } } else { fLen := uint64(len(fromBase)) @@ -67,6 +80,12 @@ func BaseConvByTpl(src string, fromBase, toBase string) string { } } + // convert to new base + return BaseConvIntByTpl(dec, toBase) +} + +// BaseConvIntByTpl convert base int to new base string. +func BaseConvIntByTpl(dec uint64, toBase string) string { // convert to new base var res string toLen := uint64(len(toBase)) diff --git a/vendor/github.com/gookit/goutil/strutil/convert.go b/vendor/github.com/gookit/goutil/strutil/convert.go index d258621f8d3..f7bbac4b0d9 100644 --- a/vendor/github.com/gookit/goutil/strutil/convert.go +++ b/vendor/github.com/gookit/goutil/strutil/convert.go @@ -88,7 +88,7 @@ func JoinAny(sep string, parts ...any) string { func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) } /************************************************************* - * convert value to string + * region value to string *************************************************************/ // String convert value to string, return error on failed @@ -144,7 +144,7 @@ func AnyToString(val any, defaultAsErr bool) (s string, err error) { if !defaultAsErr { optFn = comfunc.WithUserConvFn(comfunc.StrBySprintFn) } - return ToStringWith(val, optFn) + return comfunc.ToStringWith(val, optFn) } // ToStringWith try to convert value to string. can with some option func, more see comfunc.ConvOption. @@ -153,7 +153,7 @@ func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error) { } /************************************************************* - * convert string value to bool + * region string value to bool *************************************************************/ // ToBool convert string to bool @@ -185,7 +185,7 @@ func Bool(s string) (bool, error) { } /************************************************************* - * convert string value to int + * region string value to int *************************************************************/ // Int convert string to int, alias of ToInt() @@ -234,7 +234,7 @@ func IntOrPanic(s string) int { } /************************************************************* - * convert string value to int64 + * region convert string to int64 *************************************************************/ // Int64 convert string to int, will ignore error @@ -286,7 +286,7 @@ func Int64OrPanic(s string) int64 { } /************************************************************* - * convert string value to uint + * region string value to uint *************************************************************/ // Uint convert string to uint, will ignore error @@ -335,7 +335,7 @@ func UintOr(s string, defVal uint64) uint64 { } /************************************************************* - * convert string value to byte + * region string value to byte * refer from https://github.com/valyala/fastjson/blob/master/util.go *************************************************************/ @@ -361,7 +361,7 @@ func ToBytes(s string) (b []byte) { } /************************************************************* - * convert string value to int/string slice, time.Time + * region string to int/string slice, time.Time *************************************************************/ // Ints alias of the ToIntSlice(). default sep is comma(,) @@ -406,11 +406,6 @@ func ToSlice(s string, sep ...string) []string { return Split(s, ",") } -// ToOSArgs split string to string[](such as os.Args) -// func ToOSArgs(s string) []string { -// return cliutil.StringToOSArgs(s) // error: import cycle not allowed -// } - // ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m". // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". func ToDuration(s string) (time.Duration, error) { diff --git a/vendor/github.com/gookit/goutil/strutil/format.go b/vendor/github.com/gookit/goutil/strutil/format.go index 05150f5a4ad..c63b5d8b848 100644 --- a/vendor/github.com/gookit/goutil/strutil/format.go +++ b/vendor/github.com/gookit/goutil/strutil/format.go @@ -1,6 +1,7 @@ package strutil import ( + "fmt" "regexp" "strings" "unicode" @@ -184,3 +185,46 @@ func IndentBytes(b, prefix []byte) []byte { } return res } + +// Replaces replace multi strings +// +// pairs: {old1: new1, old2: new2, ...} +// +// Can also use: +// +// strings.NewReplacer("old1", "new1", "old2", "new2").Replace(str) +func Replaces(str string, pairs map[string]string) string { + return NewReplacer(pairs).Replace(str) +} + +// ReplaceVars replaces simple variables in a string. format: {varName} +func ReplaceVars(s string, vars map[string]string) string { + if !ContainsByte(s, '{') { + return s + } + + // format var name to {name} + pairs := make(map[string]string) + for k, v := range vars { + vName := "{" + k + "}" + pairs[vName] = v + } + return NewReplacer(pairs).Replace(s) +} + +// NewReplacer instance +func NewReplacer(pairs map[string]string) *strings.Replacer { + ss := make([]string, len(pairs)*2) + for old, newVal := range pairs { + ss = append(ss, old, newVal) + } + return strings.NewReplacer(ss...) +} + +// WrapTag for given string. +func WrapTag(s, tag string) string { + if s == "" { + return s + } + return fmt.Sprintf("<%s>%s", tag, s, tag) +} diff --git a/vendor/github.com/gookit/goutil/strutil/gensn.go b/vendor/github.com/gookit/goutil/strutil/gensn.go index 31cf6cbf74d..2c2981741dc 100644 --- a/vendor/github.com/gookit/goutil/strutil/gensn.go +++ b/vendor/github.com/gookit/goutil/strutil/gensn.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/gookit/goutil/mathutil" "github.com/gookit/goutil/x/basefn" ) @@ -34,12 +33,12 @@ func MicroTimeID() string { return MTimeBaseID(10) } // MicroTimeHexID micro time HEX ID generate. // -// return like: 5b5f0588af1761ad3(len: 16-17) +// return like: 643d4cec7db9e(len: 13) func MicroTimeHexID() string { return MTimeHexID() } // MTimeHexID micro time HEX ID generate. // -// return like: 5b5f0588af1761ad3(len: 16-17) +// return like: 643d4cec7db9e(len: 13) func MTimeHexID() string { return MTimeBaseID(16) } // MTimeBase36 micro time BASE36 id generate. @@ -48,12 +47,19 @@ func MTimeBase36() string { return MTimeBaseID(36) } // MTimeBaseID micro time BASE id generate. toBase: 2-36 // // Examples: -// - MTimeBaseID(16): 5b5f0588af1761ad3(len: 16-17) -// - MTimeBaseID(36): gorntzvsa73mo(len: 13) +// - toBase=16: 643d4cec7db9e(len: 13) +// - toBase=36: hd312z9ka2(len: 10) func MTimeBaseID(toBase int) string { + // eg: 1763431181849557 ms := time.Now().UnixMicro() - ri := mathutil.RandomInt(DefMinInt, DefMaxInt) - return strconv.FormatInt(ms, toBase) + strconv.FormatInt(int64(ri), toBase) + // rand 1000 - 9999 + // ri := mathutil.RandomInt(DefMinInt, DefMaxInt) + ri := 1000 + rand.Int63n(8999) + + if toBase > 36 { + return BaseConvInt(uint64(ms)+uint64(ri), toBase) + } + return strconv.FormatInt(ms+ri, toBase) } // DatetimeNo generate. can use for order-no. @@ -84,20 +90,20 @@ func DateSN(prefix string) string { bs = strconv.AppendUint(bs, uint64(c32%99), 10) // rand 1000 - 9999 - rs := rand.New(rand.NewSource(nt.UnixNano())) - bs = strconv.AppendInt(bs, 1000+rs.Int63n(8999), 10) + // rs := rand.New(rand.NewSource(nt.UnixNano())) + bs = strconv.AppendInt(bs, 1000+rand.Int63n(8999), 10) return string(bs) } -// DateSNV2 generate date serial number. +// DateSNv2 generate date serial number. // - 2 < extBase <= 36 // - return: PREFIX + yyyyMMddHHmmss + extBase(6bit micro + 5bit random number) // // Example: // - prefix=P, extBase=16, return: P2023091414361354b4490(len=22) // - prefix=P, extBase=36, return: P202309141436131gw3jg(len=21) -func DateSNV2(prefix string, extBase ...int) string { +func DateSNv2(prefix string, extBase ...int) string { pl := len(prefix) bs := make([]byte, 0, 22+pl) if pl > 0 { @@ -109,9 +115,9 @@ func DateSNV2(prefix string, extBase ...int) string { bs = nt.AppendFormat(bs, "20060102150405.000000") // rand 10000 - 99999 - rs := rand.New(rand.NewSource(nt.UnixNano())) + // rs := rand.New(rand.NewSource(nt.UnixNano())) // 6bit micro + 5bit rand - ext := strconv.AppendInt(bs[16+pl:], 10000+rs.Int63n(89999), 10) + ext := strconv.AppendInt(bs[16+pl:], 10000+rand.Int63n(89999), 10) base := basefn.FirstOr(extBase, 16) // prefix + yyyyMMddHHmmss + ext(convert to base) @@ -119,3 +125,43 @@ func DateSNV2(prefix string, extBase ...int) string { return string(bs) } + +// DateSNv3 generate date serial number. +// - 2 < extBase <= 64 +// - return: PREFIX + DATETIME(yyyyMMddHHmmss).dateLen + extBase(DATETIME.after+6bit micro + 5bit random number) +// - dateLen: 为 DATETIME(yyyyMMddHHmmss) 保留的长度,默认为 8(yyyyMMdd) 后面的给 extBase 使用 +// +// Example: +// - prefix=P, dateLen=8, extBase=16, return: P202511139vs99gbifnj len: 20 +// - prefix=P, dateLen=6, extBase=36, return: P2025119yn52qhefati len: 19 +// - prefix=P, dateLen=6, extBase=48, return: P202511k9ksgD1fe6x len: 18 +// - prefix=P, dateLen=4, extBase=62, return: P2025aZl8N0y58M7 len: 16 +func DateSNv3(prefix string, dateLen int, extBase ...int) string { + pl := len(prefix) + bs := make([]byte, 0, 22+pl) + if pl > 0 { + bs = append(bs, prefix...) + } + + // micro datetime + nt := time.Now() + bs = nt.AppendFormat(bs, "20060102150405.000000") + // 去掉 bs 中间的 . + bs = append(bs[:14+pl], bs[15+pl:]...) + + // rand 10000 - 99999 + // rs := rand.New(rand.NewSource(nt.UnixNano())) // ERR: 这样使用,同时调用产生随机数相同,导致重复 + // 6bit micro + 5bit rand + // ext := strconv.AppendInt(bs[dateLen+pl:], 10000+rs.Int63n(89999), 10) + ext := strconv.AppendInt(bs[dateLen+pl:], 10000+rand.Int63n(89999), 10) + extI64, _ := strconv.ParseInt(string(ext), 10, 0) + + base := basefn.FirstOr(extBase, 32) + // PREFIX + DATETIME(yyyyMMddHHmmss).dateLen + extBase(convert to base) + if base > 36 { + bs = append(bs[:dateLen+pl], BaseConvInt(uint64(extI64), base)...) + } else { + bs = append(bs[:dateLen+pl], strconv.FormatInt(extI64, base)...) + } + return string(bs) +} diff --git a/vendor/github.com/gookit/goutil/strutil/parse.go b/vendor/github.com/gookit/goutil/strutil/parse.go index c03cb125d20..580b66baae7 100644 --- a/vendor/github.com/gookit/goutil/strutil/parse.go +++ b/vendor/github.com/gookit/goutil/strutil/parse.go @@ -2,6 +2,7 @@ package strutil import ( "errors" + "regexp" "strconv" "strings" "time" @@ -10,6 +11,13 @@ import ( "github.com/gookit/goutil/byteutil" ) +var regNumVersion = regexp.MustCompile(`[0-9][\d.]+([_-]\d+)?`) + +// NumVersion parse input string, get valid number version. eg: go-1.22.3 -> 1.22.3 +func NumVersion(s string) string { + return regNumVersion.FindString(s) +} + // MustToTime convert date string to time.Time func MustToTime(s string, layouts ...string) time.Time { t, err := ToTime(s, layouts...) diff --git a/vendor/github.com/gookit/goutil/strutil/runes.go b/vendor/github.com/gookit/goutil/strutil/runes.go index 6a3debba025..1bb678aad35 100644 --- a/vendor/github.com/gookit/goutil/strutil/runes.go +++ b/vendor/github.com/gookit/goutil/strutil/runes.go @@ -75,8 +75,8 @@ func Utf8Width(s string) int { return RunesWidth([]rune(s)) } // str := "hi,你好" // // len(str) // 9 -// strutil.Utf8Width(str) // 7 -// len([]rune(str)) = utf8.RuneCountInString(s) // 5 +// strutil.RunesWidth(str) // 7 一个中文字占两个字符 +// len([]rune(str)) = utf8.RuneCountInString(s) // 5 按字算 func RunesWidth(rs []rune) (w int) { if len(rs) == 0 { return diff --git a/vendor/github.com/gookit/goutil/strutil/strutil.go b/vendor/github.com/gookit/goutil/strutil/strutil.go index 9e46773b942..2e15436f903 100644 --- a/vendor/github.com/gookit/goutil/strutil/strutil.go +++ b/vendor/github.com/gookit/goutil/strutil/strutil.go @@ -3,7 +3,6 @@ package strutil import ( "errors" - "fmt" "strings" "github.com/gookit/goutil/comdef" @@ -77,34 +76,6 @@ func Valid(ss ...string) string { return "" } -// Replaces replace multi strings -// -// pairs: {old1: new1, old2: new2, ...} -// -// Can also use: -// -// strings.NewReplacer("old1", "new1", "old2", "new2").Replace(str) -func Replaces(str string, pairs map[string]string) string { - return NewReplacer(pairs).Replace(str) -} - -// NewReplacer instance -func NewReplacer(pairs map[string]string) *strings.Replacer { - ss := make([]string, len(pairs)*2) - for old, newVal := range pairs { - ss = append(ss, old, newVal) - } - return strings.NewReplacer(ss...) -} - -// WrapTag for given string. -func WrapTag(s, tag string) string { - if s == "" { - return s - } - return fmt.Sprintf("<%s>%s", tag, s, tag) -} - // SubstrCount returns the number of times the substr substring occurs in the s string. // Actually, it comes from strings.Count(). // diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go b/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go index bcfff8d2485..ea91b5b4f99 100644 --- a/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go +++ b/vendor/github.com/gookit/goutil/strutil/textutil/strvar_expr.go @@ -15,8 +15,8 @@ import ( // StrVarRenderer implements like shell vars renderer // 简单的实现类似 php, kotlin, shell 插值变量渲染,表达式解析处理。 // -// - var format: $var_name, ${some_var}, ${top.sub_var} -// - func call: ${func($var_name, 'const string')} +// - var format: $var_name, ${some_var}, ${top.sub_var} +// - func call: ${func($var_name, 'const string')} type StrVarRenderer struct { // global variables vars map[string]any diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go b/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go index 7ade12c091f..931e9221daa 100644 --- a/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go +++ b/vendor/github.com/gookit/goutil/strutil/textutil/textutil.go @@ -13,14 +13,14 @@ import ( // ReplaceVars by regex replace given tpl vars. // -// If a format is empty, will use {const defaultVarFormat} +// If a format is empty, will use {const DefaultVarFormat} func ReplaceVars(text string, vars map[string]any, format string) string { return NewVarReplacer(format).Replace(text, vars) } // RenderSMap by regex replacement given tpl vars. // -// If a format is empty, will use {const defaultVarFormat} +// If a format is empty, will use {const DefaultVarFormat} func RenderSMap(text string, vars map[string]string, format string) string { return NewVarReplacer(format).RenderSimple(text, vars) } diff --git a/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go b/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go index 40044f300f6..798e0b25b6b 100644 --- a/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go +++ b/vendor/github.com/gookit/goutil/strutil/textutil/var_replacer.go @@ -1,6 +1,7 @@ package textutil import ( + "os" "reflect" "regexp" "strings" @@ -11,7 +12,8 @@ import ( "github.com/gookit/goutil/strutil" ) -const defaultVarFormat = "{{,}}" +// DefaultVarFormat var template +const DefaultVarFormat = "{{,}}" // FallbackFn type type FallbackFn = func(name string) (val string, ok bool) @@ -28,7 +30,7 @@ type VarReplacer struct { // // eg: {name: {a: 1, b: 2}} => {name.a: 1, name.b: 2} flatSubs bool - // do parse env value. default: false + // do parse env value in var-value and tpl var-name. default: false parseEnv bool // do parse default value. default: false // @@ -46,7 +48,7 @@ type VarReplacer struct { RenderFn func(s string, vs map[string]string) string } -// NewVarReplacer instance. +// NewVarReplacer instance. default format is: DefaultVarFormat // // Usage: // @@ -104,7 +106,7 @@ func (r *VarReplacer) OnNotFound(fn FallbackFn) *VarReplacer { // WithFormat custom var template func (r *VarReplacer) WithFormat(format string) *VarReplacer { - r.Left, r.Right = strutil.QuietCut(strutil.OrElse(format, defaultVarFormat), ",") + r.Left, r.Right = strutil.QuietCut(strutil.OrElse(format, DefaultVarFormat), ",") r.Init() return r } @@ -183,7 +185,11 @@ func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string { if r.parseEnv { for name, val := range varMap { - varMap[name] = varexpr.SafeParse(val) + if strings.Contains(val, "${") { + varMap[name] = varexpr.SafeParse(val) + } else { + varMap[name] = val + } } } @@ -223,6 +229,11 @@ func (r *VarReplacer) doReplace(s string, varMap map[string]string) string { if val, ok := varMap[name]; ok { return val } + if r.parseEnv && strutil.IsEnvName(name) { + if val := os.Getenv(name); val != "" { + return val + } + } // has custom not found handle func if r.NotFound != nil { diff --git a/vendor/github.com/gookit/goutil/syncs/syncs.go b/vendor/github.com/gookit/goutil/syncs/syncs.go index e8933afdc75..64f7aab3fec 100644 --- a/vendor/github.com/gookit/goutil/syncs/syncs.go +++ b/vendor/github.com/gookit/goutil/syncs/syncs.go @@ -1,7 +1,10 @@ // Package syncs provides synchronization primitives util functions. package syncs -import "sync" +import ( + "context" + "sync" +) // WaitGroup is a wrapper of sync.WaitGroup. type WaitGroup struct { @@ -16,3 +19,8 @@ func (wg *WaitGroup) Go(fn func()) { fn() }() } + +// ContextValue create a new context with given value +func ContextValue(key, value any) context.Context { + return context.WithValue(context.Background(), key, value) +} diff --git a/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go b/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go index a9bb0c98347..279d6027c41 100644 --- a/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go +++ b/vendor/github.com/gookit/goutil/sysutil/os_version_windows.go @@ -43,7 +43,7 @@ func VersionInfoBySys() *OSVersionInfo { // OsVersionByParse Get Windows system version information by parse string // -// cmdOut eg: "Microsoft Windows [Version 10.0.22631.4391]" +// cmdOut eg: "Microsoft Windows [Version 10.0.22631.4391]" func OsVersionByParse(cmdOut string) (*OSVersionInfo, error) { return parseOsVersionString(cmdOut) } @@ -153,4 +153,3 @@ func parseOsVersionString(out string) (*OSVersionInfo, error) { } return &ovi, nil } - diff --git a/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go b/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go index f0962d8a2da..f8bb1503c70 100644 --- a/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go +++ b/vendor/github.com/gookit/goutil/sysutil/sysutil_darwin.go @@ -1,4 +1,5 @@ //go:build darwin + package sysutil import "os/exec" diff --git a/vendor/github.com/gookit/goutil/x/basefn/basefn.go b/vendor/github.com/gookit/goutil/x/basefn/basefn.go index ec9e8956e0c..3ab144eee7a 100644 --- a/vendor/github.com/gookit/goutil/x/basefn/basefn.go +++ b/vendor/github.com/gookit/goutil/x/basefn/basefn.go @@ -4,40 +4,18 @@ package basefn import ( "errors" "fmt" + + "github.com/gookit/goutil/internal/comfunc" ) // Panicf format panic message use fmt.Sprintf -func Panicf(format string, v ...any) { - panic(fmt.Sprintf(format, v...)) -} +func Panicf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) } // PanicIf if cond = true, panics with an error message func PanicIf(cond bool, fmtAndArgs ...any) { if cond { - panic(errors.New(formatWithArgs(fmtAndArgs))) - } -} - -func formatWithArgs(fmtAndArgs []any) string { - ln := len(fmtAndArgs) - if ln == 0 { - return "" - } - - first := fmtAndArgs[0] - - if ln == 1 { - if msgAsStr, ok := first.(string); ok { - return msgAsStr - } - return fmt.Sprintf("%+v", first) - } - - // is template string. - if tplStr, ok := first.(string); ok { - return fmt.Sprintf(tplStr, fmtAndArgs[1:]...) + panic(errors.New(comfunc.FormatWithArgs(fmtAndArgs))) } - return fmt.Sprint(fmtAndArgs...) } // PanicErr panics if error is not empty diff --git a/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go b/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go index 69e554342fe..9162dce8447 100644 --- a/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go +++ b/vendor/github.com/gookit/goutil/x/ccolor/color_tag.go @@ -4,8 +4,6 @@ import ( "fmt" "regexp" "strings" - - "github.com/gookit/goutil/x/termenv" ) // output colored text like uses custom tag. @@ -67,6 +65,7 @@ var colorTags = map[string]string{ "normal": "0;39", // no color "brown": "0;33", // #A52A2A "yellow": "0;33", + "ylw": "0;33", "ylw0": "0;33", "yellowB": "1;33", // with bold "ylw1": "1;33", @@ -178,7 +177,7 @@ func ReplaceTag(str string) string { return ParseTagByEnv(str) } // ParseTagByEnv parse given string. will check package setting. func ParseTagByEnv(str string) string { // disable OR not support color - if termenv.NoColor() || !termenv.IsSupportColor() { + if shouldCleanColor() { return ClearTag(str) } return ParseTag(str) diff --git a/vendor/github.com/gookit/goutil/x/ccolor/style.go b/vendor/github.com/gookit/goutil/x/ccolor/style.go index 5a808912024..2fd9f5791f2 100644 --- a/vendor/github.com/gookit/goutil/x/ccolor/style.go +++ b/vendor/github.com/gookit/goutil/x/ccolor/style.go @@ -8,8 +8,9 @@ import ( // String color style string. TODO // eg: -// s := String("red,bold") -// s.Println("some text message") +// +// s := String("red,bold") +// s.Println("some text message") type String string // Style for color render. diff --git a/vendor/github.com/gookit/goutil/x/ccolor/util.go b/vendor/github.com/gookit/goutil/x/ccolor/util.go index 46ed2b4bbc4..a00110dc29e 100644 --- a/vendor/github.com/gookit/goutil/x/ccolor/util.go +++ b/vendor/github.com/gookit/goutil/x/ccolor/util.go @@ -22,6 +22,10 @@ func ColorsToCode(colors ...Color) string { return strings.Join(codes, ";") } +func shouldCleanColor() bool { + return termenv.NoColor() || !termenv.IsSupportColor() +} + /************************************************************* * render color code *************************************************************/ @@ -43,7 +47,7 @@ func RenderCode(code string, args ...any) string { } // disabled OR not support color - if !termenv.IsSupportColor() { + if shouldCleanColor() { return ClearCode(message) } @@ -62,7 +66,7 @@ func RenderString(code string, str string) string { } // disabled OR not support color - if !termenv.IsSupportColor() { + if shouldCleanColor() { return ClearCode(str) } @@ -79,7 +83,7 @@ func RenderWithSpaces(code string, args ...any) string { } // disabled OR not support color - if !termenv.IsSupportColor() { + if shouldCleanColor() { return ClearCode(msg) } return StartSet + code + "m" + msg + ResetSet @@ -128,4 +132,3 @@ func formatLikePrintln(args []any) (message string) { } return } - diff --git a/vendor/github.com/gookit/goutil/x/goinfo/stack.go b/vendor/github.com/gookit/goutil/x/goinfo/stack.go index 3912b7aa9b3..e6183569d77 100644 --- a/vendor/github.com/gookit/goutil/x/goinfo/stack.go +++ b/vendor/github.com/gookit/goutil/x/goinfo/stack.go @@ -124,12 +124,12 @@ type CallerFilterFunc func(file string, fc *runtime.Func) bool // // Usage: // -// cs := sysutil.CallersInfos(3, 2) -// for _, ci := range cs { -// fc := runtime.FuncForPC(pc) -// // maybe need check fc = nil -// fnName = fc.Name() -// } +// cs := sysutil.CallersInfos(3, 2) +// for _, ci := range cs { +// fc := runtime.FuncForPC(pc) +// // maybe need check fc = nil +// fnName = fc.Name() +// } func CallersInfos(skip, num int, filters ...CallerFilterFunc) []*CallerInfo { filterLn := len(filters) callers := make([]*CallerInfo, 0, num) diff --git a/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go b/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go index 39f8e367873..94ca89cda3f 100644 --- a/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go +++ b/vendor/github.com/gookit/goutil/x/termenv/detect_nonwin.go @@ -43,4 +43,4 @@ func detectSpecialTermColor(termVal string) (ColorLevel, bool) { func syscallStdinFd() int { return syscall.Stdin -} \ No newline at end of file +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 515ece4f837..12695f2da16 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -738,7 +738,7 @@ github.com/google/uuid ## explicit; go 1.19 github.com/gookit/config/v2 github.com/gookit/config/v2/yaml -# github.com/gookit/goutil v0.7.1 +# github.com/gookit/goutil v0.7.2 ## explicit; go 1.19 github.com/gookit/goutil github.com/gookit/goutil/arrutil ``` ### RFC 1925 URL: https://ostreff.info/rfc-1925/ Last updated: 2025-11-10T18:01:29.000Z [RFC 1925: The Twelve Networking Truths![](https://ostreff.info/content/images/icon/favicon.ico)](https://www.rfc-editor.org/rfc/rfc1925.html?ref=ostreff.info) ### How to build and use Python 3.14.x on FreeBSD as the default version URL: https://ostreff.info/how-to-use-python-3-14-x-on-freebsd/ Last updated: 2025-11-02T12:21:57.000Z First important thing is to add needed pieces in /etc/make.conf file ```bash DEFAULT_VERSIONS+=python3=3.14 DEFAULT_VERSIONS+=python=3.14 ``` The you must get working copy of feature python314 port from [here](https://bugs.freebsd.org/bugzilla/show%5Fbug.cgi?id=282176&ref=ostreff.info). Here is my example to upgrade from Python 3.13.8 to 3.14.0: ```bash # sh # portmaster -o lang/python314 python313 # REINSTALL="$(pkg info -oq '*py313*')" # pkg delete -f "*py313*" # portmaster $REINSTALL # REBUILD=$(pkg query -g "%n:%dn" '*' | grep py3 | grep -v py314 | cut -d : -f 1 | sort -u) # portmaster $REBUILD # REBUILD2=$(pkg list | grep python-313 | xargs pkg which | awk '{print $6}' | sort -u) # portmaster $REBUILD2 ``` There is visible difference in [performance](https://blog.miguelgrinberg.com/post/python-3-14-is-here-how-fast-is-it?ref=ostreff.info) after the upgrade. - The official inclusion into main ports tree was from 1 november 2025. ### Upgrade to PostgreSQL 18.0 URL: https://ostreff.info/upgrade-to-postgresql-18/ Last updated: 2025-10-06T20:24:41.000Z The [postgresql 1](https://www.postgresql.org/about/news/postgresql-18-released-3142/?ref=ostreff.info)[8](https://www.postgresql.org/about/news/postgresql-18-released-3142/?ref=ostreff.info) was released. So here is quick manual to upgrade to latest version from 17.6. ```bash pkg create postgresql17-server mkdir /tmp/pg-upgrade tar xf postgresql17-server-17.6.pkg -C /tmp/pg-upgrade pkg delete -f databases/postgresql17-server databases/postgresql17-client portmaster databases/postgresql18-server databases/postgresql18-client su -l postgres -c "/usr/local/bin/initdb --encoding=utf-8 --lc-collate=C -D /var/db/postgres/data18 -U postgres" su -l postgres -c "/tmp/pg-upgrade/usr/local/bin/pg_checksums --pgdata=/var/db/postgres/data17/ --enable --progress --verbose" su -l postgres -c "pg_upgrade -b /tmp/pg-upgrade/usr/local/bin/ -d /var/db/postgres/data17/ -B /usr/local/bin/ -D /var/db/postgres/data18/ -U postgres" service postgresql start /usr/local/bin/vacuumdb -U postgres --all --analyze-in-stages /var/db/postgres/delete_old_cluster.sh ``` Don't forget to check and rebuild all ports with dependency to `postgresql-client` Full manual for general upgrades comes from [here](https://ostreff.info/upgrade-postgresql-on-freebsd/). ### Use SNI in dovecot and postfix URL: https://ostreff.info/use-sni-in-dovecot-and-postfix/ Last updated: 2025-10-23T11:33:38.000Z **Server Name Indication** (**SNI**) is an extension to the [Transport Layer Security (TLS)](https://en.wikipedia.org/wiki/Transport%5FLayer%5FSecurity?ref=ostreff.info) computer networking protocol by which a [client](https://en.wikipedia.org/wiki/Client%5F%28computing%29?ref=ostreff.info) indicates which [hostname](https://en.wikipedia.org/wiki/Hostname?ref=ostreff.info) it is attempting to connect to at the start of the handshaking process. The extension allows a server to present one of multiple possible [certificates](https://en.wikipedia.org/wiki/Public%5Fkey%5Fcertificate?ref=ostreff.info) on the same [IP address](https://en.wikipedia.org/wiki/IP%5Faddress?ref=ostreff.info) and [TCP port](https://en.wikipedia.org/wiki/TCP%5Fport?ref=ostreff.info) number and hence allows multiple secure sites (or any other [service](https://en.wikipedia.org/wiki/Server%5F%28computing%29?ref=ostreff.info#Purpose) over TLS) to be served by the same IP address without requiring all those sites to use the same certificate. The desired hostname is not encrypted in the original SNI extension, so an eavesdropper can see which site is being requested. The SNI extension was specified in 2003 in [RFC](https://en.wikipedia.org/wiki/RFC%5F%28identifier%29?ref=ostreff.info) [3546](https://www.rfc-editor.org/rfc/rfc3546?ref=ostreff.info) In order to make my mail server capable to handle with multiple certificates ( in my case this files are generated by [caddy](https://caddyserver.com/?ref=ostreff.info) web server, which takes care for renewal also ) for dovecot and postfix following is needed. For [dovecot](https://dovecot.org/?ref=ostreff.info) you have to add into `/usr/local/etc/dovecot/dovecot.conf` ```bash # mail.ostreff.info local_name mail.ostreff.info { ssl_key = =250MB free space, no need to do repartition # - If there is enough unallocated space to grow the WinRE partition size, skip shrinking OS # # However, if the WinRE partition is before the OS partition, there is no chance to extend it # As a result, it's better to create a new WinRE partition after the OS partition # ------------------------------------ # Perform a few checks LogMessage("") LogMessage("Verifying if the WinRE partition needs to be extended or not...") if (!(($diskType -ieq "MBR") -Or ($diskType -ieq "GPT"))) { LogMessage("Error: Got an unexpected disk partition style: " +$diskType) exit 1 } # WinRE partition must be after OS partition for the repartition if ($WinREPartitionIndex -eq $OSPartition.PartitionNumber) { LogMessage("WinRE and OS are on the same partition, should not perform extension") exit 0 } $supportedSize = Get-PartitionSupportedSize -DriveLetter $OSDrive # if there is enough free space, skip extension if ($WinREPartitionSizeInfo[1] -ge 250MB) { LogMessage("More than 250 MB of free space was detected in the WinRE partition, there is no need to extend the partition") exit 0 } if ($WinREPartition.Offset -lt $OSPartitionEnds) { LogMessage("WinRE partition is not after OS partition, cannot perform extension") LogMessage("Need to create a new WinRE partition after OS partition") $NeedCreateNew = $true $NeedShrink = $true # Calculate the size of repartition # Will create a new WinRE partition with current WinRE partition size + 250 MB # The OS partition size will be shrunk by the new WinRE partition size $targetWinREPartitionSize = $WinREPartitionSizeInfo[0] + 250MB $shrinkSize = [Math]::Ceiling($targetWinREPartitionSize / 1MB) * 1MB $targetOSPartitionSize = $OSPartition.Size - $shrinkSize if ($targetOSPartitionSize -lt $supportedSize.SizeMin) { LogMessage("Error: The target OS partition size after shrinking is smaller than the supported minimum size, cannot perform the repartition") exit 1 } } else { if ($WinREIsOnSystemPartition) { LogMessage("WinRE parititon is after the OS partition and it's also System partition") LogMessage("Error: Got unexpected disk layout, cannot proceed") exit 1 } if (!($WinREPartitionIndex -eq ($OSPartition.PartitionNumber + 1))) { LogMessage("Error: WinRE partition is not right after the OS partition, cannot extend WinRE partition") exit 1 } # Calculate the size of repartition # Will shrink OS partitition by 250 MB $shrinkSize = 250MB $targetOSPartitionSize = $OSPartition.Size - $shrinkSize $targetWinREPartitionSize = $WinREPartitionSizeInfo[0] + 250MB $UnallocatedSpace = $WinREPartition.Offset - $OSPartitionEnds; # If there is unallocated space, consider using it if ($UnallocatedSpace -ge 250MB) { $UnallocatedSpace = $WinREPartition.Offset - $OSPartitionEnds; LogMessage("Found unallocated space between OS and WinRE partition: " + $UnallocatedSpace) LogMessage("There is already enough space to extend WinRE partition without shrinking the OS partition") $NeedShrink = $false $targetOSPartitionSize = 0 } else { $shrinkSize = [Math]::Ceiling((250MB - $UnallocatedSpace)/ 1MB) * 1MB if ($shrinkSize > 250MB) { $shrinkSize = 250MB } $targetOSPartitionSize = $OSPartition.Size - $shrinkSize if ($targetOSPartitionSize -lt $supportedSize.SizeMin) { LogMessage("Error: The target OS partition size after shrinking is smaller than the supported minimum size, cannot perform the repartition") exit 1 } } } # ------------------------------------ # Report execution plan and ask for user confirmation to continue # ------------------------------------ # Report the changes planned to be executed, waiting for user confirmation LogMessage("") LogMessage("Summary of proposed changes") if ($NeedCreateNew) { LogMessage("Note: WinRE partition is before OS partition, need to create a new WinRE partition after OS partition") LogMessage("Will shrink OS partition by " + $shrinkSize) LogMessage(" Current OS partition size: " + $OSPartition.Size) LogMessage(" Target OS partition size after shrinking: " + $targetOSPartitionSize) LogMessage("New WinRE partition will be created with size: ", $targetWinREPartitionSize) if ($WinREIsOnSystemPartition) { LogMessage("Existing WinRE partition is also system partition, it will be preserved") } else { LogMessage("Existing WinRE partition will be deleted") LogMessage(" WinRE partition: Disk [" + $OSDiskIndex + "] Partition [" + $WinREPartitionIndex + "]") LogMessage(" Current WinRE partition size: " + $WinREPartitionSizeInfo[0]) } } else { if ($NeedShrink) { LogMessage("Will shrink OS partition by " + $shrinkSize) LogMessage(" Current OS partition size: " + $OSPartition.Size) LogMessage(" Target OS partition size after shrinking: " + $targetOSPartitionSize) if ($UnallocatedSpace -ge 0) { LogMessage("Unallocated space between OS and WinRE partition that will be used towards the new WinRE partition: " + $UnallocatedSpace) } } else { LogMessage("Will use 250MB from unallocated space between OS and WinRE partition") } LogMessage("Will extend WinRE partition size by 250MB") LogMessage(" WinRE partition: Disk [" + $OSDiskIndex + "] Partition [" + $WinREPartitionIndex + "]") LogMessage(" Current WinRE partition size: " + $WinREPartitionSizeInfo[0]) LogMessage(" New WinRE partition size: " + $targetWinREPartitionSize) LogMessage("WinRE will be temporarily disabled before extending the WinRE partition and enabled automatically in the end") if ($UnallocatedSpace -ge 100MB) { LogMessage("Warning: More than 100MB of unallocated space was detected between the OS and WinRE partitions") LogMessage("Would you like to proceed by using the unallocated space between the OS and the WinRE partitions?") } } if ($Needbackup) { LogMessage("") LogMessage("The contents of the old WinRE partition will be backed up to [" + $BackupFolder + "]") } LogMessage("") LogMessage("Please reboot the device before running this script to ensure any pending partition actions are finalized") LogMessage("") if ($SkipConfirmation) { LogMessage("User chose to skip confirmation") LogMessage("Proceeding with changes...") } else { $userInput = Read-Host -Prompt "Would you like to proceed? Y for Yes and N for No" if ($userInput -ieq "Y") { LogMessage("Proceeding with changes...") } elseif ($userInput -ieq "N") { LogMessage("Canceling based on user request, no changes were made to the system") exit 0 } else { LogMessage("Error: Unexpected user input: [" + $userInput + "]") exit 0 } } LogMessage("") LogMessage("Note: To prevent unexpected results, please do not interrupt the execution or restart your system") # ------------------------------------ # Do the actual execution # The main flow is: # 1. Check whether ReAgent.xml has stage location and clear it for repartiion # 2. Disable WinRE as WinRE partition will be deleted # 3. Perform the repartition to create a larger WinRE partition # 4. Re-enable WinRE # ------------------------------------ LogMessage("") # Load ReAgent.xml to clear Stage location LogMessage("Loading [" + $ReAgentXmlPath + "] ...") $xml = [xml](Get-Content -Path $ReAgentXmlPath) $node = $xml.WindowsRE.ImageLocation if (($node.path -eq "") -And ($node.guid -eq "{00000000-0000-0000-0000-000000000000}") -And ($node.offset -eq "0") -And ($node.id -eq "0")) { LogMessage("Stage location info is empty") } else { LogMessage("Clearing stage location info...") $node.path = "" $node.offset = "0" $node.guid= "{00000000-0000-0000-0000-000000000000}" $node.id="0" # Save the change LogMessage("Saving changes to [" + $ReAgentXmlPath + "]...") $xml.Save($ReAgentXmlPath) } # Disable WinRE LogMessage("Disabling WinRE...") reagentc /disable if (!($LASTEXITCODE -eq 0)) { LogMessage("Warning: encountered an error when disabling WinRE: " + $LASTEXITCODE) exit $LASTEXITCODE } # Verify WinRE is under C:\Windows\System32\Recovery\WinRE.wim $disableWinREPath = Join-Path -Path $system32Path -ChildPath "\Recovery\WinRE.wim" LogMessage("Verifying that WinRE wim exists in downlevel at default location") if (!(Test-Path $disableWinREPath)) { LogMessage("Error: Cannot find " + $disableWinREPath) # Re-enable WinRE LogMessage("Re-enabling WinRE on error...") reagentc /enable if (!($LASTEXITCODE -eq 0)) { LogMessage("Warning: encountered an error when enabling WinRE: " + $LASTEXITCODE) } exit 1 } # ------------------------------------ # Perform the repartition # 1. Resize the OS partition # 2. Delete the WinRE partition # 3. Create a new WinRE partition # ------------------------------------ LogMessage("Performing repartition to extend the WinRE partition ...") # 1. Resize the OS partition if ($NeedShrink) { LogMessage("Shrinking the OS partition to create a larger WinRE partition") LogMessage("Resizing the OS partition to: [" + $targetOSPartitionSize + "]...") Resize-Partition -DriveLetter $OSDrive -Size $targetOSPartitionSize if ($Error.Count -gt 0) { LogMessage("Error: Resize-Partition encountered errors: " + $Error[0].Exception.Message) # Re-enable WinRE LogMessage("Re-enabling WinRE on error...") reagentc /enable if (!($LASTEXITCODE -eq 0)) { LogMessage("Warning: encountered an error when enabling WinRE: " + $LASTEXITCODE) } exit 1 } $OSPartitionAfterShrink = Get-Partition -DriveLetter $OSDrive LogMessage("Target partition size: " + $targetOSPartitionSize) LogMessage("Size of OS partition after shrinking: " + $OSPartitionAfterShrink.Size) } # 2. Delete the WinRE partition LogMessage("") if ($WinREIsOnSystemPartition) { LogMessage("Existing WinRE partition is System partition, skipping deletion") } else { # If requested by user, backup rest of the content on WinRE partition to backup directory if ($Needbackup) { $sourcePath = $WinREPartition.AccessPaths[0] LogMessage("Copying content on WinRE partition from [" + $sourcePath + "] to [" + $BackupFolder + "]...") # Copy-Item may have access issue with certain system folders, enumerate the children items and exlcude them $items = Get-ChildItem -LiteralPath $sourcePath -Force foreach ($item in $items) { if ($item.Name -ieq "System Volume Information") { continue } $sourceItemPath = Join-Path -Path $sourcePath -ChildPath $item.Name $destItemPath = Join-Path -Path $BackupFolder -ChildPath $item.Name try { LogMessage("Copying [" + $sourceItemPath + "] to [" + $destItemPath + "]...") Copy-Item -LiteralPath $sourceItemPath -Destination $destItemPath -Recurse -Force } catch { LogMessage("Error: An error occurred during copy: $_") exit 1 } } LogMessage("Backup completed") LogMessage("") } LogMessage("Deleting WinRE partition: Disk [" + $OSDiskIndex + "] Partition [" + $WinREPartitionIndex + "]...") Remove-Partition -DiskNumber $OSDiskIndex -PartitionNumber $WinREPartitionIndex -Confirm:$false if ($Error.Count -gt 0) { LogMessage("Error: Remove-Partition encountered errors: " + $Error[0].Exception.Message) exit 1 } } # A short sleep for the partition change Sleep 5 # 3. Create a new WinRE partition LogMessage("") LogMessage("Creating new WinRE partition...") LogMessage("Target size: " + $targetWinREPartitionSize) if ($diskType -ieq "GPT") { $partition = New-Partition -DiskNumber $OSDiskIndex -Size $targetWinREPartitionSize -GptType "{de94bba4-06d1-4d40-a16a-bfd50179d6ac}" $newPartitionIndex = $partition.PartitionNumber # A short sleep to make sure the partition is ready for formatting Sleep 2 LogMessage("Formating the partition...") $result = Format-Volume -Partition $partition -FileSystem NTFS -Confirm:$false if ($Error.Count -gt 0) { LogMessage("Error: Format-Volume encountered errors: " + $Error[0].Exception.Message) exit 1 } } else { #$partition = New-Partition -DiskNumber $OSDiskIndex -Size $targetWinREPartitionSize -MbrType 0x27 $targetWinREPartitionSizeInMb = [int]($targetWinREPartitionSize/1MB) $diskpartScript = @" select disk $OSDiskIndex create partition primary size=$targetWinREPartitionSizeInMb id=27 format quick fs=ntfs label="Recovery" set id=27 "@ $TempPath = $env:Temp $diskpartSciptFile = Join-Path -Path $TempPath -ChildPath "\ExtendWinRE_MBR_PowershellScript.txt" LogMessage("Creating temporary diskpart script to create Recovery partition on MBR disk...") LogMessage("Temporary diskpart script file: " + $diskpartSciptFile) $diskpartScript | Out-File -FilePath $diskpartSciptFile -Encoding ascii LogMessage("Executing diskpart script...") try { $diskpartOutput = diskpart /s $diskpartSciptFile if ($diskpartOutput -match "DiskPart successfully") { LogMessage("Diskpart script executed successfully") } else { LogMessage("Error executing diskpart script:" + $diskpartOutput) exit 1 } LogMessage("Deleting temporary diskpart script file...") Remove-Item $diskpartSciptFile } catch { LogMessage("Error executing diskpart script: $_") exit 1 } $vol = Get-Volume -FileSystemLabel "Recovery" $newPartitionIndex = (Get-Partition | Where-Object { $_.AccessPaths -contains $vol.Path } ).PartitionNumber } if ($Error.Count -gt 0) { LogMessage("Error: New-Partition encountered errors: " + $Error[0].Exception.Message) exit 1 } LogMessage("New Partition index: " + $newPartitionIndex) # Re-enable WinRE LogMessage("Re-enabling WinRE...") reagentc /enable if (!($LASTEXITCODE -eq 0)) { LogMessage("Warning: encountered an error when enabling WinRE: " + $LASTEXITCODE) exit $LASTEXITCODE } # In the end, Display WinRE status to verify WinRE is enabled correctly LogMessage("") LogMessage("WinRE Information:") $FinalWinREStatus = DisplayWinREStatus $WinREStatus = $FinalWinREStatus[0] $WinRELocation = $FinalWinREStatus[1] if (!$WinREStatus) { LogMessage("Warning: WinRE Disabled") } $WinRELocationItems = $WinRELocation.Split('\\') foreach ($item in $WinRELocationItems) { if ($item -like "partition*") { $WinREPartitionIndex = ExtractNumbers($item) } } LogMessage("WinRE Partition Index: " + $WinREPartitionIndex) $WinREPartition = Get-Partition -DiskNumber $OSDiskIndex -PartitionNumber $WinREPartitionIndex $WinREPartitionSizeInfoAfter = DisplayPartitionInfo($WinREPartition.AccessPaths) LogMessage("") LogMessage("OS Information:") $OSPartition = Get-Partition -DriveLetter $OSDrive LogMessage("OS partition size: " + $OSPartition.Size) LogMessage("OS partition Offset: " + $OSPartition.Offset) if (!($WinREPartitionIndex -eq $newPartitionIndex)) { LogMessage("Warning: WinRE is installed to partition [" + $WinREPartitionIndex +"], but the newly created Recovery partition is [" + $newPartitionIndex + "]") } LogMessage("End time: $([DateTime]::Now)") if ($NeedBackup) { LogMessage("") LogMessage("The contents of the old WinRE partition has been backed up to [" + $BackupFolder + "]") } LogMessage("") LogMessage("Successfully completed the operation") ``` [winre-resizescript for resizewinre-resize.ps120 KBdownload-circle](https://ostreff.info/content/files/2024/05/winre-resize.ps1 "Download") ### How-to create bootable Windows usb flash on MacOS URL: https://ostreff.info/how-to-create-bootable-windows-usb-flash-on-macos/ Last updated: 2024-05-18T11:24:09.000Z First see what disks you have on your MacOS ```bash % diskutil list /dev/disk0 (internal, physical): #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme *500.3 GB disk0 1: EFI EFI 209.7 MB disk0s1 2: Apple_APFS Container disk1 500.1 GB disk0s2 /dev/disk1 (synthesized): #: TYPE NAME SIZE IDENTIFIER 0: APFS Container Scheme - +500.1 GB disk1 Physical Store disk0s2 1: APFS Volume Macintosh HD - Data 208.3 GB disk1s1 2: APFS Volume Preboot 1.5 GB disk1s2 3: APFS Volume Recovery 1.1 GB disk1s3 4: APFS Volume VM 2.1 GB disk1s4 5: APFS Volume Macintosh HD 15.4 GB disk1s5 6: APFS Snapshot com.apple.os.update-... 15.4 GB disk1s5s1 ``` Then insert your USB flash disk and see what is changed executing above command again: ```bash diskutil list ``` Imagine that new disk is received device named `disk2`. Now it's time to format the usb flash drive - my recommendations is your target windows computer is older machine to use `mbr` partitions. To do this use command: ```bash % sudo diskutil eraseDisk FAT32 "WIN10" MBRFormat /dev/disk2 Started erase on disk2 (CCCOMA_X64FRE_BG-BG_DV9) Unmounting disk Creating the partition map Waiting for partitions to activate Formatting disk2s1 as MS-DOS (FAT32) with name WIN10 512 bytes per physical sector /dev/rdisk2s1: 60597312 sectors in 1893666 FAT32 clusters (16384 bytes/cluster) bps=512 spc=32 res=32 nft=2 mid=0xf8 spt=32 hds=255 hid=2048 drv=0x80 bsec=60626944 bspf=14795 rdcl=2 infs=1 bkbs=6 Mounting disk Finished erase on disk2 ``` Then you must download and install on your computer [unetbootin](https://unetbootin.github.io/?ref=ostreff.info). Using it we will write `*.iso` file to the USB disk. ![](https://ostreff.info/content/images/2024/05/image.png) Here is the trickiest part, because on FAT32 filesystem maximum file size is 4G but actually file named `install.wim` which typically is biggest. So you must split it in pieces. Because this is special Microsoft archive file format, you need special application to do this. Download source of [wimlib](https://wimlib.net/?ref=ostreff.info) and uncompress it. Then build and install it using this: ```bash % ./configure --without-ntfs-3g % make % sudo make install ``` Now you can do the following: ```bash % wimlib-imagex split /Volumes/CCCOMA_X64FRE_BG-BG_DV9/sources/install.wim /Volumes/WIN10/sources/install.swm 4000 Splitting WIM: 4614 MiB of 4614 MiB (100%) written, part 2 of 2 Finished splitting "/Volumes/CCCOMA_X64FRE_BG-BG_DV9/sources/install.wim" % rm /Volumes/WIN10/sources/install.wim ``` That's all! ### Ghost CMS - how to set it to use smtp email via postfix URL: https://ostreff.info/ghost-how-to-setting-smtp-email-via-postfix/ Last updated: 2024-05-14T08:07:53.000Z It is really not easy task to set [Ghost CMS](https://ghost.org/?ref=ostreff.info) to use localhost postfix. Actually everything is based on [Nodemailer](https://nodemailer.com/smtp/?ref=ostreff.info) javascript module. Here is my working config: ```bash "mail": { "from": "'Ostreff Ghost Support' ", "transport": "SMTP", "options": { "service": "postfix", "host": "localhost", "port": "587", "secure": false, "secureConnection": false, "ignoreTLS": false, "requireTLS":false, "auth": { "user": "ghost@ostreff.info", "pass": "XXXXXX { hidden } XXXXXX" }, "tls": { "rejectUnauthorized": false, "servername": "mail.ostreff.info" }, "debug": true, "log": true } }, ``` ### Déjà vu URL: https://ostreff.info/deja-vu/ Last updated: 2024-05-09T22:00:26.000Z ![](https://ostreff.info/content/images/2024/05/225441653_10220053583553340_1063418785994713010_n.jpg) ![](https://ostreff.info/content/images/2024/05/225822893_10220053591873548_319900415211652483_n.jpg) ![](https://ostreff.info/content/images/2024/05/226223383_10220053569912999_249242610170903031_n.jpg) ![](https://ostreff.info/content/images/2024/05/226947747_10220053583353335_3469176214988171635_n.jpg) ![](https://ostreff.info/content/images/2024/05/227134290_10220053571553040_7111412805864051677_n.jpg) ![](https://ostreff.info/content/images/2024/05/227144819_10220053575353135_7117891515198851049_n.jpg) ![](https://ostreff.info/content/images/2024/05/227151544_10220053592353560_4604924559985668448_n.jpg) ![](https://ostreff.info/content/images/2024/05/227178606_10220053580633267_5611809167543111509_n.jpg) ![](https://ostreff.info/content/images/2024/05/227190536_10220053576513164_518508243576378552_n.jpg) ![](https://ostreff.info/content/images/2024/05/227466004_10220053586073403_690794504711695592_n.jpg) ![](https://ostreff.info/content/images/2024/05/227466004_10220053587473438_4213814652852054563_n.jpg) ![](https://ostreff.info/content/images/2024/05/227472705_10220053581753295_1200512514520492018_n.jpg) ![](https://ostreff.info/content/images/2024/05/227504482_10220053588953475_8186229837507707307_n.jpg) ![](https://ostreff.info/content/images/2024/05/227514162_10220053592113554_2105895725680088289_n.jpg) ![](https://ostreff.info/content/images/2024/05/227521692_10220053574473113_557821475397459292_n.jpg) ![](https://ostreff.info/content/images/2024/05/227521692_10220053582513314_4443045960384116275_n.jpg) ![](https://ostreff.info/content/images/2024/05/227524969_10220053587993451_1653307077260306542_n.jpg) ![](https://ostreff.info/content/images/2024/05/227528157_10220053588273458_4116182911873609926_n.jpg) ![](https://ostreff.info/content/images/2024/05/227538885_10220053581433287_2169886171780108434_n.jpg) ![](https://ostreff.info/content/images/2024/05/227544806_10220053582793321_1090823284165276822_n.jpg) ![](https://ostreff.info/content/images/2024/05/227544815_10220053577553190_1374024780170330328_n.jpg) ![](https://ostreff.info/content/images/2024/05/227564298_10220053571233032_5716949702772761641_n.jpg) ![](https://ostreff.info/content/images/2024/05/227619894_10220053585913399_2197848754091746931_n.jpg) ![](https://ostreff.info/content/images/2024/05/227624658_10220053570873023_3639915952865409917_n.jpg) ![](https://ostreff.info/content/images/2024/05/227661400_10220053574713119_492629766004424307_n.jpg) ![](https://ostreff.info/content/images/2024/05/227709423_10220053573833097_8693336830168799339_n.jpg) ![](https://ostreff.info/content/images/2024/05/227711926_10220053569712994_1660398321123331807_n.jpg) ![](https://ostreff.info/content/images/2024/05/227757424_10220053584913374_816946839268798298_n.jpg) ![](https://ostreff.info/content/images/2024/05/227775266_10220053575593141_2984461374201289728_n.jpg) ![](https://ostreff.info/content/images/2024/05/227813675_10220053583833347_3551935066957126846_n.jpg) ![](https://ostreff.info/content/images/2024/05/227824443_10220053569392986_1716520487261970958_n.jpg) ![](https://ostreff.info/content/images/2024/05/227837203_10220053571993051_5528161581428993627_n.jpg) ![](https://ostreff.info/content/images/2024/05/227887368_10220053587873448_2571283027028000207_n.jpg) ![](https://ostreff.info/content/images/2024/05/227890888_10220053572633067_53461524266293842_n.jpg) ![](https://ostreff.info/content/images/2024/05/227904349_10220053582153305_1303753721141545946_n.jpg) ![](https://ostreff.info/content/images/2024/05/227914727_10220053579313234_3757468282322167545_n.jpg) ![](https://ostreff.info/content/images/2024/05/227950768_10220053578953225_178170134017619736_n.jpg) ![](https://ostreff.info/content/images/2024/05/227983317_10220053573113079_2429085452820872537_n.jpg) ![](https://ostreff.info/content/images/2024/05/227986927_10220053586473413_1179052582396061474_n.jpg) ![](https://ostreff.info/content/images/2024/05/228017254_10220053575753145_2055649190746020232_n.jpg) ![](https://ostreff.info/content/images/2024/05/228029021_10220053586833422_7294716148159618183_n.jpg) ![](https://ostreff.info/content/images/2024/05/228054292_10220053586993426_6129106569580989734_n.jpg) ![](https://ostreff.info/content/images/2024/05/228068921_10220053571433037_798278288005892960_n.jpg) ![](https://ostreff.info/content/images/2024/05/228071635_10220053572833072_5234170124661014852_n.jpg) ![](https://ostreff.info/content/images/2024/05/228091191_10220053576993176_4578324081883973324_n.jpg) ![](https://ostreff.info/content/images/2024/05/228100118_10220053578433212_7467944366916112665_n.jpg) ![](https://ostreff.info/content/images/2024/05/228118942_10220053569192981_8161221854694473497_n.jpg) ![](https://ostreff.info/content/images/2024/05/228129999_10220053590873523_3270661948396155608_n.jpg) ![](https://ostreff.info/content/images/2024/05/228139485_10220053585113379_2098401554860082471_n.jpg) ![](https://ostreff.info/content/images/2024/05/228307442_10220053585473388_4504989690438835610_n.jpg) ![](https://ostreff.info/content/images/2024/05/228310163_10220053589353485_2333323843173747910_n.jpg) ![](https://ostreff.info/content/images/2024/05/228315318_10220053581153280_8298522030254925075_n.jpg) ![](https://ostreff.info/content/images/2024/05/228537163_10220053590033502_8361145718086981314_n.jpg) ![](https://ostreff.info/content/images/2024/05/228543757_10220053591113529_1870854483065414286_n.jpg) ![](https://ostreff.info/content/images/2024/05/228576492_10220053590233507_4565231972769153931_n.jpg) ![](https://ostreff.info/content/images/2024/05/228599863_10220053573273083_8352959800369862697_n.jpg) ![](https://ostreff.info/content/images/2024/05/228660386_10220053577513189_7426197763556246086_n.jpg) ![](https://ostreff.info/content/images/2024/05/228696405_10220053584193356_1699578577995947118_n.jpg) ![](https://ostreff.info/content/images/2024/05/228738732_10220053591313534_1858532824937113219_n.jpg) ![](https://ostreff.info/content/images/2024/05/228762388_10220053576753170_1560746080256983272_n.jpg) ![](https://ostreff.info/content/images/2024/05/228804155_10220053577833197_9067378917625575120_n.jpg) ![](https://ostreff.info/content/images/2024/05/228833742_10220053589993501_284331859458240431_n.jpg) ![](https://ostreff.info/content/images/2024/05/228929585_10220053580433262_7746341879619762215_n.jpg) ![](https://ostreff.info/content/images/2024/05/229272577_10220053584473363_2616835609924064610_n.jpg) ![](https://ostreff.info/content/images/2024/05/242323219_10220296799273581_5156460411918604276_n.jpg) ![](https://ostreff.info/content/images/2024/05/243410447_10220336001653616_2688649899074991484_n.jpg) ![](https://ostreff.info/content/images/2024/05/243503297_10220347068450279_1948341083456616764_n.jpg) ![](https://ostreff.info/content/images/2024/05/244197047_10220353115761458_8430395153990726787_n.jpg) ![](https://ostreff.info/content/images/2024/05/272620492_10220918138646677_2672878759472016314_n.jpg) ![](https://ostreff.info/content/images/2024/05/277241293_10221191220673557_5634778500710906095_n.jpg) ![](https://ostreff.info/content/images/2024/05/277538722_10221202184787653_7040434877061151768_n.jpg) ![](https://ostreff.info/content/images/2024/05/277745590_10221217878179978_216028727171758679_n.jpg) ![](https://ostreff.info/content/images/2024/05/277807640_10221217896380433_2018460759776405006_n.jpg) ![](https://ostreff.info/content/images/2024/05/283465981_10221454865904523_7395253767393095072_n.jpg) ![](https://ostreff.info/content/images/2024/05/288119069_10221581789557535_5551777249388459429_n.jpg) ![](https://ostreff.info/content/images/2024/05/317554615_10222481790897006_9004454648246044518_n.jpg) ![](https://ostreff.info/content/images/2024/05/347411513_10223658535194878_6977399273700868921_n.jpg) ![](https://ostreff.info/content/images/2024/05/378388561_10224328544344688_6919559871229729733_n.jpg) ![](https://ostreff.info/content/images/2024/05/400900156_10224575476157829_7514940082846868619_n.jpg) ![](https://ostreff.info/content/images/2024/05/408157382_10224666127064045_2529382359872623444_n.jpg) ![](https://ostreff.info/content/images/2024/05/438165585_10225285291262763_7923494484978037118_n-1.jpg) ### Свети великомъченик Георги Победоносец URL: https://ostreff.info/svieti-vielikomchienik-gieorghi-pobiedonosiets/ Last updated: 2024-05-06T13:50:00.000Z **Свети великомъченик Георги Победоносец** ([275](https://bg.wikipedia.org/wiki/275?ref=ostreff.info) – [281](https://bg.wikipedia.org/wiki/281?ref=ostreff.info) г. – [23 април](https://bg.wikipedia.org/wiki/23%5F%D0%B0%D0%BF%D1%80%D0%B8%D0%BB?ref=ostreff.info) [303](https://bg.wikipedia.org/wiki/303?ref=ostreff.info) г.) е един от най-почитаните светци, [мъченик за Христовата вяра](https://bg.wikipedia.org/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%8F:%D0%A5%D1%80%D0%B8%D1%81%D1%82%D0%B8%D1%8F%D0%BD%D1%81%D0%BA%D0%B8%5F%D0%BC%D1%8A%D1%87%D0%B5%D0%BD%D0%B8%D1%86%D0%B8%5F%D0%BE%D1%82%5F%D0%A0%D0%B8%D0%BC%D1%81%D0%BA%D0%B0%D1%82%D0%B0%5F%D0%B5%D0%BF%D0%BE%D1%85%D0%B0?ref=ostreff.info), убит по време на [Диоклециановите гонения](https://bg.wikipedia.org/wiki/%D0%94%D0%B8%D0%BE%D0%BA%D0%BB%D0%B5%D1%86%D0%B8%D0%B0%D0%BD%D0%BE%D0%B2%D0%B8%5F%D0%B3%D0%BE%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F?ref=ostreff.info). [Мощите](https://bg.wikipedia.org/wiki/%D0%9C%D0%BE%D1%89%D0%B8?ref=ostreff.info) на свети Георги са погребани в град Лида/[Диосполис](https://bg.wikipedia.org/w/index.php?title=%D0%94%D0%B8%D0%BE%D1%81%D0%BF%D0%BE%D0%BB%D0%B8%D1%81&action=edit&redlink=1&ref=ostreff.info) в римската провинция Палестина, част от реформираната [Сирия Палестина](https://bg.wikipedia.org/wiki/%D0%A1%D0%B8%D1%80%D0%B8%D1%8F%5F%D0%9F%D0%B0%D0%BB%D0%B5%D1%81%D1%82%D0%B8%D0%BD%D0%B0?ref=ostreff.info) (дн. [Лод](https://bg.wikipedia.org/w/index.php?title=%D0%9B%D0%BE%D0%B4&action=edit&redlink=1&ref=ostreff.info), [Израел](https://bg.wikipedia.org/wiki/%D0%98%D0%B7%D1%80%D0%B0%D0%B5%D0%BB?ref=ostreff.info)), в от който град е майка му[\[3\]](https://bg.wikipedia.org/wiki/%D0%93%D0%B5%D0%BE%D1%80%D0%B3%D0%B8%5F%D0%9F%D0%BE%D0%B1%D0%B5%D0%B4%D0%BE%D0%BD%D0%BE%D1%81%D0%B5%D1%86?ref=ostreff.info#cite%5Fnote-:0-3) и където живее известно време, след смъртта на баща си[\[3\]](https://bg.wikipedia.org/wiki/%D0%93%D0%B5%D0%BE%D1%80%D0%B3%D0%B8%5F%D0%9F%D0%BE%D0%B1%D0%B5%D0%B4%D0%BE%D0%BD%D0%BE%D1%81%D0%B5%D1%86?ref=ostreff.info#cite%5Fnote-:0-3). Скоро се прочува, чрез множество чудеса, които извършва след смъртта си, като небесен воин и закрилник. ![](https://ostreff.info/content/images/2024/05/31957597_10214638744455766_3687374773899231232_n-1.jpg) ![](https://ostreff.info/content/images/2024/05/Agios_Georgios_icon-1.jpg) ![](https://ostreff.info/content/images/2024/05/Icon8.jpg) ![](https://ostreff.info/content/images/2024/05/La_tomba_di_San_Giorgio_-Lod-_Israele-_02.JPG) ### News URL: https://ostreff.info/mattermost/ Last updated: 2025-02-22T10:32:18.000Z [Mattermost](https://mattermost.com/?ref=ostreff.info) slack was recovered again on this [url](https://slack.ostreff.info/?ref=ostreff.info). [OCIS](https://owncloud.dev/ocis/?ref=ostreff.info) ownCloud Infinite Scale is hosted [here](https://owncloud.ostreff.info/?ref=ostreff.info). Interesting web site is hosted also [here](https://realesr.ostreff.info/?ref=ostreff.info). ### Easter URL: https://ostreff.info/easter/ Last updated: 2024-05-06T11:56:10.000Z ![](https://ostreff.info/content/images/2024/05/IMG_8437.JPG) ![](https://ostreff.info/content/images/2024/05/easter-wish-1.jpg) ![](https://ostreff.info/content/images/2024/05/easter-dates-2021-1610031842.jpg) ### DOS 4.0 src URL: https://ostreff.info/dos-4-0-src/ Last updated: 2024-05-06T05:42:41.000Z [MS-DOS/v4.0 at main · microsoft/MS-DOSThe original sources of MS-DOS 1.25, 2.0, and 4.0 for reference purposes - microsoft/MS-DOS![](https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg)GitHubmicrosoft![](https://opengraph.githubassets.com/1d9957f4820d258a1faa1045ba77ccd05dc1640aadbd3cfc7a85a0d4bfc0946f/microsoft/MS-DOS)](https://github.com/microsoft/MS-DOS/tree/main/v4.0?ref=ostreff.info) ### Dnipro 20/04/2024 URL: https://ostreff.info/dnipro/ Last updated: 2024-05-06T05:56:32.000Z _This post is for subscribers only._ ### Happy 2024 Year URL: https://ostreff.info/happy-2024-year/ Last updated: 2024-01-01T11:28:17.000Z ![](https://ostreff.info/content/images/2024/01/orig-2.jpg) ![](https://ostreff.info/content/images/2024/01/orig-10.jpg) ![](https://ostreff.info/content/images/2024/01/orig-9.jpg) ![](https://ostreff.info/content/images/2024/01/orig-8.jpg) ![](https://ostreff.info/content/images/2024/01/orig-7.jpg) ![](https://ostreff.info/content/images/2024/01/orig-6.jpg) ![](https://ostreff.info/content/images/2024/01/orig-5.jpg) ![](https://ostreff.info/content/images/2024/01/orig-4.jpg) ![](https://ostreff.info/content/images/2024/01/orig-3.jpg) ### Caddy security recommendation URL: https://ostreff.info/ghost-recommendation/ Last updated: 2025-02-22T10:33:50.000Z Use security/portacl-rc to enable privileged port binding: ```bash # portmaster security/portacl-rc # sysrc portacl_users+=www # sysrc portacl_user_www_tcp="http https" # sysrc portacl_user_www_udp="https" # service portacl enable # service portacl start ``` Configure caddy to run as www:www ```bash # sysrc caddy_user=www caddy_group=www ``` Note if Caddy has been started as root previously, files in /var/log/caddy, /var/db/caddy, and /var/run/caddy may require their ownership changing manually. ```bash # chown -R www:www /var/log/caddy && chown -R www:www /var/db/caddy && chown -R www:www /var/run/caddy ``` ### HTTP/3 test passed URL: https://ostreff.info/http-3-pass/ Last updated: 2024-05-06T05:44:42.000Z [HTTP/3 Check - ostreff.infoIs my site using HTTP3? Check HTTP3 service on any URL. Test QUIC service, too.![](https://http3check.net/static/favicon-32x32.png)ostreff.info](https://http3check.net/?host=ostreff.info&ref=ostreff.info) ### ZFS snapshots URL: https://ostreff.info/zfs-snapshots/ Last updated: 2024-05-06T05:49:17.000Z After long usage of zfs file system, probably you will finish like me with too much space ocupied by snapshots. Quick way to free this space is to use commands like this: ``` zfs list -H -o name -t snapshot | xargs -n1 zfs destroy -R ``` ### MySQL om ZFS URL: https://ostreff.info/mysql-om-zfs/ Last updated: 2023-07-05T19:57:24.000Z It's highly recommended to provide special containers for [MySQL](https://www.mysql.com/?ref=ostreff.info) logs and database. Personally I'm doing somethink like this, just before start database for the first time: ``` zfs create zroot/var/mysql-db ; \ zfs create zroot/var/mysql-log ; \ zfs set recordsize=16k zroot/var/mysql-db ; \ zfs set atime=off zroot/var/mysql-db ; \ zfs set compression=off zroot/var/mysql-db ; \ zfs set mountpoint=/var/db/mysql zroot/var/mysql-db ; \ zfs set mountpoint=/var/log/mysql zroot/var/mysql-log ; ``` Don't forget to also include following 2 [mysql](https://mariadb.com/?ref=ostreff.info) options ``` # innodb_doublewrite = 0 # innodb_flush_method = O_DSYNC ``` ### Conversion PFX to PEM certificate suitable for HAproxy URL: https://ostreff.info/conversion-pfx-to-pem-suitable-for-haproxy/ Last updated: 2023-07-05T21:44:54.000Z If you are maintaining such [haproxy](https://www.haproxy.org/?ref=ostreff.info) [ssl offloading](https://www.haproxy.com/blog/haproxy-ssl-termination?ref=ostreff.info), probably this quick'n'dirty script will be useful for you. ``` !#/bin/sh openssl pkcs12 -in $1.pfx -out $1_nokey.pem -nokeys && \ openssl pkcs12 -in $1.pfx -out $1_withkey.pem && \ openssl rsa -in $1_withkey.pem -out $1.key && \ cat $1_nokey.pem $1.key > $1.pem ``` ### Report responds to the COVID-19 Origin Act of 2023 URL: https://ostreff.info/report-responds-to-the-covid-19-origin-act-of-2023/ Last updated: 2024-11-12T14:33:27.000Z [ Report on Potential Links Between the Wuhan Institute of Virology and the Origins of COVID 19 20230623 Report-on-Potential-Links-Between-the-Wuhan-Institute-of-Virology-and-the-Origins-of-COVID-19-20230623.pdf 974 KB download-circle ](https://ostreff.info/content/files/2023/06/Report-on-Potential-Links-Between-the-Wuhan-Institute-of-Virology-and-the-Origins-of-COVID-19-20230623.pdf "Download") Official source is [here](https://www.odni.gov/files/ODNI/documents/assessments/Report-on-Potential-Links-Between-the-Wuhan-Institute-of-Virology-and-the-Origins-of-COVID-19-20230623.pdf?ref=ostreff.info) on OFFICE *of the* DIRECTOR *of* NATIONAL INTELLIGENCE of the USA. ### July 25, 1946 The Baker Trial URL: https://ostreff.info/july-25-1946-the-baker-trial/ Last updated: 2023-06-18T13:55:17.000Z _No content available._ ### Български за рускоговорящи - аудио, книги + тест (Болгарский для русскоговорящих аудио, книги + ТЕСТЫ) URL: https://ostreff.info/bg4ru/ Last updated: 2023-07-24T10:25:19.000Z [С електронните книги на „Каритас“, ученето на български език е удобно и лесно | CaritasОбучителните материали са подходящи както за хора, които в момента изучават български език, така и за завършилите курсове и желаещи да усъвършенстват своето произношение.![](https://caritas.bg/cms/wp-content/uploads/2017/06/favicon.ico?x36115)Caritasvanya klecherova![](https://caritas.bg/cms/wp-content/uploads/2021/08/safe_image.php_-1-600x547.jpg)](https://caritas.bg/novini/novini-bezhantsi/elektronni-knigi-balgarski-ezik/?ref=ostreff.info) Учебник за покриване на ниво B1 предоставен от "[Български червен кръст](https://www.redcross.bg/files/914-bulgarian.pdf?ref=ostreff.info)" и създаден от [UNHCR](https://www.unhcr.org/bg/%D1%83%D1%87%D0%B5%D0%B1%D0%BD%D0%B8-%D0%BF%D0%BE%D0%BC%D0%B0%D0%B3%D0%B0%D0%BB%D0%B0?ref=ostreff.info) ( The UN Refugee Agency ): [ 914 bulgarian Упражнения за новата лексика, езиковите функции и граматиката;. • Текстове. Учебното помагало има сп 914-bulgarian.pdf 6 MB download-circle ](https://ostreff.info/content/files/2023/07/914-bulgarian.pdf "Download") [ 914 english english version 914-english.pdf 718 KB download-circle ](https://ostreff.info/content/files/2023/07/914-english.pdf "Download") Това е безплатният материал към учебника ["Учим болгарский легко"](https://www.book.store.bg/p104969/uchim-bolgarskij-legko.html?ref=ostreff.info). Точният линк към източника е [тук](https://www.book.store.bg/ctfile/42/uchim-bolgarskij-legko.rar?ref=ostreff.info). Постарал съм се да го препубликувам във вид, който да е удобен за прослушване от устройства нямащи достатъчно памет, но разполагащи с връзка към интернет и броузър - т.е. дори и от мобилен телефон да могат да се прослушват уроците към книгата. Это бесплатный материал из учебника "[Учим болгарский легко](https://www.book.store.bg/p104969/uchim-bolgarskij-legko.html?ref=ostreff.info)". Точная ссылка на источник [здесь](https://www.book.store.bg/ctfile/42/uchim-bolgarskij-legko.rar?ref=ostreff.info). Я постарался переиздать его в виде, удобном для прослушивания на устройствах, не обладающих достаточным объемом памяти, но имеющих подключение к Интернету и браузер - т.е. уроки книги можно слушать с мобильного телефона. Диск 1: 01 Track 1 0:00 /0:36 1× 02 Track 2 0:00 /2:42 1× 03 Track 3 0:00 /5:24 1× 04 Track 4 0:00 /1:15 1× 05 Track 5 0:00 /1:55 1× 06 Track 6 0:00 /1:05 1× 07 Track 7 0:00 /2:14 1× 08 Track 8 0:00 /1:48 1× 09 Track 9 0:00 /0:38 1× 10 Track 10 0:00 /0:47 1× 11 Track 11 0:00 /1:19 1× 12 Track 12 0:00 /0:47 1× 13 Track 13 0:00 /1:24 1× 14 Track 14 0:00 /1:49 1× 15 Track 15 0:00 /1:24 1× 16 Track 16 0:00 /1:09 1× 17 Track 17 0:00 /1:48 1× 18 Track 18 0:00 /4:10 1× 19 Track 19 0:00 /1:16 1× 20 Track 20 0:00 /1:22 1× 21 Track 21 0:00 /1:13 1× 22 Track 22 0:00 /1:28 1× Диск 2: 01 Track 1 0:00 /5:00 1× 02 Track 2 0:00 /0:33 1× 03 Track 3 0:00 /0:59 1× 04 Track 4 0:00 /1:31 1× 05 Track 5 0:00 /1:38 1× 06 Track 6 0:00 /1:49 1× 07 Track 7 0:00 /0:43 1× 08 Track 8 0:00 /1:41 1× 09 Track 9 0:00 /0:47 1× 10 Track 10 0:00 /0:47 1× 11 Track 11 0:00 /2:30 1× 12 Track 12 0:00 /1:45 1× 13 Track 13 0:00 /1:18 1× 14 Track 14 0:00 /1:29 1× 15 Track 15 0:00 /1:21 1× 16 Track 16 0:00 /0:53 1× 17 Track 17 0:00 /2:27 1× 18 Track 18 0:00 /0:53 1× 19 Track 19 0:00 /1:27 1× 20 Track 20 0:00 /0:36 1× 21 Track 21 0:00 /2:01 1× 22 Track 22 0:00 /0:36 1× 23 Track 23 0:00 /2:01 1× 24 Track 24 0:00 /0:42 1× 25 Track 25 0:00 /1:42 1× 26 Track 26 0:00 /0:54 1× 27 Track 27 0:00 /0:30 1× 28 Track 28 0:00 /1:15 1× 29 Track 29 0:00 /1:43 1× 30 Track 30 0:00 /2:01 1× 31 Track 31 0:00 /1:10 1× 32 Track 32 0:00 /2:26 1× 33 Track 33 0:00 /0:41 1× 34 Track 34 0:00 /1:09 1× 35 Track 35 0:00 /1:56 1× 36 Track 36 0:00 /2:05 1× 37 Track 37 0:00 /1:06 1× 38 Track 38 0:00 /0:24 1× 39 Track 39 0:00 /2:22 1× 40 Track 40 0:00 /3:30 1× 41 Track 41 0:00 /4:23 1× 42 Track 42 0:00 /1:02 1× 43 Track 43 0:00 /1:16 1× Диск 3: 01 Track 1 0:00 /4:10 1× 02 Track 2 0:00 /3:24 1× 03 Track 3 0:00 /2:42 1× 04 Track 4 0:00 /0:55 1× 05 Track 5 0:00 /2:34 1× 06 Track 6 0:00 /1:09 1× 07 Track 7 0:00 /3:39 1× 08 Track 8 0:00 /3:00 1× 09 Track 9 0:00 /2:58 1× 10 Track 10 0:00 /3:08 1× 11 Track 11 0:00 /0:45 1× 12 Track 12 0:00 /2:28 1× 13 Track 13 0:00 /0:52 1× 14 Track 14 0:00 /0:30 1× 15 Track 15 0:00 /3:40 1× 16 Track 16 0:00 /3:56 1× 17 Track 17 0:00 /2:05 1× 18 Track 18 0:00 /2:17 1× Диск 4: 01 Track 1 0:00 /3:06 1× 02 Track 2 0:00 /2:11 1× 03 Track 3 0:00 /2:35 1× 04 Track 4 0:00 /2:28 1× 05 Track 5 0:00 /2:16 1× 06 Track 6 0:00 /2:58 1× 07 Track 7 0:00 /1:31 1× 08 Track 8 0:00 /1:27 1× 09 Track 9 0:00 /4:02 1× 10 Track 10 0:00 /2:14 1× 11 Track 11 0:00 /1:25 1× 12 Track 12 0:00 /1:16 1× 13 Track 13 0:00 /1:43 1× 14 Track 14 0:00 /3:20 1× 15 Track 15 0:00 /1:36 1× 16 Track 16 0:00 /1:32 1× 17 Track 17 0:00 /1:13 1× Други полезни учебници според мен са ( Другими полезными учебниками, на мой взгляд, являются ): - [Български език и граматика за чужденци Болгарский язык и грамматика для иностранцев](https://www.book.store.bg/p256775/bylgarski-ezik-i-gramatika-za-chuzhdenci-bolgarskij-iazuik-i-grammatika-dlia-inostrancev.html?ref=ostreff.info) - [Добре дошли в България - ниво 1: Учебник по български език за рускоговорещи бежанци](https://www.book.store.bg/p342329/dobre-doshli-v-bylgaria-nivo-1-uchebnik-po-bylgarski-ezik-za-ruskogovoreshti-bezhanci.html?ref=ostreff.info) - [Здравейте! - Учебник по български за чужденци (ниво A1 - A2) - част 1 + CD](https://www.book.store.bg/p106605/zdravejte-uchebnik-po-bylgarski-za-chuzhdenci-nivo-a1-a2-chast-1-cd-galina-kurteva-krystinka-bumbarova-stefka-bychvarova.html?ref=ostreff.info) - [Здравейте! - Учебник по български за чужденци (ниво B1 - B2) - част 2 + CD](https://www.book.store.bg/p204192/zdravejte-uchebnik-po-bylgarski-za-chuzhdenci-nivo-b1-b2-chast-2-cd.html?ref=ostreff.info) - [Здравейте! - Учебник по български за чужденци (ниво C1 - C1+) - част 3 + USB flash памет](https://www.book.store.bg/p315622/zdravejte-uchebnik-po-bylgarski-za-chuzhdenci-nivo-c1-c1-chast-3.html?ref=ostreff.info) --- ## Учебник болгарского языка для иностранцев ### Гинина, Николова, Сакызова издателство Наука и изкуство -София 1972 Още един учебник по български език. Намерих го в интернет, надявам се че заради изминалите 50 години от издаването му, не нарушавам авторските права. ( Еще один учебник болгарского языка. Я нашел его в Интернете, я надеюсь, что за 50 лет, прошедших с момента его публикации, я не нарушаю авторские права. ) [ Ginina Bolgarsky Dlya Inostrantsev Гинина, Николова, Сакызова, Учебник болгарского языка для иностранцев 1972 Ginina\_-\_Bolgarsky\_Dlya\_Inostrantsev.pdf 14 MB download-circle ](https://ostreff.info/content/files/2023/07/Ginina%5F-%5FBolgarsky%5FDlya%5FInostrantsev.pdf "Download") ## ****ТЕСТОВЕ ЗА САМОПРОВЕРКА НА СТЕПЕНТА НА ВЛАДЕЕНЕ НА БЪЛГАРСКИ ЕЗИК** ### ТЕСТЫ ДЛЯ САМОПРОВЕРКИ СТЕПЕНИ ЗНАНИЯ БОЛГАРСКОГО ЯЗЫКА ![](https://ostreff.info/content/images/2023/06/----.-----1-1536x610-2.png) Благодарение на публикуваните безплатни тестове от [Департамент за езикова и специализирана подготовка на чуждестранни студенти](https://blogs.uni-plovdiv.net/despchs/%D1%82%D0%B5%D1%81%D1%82-%D0%B7%D0%B0-%D0%B2%D1%85%D0%BE%D0%B4%D0%BD%D0%BE-%D0%BD%D0%B8%D0%B2%D0%BE/?ref=ostreff.info) можете да си проверите нивото така: (Благодаря бесплатным тестам, опубликованным Департаментом языковой и специальной подготовки иностранных студентов, вы можете проверить свой уровень следующим образом:) [А2](https://docs.google.com/forms/d/e/1FAIpQLSfGYKdxOIe3ZiGVx%5Fug5tLWUnWRhsjpDj28TMhwGI7as5ki2w/viewform?usp=sf%5Flink&ref=ostreff.info) [B1](https://docs.google.com/forms/d/e/1FAIpQLScszTEYcnj6%5FfyKh0s3cwI30Zsxj0ZIcIuGaz9IdaJwTSTFQA/viewform?usp=sf%5Flink&ref=ostreff.info) [B2](https://docs.google.com/forms/d/e/1FAIpQLSedrOFgUwYfg2r1ZF4M1Fvf0YCQWJJDkIInpnhw9CytzqM2mA/viewform?usp=sf%5Flink&ref=ostreff.info) ### Инструкции за даване на хапче на котка URL: https://ostreff.info/instructions_for_cat_lovers/ Last updated: 2023-03-21T10:34:08.000Z Вземете котката и я сложете да легне върху лявата си ръка както се държи бебе. Поставете десния си палец и показалец от двете страни на устата на котката и притиснете бузите й, докато държите хапчето в дясната си ръка. Когато котката отвори уста, хвърлете хапчето вътре. Позволете на котката да затвори уста и да преглътне. Приберете хапчето от пода и котката иззад дивана. Сложете котката върху лявата си ръка и повторете процедурата. Измъкнете котката от спалнята и изхвърлете олигавеното хапче. Вземете ново хапче от опаковката и сложете котката да легне върху лявата си ръка, държейки здраво задните й лапи с нея. Отворете челюстите й и натъпчете хапчето в гърлото й с десния показалец. Дръжте устата на котката затворена поне 10 секунди. Извадете хапчето от аквариума и свалете котката от гардероба. Извикайте съпруга/съпругата си от двора. Клекнете на пода, така че котката да е приклещена между коленете ви, и дръжте здраво предните и задни й лапи. Не обръщайте внимание на гърленото ръмжене. Накарайте съпруга/съпругата да хване здраво главата на котката с едната ръка и да напъха дървена линийка в устата й с другата. Пуснете хапчето по линийката и разтъркайте енергично гърлото на котката. Свалете котката от корниза и вземете ново хапче от опаковката. Отбележете си да купите нов корниз и пердета. Внимателно съберете парчетата от вазата и ги отделете настрана, за да ги залепите по-късно. Увийте котката в пешкир и накарайте съпруга/съпругата си да притисне животинчето по такъв начин, че единствено главата му да се показва изпод мишницата му/й. Вкарайте хапчето в единия край на сламка, отворете устата на котката с молив и духнете хапчето в устата й от сламката. Прочетете листовката на хапчето, за да се уверите, че не е вредно за хора и изпийте чаша вода или сок, за да си подобрите вкуса. Превържете ръката на съпруга/съпругата си с лейкопласт и почистете кръвта от килима със студена вода и сапун. Приберете си котката от бараката на съседа. Вземете друго хапче. Вкарайте котката в долапа и затворете вратата върху врата й, така че главата й да остане отвън. Отворете устата на котката с лъжичка. Изстреляйте хапчето в гърлото й с ластик. Вземете инструменти и монтирайте обратно вратата на долапа. Сложете студен компрес на бузата си, като междувременно проверите актуалността на ваксинацията си против тетанус в медицинския си картон. Изхвърлете съсипаната фанелка и си вземете нова от спалнята. Повикайте пожарната да свали котката от дървото от другата страна на пътя. Извинете се на съседа си, който се блъсна с колата в оградата в опит да избегне търчащата котка. Вземете последното хапче от опаковката. Вържете предните лапи на котката за задните с помощта на "свинска опашка", след което я вържете за кухненската маса по същия начин. Вземете работните ръкавици от бараката. Отворете устата на котката с клещите за зегерки. Натъпчете хапчето в устата на котката, след което и половин пържола. След това, държейки главата й вертикално, излейте поне 100 грама вода в гърлото й, за да сте сигурни, че е преглътнала хапчето. Накарайте съпруга/съпругата си да ви закара до Пирогов. Стойте мирно, докато докторите ви зашият пръстите и извадят остатъците от хапчето от дясното ви око. На връщане към къщи минете през магазин за мебели и си поръчайте нова маса. Обадете се на ветеринаря и си запишете час за домашно посещение. Бонус - Инструкция за къпане на котка ![](https://ostreff.info/content/images/2023/03/fun24.org-2021-06-29_09-24-38_247571.jpg) ### Cisco Live 2023 URL: https://ostreff.info/cisco-live-2022/ Last updated: 2023-06-10T22:58:15.000Z Amsterdam Feb 6-10 - Attend Online Join online for the[ live broadcast](https://www.ciscolive.com/emea/attend-online.html?ref=ostreff.info) from Cisco Live 2023 Amsterdam – three days of inspiration, learning and fun. Save the date for February 7-9. ### Guest Access Disabled By Default In Windows 10 for SMB2 protocol URL: https://ostreff.info/guest-access-disabled-by-default-in-windows-10-for-smb2/ Last updated: 2024-05-06T05:46:27.000Z In Windows 10 after version 1709, Windows Server also after version 1709, the SMB2 client no longer allows the following actions: - Guest account access to a remote server - Fallback to the Guest account after invalid credentials are provided SMBv2 has the following behavior in these versions of Windows: - Windows 10 Enterprise and Windows 10 Education no longer allow a user to connect to a remote share by using guest credentials by default, even if the remote server requests guest credentials. - Windows Server 2016 Datacenter and Standard edition no longer allow a user to connect to a remote share by using guest credentials by default, even if the remote server requests guest credentials. - Windows 10 Home and Professional editions are unchanged from their previous default behavior. ### Solution 1 Re-Enable the guest account fallback - Microsoft are updating his [manual](https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/guest-access-in-smb2-is-disabled-by-default?ref=ostreff.info) periodically. In general idea is 1. Go into Local Group Policy Editor 2. Navigate into Administrative Templates - Network - Lanman Workstation. 3. Then the Setting : Enable insecure guest logons has to be set to "Enable". 4. Enable : Allow guest usage and solve the problem 5. Disable : Will not allow guest usage and provide this problem. ![enable insecure guest logons](https://ostreff.info/content/images/2023/01/image2019-8-1_17-26-4.png) ### Solution 2 Change the Samba "map to guest" parameter into configuration file ( smb.conf or smb4.conf ). Standard setting by default is "map to guest = bad user". This setting sets a guest session flag during initial SMB tree connect, and the listed Windows versions can end up failing to establish a session. When changed to "map to guest = Never", then instead of silently dropping the connection, the Windows client prompts for a password. - reload Samba : service smb restart ### zpool migrations URL: https://ostreff.info/zpool/ Last updated: 2023-01-27T14:51:56.000Z _This post is for subscribers only._ ### Happy New Year URL: https://ostreff.info/happy-new-year/ Last updated: 2023-01-01T11:12:08.000Z ![](https://ostreff.info/content/images/2023/01/322181071_1565152397286206_5382030852206402699_n.jpg) ### Merry Christmas! URL: https://ostreff.info/merry-christmas-2-2/ Last updated: 2022-12-25T09:08:14.000Z ![](https://ostreff.info/content/images/2022/12/FB_IMG_1671870813318.jpg) ![](https://ostreff.info/content/images/2022/12/merry-christmas-european-languages.jpg) ### Functions utf8_encode and utf8_decode are Deprecated in PHP 8.2 URL: https://ostreff.info/functions-utf8_encode-and-utf8_decode-are-deprecated-in-php-8-2/ Last updated: 2022-12-23T14:29:37.000Z `utf8_encode` and `utf8_decode` functions, despite their names, are used to convert strings between ISO-8859-1 (Also known as "Latin 1") and UTF-8 encodings. These functions do *not* attempt to detect the actual character encoding in a given text, and always convert character encodings between ISO-8859-1 and UTF-8, even if the source text is *not* encoded in ISO-8859-1. Although PHP includes `utf8_encode` and `utf8_decode` functions in its standard library, these functions cannot be used to detect and convert other character encodings such as Windows-1252, UTF-16, and UTF-32 to UTF-8\. Passing arbitrary text to `utf8_encode` function is prone to bugs that do not result in any warnings or errors but may lead to undesired results. Because of the misleading function names, lack of error messages and warnings, and the lack of support for character encodings other than ISO-8859-1, ****`utf8_encode` and `utf8_decode` functions are deprecated in [PHP 8.2](https://php.watch/versions/8.2?ref=ostreff.info)**. ```bash UTF-8 $iso88591 = utf8_decode($utf8); // UTF-8 -> ISO-8859-1 Deprecated: Function utf8_encode() is deprecated in main.php on line 3 Deprecated: Function utf8_decode() is deprecated in main.php on line 4 ``` Better replacement is to use syntax: ```bash UTF-8 $iso88591 = mb_convert_encoding($utf8, 'ISO-8859-1', 'UTF-8'); // UTF-8 -> ISO-8859-1 ``` ### How to make your Synology Router perform as Apple Time Capsule compatible device URL: https://ostreff.info/how-to-make-your-synology-router-timecapsule-compatible/ Last updated: 2022-12-11T08:47:50.000Z Once you have found a suitable external USB drive compatible with usb 3.2/type A connector. You connect it to your router, and you're surprised to see that the [SRM OS](https://www.synology.com/en-global/srm?ref=ostreff.info) on device says you can't use that disk. You go to the [synology](https://www.synology.com/?ref=ostreff.info) website to check if there is a [compatibility list](https://www.synology.com/en-eu/compatibility?ref=ostreff.info) and you are unpleasantly surprised by the fact that it is very poor. Of course, the external drive you have already purchased is not on the list. What next? ![](https://ostreff.info/content/images/2022/12/srm13-1.jpeg) Connect the drive to your computer and see what's on it using the command ```bash diskutil list /dev/disk2 #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme *2000.0 GB disk2 ``` Is the problem not due to the disk being GPT and not MBR? Let's try to change it. Please note that this operation erases whatever is on the disk. ```bash diskutil unmount /dev/disk2 diskutil eraseDisk FAT32 EMPTY MBRFormat /dev/disk2 ``` If you are using an old version of MacOS ( The error may occur from Mac OS Leopard 10.5 to Yosemite 10.10\. ) you will see an error like this: ```bash FAT32 does not appear to be a valid file system format Use diskutil listFilesystems to view a list of supported file systems ``` Find the correct filesystem alias by listing all available options: ```bash diskutil listFilesystems diskutil eraseDisk "ms-dos fat32" EMPTY MBRFormat /dev/disk2 ``` The miracle happens and now SRM sees the disk, can reformat it, etc. I highly recommend reformatting it as ext4 filesystem. ![](https://ostreff.info/content/images/2022/12/time-machine-running.jpg) ### Relaxation video URL: https://ostreff.info/relaxation-video/ Last updated: 2022-11-27T11:25:34.000Z _This post is for subscribers only._ ### HTTP/3 implemented URL: https://ostreff.info/http3/ Last updated: 2024-05-06T05:50:24.000Z My website now supports all versions of the http protocol, including [http/3](https://http3check.net/?host=ostreff.info&ref=ostreff.info). IPv6/IPv4 are also supported. ![](https://ostreff.info/content/images/2022/11/image.png) Added also `SVCB record type` into DNS server: ![](https://ostreff.info/content/images/2022/11/image-1.png) For questions related to `how-to` enable `HTTP/3` support in your favorite browser, you need to google about that information. ### How to display TLS server extensions on FreeBSD URL: https://ostreff.info/how-to-display-tls-server-extensions-on-freebsd/ Last updated: 2022-11-02T16:38:19.000Z Use `openssl` command-line utility to display TLS server extensions. ```bash #!/usr/local/bin/bash # Display TLS extensions # # Example: # $ get_tls_extensions.sh sleeplessbeastie.eu # Negotiated TLS version: TLSv1.2 # TLS extensions: # - EC point formats # - extended master secret # - session ticket # - renegotiation info # # $ get_tls_extensions.sh debian.org # Negotiated TLS version: TLSv1.2 # TLS extensions: # - server name # - EC point formats # - session ticket # - renegotiation info # # temporary file temp_file=$(mktemp) # delete temporary file on exit trap "unlink $temp_file" EXIT if [ "$#" -eq "1" ]; then website="$1" host "$website" >&- if [ "$?" -eq "0" ]; then echo -n | openssl s_client -servername "$website" -connect "$website":443 -tlsextdebug 2>/dev/null > $temp_file tls_version=$(cat $temp_file | awk -F: '/^\ *Protocol/ {gsub(" ","",$2);print $2}') tls_extensions=$(cat $temp_file | gsed -n -e '1,/---/ {s/^TLS server extension \"\(.*\)\" (id=\(.*\)).*/\2:\1/p}' | sort | awk -F: '{print " - " $2}') echo "Negotiated TLS version: $tls_version" echo "TLS extensions:" echo -e "$tls_extensions" fi fi ``` You need to have install port `textproc/gsed`. Based on [sleeplessbeastie.eu](https://sleeplessbeastie.eu/2019/03/04/how-to-display-tls-server-extensions/?ref=ostreff.info) ### Ghost CMS advices URL: https://ostreff.info/pm2-rcd/ Last updated: 2022-10-21T15:55:23.000Z After some period of problems related to the complicated system dependent on many different node.js components, I had to redo the mechanism by which my [GhostCMS](https://ghost.org/?ref=ostreff.info) based blog is launched on FreeBSD 13/stable. First my experience on what to do when you find yourself in the situation where you can no longer start the Ghost app. In short, my approach is simple - don't bother trying to figure out which module is causing the problem. Just start clean. Go to /usr/local/lib/node\_modules directory on your freebsd machine. ```bash cd /usr/local/lib/node_modules ``` Save the list of directories you see somewhere in a text editor. ```bash ls -1 > ~/node-modules-list-backup.txt ``` Rename node\_modules directory to node\_modules.bak just in case. ```bash cd /usr/local/lib/ ; mv node_modules node_modules.bak ``` Reinstall [www/npm](https://www.npmjs.com/?ref=ostreff.info) again. ```bash portmaster www/npm ``` Reinstall [ghost-cli](https://ghost.org/docs/ghost-cli/?ref=ostreff.info) again. ```bash npm install -g ghost-cli@latest ``` Install [pm2](https://pm2.keymetrics.io/?ref=ostreff.info) for easy management of node.js applications. Previously I used [forever](https://www.npmjs.com/package/forever?ref=ostreff.info) and the method already described in my blog [here](https://ostreff.info/automate-ghost-update/), but that's how I ended up with the incompatible combination of node-modules. ```bash npm install -g pm2@latest ``` Here comes the point, how do you tell "pm2" to start and keep your blog (node.js app) up and running? Because I want the entire site to run on behalf of an unprivileged account, I've created an account called "ghost" for this application. I personally do the initial test launch like this: ```bash sudo -u ghost -s NODE_ENV=production pm2 start current/index.js --name GhostCMS ``` I recommend also adding [pm2-logrotate](https://github.com/keymetrics/pm2-logrotate?ref=ostreff.info) to keep your logs from overflowing. ```bash pm2 install pm2-logrotate ``` After you are convinced that your site is working correctly, it is good to save the thus made configuration of pm2 running ```bash pm2 save ``` You can monitor the behavior of a running node.js application using the following command ```bash pm2 monit ``` ![](https://ostreff.info/content/images/2022/10/image.png) To be able to restart pm2 every time the FreeBSD host is restarted, so that it can in turn start and keep the ghost application running, you need to add the necessary rcd script to /usr/local/etc/rc.d/. For your convenience, I am implementing my version relying on already installed security/sudo. If missing install it ```bash portmaster security/sudo ``` The standard built-in pm2 way of creating a startup using command "pm2 startup rcd -u ghost" does not create a convenient and working script, at least for me. My variant is published on github and you can easily download and install it like this. ```bash cd /usr/local/etc/rc.d &&\ fetch https://raw.githubusercontent.com/jostreff/ghost-rc.d-script/main/pm2 -o pm2 &&\ chmod +x pm2 ``` If you need to automate the process of updating your self-hosted ghost cms - take a look at this [https://github.com/jostreff/ghost-cli-update/blob/main/ghost-cli-update](https://github.com/jostreff/ghost-cli-update/blob/main/ghost-cli-update?ref=ostreff.info) ### HOWTO use Python 3.11-rc2 under FreeBSD13-stable URL: https://ostreff.info/python311/ Last updated: 2022-11-02T16:39:26.000Z `Update` Python 3.11 is out officially. You can easily change the version used in the operating system. I highly recommend only having one version of python installed - otherwise it becomes a big mess. To do this, you must first specify in the /etc/make.conf file which version you want to use. ```bash DEFAULT_VERSIONS+=python3=3.11 DEFAULT_VERSIONS+=python=3.11 ``` It is a good idea to clean all packages using the previous version first. I personally do it with a similar script below to find all python 3.10 files in my example. ```bash #!/usr/local/bin/bash LIST_DEL="" LIST_INS="" for FILE in `pkg info | grep py310- | cut -f1 -d' '` ; do PKGNAME=`pkg info "$FILE" | grep 'Origin :' | cut -f2 -d':'` ; NEWPKGNAME="${PKGNAME/py310/py311}" ; LIST_DEL="$LIST_DEL $FILE"; LIST_INS="$LIST_INS $NEWPKGNAME"; done echo "pkg delete -f $LIST_DEL"; echo "\n"; echo "portmaster $LIST_INS"; ``` We save the result of the script execution, preferably in a text file. We run the line starting with "pkg delete -f" from the output. Then we change the python version to the desired version 3.11 using command like following: ```bash portmaster -o lang/python311 python310-3.10.8 ; portmaster lang/python lang/python3 ``` After execution, you should see something like this: ```bash # pkg info | grep python python-3.11_3,2 "meta-port" for the default version of Python interpreter python3-3_3 Meta-port for the Python interpreter 3.x python311-3.11.0.r2 Interpreted object-oriented programming language (RC version) ``` We execute the saved result of executing the script starting with "portmaster". We check with "pkg check -da" for broken packages and possibly recompile them as well. Issues noted till now: 1. www/nodejs cannot be updated due to unsupported python version. Even if the Makefile is modified, the resulting version does not work well. 2. net-im/py-matrix-synapse does not work - at least because of devel/py-frozendict which cannot be compiled. 3. there are ports that don't want to compile with python3.11 but a simple Makefile edit can change that. ### Safari 16.0 problems on older Macs URL: https://ostreff.info/safari-16-0-problems-on-older-macs/ Last updated: 2022-09-22T11:53:55.000Z Was having the issue with a mid 2015 MacBook Pro Retina running Monterey 12.6 and Safari 16.0 -- Google Maps, Apple Maps, and Mapquest.com all "crashed" to black screen with compressed artifacts running across middle of page horizontally. Solution - Go to Safari > Develop > Experimental Features and "unchecked" WebGL via Metal. All three seem to be working at this time. ### Who's Who in terms of GDP in 2022 URL: https://ostreff.info/who-is-who-in-gdp-plane/ Last updated: 2022-07-29T09:32:16.000Z Source: [https://www.visualcapitalist.com/100-trillion-global-economy/](https://www.visualcapitalist.com/100-trillion-global-economy/?ref=ostreff.info) ![](https://ostreff.info/content/images/2022/07/100-trillion-global-economy-1800px.webp) ### Любопитно ... URL: https://ostreff.info/liubopitno/ Last updated: 2022-07-03T10:27:35.000Z ![](https://ostreff.info/content/images/2022/07/EB9515E7-103A-48EE-8888-40323E01789C-1.jpeg) Bill Gates CV from 1974 Source: [LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:6948310563722579970?updateEntityUrn=urn%3Ali%3Afs%5FfeedUpdate%3A%28V2%2Curn%3Ali%3Aactivity%3A6948310563722579970%29&ref=ostreff.info) Знаете че никога не съм бил запален фен на[ г-н Гейтс III](https://www.gatesfoundation.org/?ref=ostreff.info), но трябва да му се признае че има опит, постигнал е успех и има визия, какво се случва в света. ### How to solve NSPOSIXErrorDomain:40 URL: https://ostreff.info/how-to-solve-nsposixerrordomain-40-2/ Last updated: 2022-07-02T17:33:52.000Z I’m so frustrated that I need to use another browser because Safari randomly pops up this error whenever he wants, just on the MBP ( on iPhone and iPad works just fine )... It’s firewall related problem. Disable firewall and you’re good. ### Slack integration URL: https://ostreff.info/slack-integration/ Last updated: 2022-03-05T17:30:45.000Z In previous year I have thinked many times that [Facebook](https://facebook.com/) and other social media places are not going into right direction. So I've started my own private [Slack](https://slack.ostreff.info/?ref=ostreff.info) instance in the internet. Everybody is welcome to join there! All blogs messages will appear also [here](https://slack.ostreff.info/jsp-ltd/channels/blog?ref=ostreff.info) for discussions. On all other topics welcome[ here](https://slack.ostreff.info/jsp-ltd/channels/town-square?ref=ostreff.info). ### Economy 101 quick'n'simple explanation URL: https://ostreff.info/economy-101-explain/ Last updated: 2022-02-20T09:58:32.000Z ![Communism](https://ostreff.info/content/images/2022/02/44941763_2291982344354907_5887779509976956928_n.jpg) ![Socialism](https://ostreff.info/content/images/2022/02/44902170_2291982371021571_923228255639568384_n.jpg) ![Fascism](https://ostreff.info/content/images/2022/02/44893334_2291982404354901_1403830139669708800_n.jpg) ![BUREAUCRATISM](https://ostreff.info/content/images/2022/02/44991653_2291982441021564_7679170546201788416_n.jpg) ![TRADITIONAL CAPITALISM](https://ostreff.info/content/images/2022/02/44983325_2291982481021560_237888660748369920_n.jpg) ![VENTURE CAPITALISM](https://ostreff.info/content/images/2022/02/44940903_2291982511021557_8093032658541477888_n.jpg) ![AN ITALIAN CORPORATION](https://ostreff.info/content/images/2022/02/44918371_2291982544354887_6276384487311933440_n.jpg) ![A FRENCH CORPORATION](https://ostreff.info/content/images/2022/02/44916334_2291982574354884_4056070713801441280_n.jpg) ![AN AMERICAN CORPORATION](https://ostreff.info/content/images/2022/02/44938635_2291982601021548_7345308285718757376_n.jpg) ![A SWISS CORPORATION](https://ostreff.info/content/images/2022/02/44948610_2291982674354874_3694727474442141696_n.jpg) ![AN IRISH CORPORATION](https://ostreff.info/content/images/2022/02/44890261_2291982704354871_753417807240101888_n.jpg) ![AN AUSTRALIAN CORPORATION](https://ostreff.info/content/images/2022/02/44939829_2291982747688200_3395044890118193152_n.jpg) ![A CHINESE CORPORATION](https://ostreff.info/content/images/2022/02/44987611_2291982787688196_2361242624133693440_n.jpg) ![AN INDIAN CORPORATION](https://ostreff.info/content/images/2022/02/43717988_2291982824354859_8414018499696918528_n.jpg) ![AN IRAQI CORPORATION](https://ostreff.info/content/images/2022/02/44927127_2291982871021521_786488969519955968_n.jpg) ![A BRITISH CORPORATION](https://ostreff.info/content/images/2022/02/45006826_2291982904354851_7457279436040175616_n.jpg) ![A GREEK CORPORATION](https://ostreff.info/content/images/2022/02/44892776_2291982927688182_8365147780527685632_n.jpg) ### Find out which sleep mode your Mac uses URL: https://ostreff.info/find-out-which-sleep-mode-your-mac-uses/ Last updated: 2022-01-20T10:55:40.000Z You should use Terminal to find out which sleep mode your Mac is currently set to use. Then you can use Terminal to change it to a different mode if you want to. ```console pmset -g | grep hibernatemode ``` Possible results are: ```console hibernatemode 0 (standard sleep) hibernatemode 1 (hibernate mode, for pre-2005 portable MacBooks) hibernatemode 3 (safe sleep) hibernatemode 25 (hibernate mode for post-2005 MacBooks) ``` Changing is possible executing following command: ```console sudo pmset -a hibernatemode X ``` Sleep modes are explained in more details [here](https://mackeeper.com/blog/mac-sleep-modes-explained/?ref=ostreff.info). ### Happy New 2022 Year! URL: https://ostreff.info/happy-new-2022-year/ Last updated: 2021-12-31T22:00:00.000Z ![](https://ostreff.info/content/images/2021/12/HNY2022.jpg) Happy New 2022 Year! ### Mobile QoS URL: https://ostreff.info/mobile-qos/ Last updated: 2021-12-31T11:17:59.000Z ![](https://ostreff.info/content/images/2021/04/image.png) ### Automate NextCloud updates URL: https://ostreff.info/automate-nextcloud-updates/ Last updated: 2021-12-31T10:45:36.000Z Here is my script to automate NextCloud updates. They can be put into `crontab` in example: ```bash #!/bin/csh setenv nextcloud "/usr/local/www/nextcloud" pushd $nextcloud sudo -u www -D $nextcloud /usr/local/bin/php --define apc.enable_cli=1 updater/updater.phar --no-interaction sudo -u www -D $nextcloud /usr/local/bin/php ./occ app:update --all popd ``` ### Automate GHOST updates URL: https://ostreff.info/automate-ghost-update/ Last updated: 2021-12-31T10:36:00.000Z Here are my scripts for automaticaly update my GHOST updates on FreeBSD. First i'm listing my rc.d script for starting it: ```bash #!/bin/sh # PROVIDE: ghost # REQUIRE: DAEMON SERVERS mysql postgresql slapd # KEYWORD: shutdown # # Add the following lines to /etc/rc.conf to enable powerdns: # # ghost_enable="YES" # PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" . /etc/rc.subr name="ghost" rcvar="ghost_enable" extra_commands="status" load_rc_config ghost : ${ghost_enable:="NO"} status_cmd="ghost_status" start_cmd="ghost_start" stop_cmd="ghost_stop" restart_cmd="ghost_restart" ghost="/usr/home/ghost" log="/var/log/ghost/ghost.log" ghost_start() { sudo -u ghost sh -c "cd $ghost && NODE_ENV=production forever start --minUptime 10000 --spinSleepTime 30000 -al $log current/index.js" } ghost_stop() { sudo -u ghost sh -c "cd $ghost && NODE_ENV=production forever stop current/index.js" } ghost_status() { sudo -u ghost sh -c "NODE_ENV=production forever list" } ghost_restart() { ghost_stop; ghost_start; } run_rc_command "$1" ``` And here is the script which can be put into the `crontab` as example: ```bash #!/usr/local/bin/bash ghost="/usr/home/ghost" pushd $ghost output=`sudo -u ghost -D $ghost /usr/local/bin/ghost check-update` if [[ "$output" == *"available:"* ]]; then echo "Checking the permission ..." sudo -u ghost -D $ghost /usr/bin/find ./ ! -path "./versions/*" -type f -exec chmod 664 {} \; sudo -u ghost -D $ghost GHOST_NODE_VERSION_CHECK=false /usr/local/bin/ghost update echo "Restarting ..." /usr/sbin/service ghost restart else echo "No new version of GHOST-CMS detected." fi popd ``` ### Unbelievable URL: https://ostreff.info/art-in-coding/ Last updated: 2021-12-20T20:15:08.000Z 💡 JBIG2 doesn't have scripting capabilities, but when combined with a vulnerability, it does have the ability to emulate circuits of arbitrary logic gates operating on arbitrary memory. So why not just use that to build your *own* computer architecture and script that!? That's exactly what this exploit does. Using over 70,000 segment commands defining logical bit operations, they define a small computer architecture with features such as registers and a full 64-bit adder and comparator which they use to search memory and perform arithmetic operations. It's not as fast as Javascript, but it's fundamentally computationally equivalent. 💡 The bootstrapping operations for the sandbox escape exploit are written to run on this logic circuit and the whole thing runs in this weird, emulated environment created out of a single decompression pass through a JBIG2 stream. It's pretty incredible, and at the same time, pretty terrifying. Whole article how they made it - [https://googleprojectzero.blogspot.com](https://googleprojectzero.blogspot.com/2021/12/a-deep-dive-into-nso-zero-click.html?ref=ostreff.info) Explanation what they have made - [Pegasus\_(spyware)](https://en.wikipedia.org/wiki/Pegasus%5F%28spyware%29?ref=ostreff.info). The lesson from the whole article is that there are still programmers who can work without using [https://stackoverflow.com](https://l.facebook.com/l.php?u=https%3A%2F%2Fstackoverflow.com%2F&h=AT0zTKM6h6vkQWhuX1rLZlO4wpmq2D7U9yqJ1vOLBbQIOM2r%5Fyw-GdQgPwnWHmlnJK%5FVRaR0BnfPk1WsmfNMgZ6bE3I0d9glfAhuNpOTS8OJNtecqiLAKr4Z4y%5FoDsCV1pKmpKfNEisuzCuu8nUIx-s&h=AT0zTKM6h6vkQWhuX1rLZlO4wpmq2D7U9yqJ1vOLBbQIOM2r%5Fyw-GdQgPwnWHmlnJK%5FVRaR0BnfPk1WsmfNMgZ6bE3I0d9glfAhuNpOTS8OJNtecqiLAKr4Z4y%5FoDsCV1pKmpKfNEisuzCuu8nUIx-s&h=AT0zTKM6h6vkQWhuX1rLZlO4wpmq2D7U9yqJ1vOLBbQIOM2r%5Fyw-GdQgPwnWHmlnJK%5FVRaR0BnfPk1WsmfNMgZ6bE3I0d9glfAhuNpOTS8OJNtecqiLAKr4Z4y%5FoDsCV1pKmpKfNEisuzCuu8nUIx-s&ref=ostreff.info)! As we say in my homeland - "the road to hell is paved with good intentions ..." The result of their briliant work is simply awwful! Please note another interesting idea, using the same concept - "[Open Redstone project](https://openredstone.org/?ref=ostreff.info)" and his [description](https://www.pcworld.com/article/559794/8-bit-computer-processor-built-in-minecraft-can-run-its-own-games.html?ref=ostreff.info). ### Merry Christmas! URL: https://ostreff.info/merry-christmas/ Last updated: 2021-12-31T10:48:41.000Z ![](https://jakubmarian.com/wp-content/uploads/2014/12/merry-christmas-european-languages.jpg) ![](https://ostreff.info/content/images/2021/12/giphy.gif) ### MySQL 8.0.27 upgrade failure URL: https://ostreff.info/mysql-8-0-27-upgrade-failure/ Last updated: 2021-11-25T16:12:23.000Z If after the upgrade the mysql doesn't start don't panic! In mysql8 mysql\_upgrade command is dummy, and the database upgrades automatic on first restart. In my scenario I was upgrading from 8.0.26 to mysql8.0.27 and I see errors like bellow and the database won't start: ```bash 2021-11-25T13:44:12.805159Z 4 [System] [MY-013381] [Server] Server upgrade from '80026' to '80027' started. 2021-11-25T13:44:13.792252Z 4 [ERROR] [MY-013178] [Server] Execution of server-side SQL statement '-- Create slow_log CREATE TABLE IF NOT EXISTS slow_log (start_time TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), user_host MEDIUMTEXT NOT NULL, query_time TIME(6) NOTNULL, lock_time TIME(6) NOT NULL, rows_sent INTEGER NOT NULL, rows_examined INTEGER NOT NULL, db VARCHAR(512) NOT NULL, last_insert_id INTEGER NOT NULL, insert_id INTEGER NOT NULL, server_id INTEGER UNSIGNED NOT NULL, sql_text MEDIUMBLOB NOT NULL, thread_id BIGINT UNSIGNED NOT NULL) engine=CSV CHARACTER SET utf8 comment="Slow log"; ' failed with error code = 13, error message = 'Can't get stat of './mysql/slow_log.CSV' (OS errno 2 - No such file ordirectory)'. 2021-11-25T13:44:13.821868Z 0 [ERROR] [MY-013380] [Server] Failed to upgrade server. 2021-11-25T13:44:13.822120Z 0 [ERROR] [MY-010119] [Server] Aborting ``` The solution is really simple: ```bash sudo -u mysql -s touch ./mysql/general_log.CSV chown mysql:mysql ./mysql/general_log.CSV touch ./mysql/slow_log.CSV chown mysql:mysql ./mysql/slow_log.CSV ``` ### Upgrade PostgreSQL 14.5 to 15.x on FreeBSD URL: https://ostreff.info/upgrade-postgresql-on-freebsd/ Last updated: 2022-10-14T10:29:59.000Z New version of [postgresql 15 database](https://www.postgresql.org/about/featurematrix/?ref=ostreff.info) have arrived. I have updated the manual to newest possible upgraded version on 14 october 2022. Stop your [postgresql](https://www.postgresql.org/?ref=ostreff.info) database daemon on [FreeBSD](https://freebsd.org/?ref=ostreff.info) machine: ```bash service postgresql stop ``` Create package containing your old version ( 14.5 ) binaries ( depends are you using postgresql14-contrib package, but command will not fail if it is missing ) ```bash pkg create postgresql14-server postgresql14-contrib ``` Uncompress them somewhere in the filesystem - in our example it is into /tmp/pg-upgrade directory. ```bash mkdir /tmp/pg-upgrade tar xf postgresql13-server-14.5.pkg -C /tmp/pg-upgrade tar xf postgresql13-contrib-14.5.pkg -C /tmp/pg-upgrade /* only needed if you have postgresql14-contrib package installed */ ``` Remove old installed binaries ```bash pkg delete -f databases/postgresql14-server databases/postgresql14-contrib databases/postgresql14-client ``` Install new version of PostgreSQL 15.x ```bash portmaster databases/postgresql15-server databases/postgresql15-client portmaster databases/postgresql15-contrib /* only if need it */ ``` Create new empty database with ```bash su -l postgres -c "/usr/local/bin/initdb --encoding=utf-8 --lc-collate=C -D /var/db/postgres/data15 -U postgres" ``` Migrate the data from old database to the new empty database. Assuming that previos database is leaving into /var/db/postgres/data14/ directory. ```bash su -l postgres -c "pg_upgrade -b /tmp/pg-upgrade/usr/local/bin/ -d /var/db/postgres/data14/ -B /usr/local/bin/ -D /var/db/postgres/data15/ -U postgres" ``` Start your database ```bash service postgresql start ``` Once you start the new server, consider running: ```bash /usr/local/bin/vacuumdb -U postgres --all --analyze-in-stages ``` Running this script will delete the old cluster's data files: ```bash /var/db/postgres/delete_old_cluster.sh ``` Some decrease of needed disk space is easy visible: 1,5G data14 1,4G data15 Source of the manual is comming from [FreeBSD Ports tree](https://cgit.freebsd.org/src/tree/UPDATING?h=stable/13&ref=ostreff.info). ### Howto run and update ghost 4.x on node.js 17.x URL: https://ostreff.info/ghost-4-x-running-on-node-17-x/ Last updated: 2021-11-25T16:13:07.000Z It looks that there is big inconsistency in [FreeBSD ports](https://www.freebsd.org/ports/?ref=ostreff.info) \- related to [python 3.10](https://www.python.org/?ref=ostreff.info) and [node.js](https://nodejs.org/en/?ref=ostreff.info). Currently only node.js 17.x can be build if you have such combination of programming languages on your system. And the greatest news is that ghost 4.x runs on that officially unsupported combination. To workaround verifications buildin [ghost](https://ghost.org/?ref=ostreff.info) and to made this great CMS to run on unstable and not LTS version of [node.js](https://nodejs.org/en/?ref=ostreff.info) as 17.x branch - you must to modify [ghost-cli](https://ghost.org/docs/ghost-cli/?ref=ostreff.info). It installs file **/usr/local/lib/node\_modules/ghost-cli/package.json** which contains something like: ```bash "engines": {"node": "^12.22.1 || ^14.17.0 || ^16.13.0 || ^17.0.1"}, ``` and then use ghost-cli commands in this way: ```bash GHOST_NODE_VERSION_CHECK=false ghost check-update GHOST_NODE_VERSION_CHECK=false ghost update ``` [Here](https://forum.ghost.org/t/ghost-on-freebsd-13-with-python-3-10-node-js-17-0-1/26584/3?ref=ostreff.info) is my communication with official Ghost forum. ### Ghost and sqlite3 URL: https://ostreff.info/ghost-and-sqlite3/ Last updated: 2021-10-24T19:54:52.000Z After many problems and outages of my blog now It is clear for me that it is better to use mysql as database backend in Ghost CMS! It looks that with Node14 it doesn't work with sqlite3 database backend. Migration is easy - just take the latest backup \*.json file from ./content/db/ and then follow [this manual](https://stanislas.blog/2019/09/migrating-ghost-sqlite-to-mysql/?ref=ostreff.info). I hope that [ghost team](https://ghost.org/?ref=ostreff.info) will add another possible database backends! ### Matomo INFILE usage with MySQL8 and PHP8 URL: https://ostreff.info/matomo-with-mysql8-and-php8/ Last updated: 2021-11-25T16:13:41.000Z It looks tricky to cheat [matomo](https://matomo.org/?ref=ostreff.info) INFILE usage when you use [PHP8](https://www.php.net/releases/8.0/en.php?ref=ostreff.info) and [MySQL8](https://www.mysql.com/?ref=ostreff.info). My quick hints are: 1. Did you have put in my.cnf file: secure-file-priv = //matomo/tmp/assets/ 2. Do you have in php-fpm.conf file line like: ```bash php_admin_value[mysqli.allow_local_infile] = 1 ``` ### Триумфът на стадния имунитет ... URL: https://ostreff.info/triumft-na-stadniia-imunitiet/ Last updated: 2022-11-11T11:51:35.000Z Поредния анализ публикуван от Георги К. Маринов работещ в Department of Genetics, Stanford University, Stanford, California 94305, USA. Оригиналният текст е публикуван от г-н Маринов в Google Disk тук: [2021-06-05-herd-immunity.pdf![](https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png)Google Docs![](https://lh4.googleusercontent.com/mUIg1rpebMLm4r-Mtdri_MNml4bSEWMXNrOc2C_cYIxgXk0WKI0mvTEiW1ygZHQfpVY=w1200-h630-p)](https://drive.google.com/file/d/1riRoxXHBbUmEY0u-1ZhDm2Q4bnTc0NAB/view?fbclid=IwAR2ZN5LYXSybHQufyYi-Qc0j9ryVsduOS4kkdW6ghyDTXY-hXeiU1KXNmgg&ref=ostreff.info) ### Локдаун за последно или до безкрай? URL: https://ostreff.info/lokdaun-za-posliedno-ili-do-biezkrai/ Last updated: 2021-05-17T19:18:16.000Z Поредният много силен материал от [Георги К. Маринов](https://www.facebook.com/gkmarinov) работещ в Department of Genetics, Stanford University, Stanford, California 94305, USA [2021-03-18-ZeroCOVID.pdf![](https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png)Google Docs![](https://lh5.googleusercontent.com/FTnCHXAqUnXSV-Hj7EuGf5erosJ_AzBoJKo3WffX32y2cS7SGkcp_9NoolV8Ze4PIjY=w1200-h630-p)](https://drive.google.com/file/d/1eOHEejDD77sCWQk6nkazAejgi2fbhjWQ/view?ref=ostreff.info) И локално копие: [2021-03-18-ZeroCOVID.pdfNextcloud - a safe home for all your data![](https://cloud.ostreff.info/index.php/apps/theming/icon?v=0)Nextcloud![](https://cloud.ostreff.info/index.php/apps/theming/icon?v=0)](https://cloud.ostreff.info/index.php/s/JkxisCaeH9jxrTX?ref=ostreff.info) Други материали от същият автор [тук](https://bivol.bg/coronavirus-thoughts.html?ref=ostreff.info) или [тук](https://pp-podem.bg/2020/04/19/%D0%9A%D0%BE%D1%80%D0%BE%D0%BD%D0%B0%D0%B2%D0%B8%D1%80%D1%83%D1%81%D0%BD%D0%B8-%D1%80%D0%B0%D0%B7%D0%BC%D0%B8%D1%88%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F-%D0%93%D0%95%D0%9E%D0%A0%D0%93%D0%98-%D0%9A-%D0%9C/?ref=ostreff.info#%5Fftn1) \- с давност почти една година. Има и [видео интервю](https://www.youtube.com/watch?v=FXUlIm0w2e0&ref=ostreff.info) от миналата година. Още размисли публикувани в пресата ни [тук](https://www.capital.bg/politika%5Fi%5Fikonomika/redakcionni%5Fkomentari/2020/11/21/4155405%5Fcovid-19%5Fv%5Fbulgariia%5F-%5Fkontrol%5Fvurhu%5Fkrizata%5Fili%5Fnova/?ref=ostreff.info), [тук](https://www.economy.bg/featured/view/43420/Koi-sa-naj-obeshtavashtite-vaksini-sreshtu-Covid-19-kym-momenta?ref=ostreff.info) или [тук](https://dversia.net/6340/covid-prez-2021-krai-ili-nachalo-na-krizata/?fbclid=IwAR0JzszVEURL9bQ1oyR00aVsneg1FsM%5FrXTdzu5Hjmg4KR5I7pnNDQbVhwk&ref=ostreff.info). Дискусията под [публикацията](https://m.facebook.com/story.php?story%5Ffbid=10218265780657780&id=1288605388&ref=ostreff.info) на Петър Велков във Facebook. "Колкото повече хора боледуват, толкова повече варианти може да се появят" - казва Георги Маринов в поредното си [интервю](https://www.economy.bg/science/view/45089/Ne-e-dalech-momentyt-v-kojto-shte-se-poyavi-napylno-rezistenten-kym-vaksinite-variant-na-Covid-19?fbclid=IwAR26SzgJQ5qIXXPajseovbPAWMMfvBsWWatyRwCbu1D5yieKvEol9Qxxqcs&ref=ostreff.info). Как се [справя България и другите страни от Източна Европа](https://www.medrxiv.org/content/10.1101/2021.04.06.21254958v1?ref=ostreff.info) с Covid-19? **[Droplets! No, aerosols!](https://www.wired.com/story/the-teeny-tiny-scientific-screwup-that-helped-covid-kill/?ref=ostreff.info) \-** интересно четиво по темата как точно се разпространяват вирусите, и защо никой не иска да говори за аерозоли ... ### Cisco Live 2021 URL: https://ostreff.info/cisco-live-2021/ Last updated: 2021-10-10T11:48:12.000Z [Cisco Live 2021 | Digital Networking EventIn 2021, Cisco’s premier education and training event for IT professionals is going global. Join us on March 31 - April 1, 2021.Cisco![](https://www.ciscolive.com/c/dam/r/ciscolive/fw/i/cisco-live-logo-open-graph.gif)](https://www.ciscolive.com/emea.html?ref=ostreff.info) ### Installing [matrix]-bridges on FreeBSD URL: https://ostreff.info/installing-matrix-bridges-on-freebsd/ Last updated: 2022-04-05T10:37:12.000Z Here is my example using [mautrix-facebook](https://github.com/tulir/mautrix-facebook?ref=ostreff.info) as example‌ ``` $ mkdir bridges/facebook $ cd bridges/facebook $ virtualenv -p /usr/local/bin/python3.7 . $ bash $ source bin/activate $ pip install --global-option=build_ext --global-option="-I/usr/local/include" --upgrade git+https://github.com/tulir/mautrix-facebook.git#egg=mautrix-facebook[all] $ cp example-config.yaml config.yaml ``` Edit config.yaml file to suit your current setup. Please note line ‌ ``` database: postgres://bridge_facebook:[secret_password]@127.0.0.1/bridge_facebook ``` Next step is to create postgres database for bridge.‌ ``` su - postgres $ createuser --pwprompt bridge_facebook $ psqlCREATE DATABASE bridge-facebook ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' template=template0 OWNER bridge_facebook;‌ ``` Start generation of registration.yaml file‌ ``` $ python -m mautrix_telegram -g $ vi registration.yaml‌ ``` Add additinaly listening port again, bellow ****url:** line.‌ ``` url: http://localhost:29319 port: 29319‌‌ ``` ``` $ vi /usr/local/etc/matrix-synapse/homeserver.yaml‌ ``` Put additional lines like bellow:‌ ``` app_service_config_files: ``` ``` /usr/home/synapse/bridges/facebook/registration.yaml ``` ``` service synapse restart‌ ``` Next step is to write rc startup script for your daemon... ### ELF binary type "0" not known. URL: https://ostreff.info/elf-binary-type-0-not-known/ Last updated: 2021-02-13T21:05:25.000Z Today I've invested some time to bring back to life my personal [Cloud storage](https://cloud.ostreff.info/?ref=ostreff.info). The reason for his death was upgrade to [PHP8](https://www.php.net/releases/8.0/en.php?ref=ostreff.info). So after some research I've found that at this moment the only compatible to that php version was [NextCloud 21](https://nextcloud.com/install/?ref=ostreff.info). I have made an quick migration from ownCloud to NextCloud 21 ( [RC2](https://github.com/nextcloud/server/milestone/120?ref=ostreff.info) ), till next ownCloud [release](https://owncloud.github.io/ocis/release%5Froadmap/?ref=ostreff.info). I have been surprised that ownCloud are not talking for php8 compatibility - so latter you will fid the reason for this ... Check the downloaded file what kind is: $ file ocis ocis: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, Go BuildID=Q1QARbCPb-T4jvNx\_o5P/\_h2AG0yzdDeW\_AGAAQUy/xF9-8aLfT93eA51lKJg7/fnNkWegdotRsUI2zrKyc, stripped When I executed it **starnge** error is showing: $ ./ocis -h ELF binary type "0" not known. \-sh: ./ocis: Exec format error I have checked that linux emulation modules are up and running in kernel space: $ kldstat Id Refs Address Size Name 1 65 0xffffffff80200000 228e368 kernel 2 1 0xffffffff82490000 a938 cuse.ko 3 1 0xffffffff8249b000 ff18 carp.ko 4 1 0xffffffff824ab000 2ba0 coretemp.ko 5 1 0xffffffff824ae000 3be278 zfs.ko 6 2 0xffffffff8286d000 a468 opensolaris.ko 7 1 0xffffffff836fa000 5688 linprocfs.ko **8 4 0xffffffff83700000 4bb0 linux\_common.ko** 9 1 0xffffffff83705000 1f3c linsysfs.ko 10 1 0xffffffff83707000 8b30 tmpfs.ko 11 1 0xffffffff83710000 2130 acpi\_wmi.ko 12 1 0xffffffff83713000 4240 ng\_ubt.ko 13 6 0xffffffff83718000 9b60 netgraph.ko 14 2 0xffffffff83722000 90f8 ng\_hci.ko 15 3 0xffffffff8372c000 9b0 ng\_bluetooth.ko 16 1 0xffffffff8372d000 cae0 ng\_l2cap.ko 17 1 0xffffffff8373a000 1aef0 ng\_btsocket.ko 18 1 0xffffffff83755000 2140 ng\_socket.ko 19 1 0xffffffff83758000 25650 ipfw.ko 20 1 0xffffffff8377e000 acf mac\_ntpd.ko **21 1 0xffffffff8377f000 35e60 linux64.ko** **22 1 0xffffffff837b5000 3c670 linux.ko** I have checked what kind of ELF applications are available for execution in that kernel: $ **brandelf -l** known ELF types are: FreeBSD(9) **Linux(3)** Solaris(6) SVR4(0) It looks that we must tell to FreeBSD that this "ocis" file is Linux kind ELF binary. To do this I used command: $ **brandelf -t Linux ocis** And now our example application is runing as we expected: $ ./ocis -h Incorrect Usage. flag: help requested NAME: ocis - ownCloud Infinite Scale Stack USAGE: ocis \[global options\] command \[command options\] \[arguments...\] VERSION: 1.1.0 AUTHOR: ownCloud GmbH [support@owncloud.com](mailto:support@owncloud.com) In my example I have used linux application named "ocis". In fact this is application from excelent [ownCloud](https://owncloud.com/?ref=ostreff.info) platform - it is part from their next generation cloud platform named "**[ownCloud Infinite Scale](https://owncloud.com/beta-testing/?ref=ostreff.info)**". I believe that this will be **better** version for "Cloud Platform" from performance, scalability and security perspective. The next "Infine Scale" version as it is visible from file information is written on [go language](https://golang.org/?ref=ostreff.info). I hope that "ownCloud GmbH" will respect users not only in linux world - they are currently providing precompiled binaries only for linux and also docker images. But as you know actually there are also many other great UNIXes like BSD clones ([Free](https://www.freebsd.org/?ref=ostreff.info)/[Net](https://www.netbsd.org/?ref=ostreff.info)/[Open](https://www.openbsd.org/?ref=ostreff.info) BSD), Solaris ( [Oracle](https://www.oracle.com/solaris/solaris11/?ref=ostreff.info), [illumos](https://illumos.org/?ref=ostreff.info), [openindiana](https://www.openindiana.org/?ref=ostreff.info) ). ### Build graphics/pecl-imagick PHP 8 compatible extension URL: https://ostreff.info/magick-php-extension-from/ Last updated: 2021-02-13T14:19:42.000Z The version in the ports tree is not working - it shows some errors when installed. But the source on the master brach of [the Imagick repo](https://github.com/Imagick/imagick?ref=ostreff.info) seems to work. Here's how you can use it. 1. Clone the repo somewhere on disk 2. Execute following commands: cd phpize ./configure make make install cat > /usr/local/etc/php/ext-20-imagick.ini extension=imagick.so ^D php -i | grep imagick /usr/local/etc/php/ext-20-imagick.ini, imagick imagick module => **enabled** imagick module version => @PACKAGE\_VERSION@ imagick classes => Imagick, ImagickDraw, ImagickPixel, ImagickPixelIterator, ImagickKernel imagick.locale\_fix => 0 => 0 imagick.progress\_monitor => 0 => 0 imagick.set\_single\_thread => 0 => 0 imagick.shutdown\_sleep\_count => 10 => 10 imagick.skip\_version\_check => 0 => 0 ### How to build and install Mattermost plugins on FreeBSD URL: https://ostreff.info/how-to-install-mattermost-plugins-on-freebsd/ Last updated: 2021-01-31T10:38:44.000Z I was unpleasantly surprised to find that all plugins published on [Mattermost plugin marketplace](https://integrations.mattermost.com/?ref=ostreff.info) are not working on installed server on FreeBSD. They all are failing to start - it doesn't matter did you enabled [linux64 emulation](https://docs.freebsd.org/en/books/handbook/linuxemu/?ref=ostreff.info) inside the OS. After some research I have found that the problematic area is easy visible from file packages.json - as example here I'm givin a snippet from [mattermost-plugin-jitsi](https://github.com/mattermost/mattermost-plugin-jitsi?ref=ostreff.info): { "id": "jitsi", "name": "Jitsi", "description": "Jitsi audio and video conferencing plugin for Mattermost.", "homepage\_url": "[https://github.com/mattermost/mattermost-plugin-jitsi](https://github.com/mattermost/mattermost-plugin-jitsi?ref=ostreff.info)", "support\_url": "[https://github.com/mattermost/mattermost-plugin-jitsi/issues](https://github.com/mattermost/mattermost-plugin-jitsi/issues?ref=ostreff.info)", "release\_notes\_url": "[https://github.com/mattermost/mattermost-plugin-jitsi/releases/tag/v2.0.0](https://github.com/mattermost/mattermost-plugin-jitsi/releases/tag/v2.0.0?ref=ostreff.info)", "icon\_path": "assets/icon.svg", "version": "2.0.0", "min\_server\_version": "5.2.0", "server": { "executables": { "linux-amd64": "server/dist/plugin-linux-amd64", "darwin-amd64": "server/dist/plugin-darwin-amd64", "windows-amd64": "server/dist/plugin-windows-amd64.exe" } }, It's easy visible and clear that the source must be build for FreeBSD too. The good news is that GO language has excelent cross compiling features. $GOOS $GOARCH aix ppc64 android 386 android amd64 android arm android arm64 darwin 386 darwin amd64 darwin arm darwin arm64 dragonfly amd64 freebsd 386 freebsd amd64 freebsd arm illumos amd64 js wasm linux 386 linux amd64 linux arm linux arm64 linux ppc64 linux ppc64le linux mips linux mipsle linux mips64 linux mips64le linux s390x netbsd 386 netbsd amd64 netbsd arm openbsd 386 openbsd amd64 openbsd arm openbsd arm64 plan9 386 plan9 amd64 plan9 arm solaris amd64 windows 386 windows amd64 Edit the Makefile in this way: ``` cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-linux-amd64; cd server && env GOOS=freebsd GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-freebsd-amd64; cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-darwin-amd64; cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-windows-amd64.exe; ``` Change the packages.json and server/manifest.go files to include new supported architecture: ``` "server": { "executables": { "linux-amd64": "server/dist/plugin-linux-amd64", "freebsd-amd64": "server/dist/plugin-freebsd-amd64", "darwin-amd64": "server/dist/plugin-darwin-amd64", "windows-amd64": "server/dist/plugin-windows-amd64.exe" } }, ``` If there are failures when building package, replace whole "server" section with: ``` "server": { "executable": "server/dist/plugin-freebsd-amd64" }, ``` Then build package using "gmake dist" to generate distribution into "dist/\*.gz". You can grab generated file and upload it to [your mattermost instance](https://slack.ostreff.info/?ref=ostreff.info) running on FreeBSD. ### How to made Websocket traffic to go via HAProxy URL: https://ostreff.info/how-to-haproxy-made-web-sockets/ Last updated: 2025-10-08T15:14:39.000Z The solution was simple: Tell HAProxy to tag Websocket traffic, and set up a separate but otherwise identical backend for this specific use case. One of typical applications which is using Websocket traffic is [Slack](https://slack.com/?ref=ostreff.info). An excelent opensource alternative of it named [Mattermost](https://mattermost.com/?ref=ostreff.info). The result looks something like this in the config file named haproxy.conf: acl host\_ws hdr\_beg(Host) -i ws. acl hdr\_connection\_upgrade hdr(Connection) -i upgrade acl hdr\_upgrade\_websocket hdr(Upgrade) -i websocket use\_backend bk\_ws\_mattermost if host\_ws { hdr(Host) -i slack.ostreff.info } use\_backend bk\_ws\_mattermost if hdr\_connection\_upgrade hdr\_upgrade\_websocket { hdr(Host) -i slack.ostreff.info } use\_backend bk\_mattermost if { hdr(Host) -i slack.ostreff.info } backend bk\_mattermost server mattermost 127.0.0.1:8065 check backend bk\_ws\_mattermost server mattermost 127.0.0.1:8065 check ### IP QoS URL: https://ostreff.info/ip-qos-values/ Last updated: 2021-05-10T18:51:16.000Z Excelent [arcticle](http://www.netcontractor.pl/blog/?p=1161&ref=ostreff.info) explaining IPv4 QoS ![](https://ostreff.info/content/images/2021/01/image-3.png) QoS Values ### How to set DSCP/COS in FreeBSD and use it in Telco environment URL: https://ostreff.info/3gpp2ipv4/ Last updated: 2021-01-31T10:39:25.000Z Preserving [QoS](https://en.wikipedia.org/wiki/Quality%5Fof%5Fservice?ref=ostreff.info) parameters in Telco environment, where part of the equipment is following [3gpp](https://www.3gpp.org/?ref=ostreff.info) rules and other part is followin [ietf](https://www.ietf.org/?ref=ostreff.info) rfcs rules is really chalenging. And of course there is allways need to add some additional freeware equipment ... ![](https://ostreff.info/content/images/2021/01/image-4.png) Reference (recomendation) table In above table is used information from [Ericsson](https://www.ericsson.com/?ref=ostreff.info) excelent documentation/recomendations and information published already [here](https://ostreff.info/ip-qos-values/). Imagine the scenario that you must mark ( with [dscp](https://en.wikipedia.org/wiki/Differentiated%5Fservices?ref=ostreff.info) cs6, [cos](https://en.wikipedia.org/wiki/IEEE%5FP802.1p?ref=ostreff.info) 6 ) all DNS related conversations of your machine. This means you should mark all incomming packets via tcp and udp on port 53 and also all outgoing tcp and udp packets to port 53. Last days I've made some research what can be done with [FreeBSD](https://www.freebsd.org/?ref=ostreff.info). I have found that it's possible to do such marking on the OS level, if programmers doesn't made such marking to be possible via specific configuration, in his application configuration. It looks that COS and DSCP values can be set in two different places. COS value is part of Layer 2 interface configuraton on vlan level in **[vlanpcp](https://www.freebsd.org/cgi/man.cgi?ifconfig%288%29&ref=ostreff.info)** parameter as it's visible bellow it is set to COS=6: vlanXXXX: flags=8943 metric 0 mtu 1500 options=403 ether 4a:4f:52:44:41:4e inet 10.0.0.1 netmask 0xfffffffe broadcast 255.255.255.255 groups: vlan vlan: XXXX **vlanpcp: 6** parent interface: lagg0 fib: 5 media: Ethernet autoselect status: active nd6 options=29 ![](https://ostreff.info/content/images/2021/01/image-1.png) Wireshark check of 802.1q So the only solution I have found is to design your DNS servers to use dedicated interfaces on your server. DSCP value is more complicated to be set. It looks that it can be modified/set in the firewall. Using [IPFW](https://docs.freebsd.org/en/books/handbook/?ref=ostreff.info) the job can be done using following rules inside your ruleset: ``` ${fwcmd} add setdscp cs6 { tcp or udp } from any 53 to any ${fwcmd} add setdscp cs6 { tcp or udp } from any to any 53 ``` ![](https://ostreff.info/content/images/2021/01/dscp-qos.jpg) Wireshark check of DSCP in IPv4 headers It looks that it's not possible same thing to be done via my favorite firewall [PF](https://docs.freebsd.org/en/books/handbook/?ref=ostreff.info). For [linux](https://linux.org/?ref=ostreff.info) implementations - for dscp look in [iptables examples](https://mediaonfire.com/blog/2013%5F11%5F01%5Fdscp%5Ftagging%5Fwith%5Fiptables.html?ref=ostreff.info) already published in Internet, for cos research begin from [this article](https://community.mellanox.com/s/article/howto-set-egress-priority-vlan-on-linux?ref=ostreff.info). ### Using Hurricane Electric free DNS service to speedup your DNS service URL: https://ostreff.info/using-hurricane-electric-free-dns-service-for-slaves/ Last updated: 2021-01-28T10:10:20.000Z So apart from setting ns\[2345\].he.net as your DNS servers at your registrar and adding a slave at [http://dns.he.net](http://t.umblr.com/redirect?z=http%3A%2F%2Fdns.he.net&t=ODAzYzgyYzgyYjc4Nzc3MWYzMGU5ODcxZmU0M2Q2YzA2ZDc5MTQ3NCxjRmFOU21PWA%3D%3D&b=t%3AyC21hTB-EApArzTh4CNbnA&p=http%3A%2F%2Fstuff.baturin.org%2Fpost%2F86747317815&m=1&ref=ostreff.info) you need to do the following: 1. Allow transfer AXFR to **slave.dns.he.net.** Server that pulls zones is **slave.dns.he.net**, not **ns1.he.net**. 2. Remove **ns1.he.net** from allow transfer ACLs if it was there. 3. Set the server to send NOTIFY’s to **ns1.he.net**. Yes, to ns1, not to slave.dns.he.net. slave.dns.he.net doesn’t listen for any DNS requests including NOTIFY’s. ### BIND example The NOTIFY part is a bit tricky, so here’s an example from my setup. #### Creating an ACL for slave.dns.he.net At the top level of named.conf: > acl he-slaves > { > 216.218.133.2; // slave.dns.he.net IPv4 > 2001:470:600::2; // slave.dns.he.net IPv6 > }; #### Basic zone setup > zone "example.org" in > { > type master; > allow-transfer > { > he-slaves; > }; > file "data/example.org"; > }; #### Notification setup Add this to the zone: > notify explicit; > also-notify > { > 216.218.130.2; > }; So the zone looks like: > zone "example.org" in > { > type master; > allow-transfer > { > he-slaves; > }; > > notify explicit; > also-notify > { > 216.218.130.2; // ns1.he.net > }; > > file "data/example.org"; > }; ### PowerDNS 4 example execute this commands on PowerDNS machine > pdnsutil set-meta example.org ALLOW-AXFR-FROM AUTO-NS 216.218.133.2 > pdnsutil set-meta example.org ALSO-NOTIFY 216.218.130.2 The result can be checked via: > pdnsutil get-meta example.org ### Problems with transmission-daemon with IPv6 and some other popular issues URL: https://ostreff.info/problems-with-transmission-daemon-with-ipv6-and-some-other-popular-issues/ Last updated: 2023-12-20T20:55:46.000Z ### Remove IPv6 detection Current port version inside `net-p2p/transmission-components` must be extracted with command `FLAVOR=daemon make extract` then edit file `work-daemon/transmission-4.0.5/libtransmission/net.cc` searching for `ipv6`. Then just reinstall with `FLAVOR=daemon make deinstall install clean` The problem with net-p2p/transmission-daemon and IPv6 is that transmission developers are … not willing to implement such functionality as visible from: [https://trac.transmissionbt.com/ticket/4197](https://trac.transmissionbt.com/ticket/4197?ref=ostreff.info) So the user who need to use IPv6 but NOT use IPv6 with transmission are left alone. So you can use this quick patch: > cd /usr/ports/net-p2p/transmission-daemon ; > make extract ; > vi work/transmission-2.94/libtransmission/net.c > > @@ -598,7 +598,7 @@ > { > int addrlen = 16; > const int rc = tr\_globalAddress( AF\_INET6, > ipv6, &addrlen ); > \- have\_ipv6 = ( rc >= 0 ) && ( addrlen == 16 ); > \+ have\_ipv6 = 0; /\*\* NO, we do not have IPv6 \*\*/ > last\_time = now; > } > cd ../../../ ; make deinstall install package clean ; > service transmission restart Enjoy your non-ipv6 enabled daemon on ipv6/ipv4 dual-stack system! ### If you see the error messages like "UDP failed to set receive buffer: No buffer space available " in you syslog then you must increase sysctl kern.ipc.maxsockbuf with variable greater then 5242880\. To do this permanently you must put into **/etc/sysctl.conf** following: > kern.ipc.maxsockbuf=5242880 ### Message "IPv4 DHT announce failed" flooding syslog vi work/transmission-2.94/third-party/dht/dht.c > @@ -1239,6 +1346,8 @@ > sr = sr->next; > } > \+ int sr\_duplicate = sr && !sr->done; > \+ > if(sr) { /\* We're reusing data from an old search. Reusing the same tid > means that we can merge replies for both searches. \*/ > > @@ -1288,7 +1397,11 @@ > search\_step(sr, callback, closure); > search\_time = now.tv\_sec; > \- return 1; > \+ if(sr\_duplicate) { > \+ return 0; > \+ } else { > \+ return 1; > \+ } } ### How to grow zfs on FreeBSD running inside VMWare URL: https://ostreff.info/how-to-grow-zfs-on-freebsd-running-inside-vmware/ Last updated: 2022-04-04T14:38:27.000Z Live ZFS resizing inside VMWare should be possible, but there are several steps: First you should resize your virtual disk in VMWare configuration. Recomendation is all virtual disks to be similar size for best performance in VMWare. Then check what is disk configuration using: ``` # gpart show => 34 629145526 da0 GPT (300G) 34 1024 1 freebsd-boot (512K) 1058 4194304 2 freebsd-swap (2.0G) 4195362 624950198 3 freebsd-zfs (298G) => 34 629145526 da1 GPT (300G) 34 1048576 1 freebsd-boot (512M) 1048610 4194304 2 freebsd-swap (2.0G) 5242914 623902646 3 freebsd-zfs (297G) => 34 629145526 da2 GPT (300G) 34 1048576 1 freebsd-boot (512M) 1048610 4194304 2 freebsd-swap (2.0G) 5242914 623902646 3 freebsd-zfs (297G) ``` ``` # zpool status -v pool: zroot state: ONLINE scan: scrub in progress since Mon Apr 4 17:16:38 2022 458G scanned at 670M/s, 319G issued at 466M/s, 458G total 0B repaired, 69.54% done, 00:05:06 to go config: NAME STATE READ WRITE CKSUM zroot ONLINE 0 0 0 gpt/zfs0 ONLINE 0 0 0 gpt/zfs1 ONLINE 0 0 0 gpt/zfs2 ONLINE 0 0 0 ``` VMware should notify guest OS that resize happened. You should be able to see that with \`diskinfo -v /dev/daX\` command. If it didn't happen - you'll probably need to execute at this point following commands: ``` camcontrol reprobe da0 ;\ camcontrol reprobe da1 ;\ camcontrol reprobe da2 ``` Next it is good to made: ``` gpart recover da0 ; \ gpart recover da1 ; \ gpart recover da2 ``` The new space will appear visible after partition 3 on each disk. ``` # gpart show => 34 629145526 da0 GPT (300G) 34 1024 1 freebsd-boot (512K) 1058 4194304 2 freebsd-swap (2.0G) 4195362 331348925 3 freebsd-zfs (158G) 335544287 293601273 - free - (140G) => 34 629145526 da1 GPT (300G) 34 1048576 1 freebsd-boot (512M) 1048610 4194304 2 freebsd-swap (2.0G) 5242914 330301373 3 freebsd-zfs (157G) 335544287 293601273 - free - (140G) => 34 629145526 da2 GPT (300G) 34 1048576 1 freebsd-boot (512M) 1048610 4194304 2 freebsd-swap (2.0G) 5242914 351272893 3 freebsd-zfs (167G) 356515807 272629753 - free - (130G) ``` Next you should resize your partition with \`gpart resize ...\`. Hope it is the past one, otherwise this problem may have no easy solution. Please note that additional free space appears after partition 3\. It should be doable for mounted filesystem. In my example commands are: ``` gpart resize -i 3 da0 ; \ gpart resize -i 3 da1 ; \ gpart resize -i 3 da2 ``` Next you should make ZFS to resize with \`zpool online -e ...\`. So in the example you must execute following commands: ``` zpool online -e zroot gpt/zfs0 ; \ zpool online -e zroot gpt/zfs1 ; \ zpool online -e zroot gpt/zfs2 ``` It’s good to execute \`zpool scrub zroot\`. ### How to serve multiple search domains from Cisco IOS DHCP server? URL: https://ostreff.info/how-to-serve-multiple-search-domains-from-cisco-ios-dhcp-server/ Last updated: 2021-01-27T18:48:50.000Z Source - [https://www.perkin.org.uk/](https://www.perkin.org.uk/posts/serving-multiple-dns-search-domains-in-ios-dhcp.html?ref=ostreff.info) The expected \[code language="plain"\]ip dhcp pool host.net.example.com option 119 ascii net.example.com,example.com\[/code\] doesn't work. So you need to encode search domains in hex using following example script written in Python: \[code language="python"\]#!/usr/bin/env python import syshexlist = \[\]for domain in sys.argv\[1:\]: for part in domain.split("."): hexlist.append("%02x" % len(part)) for c in part: hexlist.append(c.encode("hex")) hexlist.append("00")print "".join(\[(".%s" % (x) if i and not i % 2 else x) \\ for i, x in enumerate(hexlist)\])\[/code\] Execute it: \[code language="plain"\]$ ./cisco.py net.example.com example.com 036e.6574.0765.7861.6d70.6c65.0363.6f6d.0007.6578.616d.706c.6503.636f.6d00\[/code\] So the config in ios must look like: \[code language="plain"\]ip dhcp pool host.net.example.com option 119 hex 036e.6574.0765.7861.6d70.6c65.0363.6f6d.0007.6578.616d.706c.6503.636f.6d00\[/code\] It's done! ### MySQL command for finding all MyISAM databases, and convert them to innodb URL: https://ostreff.info/mysql-command-for-finding-all-myisam-databases-and-convert-them-to-innodb/ Last updated: 2021-01-27T18:44:18.000Z Today most popular storage engine in latests MySQL databases is InnoDB. But if you have older databases, before the times when InnoDB becomes the king, you must somehow convert your data. You can easy find which tables are using an older storage engine using: > SELECT TABLE\_SCHEMA as DbName ,TABLE\_NAME as TableName ,ENGINE as Engine FROM information\_schema.TABLES WHERE ENGINE='MyISAM' AND TABLE\_SCHEMA NOT IN ('mysql', 'information\_schema', 'performance\_schema'); You can generate command sequence which will ALTER needed tables using following: > SELECT CONCAT('ALTER TABLE ', TABLE\_SCHEMA,'.',TABLE\_NAME, ' ENGINE = InnoDB;') FROM information\_schema.TABLES WHERE ENGINE='MyISAM' AND TABLE\_SCHEMA NOT IN ('mysql', 'information\_schema', 'performance\_schema'); Changing "MyISAM" with needed type to be converted is also possible (from "Aria" e.t.c.). ### TCP Fast Open (TFO) on FreeBSD URL: https://ostreff.info/tcp-fast-open-tfo-on-freebsd/ Last updated: 2022-11-11T11:55:03.000Z **[TCP Fast Open](https://en.wikipedia.org/wiki/TCP%5FFast%5FOpen?ref=ostreff.info)**[ (](https://en.wikipedia.org/wiki/TCP%5FFast%5FOpen?ref=ostreff.info)**[TFO](https://en.wikipedia.org/wiki/TCP%5FFast%5FOpen?ref=ostreff.info)**[) ](https://en.wikipedia.org/wiki/TCP%5FFast%5FOpen?ref=ostreff.info)is an extension to speed up the opening of successive [Transmission Control Protocol](https://en.wikipedia.org/wiki/Transmission%5FControl%5FProtocol?ref=ostreff.info) (TCP) connections between two endpoints. It works by using a *TFO cookie* (a TCP option), which is a [cryptographic](https://en.wikipedia.org/wiki/Cryptographic?ref=ostreff.info) cookie stored on the client and set upon the initial connection with the server.[\[1\]](https://en.wikipedia.org/wiki/TCP%5FFast%5FOpen?ref=ostreff.info#cite%5Fnote-lwn-1) When the client later reconnects, it sends the initial SYN packet along with the TFO cookie data to authenticate itself. If successful, the server may start sending data to the client even before the reception of the final ACK packet of the [three-way handshake](https://en.wikipedia.org/wiki/Three-way%5Fhandshake?ref=ostreff.info), skipping that way a round-trip delay and lowering the [latency](https://en.wikipedia.org/wiki/Latency%5F%28engineering%29?ref=ostreff.info) in the start of data transmission. Put the following into /etc/sysctl.conf: > net.inet.tcp.fastopen.server\_enable=1 and voala - the dns/unbound port compiled with "TFOCL" and "TFOSE" options on - now can be started and used without any warnings. More how the TFO implementation on freebsd can be read on [https://people.freebsd.org/\~pkelsey/](https://people.freebsd.org/~pkelsey/TFO%5FDesign%5FDetails.pdf?ref=ostreff.info). Same information was also attached bellow: [TFO\_Design\_Details](https://wp-blog.ostreff.info/wp-content/uploads/2019/01/TFO%5FDesign%5FDetails.pdf?ref=ostreff.info) ### 5G vs 4G URL: https://ostreff.info/5g-vs-4g/ Last updated: 2021-01-27T18:32:59.000Z [](https://youtu.be/%5FB-otIZEGnQ?ref=ostreff.info)[https://youtu.be/\_B-otIZEGnQ](https://youtu.be/%5FB-otIZEGnQ?ref=ostreff.info) ### How To Secure HAProxy with Let's Encrypt URL: https://ostreff.info/how-to-secure-haproxy-with-lets-encrypt/ Last updated: 2021-01-31T10:46:23.000Z ![HAProxy with Let's Encrypt TLS/SSL Certificate and Auto-renewal](https://assets.digitalocean.com/articles/letsencrypt/haproxy-letsencrypt.png) Combine fullchain.pem and privkey.pem > DOMAIN='example.com' ; sudo -E bash -c 'cat /usr/local/etc/letsencrypt/live/$DOMAIN/fullchain.pem /usr/local/etc/letsencrypt/live/$DOMAIN/privkey.pem > /usr/local/etc/haproxy/$DOMAIN.pem' The first thing we want to add is a frontend to handle incoming HTTP connections, and send them to a default backend (which we’ll define later). At the end of the file, let’s add a frontend called **www-http**. ``` frontend www-http bind :80 reqadd X-Forwarded-Proto:\ http default_backend www-backend ``` Next, we will add a frontend to handle incoming HTTPS connections. At the end of the file, add a frontend called **www-https** \- enabling HTTP/2 and alpn extensions also. ``` frontend www-https bind :443 ssl crt /usr/local/etc/haproxy/$DOMAIN.pem alpn h2,http/1.1 reqadd X-Forwarded-Proto:\ https acl letsencrypt-acl path_beg /.well-known/acme-challenge/ use_backend letsencrypt-backend if letsencrypt-acl default_backend www-backend ``` ``` backend www-backend redirect scheme https if !{ ssl_fc } server www-1 www_1_private_IP:80 check server www-2 www_2_private_IP:80 check ``` ``` backend letsencrypt-backend server letsencrypt 127.0.0.1:54321 ``` > service haproxy restart Then create /usr/local/bin/renew.sh script file: ``` #!/bin/sh DOMAIN=example.com # move to the correct let's encrypt directory cd /usr/local/etc/letsencrypt/live/$DOMAIN # cat files to make combined .pem for haproxy cat fullchain.pem privkey.pem > /usr/local/etc/haproxy/$DOMAIN.pem # reload haproxy service haproxy reload ``` > chmod u+x /usr/local/bin/renew.sh Edit with your favorite text editor the file /usr/local/etc/letsencrypt/renewal/$DOMAIN.conf and add or change the line like following: ``` http01_port = 54321 ``` Test the whole configuration executing: > certbot renew --dry-run Then add the crontab entry like: ``` 30 2 * * * /usr/local/bin/certbot renew --renew-hook "/usr/local/bin/renew.sh" > /dev/null ``` Source: [digitalocean.com](https://www.digitalocean.com/community/tutorials/how-to-secure-haproxy-with-let-s-encrypt-on-ubuntu-14-04?ref=ostreff.info) ### How-to fix Illegal mix of collations errors in mysql URL: https://ostreff.info/how-to-fix-illegal-mix-of-collations-errors-in-mysql/ Last updated: 2021-02-05T22:01:18.000Z If you have errors like my example bellow into your postfix logs - you can fix them easy : `warning: mysql:/usr/local/etc/postfix/mysql_virtual_alias_maps.cf: query failed: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8mb4_unicode_ci,COERCIBLE) for operation '='` Get the information(status) about all tables in the specific database using command like: ``` SHOW TABLE STATUS FROM your_db_name; ``` ![](https://ostreff.info/content/images/2021/02/image.png) example output Then change your database collation to UTF8 is my recommendation using command like that: `ALTER DATABASE your_db_name CHARACTER SET utf8 COLLATE utf8_general_ci;` Then to change all your tables listed from status command execute multiple times: `ALTER TABLE your_tablename_which_is_not_utf8 CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;` ### Procedure to replace a zfs on root disk (zroot) URL: https://ostreff.info/procedure-to-replace-a-zfs-on-root-disk-zroot/ Last updated: 2024-05-06T05:53:43.000Z # Редактиране на публикация Switch to draftПрегледОбновяванеДобавяне на заглавие The procedure, as typed, for replacing ada2 with ada8: `# gpart backup ada2 | gpart restore -F ada8` `# zpool replace zroot /dev/ada2p3 /dev/ada8p3 (wait for resilver` checking via "zpool status") `# swapoff /dev/ada2p2` `# swapon /dev/ada8p2` ( verify via swapinfo ) If the size of the new disk is different it is better instead of gpart backup/restore step do: `# zpool set autoexpand=on zroot` `# gpart show` `# gpart create -s gpt ada8` `# gpart add -a 4k -s 512k -t freebsd-boot -l gptboot2 ada8` `# gpart add -a 4k -s ‪4G ‬ -t freebsd-swap -l zfs2 ada8` `# gpart add -a 4k -t freebsd-zfs -l zfs2 ada8` ### Промяна URL: https://ostreff.info/change/ Last updated: 2025-02-22T10:48:09.000Z След дълги и мъчителни експерименти да продължа да използвам [WordPress](https://wordpress.org/?ref=ostreff.info) като система задвижваща персоналният ми [сайт](https://ostreff.info/), установих че е просто невъзможно в настоящият момент той да бъде използван като комбинация на [plugins](https://wordpress.org/plugins/?ref=ostreff.info) и [themes](https://wordpress.org/themes/?ref=ostreff.info) в комбинация с [php8](https://www.php.net/releases/8.0/en.php?ref=ostreff.info). Не че самият wordpress не [работи](https://make.wordpress.org/core/2020/11/23/wordpress-and-php-8-0/?ref=ostreff.info) \- но всичките несъвместимости които повече от месец разработчиците на теми и добавки не желаят да си оправят и ситуацията е меко казано дразнеща. В допълнение на гореописаната дразнеща ситуация: - скоростта на работа на сайтът ми спадна значително; - невъзможността да се накара php8 да му работи jit compiler, който обещаваха да бъде драматичния ускорител на php8; - ниското security на използваните plugins и themes, през които лесно може да се направи hack на целият уеб сървър; Оказа се че все пак процесът на пускане на въпросният ghost сървър е доволно комплициран процес който възнамерявам да опиша в детайли. Обновяването към нови версии с отстранени проблеми или добавени нови функционалности е редовно. Процесът е максимално лесен. Стремежът ми e да се постига оценка [A+](https://securityheaders.com/?q=ghost.ostreff.info&followRedirects=on&ref=ostreff.info) при оценката за сигурен сайт. ### 2021 URL: https://ostreff.info/2021/ Last updated: 2021-01-28T16:40:57.000Z Честита Нова година! Нека е здрава, мирна, щастлива и успешна! Happy New Year! May it be healthy, peaceful happy and successful! ### Enable WDDX extension in PHP 7.4 or 8.0 URL: https://ostreff.info/enable-wddx-extension-in-php-7-4/ Last updated: 2022-04-05T10:30:04.000Z This extension is DEPRECATED and REMOVED as of PHP 7.4 or 8.x. What is WDDX – WDDX (Web Distributed Data eXchange) is a programming language, platform and transport-neutral data interchange mechanism designed to pass data between different environments and different computers. ``` #cd ~ #git clone https://github.com/php/pecl-text-wddx.git #cd pecl-text-wddx #/usr/local/bin/phpize #./configure #make #make install ``` Enable the extension into php installation like this: ``` #cd /usr/local/etc/php/ #cat ext-30-wddx.ini extension=wddx.so ``` restart php-fpm if needed after this. Now it’s possible to use www/owncloud or www/nextcloud with PHP 7.4 or PHP 8.0. Source: [https://www.mysterydata.com/](https://www.mysterydata.com/enable-wddx-extension-in-php-7-4-install-guide/?ref=ostreff.info)