├── VERSION ├── .gitattributes ├── .gitignore ├── resources ├── showcase.gif └── aur-status.png ├── include ├── sanitize.h ├── constants.h ├── archstatus.h ├── results.h ├── front.h └── logo.h ├── meson.build ├── .github └── workflows │ └── release.yaml ├── LICENSE ├── src ├── sanitize.c ├── logo.c ├── front.c ├── main.c └── archstatus.c └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.5 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | build 4 | -------------------------------------------------------------------------------- /resources/showcase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pvtoari/archstatus/HEAD/resources/showcase.gif -------------------------------------------------------------------------------- /resources/aur-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pvtoari/archstatus/HEAD/resources/aur-status.png -------------------------------------------------------------------------------- /include/sanitize.h: -------------------------------------------------------------------------------- 1 | #ifndef SANITIZE_H 2 | #define SANITIZE_H 3 | 4 | char *replace_str(const char *str, const char *radix, const char *rep); 5 | 6 | char *sanitize_content_str(char *content_str); 7 | 8 | char *sanitize_os_release_str(char *os_release_str); 9 | 10 | #endif // SANITIZE_H 11 | -------------------------------------------------------------------------------- /include/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTANTS_H 3 | 4 | #define MONITOR_ORD_AUR 0 5 | #define MONITOR_ORD_FORUM 1 6 | #define MONITOR_ORD_SITE 2 7 | #define MONITOR_ORD_WIKI 3 8 | 9 | #define ARCHLINUX_STATUS_MONITOR_LIST_ENDPOINT "https://status.archlinux.org/api/getMonitorList/vmM5ruWEAB" 10 | #define ARCHLINUX_STATUS_EVENT_FEED_ENDPOINT "https://status.archlinux.org/api/getEventFeed/vmM5ruWEAB?from_date=1755355440" 11 | 12 | #endif // CONSTANTS_H 13 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('archstatus', 'c', 2 | version: run_command('cat', join_paths(meson.project_source_root(), 'VERSION'), check: true).stdout().strip(), 3 | default_options : ['c_std=gnu11', 'buildtype=release'] 4 | ) 5 | 6 | inc = include_directories('include') 7 | cjson_dep = dependency('libcjson', required: true) 8 | libcurl_dep = dependency('libcurl', required: true) 9 | 10 | executable('archstatus', 11 | 'src/main.c', 'src/archstatus.c', 'src/front.c', 'src/logo.c', 'src/sanitize.c', 12 | dependencies: [cjson_dep, libcurl_dep], 13 | include_directories: inc, 14 | install: true 15 | ) 16 | -------------------------------------------------------------------------------- /include/archstatus.h: -------------------------------------------------------------------------------- 1 | #ifndef ARCHSTATUS_H 2 | #define ARCHSTATUS_H 3 | 4 | #include 5 | #include "results.h" 6 | 7 | #define PROGRAM_NAME "archstatus" 8 | #define DEFAULT_DAILY_RATIO_AMOUNT 30 9 | #define DEFAULT_OS_LOGO "archlinux" 10 | 11 | typedef struct output_config_t { 12 | bool do_wiki; 13 | bool do_aur; 14 | bool do_site; 15 | bool do_forum; 16 | bool do_last_events; 17 | int daily_ratio_amount; 18 | char *os; 19 | } output_config_t; 20 | 21 | typedef struct fetch_data_t { 22 | char *memory; 23 | size_t size; 24 | } fetch_data_t; 25 | 26 | output_config_t *init_output_config(); 27 | 28 | fetch_data_t *fetch_url(char *url); 29 | 30 | latest_events_result_t *parse_events_data(fetch_data_t *data); 31 | 32 | monitor_list_result_t *parse_monitor_list_data(fetch_data_t *data); 33 | 34 | #endif // ARCHSTATUS_H 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | paths: 6 | - "VERSION" 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | 11 | jobs: 12 | release: 13 | runs-on: [self-hosted, linux, x64] 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Read version 20 | id: version 21 | run: echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV 22 | 23 | - name: Setup Meson 24 | run: sudo pacman -Syu --noconfirm meson ninja cjson curl 25 | 26 | - name: Build dist tarball 27 | run: | 28 | meson setup build 29 | meson dist -C build --no-tests 30 | 31 | - name: Create Release 32 | uses: softprops/action-gh-release@v2 33 | with: 34 | tag_name: v${{ env.VERSION }} 35 | name: Release v${{ env.VERSION }} 36 | files: build/meson-dist/archstatus-*.tar.* 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.PAT }} 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ariel Roque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /include/results.h: -------------------------------------------------------------------------------- 1 | #ifndef RESULTS_H 2 | #define RESULTS_H 3 | 4 | #define DAYS_AMOUNT 90 5 | #define DAYS_TODAY_ORD 89 6 | 7 | #include 8 | 9 | /* 10 | /api/getEventFeed STUFF 11 | */ 12 | 13 | typedef struct event_t { 14 | char *content; 15 | char *date; 16 | char *event_type; 17 | char *time; 18 | char *time_gmt; 19 | char *title; 20 | long timestamp; 21 | } event_t; 22 | 23 | typedef struct latest_events_result_t { 24 | size_t count; 25 | event_t *events; 26 | } latest_events_result_t; 27 | 28 | /* 29 | /api/getMonitorList STUFF 30 | */ 31 | 32 | typedef struct ratio_t { 33 | char *label; 34 | float ratio; 35 | } ratio_t; 36 | 37 | typedef struct monitor_t { 38 | long id; 39 | char *name; 40 | char *status; 41 | ratio_t monthly_ratio; 42 | ratio_t quarter_ratio; 43 | ratio_t daily_ratios[DAYS_AMOUNT]; 44 | } monitor_t; 45 | 46 | typedef struct statistics_t { 47 | char *count_result; 48 | size_t count_down; 49 | size_t count_paused; 50 | size_t count_up; 51 | } statistics_t; 52 | 53 | typedef struct monitor_list_result_t { 54 | char *days[DAYS_AMOUNT]; 55 | size_t total_monitors; 56 | monitor_t *monitors; 57 | statistics_t statistics; 58 | } monitor_list_result_t; 59 | 60 | #endif // RESULTS_H 61 | -------------------------------------------------------------------------------- /include/front.h: -------------------------------------------------------------------------------- 1 | #ifndef FRONT_H 2 | #define FRONT_H 3 | 4 | #include "results.h" 5 | 6 | #define COLOR_GREEN_FOREGROUND "\033[38;2;59;214;113m" 7 | #define COLOR_GREEN_BACKGROUND "\033[48;2;59;214;113m" 8 | #define COLOR_ORANGE_FOREGROUND "\033[38;2;242;144;48m" 9 | #define COLOR_ORANGE_BACKGROUND "\033[48;2;242;144;48m" 10 | #define COLOR_RED_FOREGROUND "\033[38;2;223;72;74m" 11 | #define COLOR_RED_BACKGROUND "\033[48;2;223;72;74m" 12 | #define COLOR_GREY_FOREGROUND "\033[38;2;104;119;144m" 13 | #define COLOR_GREY_BACKGROUND "\033[48;2;104;119;144m" 14 | #define ANSI_BOLD "\x1b[1m" 15 | #define ANSI_COLOR_RESET "\x1b[0m" 16 | #define BIG_BLACK_CIRCLE "\u25cf" 17 | 18 | #define BAR_RATIO_GREEN_THRESHOLD 99.0f 19 | #define BAR_RATIO_ORANGE_THRESHOLD 95.0f 20 | 21 | #define STATUS_OPERATIONAL_CODE "success" 22 | #define STATUS_DOWN_CODE "danger" 23 | 24 | void print_os_logo(char *os_release, char *status); 25 | 26 | void print_monitors_title(char *from_date, char *to_date); 27 | 28 | void print_monitor_data(monitor_t *monitor, int n_ratios); 29 | 30 | char *format_ratio(ratio_t *ratio); 31 | 32 | char *ratio_to_colored_space(ratio_t *ratio); 33 | 34 | char *format_monitor_status(char *status); 35 | 36 | void print_events(latest_events_result_t *result); 37 | 38 | char* format_event(event_t *event); 39 | 40 | #endif // FRONT_H 41 | -------------------------------------------------------------------------------- /src/sanitize.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "sanitize.h" 6 | 7 | char *replace_str(const char *str, const char *radix, const char *rep) { 8 | size_t radix_len = strlen(radix); 9 | size_t rep_len = strlen(rep); 10 | 11 | int count = 0; 12 | const char *p = str; 13 | while ((p = strstr(p, radix)) != NULL) { 14 | count++; 15 | p += radix_len; 16 | } 17 | 18 | size_t new_len = strlen(str) + count * (rep_len - radix_len) + 1; 19 | char *result = malloc(new_len); 20 | if (!result) return NULL; 21 | 22 | char *out = result; 23 | const char *in = str; 24 | while ((p = strstr(in, radix)) != NULL) { 25 | size_t len_before = p - in; 26 | memcpy(out, in, len_before); 27 | out += len_before; 28 | memcpy(out, rep, rep_len); 29 | out += rep_len; 30 | in = p + radix_len; 31 | } 32 | 33 | strcpy(out, in); 34 | 35 | return result; 36 | } 37 | 38 | char *sanitize_content_str(char *content_str) { 39 | char *nobrs = replace_str(content_str, "
", ""); 40 | char *quoted = replace_str(nobrs, """, "\""); 41 | if(nobrs) free(nobrs); 42 | 43 | return quoted; 44 | } 45 | 46 | char *sanitize_os_release_str(char *os_release_str) { 47 | char *nonewlines = replace_str(os_release_str, "\n", ""); 48 | if (nonewlines) free(os_release_str); 49 | char *noquotes = replace_str(nonewlines, "\"", ""); 50 | if (nonewlines) free(nonewlines); 51 | char *nospaces = replace_str(noquotes, " ", ""); 52 | if (noquotes) free(noquotes); 53 | for (char *p = nospaces; *p; ++p) *p = tolower(*p); 54 | 55 | return nospaces; 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Showcase](resources/showcase.gif) 2 | # archstatus 3 | 4 | `archstatus` is a command-line tool written in C which displays the current status of the services provided by the Arch Linux team. 5 | 6 | The purpose of this tool is to be used whenever the user suspects something's off with Arch Linux's services like AUR being partially down. 7 | 8 | ## How it works 9 | 10 | `archstatus` attempts to fetch status info using the available endpoints at [status.archlinux.org](https://status.archlinux.org) 11 | 12 | The JSON data replied by the status API is deserialized and used to format messages and pretty stuff to show in your terminal. 13 | 14 | ## Dependencies 15 | 16 | * [cJSON](https://github.com/DaveGamble/cJSON) 17 | * [libcurl](https://github.com/curl/curl) 18 | 19 | ## Build using meson 20 | 21 | 1. **Clone the repository:** 22 | ```bash 23 | git clone https://github.com/pvtoari/archstatus 24 | cd archstatus 25 | ``` 26 | 27 | 2. **Setup project with meson** 28 | ```bash 29 | meson setup build 30 | ``` 31 | 32 | 3. **Compile and install** 33 | ```bash 34 | meson compile -C build 35 | sudo meson install -C build 36 | ``` 37 | 38 | ## Usage 39 | 40 | |Option |Long option |Required |Description | 41 | |- |- |- |- | 42 | | `e` | `events` | No | Show latest status events | 43 | | `f` | `forum` | No | Show forum status | 44 | | `s` | `site` | No | Show website status | 45 | | `r` | `aur` | No | Show AUR status | 46 | | `w` | `wiki` | No | Show wiki status | 47 | | `d ` | `ratio-amount ` | No | Amount of daily ratios to show (default: 30) | 48 | | `h` | `help` | No | Shows the help message | 49 | 50 | For example: 51 | ```bash 52 | archstatus -r -d 15 53 | ``` 54 | 55 | This will show AUR's current status and the latest 15 days' daily ratios. 56 | 57 | ![aur status](resources/aur-status.png) 58 | 59 | ## License 60 | 61 | This project is licensed under the MIT license. See the `LICENSE` file for details. 62 | -------------------------------------------------------------------------------- /src/logo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "logo.h" 7 | 8 | char *get_os_release() { 9 | FILE *fp; 10 | char buffer[64] = ""; 11 | 12 | fp = popen("/usr/bin/lsb_release -ds", "r"); 13 | if (fgets(buffer, sizeof buffer, fp) == NULL) { 14 | buffer[0] = '\0'; /* Safe value if error or EOF */ 15 | } 16 | pclose(fp); 17 | 18 | return strdup(buffer); 19 | } 20 | 21 | char *read_fallback_file(char *path) { 22 | if (access(path, F_OK) != 0) { 23 | fprintf(stderr, "warning: no logo found for '%s', using archlinux logo as fallback.\n", path); 24 | return LOGO_ARCHLINUX; 25 | } 26 | 27 | FILE *file; 28 | file = fopen(path, "r"); 29 | 30 | fseek(file, 0L, SEEK_END); 31 | long size = ftell(file); 32 | fseek(file, 0L, SEEK_SET); 33 | 34 | char ch; 35 | size_t i = 0; 36 | char *content = malloc(size); 37 | while ((ch = fgetc(file)) != EOF) 38 | content[i++] = ch; 39 | 40 | content[i] = '\0'; 41 | 42 | fclose(file); 43 | return content; 44 | } 45 | 46 | char *get_logo_for(char *os_release) { 47 | #define COMPARE(x) if (strcmp(os_release, x) == 0) 48 | COMPARE("archbang") return LOGO_ARCHBANG; 49 | COMPARE("archex") return LOGO_ARCHEX; 50 | COMPARE("archman") return LOGO_ARCHMAN; 51 | COMPARE("archlinux") return LOGO_ARCHLINUX; 52 | COMPARE("archstrike") return LOGO_ARCHSTRIKE; 53 | COMPARE("arco") return LOGO_ARCOLINUX; 54 | COMPARE("artix") return LOGO_ARTIXLINUX; 55 | COMPARE("blackarch") return LOGO_BLACKARCH; 56 | COMPARE("bluestar") return LOGO_BLUESTARLINUX; 57 | COMPARE("chimeraos") return LOGO_CHIMERAOS; 58 | COMPARE("ctlos") return LOGO_CTLOSLINUX; 59 | COMPARE("crystal") return LOGO_CRYSTALLINUX; 60 | COMPARE("endeavouros") return LOGO_ENDEAVOUROS; 61 | COMPARE("garuda") return LOGO_GARUDALINUX; 62 | COMPARE("hyperbola") return LOGO_HYPERBOLA; 63 | COMPARE("instantos") return LOGO_INSTANTOS; 64 | COMPARE("kaos") return LOGO_KAOS; 65 | COMPARE("manjaro") return LOGO_MANJARO; 66 | COMPARE("msys2") return LOGO_MSYS2; 67 | COMPARE("obarun") return LOGO_OBARUN; 68 | COMPARE("parabola") return LOGO_PARABOLA; 69 | COMPARE("puppyrus-a") return LOGO_PUPPYRUSA; 70 | COMPARE("rebornos") return LOGO_REBORNOS; 71 | COMPARE("snal") return LOGO_SNALLINUX; 72 | COMPARE("steamos") return LOGO_STEAMOS3; 73 | COMPARE("systemrescue") return LOGO_SYSTEMRESCUE; 74 | COMPARE("tearch") return LOGO_TEARCHLINUX; 75 | COMPARE("ubos") return LOGO_UBOS; 76 | 77 | return read_fallback_file(os_release); 78 | } 79 | -------------------------------------------------------------------------------- /src/front.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "front.h" 6 | #include "logo.h" 7 | #include "sanitize.h" 8 | 9 | void print_os_logo(char *os_release, char *status) { 10 | char *sanitized_os_release = sanitize_os_release_str(os_release); 11 | char *logo = get_logo_for(sanitized_os_release); 12 | 13 | if(strcmp(status, "") == 0) { 14 | printf("%s", logo); 15 | } else if(strcmp(status, "All Clear") == 0) { 16 | char *str; 17 | asprintf(&str, "%s%s%s All systems %sOperational%s", COLOR_GREEN_FOREGROUND, BIG_BLACK_CIRCLE, ANSI_COLOR_RESET, COLOR_GREEN_FOREGROUND, ANSI_COLOR_RESET); 18 | printf(logo, str); 19 | } else { 20 | char *str; 21 | asprintf(&str, "%s%s%s%d monitor/s %sDown%s", COLOR_RED_FOREGROUND, BIG_BLACK_CIRCLE, ANSI_COLOR_RESET, status[0], COLOR_RED_FOREGROUND, ANSI_COLOR_RESET); 22 | printf(logo, str); 23 | } 24 | printf("\n\n"); 25 | } 26 | 27 | void print_monitors_title(char *from_date, char *to_date) { 28 | printf("%sMonitors (default)%s %sDate range: [%s to %s]%s\n\n", ANSI_BOLD, ANSI_COLOR_RESET, COLOR_GREY_FOREGROUND, from_date, to_date, ANSI_COLOR_RESET); 29 | } 30 | 31 | void print_monitor_data(monitor_t *monitor, int n_ratios) { 32 | printf("%s -> | %s\t", monitor->name, format_ratio(&(monitor->quarter_ratio))); 33 | for(int i = 0; i < n_ratios-8; i++) 34 | printf(" "); 35 | printf("%s\n", format_monitor_status(monitor->status)); 36 | 37 | for(int i = DAYS_AMOUNT - n_ratios; i < DAYS_AMOUNT; i++) { 38 | ratio_t daily_ratio = monitor->daily_ratios[i]; 39 | printf("%s ", ratio_to_colored_space(&daily_ratio)); 40 | } 41 | 42 | printf("\n\n"); 43 | } 44 | 45 | char *format_ratio(ratio_t *ratio) { 46 | char buf[128]; 47 | char *color; 48 | float value = ratio->ratio; 49 | if(value > BAR_RATIO_GREEN_THRESHOLD) { 50 | color = COLOR_GREEN_FOREGROUND; 51 | } else if (value > BAR_RATIO_ORANGE_THRESHOLD) { 52 | color = COLOR_ORANGE_FOREGROUND; 53 | } else { 54 | color = COLOR_RED_FOREGROUND; 55 | } 56 | sprintf(buf, "%s%.3f%%%s", color, value, ANSI_COLOR_RESET); 57 | return strdup(buf); 58 | } 59 | 60 | char *ratio_to_colored_space(ratio_t *ratio) { 61 | char buf[128]; 62 | float value = ratio->ratio; 63 | char *color; 64 | if(value > BAR_RATIO_GREEN_THRESHOLD) { 65 | color = COLOR_GREEN_BACKGROUND; 66 | } else if (value > BAR_RATIO_ORANGE_THRESHOLD) { 67 | color = COLOR_ORANGE_BACKGROUND; 68 | } else { 69 | color = COLOR_RED_BACKGROUND; 70 | } 71 | sprintf(buf, "%s %s", color, ANSI_COLOR_RESET); 72 | return strdup(buf); 73 | } 74 | 75 | char *format_monitor_status(char *status) { 76 | char buf[128]; 77 | if(strcmp(status, STATUS_OPERATIONAL_CODE) == 0) 78 | sprintf(buf, "%s%s Operational%s", COLOR_GREEN_FOREGROUND, BIG_BLACK_CIRCLE, ANSI_COLOR_RESET); 79 | else if(strcmp(status, STATUS_DOWN_CODE) == 0) 80 | sprintf(buf, "%s%s Down%s", COLOR_RED_FOREGROUND, BIG_BLACK_CIRCLE, ANSI_COLOR_RESET); 81 | else 82 | sprintf(buf, "%s%s Unknown%s", COLOR_GREY_FOREGROUND, BIG_BLACK_CIRCLE, ANSI_COLOR_RESET); 83 | return strdup(buf); 84 | } 85 | 86 | void print_events(latest_events_result_t *result) { 87 | printf("\n%sStatus updates%s %sLast 30 days%s\n\n", ANSI_BOLD, ANSI_COLOR_RESET, COLOR_GREY_FOREGROUND, ANSI_COLOR_RESET); 88 | for(int i = 0; i < result->count; i++) { 89 | printf("%s\n", format_event(&((result->events)[i]))); 90 | } 91 | } 92 | 93 | char* format_event(event_t *event) { 94 | char buf[2048] = ""; 95 | sprintf(buf, "%s[%s]%s\n", COLOR_GREY_FOREGROUND, event->date, ANSI_COLOR_RESET); 96 | sprintf(buf + strlen(buf), "%s%s%s\n\n", ANSI_BOLD, event->title, ANSI_COLOR_RESET); 97 | sprintf(buf + strlen(buf), "%s\n", event->content); 98 | sprintf(buf + strlen(buf), "%sUpdated on %s%s\n", COLOR_GREY_FOREGROUND, event->time_gmt, ANSI_COLOR_RESET); 99 | return strdup(buf); 100 | } 101 | -------------------------------------------------------------------------------- /include/logo.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGO_H 2 | #define LOGO_H 3 | 4 | #define LOGO_ARCHLINUX "\ 5 | \n\033[38;2;23;147;209m\x1b[1m .\x1b[0m\ 6 | \n\033[38;2;23;147;209m\x1b[1m /#\\ \x1b[0m # \033[38;2;23;147;209m | *\x1b[0m\ 7 | \n\033[38;2;23;147;209m\x1b[1m /###\\ \x1b[0m### #### #### \033[38;2;23;147;209m | | |---. | | \\ /\x1b[0m\ 8 | \n\033[38;2;23;147;209m\x1b[1m /p^###\\ \x1b[0m# # # # \033[38;2;23;147;209m | | | | | | X\x1b[0m\ 9 | \n\033[38;2;23;147;209m\x1b[1m /##P^q##\\ \x1b[0m# #### # # \033[38;2;23;147;209m | | | | ^._.| / \\\x1b[0m\ 10 | \n\033[38;2;23;147;209m\x1b[1m /##( )##\\\x1b[0m\ 11 | \n\033[38;2;23;147;209m\x1b[1m /###P q#,^\\\x1b[0m %s\ 12 | \n\033[38;2;23;147;209m\x1b[1m /P^ ^q\\\x1b[0m" 13 | 14 | #define LOGO_ARCHBANG "" 15 | #define LOGO_ARCHEX "" 16 | #define LOGO_ARCHMAN "" 17 | #define LOGO_ARCHSTRIKE "" 18 | 19 | #define LOGO_ARCOLINUX "" 20 | 21 | #define LOGO_ARTIXLINUX "\n\ 22 | \033[36m /\\ \033[0m \n\ 23 | \033[36m / \\ \033[0m # # # \n\ 24 | \033[36m /`'.,\\ \033[0m # # ## # # # # # # ### # # # # \n\ 25 | \033[36m / ', \033[0m # # # ### # # # # # # # # # \n\ 26 | \033[36m / ,`\\ \033[0m ##### # # # # # # # # # # # \n\ 27 | \033[36m / ,.'`. \\ \033[0m # # # # # # # # # # # # # # # \n\ 28 | \033[36m/.,'` `'.\\ \033[0m # # # ## # # # #### # # # ### # # \n\ 29 | \n\ 30 | %s" 31 | 32 | #define LOGO_BLACKARCH "" 33 | #define LOGO_BLUESTARLINUX "" 34 | #define LOGO_CHIMERAOS "" 35 | #define LOGO_CTLOSLINUX "" 36 | #define LOGO_CRYSTALLINUX "" 37 | 38 | #define LOGO_ENDEAVOUROS "\n\ 39 | \033[31m /* \033[0m \n\ 40 | \033[31m #\033[35m****\033[34m* \033[0m #### ### #### \n\ 41 | \033[31m #\033[35m********\033[34m# \033[0m # ### # ### ### # # ### # # ## # # # \n\ 42 | \033[31m ##\033[35m**********\033[34m## \033[0m ### # # # # # # # # # # # # # # # ### \n\ 43 | \033[31m ##*\033[35m************\033[34m## \033[0m # # # #### ##### #### # # # # # # # # # # \n\ 44 | \033[31m ###*\033[35m*************\033[34m## \033[0m # # # # # # # # # # # # # # # # # # \n\ 45 | \033[34m ############### \033[0m #### # # #### ### #### # ### ### # ### #### \n\ 46 | \n\ 47 | %s" 48 | 49 | #define LOGO_GARUDALINUX "" 50 | #define LOGO_HYPERBOLA "" 51 | #define LOGO_INSTANTOS "" 52 | #define LOGO_KAOS "" 53 | 54 | #define LOGO_MANJARO "\n\ 55 | \033[32m||||||||| |||| \033[0m\n\ 56 | \033[32m||||||||| |||| \033[0m # # \n\ 57 | \033[32m|||| |||| \033[0m ## ## ### ### # ### ## ### \n\ 58 | \033[32m|||| |||| |||| \033[0m # ## # # # # # # # # \n\ 59 | \033[32m|||| |||| |||| \033[0m # # #### # # # #### # # # \n\ 60 | \033[32m|||| |||| |||| \033[0m # # # # # # # # # # # # \n\ 61 | \033[32m|||| |||| |||| \033[0m # # #### # # # #### # ### \n\ 62 | ## \n\ 63 | \n\ 64 | %s" 65 | 66 | #define LOGO_MSYS2 "" 67 | #define LOGO_OBARUN "" 68 | #define LOGO_PARABOLA "" 69 | #define LOGO_PUPPYRUSA "" 70 | #define LOGO_REBORNOS "" 71 | #define LOGO_SNALLINUX "" 72 | #define LOGO_STEAMOS3 "" 73 | #define LOGO_SYSTEMRESCUE "" 74 | #define LOGO_TEARCHLINUX "" 75 | #define LOGO_UBOS "" 76 | 77 | char *get_os_release(); 78 | 79 | char *read_fallback_file(char *path); 80 | 81 | char *get_logo_for(char *os_release); 82 | 83 | #endif // LOGO_H 84 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "front.h" 7 | #include "logo.h" 8 | #include "constants.h" 9 | #include "archstatus.h" 10 | 11 | #define MIN(a,b) (((a)<(b))?(a):(b)) 12 | 13 | void usage() { 14 | printf("Usage: %s [options]\n", PROGRAM_NAME); 15 | printf("Options:\n"); 16 | printf(" -e, --events Show latest status events\n"); 17 | printf(" -f, --forum Show forum status\n"); 18 | printf(" -s, --site Show site status\n"); 19 | printf(" -r, --aur Show AUR status\n"); 20 | printf(" -w, --wiki Show wiki status\n"); 21 | printf(" -d, --ratio-amount Amount of daily ratios to show (default: %d)\n", DEFAULT_DAILY_RATIO_AMOUNT); 22 | printf(" -l, --logo OS logo to show (default: %s)\n", DEFAULT_OS_LOGO); 23 | printf(" -h, --help Shows this help message\n"); 24 | } 25 | 26 | int main(int argc, char *argv[]) { 27 | struct option long_options[] = { 28 | {"help", no_argument, NULL, 'h'}, 29 | {"ratio-amount", required_argument, NULL, 'd'}, 30 | {"logo", required_argument, NULL, 'l'}, 31 | {"wiki", no_argument, NULL, 'w'}, 32 | {"aur", no_argument, NULL, 'r'}, 33 | {"site", no_argument, NULL, 's'}, 34 | {"forum", no_argument, NULL, 'f'}, 35 | {"latest-events", no_argument, NULL, 'e'}, 36 | {0, 0, 0, 0} 37 | }; 38 | 39 | output_config_t *config = init_output_config(); 40 | 41 | bool any = true; 42 | int opt; 43 | while ((opt = getopt_long(argc, argv, "hl:d:wrsfe", long_options, NULL)) != -1) { 44 | switch (opt) { 45 | case 'h': 46 | any = false; 47 | usage(); 48 | exit(EXIT_SUCCESS); 49 | case 'd': 50 | config->daily_ratio_amount = MIN(DAYS_AMOUNT, atoi(optarg)); 51 | break; 52 | case 'l': 53 | config->os = strdup(optarg); 54 | break; 55 | case 'w': 56 | any = false; 57 | config->do_wiki = true; 58 | break; 59 | case 'r': 60 | any = false; 61 | config->do_aur = true; 62 | break; 63 | case 's': 64 | any = false; 65 | config->do_site = true; 66 | break; 67 | case 'f': 68 | any = false; 69 | config->do_forum = true; 70 | break; 71 | case 'e': 72 | any = false; 73 | config->do_last_events = true; 74 | break; 75 | default: 76 | usage(); 77 | exit(EXIT_FAILURE); 78 | } 79 | } 80 | 81 | if(any || config->do_aur || config->do_forum || config->do_site || config->do_wiki) { 82 | fetch_data_t *monitors_data = fetch_url(ARCHLINUX_STATUS_MONITOR_LIST_ENDPOINT); 83 | if(!monitors_data) { 84 | fprintf(stderr, "error: failed to fetch monitors data.\n"); 85 | exit(EXIT_FAILURE); 86 | } 87 | 88 | monitor_list_result_t *monitors = parse_monitor_list_data(monitors_data); 89 | if (!monitors) { 90 | fprintf(stderr, "error: failed to parse monitors data.\n"); 91 | exit(EXIT_FAILURE); 92 | } 93 | 94 | print_os_logo(config->os, monitors->statistics.count_result); 95 | 96 | print_monitors_title(monitors->days[0], monitors->days[DAYS_TODAY_ORD]); 97 | if(any || config->do_aur) { 98 | print_monitor_data(&(monitors->monitors[MONITOR_ORD_AUR]), config->daily_ratio_amount); 99 | } 100 | if(any || config->do_forum) { 101 | print_monitor_data(&(monitors->monitors[MONITOR_ORD_FORUM]), config->daily_ratio_amount); 102 | } 103 | if(any || config->do_site) { 104 | print_monitor_data(&(monitors->monitors[MONITOR_ORD_SITE]), config->daily_ratio_amount); 105 | } 106 | if(any || config->do_wiki) { 107 | print_monitor_data(&(monitors->monitors[MONITOR_ORD_WIKI]), config->daily_ratio_amount); 108 | } 109 | } 110 | 111 | if (any || config->do_last_events) { 112 | fetch_data_t *events_data = fetch_url(ARCHLINUX_STATUS_EVENT_FEED_ENDPOINT); 113 | if (!events_data) { 114 | fprintf(stderr, "error: failed to fetch events data.\n"); 115 | exit(EXIT_FAILURE); 116 | } 117 | 118 | latest_events_result_t *events = parse_events_data(events_data); 119 | if (!events) { 120 | fprintf(stderr, "error: failed to parse events data.\n"); 121 | exit(EXIT_FAILURE); 122 | } 123 | 124 | print_events(events); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/archstatus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "archstatus.h" 8 | #include "logo.h" 9 | #include "constants.h" 10 | #include "sanitize.h" 11 | 12 | output_config_t *init_output_config() { 13 | output_config_t *res = malloc(sizeof(output_config_t)); 14 | memset(res, 0, sizeof(output_config_t)); 15 | 16 | res->daily_ratio_amount = DEFAULT_DAILY_RATIO_AMOUNT; 17 | res->os = get_os_release(); 18 | 19 | return res; 20 | } 21 | 22 | static size_t write_memory_callback(void *content, size_t size, size_t nmemb, void *userp) { 23 | size_t real_size = size * nmemb; 24 | fetch_data_t *mem = (fetch_data_t *) userp; 25 | 26 | char *ptr = realloc(mem->memory, mem->size + real_size + 1); 27 | if (!ptr) { 28 | fprintf(stderr, "failed to realloc (returned NULL)"); 29 | return 0; 30 | } 31 | 32 | mem->memory = ptr; 33 | memcpy(&(mem->memory[mem->size]), content, real_size); 34 | mem->size += real_size; 35 | mem->memory[mem->size] = 0; 36 | 37 | return real_size; 38 | } 39 | 40 | fetch_data_t *fetch_url(char *url) { 41 | CURL *curl; 42 | CURLcode curl_res; 43 | 44 | fetch_data_t data = {0}; 45 | data.memory = malloc(1); 46 | data.size = 0; 47 | 48 | fetch_data_t *result = NULL; 49 | 50 | curl = curl_easy_init(); 51 | if (!curl) { 52 | if (data.memory) free(data.memory); 53 | return result; 54 | } 55 | 56 | curl_easy_setopt(curl, CURLOPT_URL, url); 57 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_memory_callback); 58 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &data); 59 | curl_easy_setopt(curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L); 60 | curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2); 61 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); 62 | curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); 63 | 64 | curl_res = curl_easy_perform(curl); 65 | if (curl_res == CURLE_OK) { 66 | result = malloc(sizeof(fetch_data_t)); 67 | if (result) { 68 | result->memory = data.memory; 69 | result->size = data.size; 70 | } else free(data.memory); 71 | } else { 72 | fprintf(stderr, "failed to perform curl: %s\n", curl_easy_strerror(curl_res)); 73 | free(data.memory); 74 | } 75 | 76 | curl_easy_cleanup(curl); 77 | return result; 78 | } 79 | 80 | static void free_latest_events_result(latest_events_result_t *res) { 81 | if (!res) return; 82 | 83 | if (res->events) { 84 | for (size_t i = 0; i < res->count; i++) { 85 | free(res->events[i].content); 86 | free(res->events[i].date); 87 | free(res->events[i].event_type); 88 | free(res->events[i].time); 89 | free(res->events[i].time_gmt); 90 | free(res->events[i].title); 91 | } 92 | free(res->events); 93 | } 94 | 95 | free(res); 96 | } 97 | 98 | static void free_monitor_list_result(monitor_list_result_t *res) { 99 | if (!res) return; 100 | 101 | for (size_t i = 0; i < DAYS_AMOUNT; i++) { 102 | free(res->days[i]); 103 | } 104 | 105 | if (res->monitors) { 106 | for (size_t i = 0; i < res->total_monitors; i++) { 107 | free(res->monitors[i].name); 108 | free(res->monitors[i].status); 109 | free(res->monitors[i].monthly_ratio.label); 110 | free(res->monitors[i].quarter_ratio.label); 111 | for (size_t j = 0; j < DAYS_AMOUNT; j++) { 112 | free(res->monitors[i].daily_ratios[j].label); 113 | } 114 | } 115 | 116 | free(res->monitors); 117 | } 118 | 119 | free(res->statistics.count_result); 120 | 121 | free(res); 122 | } 123 | 124 | latest_events_result_t *parse_events_data(fetch_data_t *data) { 125 | latest_events_result_t *result = malloc(sizeof(latest_events_result_t)); 126 | if (!result) return NULL; 127 | 128 | result->events = NULL; 129 | 130 | cJSON *json_root = cJSON_ParseWithLength(data->memory, data->size); 131 | if (!json_root) goto end; 132 | 133 | cJSON *json_meta = cJSON_GetObjectItemCaseSensitive(json_root, "meta"); 134 | if (!json_meta) goto end; 135 | 136 | cJSON *json_count = cJSON_GetObjectItemCaseSensitive(json_meta, "count"); 137 | if (!json_meta || !cJSON_IsNumber(json_count)) goto end; 138 | result->count = json_count->valueint; 139 | 140 | cJSON *json_results = cJSON_GetObjectItemCaseSensitive(json_root, "results"); 141 | if (!json_results || !cJSON_IsArray(json_results)) goto end; 142 | 143 | result->events = malloc(sizeof(event_t) * result->count); 144 | if (!result->events) goto end; 145 | 146 | for (size_t i = 0; i < result->count; i++) { 147 | result->events[i].content = NULL; 148 | result->events[i].date = NULL; 149 | result->events[i].event_type = NULL; 150 | result->events[i].time = NULL; 151 | result->events[i].time_gmt = NULL; 152 | result->events[i].title = NULL; 153 | } 154 | 155 | cJSON *event = NULL; 156 | size_t i = 0; 157 | cJSON_ArrayForEach(event, json_results) { 158 | cJSON *json_content = cJSON_GetObjectItemCaseSensitive(event, "content"); 159 | if (!json_content || !cJSON_IsString(json_content)) goto end; 160 | result->events[i].content = sanitize_content_str(json_content->valuestring); 161 | 162 | cJSON *json_date = cJSON_GetObjectItemCaseSensitive(event, "date"); 163 | if (!json_date || !cJSON_IsString(json_date)) goto end; 164 | result->events[i].date = strdup(json_date->valuestring); 165 | 166 | cJSON *json_event_type = cJSON_GetObjectItemCaseSensitive(event, "eventType"); 167 | if (!json_event_type || !cJSON_IsString(json_event_type)) goto end; 168 | result->events[i].event_type = strdup(json_event_type->valuestring); 169 | 170 | cJSON *json_time = cJSON_GetObjectItemCaseSensitive(event, "time"); 171 | if (!json_time || !cJSON_IsString(json_time)) goto end; 172 | result->events[i].time = strdup(json_time->valuestring); 173 | 174 | cJSON *json_time_gmt = cJSON_GetObjectItemCaseSensitive(event, "timeGMT"); 175 | if (!json_time_gmt || !cJSON_IsString(json_time_gmt)) goto end; 176 | result->events[i].time_gmt = strdup(json_time_gmt->valuestring); 177 | 178 | cJSON *json_title = cJSON_GetObjectItemCaseSensitive(event, "title"); 179 | if (!json_title || !cJSON_IsString(json_title)) goto end; 180 | result->events[i].title = strdup(json_title->valuestring); 181 | 182 | cJSON *json_timestamp = cJSON_GetObjectItemCaseSensitive(event, "timestamp"); 183 | if (!json_timestamp || !cJSON_IsNumber(json_timestamp)) goto end; 184 | result->events[i].timestamp = json_timestamp->valuedouble; 185 | 186 | i++; 187 | } 188 | 189 | return result; 190 | 191 | end: 192 | if (result) free_latest_events_result(result); 193 | cJSON_Delete(json_root); 194 | return NULL; 195 | } 196 | 197 | monitor_list_result_t *parse_monitor_list_data(fetch_data_t *data) { 198 | monitor_list_result_t *result = malloc(sizeof(monitor_list_result_t)); 199 | if (!result) return NULL; 200 | 201 | result->monitors = NULL; 202 | for (size_t i = 0; i < DAYS_AMOUNT; i++) result->days[i] = NULL; 203 | result->statistics.count_result = NULL; 204 | 205 | 206 | cJSON *json_root = cJSON_ParseWithLength(data->memory, data->size); 207 | if (!json_root) goto end; 208 | 209 | cJSON *json_days = cJSON_GetObjectItemCaseSensitive(json_root, "days"); 210 | if (!json_days) goto end; 211 | cJSON* day = NULL; 212 | size_t i = 0; 213 | cJSON_ArrayForEach(day, json_days) { 214 | if (!day || !cJSON_IsString(day)) goto end; 215 | result->days[i++] = strdup(day->valuestring); 216 | } 217 | 218 | cJSON *json_psp = cJSON_GetObjectItemCaseSensitive(json_root, "psp"); 219 | if (!json_psp) goto end; 220 | 221 | cJSON *json_total_monitors = cJSON_GetObjectItemCaseSensitive(json_psp, "totalMonitors"); 222 | if (!json_total_monitors || !cJSON_IsNumber(json_total_monitors)) goto end; 223 | result->total_monitors = json_total_monitors->valueint; 224 | 225 | result->monitors = malloc(sizeof(monitor_t) * result->total_monitors); 226 | for (size_t i = 0; i < result->total_monitors; i++) { 227 | result->monitors[i].name = NULL; 228 | result->monitors[i].status = NULL; 229 | result->monitors[i].monthly_ratio.label = NULL; 230 | result->monitors[i].quarter_ratio.label = NULL; 231 | for (size_t j = 0; j < DAYS_AMOUNT; j++) { 232 | result->monitors[i].daily_ratios[j].label = NULL; 233 | } 234 | } 235 | 236 | cJSON *json_monitors = cJSON_GetObjectItemCaseSensitive(json_psp, "monitors"); 237 | if (!json_monitors || !cJSON_IsArray(json_monitors)) goto end; 238 | cJSON *monitor = NULL; 239 | i = 0; 240 | cJSON_ArrayForEach(monitor, json_monitors) { 241 | cJSON *json_monthly_ratio = cJSON_GetObjectItemCaseSensitive(monitor, "30dRatio"); 242 | if (!json_monthly_ratio) goto end; 243 | 244 | cJSON *json_monthly_ratio_label = cJSON_GetObjectItemCaseSensitive(json_monthly_ratio, "label"); 245 | if (!json_monthly_ratio_label || !cJSON_IsString(json_monthly_ratio_label)) goto end; 246 | result->monitors[i].monthly_ratio.label = strdup(json_monthly_ratio_label->valuestring); 247 | 248 | cJSON *json_monthly_ratio_value = cJSON_GetObjectItemCaseSensitive(json_monthly_ratio, "ratio"); 249 | if (!json_monthly_ratio_value || !cJSON_IsString(json_monthly_ratio_value)) goto end; 250 | result->monitors[i].monthly_ratio.ratio = atof(json_monthly_ratio_value->valuestring); 251 | 252 | cJSON *json_quarter_ratio = cJSON_GetObjectItemCaseSensitive(monitor, "90dRatio"); 253 | if (!json_quarter_ratio) goto end; 254 | 255 | cJSON *json_quarter_ratio_label = cJSON_GetObjectItemCaseSensitive(json_quarter_ratio, "label"); 256 | if (!json_quarter_ratio_label || !cJSON_IsString(json_quarter_ratio_label)) goto end; 257 | result->monitors[i].quarter_ratio.label = strdup(json_quarter_ratio_label->valuestring); 258 | 259 | cJSON *json_quarter_ratio_value = cJSON_GetObjectItemCaseSensitive(json_quarter_ratio, "ratio"); 260 | if (!json_quarter_ratio_value || !cJSON_IsString(json_quarter_ratio_value)) goto end; 261 | result->monitors[i].quarter_ratio.ratio = atof(json_quarter_ratio_value->valuestring); 262 | 263 | cJSON *json_daily_ratios = cJSON_GetObjectItemCaseSensitive(monitor, "dailyRatios"); 264 | if (!json_daily_ratios || !cJSON_IsArray(json_daily_ratios)) goto end; 265 | cJSON *ratio = NULL; 266 | size_t j = 0; 267 | cJSON_ArrayForEach(ratio, json_daily_ratios) { 268 | cJSON *json_daily_ratio_label = cJSON_GetObjectItemCaseSensitive(ratio, "label"); 269 | if (!json_daily_ratio_label || !cJSON_IsString(json_daily_ratio_label)) goto end; 270 | result->monitors[i].daily_ratios[j].label = strdup(json_daily_ratio_label->valuestring); 271 | 272 | cJSON *json_daily_ratio_value = cJSON_GetObjectItemCaseSensitive(ratio, "ratio"); 273 | if (!json_daily_ratio_value || !cJSON_IsString(json_daily_ratio_value)) goto end; 274 | result->monitors[i].daily_ratios[j].ratio = atof(json_daily_ratio_value->valuestring); 275 | 276 | j++; 277 | } 278 | 279 | cJSON *json_id = cJSON_GetObjectItemCaseSensitive(monitor, "monitorId"); 280 | if (!json_id || !cJSON_IsNumber(json_id)) goto end; 281 | result->monitors[i].id = json_id->valueint; 282 | 283 | cJSON *json_name = cJSON_GetObjectItemCaseSensitive(monitor, "name"); 284 | if (!json_name || !cJSON_IsString(json_name)) goto end; 285 | result->monitors[i].name = strdup(json_name->valuestring); 286 | 287 | cJSON *json_status = cJSON_GetObjectItemCaseSensitive(monitor, "statusClass"); 288 | if (!json_status || !cJSON_IsString(json_status)) goto end; 289 | result->monitors[i].status = strdup(json_status->valuestring); 290 | 291 | i++; 292 | } 293 | 294 | cJSON *json_statistics = cJSON_GetObjectItemCaseSensitive(json_root, "statistics"); 295 | if (!json_statistics) goto end; 296 | 297 | cJSON *json_count_result = cJSON_GetObjectItemCaseSensitive(json_statistics, "count_result"); 298 | if (!json_count_result || !cJSON_IsString(json_count_result)) goto end; 299 | result->statistics.count_result = strdup(json_count_result->valuestring); 300 | 301 | cJSON *json_counts = cJSON_GetObjectItemCaseSensitive(json_statistics, "counts"); 302 | if (!json_counts) goto end; 303 | 304 | cJSON *json_down = cJSON_GetObjectItemCaseSensitive(json_counts, "down"); 305 | if (!json_down || !cJSON_IsNumber(json_down)) goto end; 306 | result->statistics.count_down = json_down->valueint; 307 | 308 | cJSON *json_paused = cJSON_GetObjectItemCaseSensitive(json_counts, "paused"); 309 | if (!json_paused || !cJSON_IsNumber(json_paused)) goto end; 310 | result->statistics.count_paused = json_paused->valueint; 311 | 312 | cJSON *json_up = cJSON_GetObjectItemCaseSensitive(json_counts, "up"); 313 | if (!json_up || !cJSON_IsNumber(json_up)) goto end; 314 | result->statistics.count_up = json_up->valueint; 315 | 316 | return result; 317 | 318 | end: 319 | if (result) free_monitor_list_result(result); 320 | cJSON_Delete(json_root); 321 | return NULL; 322 | } 323 | --------------------------------------------------------------------------------